From b5f8eb188076bae3048863e064492ac33f97c811 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 13 Feb 2023 13:44:50 +0800 Subject: [PATCH 001/180] chore: Remove all old API stuff --- pom.xml | 3 +- .../InventoriesListener.java | 4 +- .../MultiverseInventories.java | 107 ++---------------- .../command/AddSharesCommand.java | 82 -------------- .../command/AddWorldCommand.java | 57 ---------- .../command/CreateGroupCommand.java | 43 ------- .../command/DeleteGroupCommand.java | 42 ------- .../command/GroupCommand.java | 46 -------- .../command/ImportCommand.java | 55 --------- .../command/InfoCommand.java | 98 ---------------- .../command/InventoriesCommand.java | 34 ------ .../command/ListCommand.java | 45 -------- .../command/MigrateCommand.java | 53 --------- .../command/ReloadCommand.java | 31 ----- .../command/RemoveSharesCommand.java | 76 ------------- .../command/RemoveWorldCommand.java | 61 ---------- .../command/SpawnCommand.java | 107 ------------------ .../command/ToggleCommand.java | 55 --------- .../util/TestInstanceCreator.java | 5 +- 19 files changed, 17 insertions(+), 987 deletions(-) delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java diff --git a/pom.xml b/pom.xml index 52380322..7cb01da6 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,7 @@ and adjust the build number accordingly --> maven-surefire-plugin 3.0.0-M3 + true **/TestInstanceCreator.java @@ -208,7 +209,7 @@ and adjust the build number accordingly --> com.onarandombox.multiversecore Multiverse-Core - 4.2.2 + 5.0.0-SNAPSHOT provided diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java b/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java index 8e1d8e4d..992df2a3 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java +++ b/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java @@ -1,7 +1,7 @@ package com.onarandombox.multiverseinventories; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.api.MultiverseWorld; +import com.onarandombox.MultiverseCore.api.MVWorld; import com.onarandombox.MultiverseCore.event.MVConfigReloadEvent; import com.onarandombox.MultiverseCore.event.MVVersionEvent; import com.onarandombox.multiverseinventories.profile.GlobalProfile; @@ -389,7 +389,7 @@ private void handleRespawn(PlayerRespawnEvent event, EventPriority priority) { if (group.getSpawnPriority().equals(priority)) { String spawnWorldName = group.getSpawnWorld(); if (spawnWorldName != null) { - MultiverseWorld mvWorld = this.inventories.getCore() + MVWorld mvWorld = this.inventories.getCore() .getMVWorldManager().getMVWorld(spawnWorldName); if (mvWorld != null) { this.spawnLoc = mvWorld.getSpawnLocation(); diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java index d3d37d1c..1f71d27e 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java @@ -1,38 +1,25 @@ package com.onarandombox.multiverseinventories; +import java.io.File; +import java.io.IOException; +import java.util.Locale; +import java.util.logging.Level; + import com.dumptruckman.minecraft.util.Logging; import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.api.MVPlugin; -import com.onarandombox.MultiverseCore.commands.HelpCommand; -import com.onarandombox.commandhandler.CommandHandler; +import com.onarandombox.multiverseinventories.locale.Message; +import com.onarandombox.multiverseinventories.locale.Messager; +import com.onarandombox.multiverseinventories.locale.Messaging; +import com.onarandombox.multiverseinventories.migration.ImportManager; import com.onarandombox.multiverseinventories.profile.ProfileDataSource; import com.onarandombox.multiverseinventories.profile.WorldGroupManager; import com.onarandombox.multiverseinventories.profile.container.ContainerType; import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.command.AddSharesCommand; -import com.onarandombox.multiverseinventories.command.AddWorldCommand; -import com.onarandombox.multiverseinventories.command.CreateGroupCommand; -import com.onarandombox.multiverseinventories.command.DeleteGroupCommand; -import com.onarandombox.multiverseinventories.command.GroupCommand; -import com.onarandombox.multiverseinventories.command.ImportCommand; -import com.onarandombox.multiverseinventories.command.InfoCommand; -import com.onarandombox.multiverseinventories.command.ListCommand; -import com.onarandombox.multiverseinventories.command.MigrateCommand; -import com.onarandombox.multiverseinventories.command.ReloadCommand; -import com.onarandombox.multiverseinventories.command.RemoveSharesCommand; -import com.onarandombox.multiverseinventories.command.RemoveWorldCommand; -import com.onarandombox.multiverseinventories.command.SpawnCommand; -import com.onarandombox.multiverseinventories.command.ToggleCommand; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.locale.Messager; -import com.onarandombox.multiverseinventories.locale.Messaging; -import com.onarandombox.multiverseinventories.migration.ImportManager; import com.onarandombox.multiverseinventories.util.Perm; import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; @@ -41,13 +28,6 @@ import org.bukkit.plugin.java.JavaPluginLoader; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; -import java.util.logging.Level; - /** * Multiverse-Inventories plugin main class. */ @@ -69,7 +49,6 @@ public static MultiverseInventories getPlugin() { private ProfileContainerStore groupProfileContainerStore = null; private ImportManager importManager = new ImportManager(this); - private CommandHandler commandHandler = null; private MultiverseCore core = null; private InventoriesConfig config = null; private FlatFileProfileDataSource data = null; @@ -186,26 +165,7 @@ public void onEnable() { } private void registerCommands() { - this.commandHandler = this.getCore().getCommandHandler(); - this.getCommandHandler().registerCommand(new InfoCommand(this)); - this.getCommandHandler().registerCommand(new ImportCommand(this)); - this.getCommandHandler().registerCommand(new ListCommand(this)); - this.getCommandHandler().registerCommand(new ReloadCommand(this)); - this.getCommandHandler().registerCommand(new AddWorldCommand(this)); - this.getCommandHandler().registerCommand(new RemoveWorldCommand(this)); - this.getCommandHandler().registerCommand(new AddSharesCommand(this)); - this.getCommandHandler().registerCommand(new RemoveSharesCommand(this)); - this.getCommandHandler().registerCommand(new CreateGroupCommand(this)); - this.getCommandHandler().registerCommand(new DeleteGroupCommand(this)); - this.getCommandHandler().registerCommand(new SpawnCommand(this)); - this.getCommandHandler().registerCommand(new GroupCommand(this)); - this.getCommandHandler().registerCommand(new ToggleCommand(this)); - this.getCommandHandler().registerCommand(new MigrateCommand(this)); - for (com.onarandombox.commandhandler.Command c : this.commandHandler.getAllCommands()) { - if (c instanceof HelpCommand) { - c.addKey("mvinv"); - } - } + } private void hookImportables() { @@ -227,32 +187,6 @@ public ImportManager getImportManager() { return this.importManager; } - /** - * {@inheritDoc} - */ - @Override - public boolean onCommand(CommandSender sender, Command command, String commandLabel, String[] args) { - if (!this.isEnabled()) { - sender.sendMessage("This plugin is Disabled!"); - return true; - } - ArrayList allArgs = new ArrayList(Arrays.asList(args)); - allArgs.add(0, command.getName()); - return this.getCommandHandler().locateAndRunCommand(sender, allArgs); - } - - private CommandHandler getCommandHandler() { - return this.commandHandler; - } - - /** - * {@inheritDoc} - */ - @Override - public void log(Level level, String msg) { - Logging.log(level, msg); - } - /** * {@inheritDoc} */ @@ -277,27 +211,6 @@ public int getProtocolVersion() { return 1; } - /** - * {@inheritDoc} - */ - @Override - public String dumpVersionInfo(String buffer) { - buffer += logAndAddToPasteBinBuffer("=== Settings ==="); - buffer += logAndAddToPasteBinBuffer("First Run: " + this.getMVIConfig().isFirstRun()); - buffer += logAndAddToPasteBinBuffer("Using Bypass: " + this.getMVIConfig().isUsingBypass()); - buffer += logAndAddToPasteBinBuffer("Default Ungrouped Worlds: " + this.getMVIConfig().isDefaultingUngroupedWorlds()); - buffer += logAndAddToPasteBinBuffer("Save and Load on Log In and Out: " + this.getMVIConfig().usingLoggingSaveLoad()); - buffer += logAndAddToPasteBinBuffer("Using GameMode Profiles: " + this.getMVIConfig().isUsingGameModeProfiles()); - buffer += logAndAddToPasteBinBuffer("=== Shares ==="); - buffer += logAndAddToPasteBinBuffer("Optionals for Ungrouped Worlds: " + this.getMVIConfig().usingOptionalsForUngrouped()); - buffer += logAndAddToPasteBinBuffer("Enabled Optionals: " + this.getMVIConfig().getOptionalShares()); - buffer += logAndAddToPasteBinBuffer("=== Groups ==="); - for (WorldGroup group : this.getGroupManager().getGroups()) { - buffer += logAndAddToPasteBinBuffer(group.toString()); - } - return buffer; - } - /** * Builds a String containing Multiverse-Inventories' version info. * diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java deleted file mode 100644 index e3e98458..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv addshares Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class AddSharesCommand extends InventoriesCommand { - - public AddSharesCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Adds share(s) to a World Group."); - this.setCommandUsage("/mvinv addshares {SHARE[,EXTRA]} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv addshares"); - this.addKey("mvinv addshare"); - this.addKey("mvinv adds"); - this.addKey("mvinvas"); - this.addKey("mvinvadds"); - this.addKey("mvinvaddshares"); - this.addKey("mvinvaddshare"); - this.setPermission(Perm.COMMAND_ADDSHARES.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Shares newShares; - Shares negativeShares; - if (args.get(0).contains("all") || args.get(0).contains("everything") || args.get(0).contains("*")) { - newShares = Sharables.allOf(); - negativeShares = Sharables.noneOf(); - } else if (args.get(0).contains("-all") || args.get(0).contains("-everything") || args.get(0).contains("-*")) { - negativeShares = Sharables.allOf(); - newShares = Sharables.noneOf(); - } else { - negativeShares = Sharables.noneOf(); - newShares = Sharables.noneOf(); - String[] sharesString = args.get(0).split(","); - for (String shareString : sharesString) { - if (shareString.startsWith("-") && shareString.length() > 1) { - Shares shares = Sharables.lookup(shareString.substring(1)); - if (shares == null) { - continue; - } - negativeShares.setSharing(shares, true); - } else { - Shares shares = Sharables.lookup(shareString); - if (shares == null) { - continue; - } - newShares.setSharing(shares, true); - } - } - } - if (newShares.isEmpty() && negativeShares.isEmpty()) { - this.messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - worldGroup.getShares().mergeShares(newShares); - worldGroup.getShares().removeAll(negativeShares); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.NOW_SHARING, sender, worldGroup.getName(), - worldGroup.getShares().toString()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java deleted file mode 100644 index e4812a0a..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv addworld Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class AddWorldCommand extends InventoriesCommand { - - public AddWorldCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Adds a World to a World Group."); - this.setCommandUsage("/mvinv addworld {WORLD} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv addworld"); - this.addKey("mvinv addw"); - this.addKey("mvinvaw"); - this.addKey("mvinvaddw"); - this.addKey("mvinvaddworld"); - this.setPermission(Perm.COMMAND_ADDWORLD.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - World world = Bukkit.getWorld(args.get(0)); - if (world == null) { - this.messager.normal(Message.ERROR_NO_WORLD, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - if (worldGroup.containsWorld(world.getName())) { - this.messager.normal(Message.WORLD_ALREADY_EXISTS, sender, world.getName(), - worldGroup.getName()); - return; - } - worldGroup.addWorld(world); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.WORLD_ADDED, sender, world.getName(), - worldGroup.getName()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java deleted file mode 100644 index d9da3a99..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv creategroup Command. - */ -public class CreateGroupCommand extends InventoriesCommand { - - public CreateGroupCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Creates a new World Group with no worlds and no shares."); - this.setCommandUsage("/mvinv creategroup {NAME}"); - this.setArgRange(1, 1); - this.addKey("mvinv creategroup"); - this.addKey("mvinv createg"); - this.addKey("mvinv cg"); - this.addKey("mvinvcreategroup"); - this.addKey("mvinvcreateg"); - this.addKey("mvinvcg"); - this.setPermission(Perm.COMMAND_CREATEGROUP.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(0)); - if (worldGroup != null) { - this.messager.normal(Message.GROUP_EXISTS, sender, args.get(0)); - return; - } - - worldGroup = this.plugin.getGroupManager().newEmptyGroup(args.get(0)); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.messager.normal(Message.GROUP_CREATION_COMPLETE, sender); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java deleted file mode 100644 index dd8c903a..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv deletegroup Command. - */ -public class DeleteGroupCommand extends InventoriesCommand { - - public DeleteGroupCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Deletes a World Group."); - this.setCommandUsage("/mvinv deletegroup {NAME}"); - this.setArgRange(1, 1); - this.addKey("mvinv deletegroup"); - this.addKey("mvinv deleteg"); - this.addKey("mvinv dg"); - this.addKey("mvinvdeletegroup"); - this.addKey("mvinvdeleteg"); - this.addKey("mvinvdg"); - this.setPermission(Perm.COMMAND_DELETEGROUP.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(0)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(0)); - return; - } - - this.plugin.getGroupManager().removeGroup(worldGroup); - this.messager.normal(Message.GROUP_REMOVED, sender, args.get(0)); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java deleted file mode 100644 index e4baefc6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.command.prompts.GroupControlPrompt; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.Conversable; -import org.bukkit.conversations.Conversation; -import org.bukkit.conversations.ConversationFactory; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class GroupCommand extends InventoriesCommand { - - public GroupCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Creates a world group."); - this.setCommandUsage("/mvinv group"); - this.setArgRange(0, 0); - this.addKey("mvinv group"); - this.addKey("mvinv g"); - this.addKey("mvinvgroup"); - this.addKey("mvinvg"); - this.setPermission(Perm.COMMAND_GROUP.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - if (!(sender instanceof Conversable)) { - this.messager.normal(Message.NON_CONVERSABLE, sender); - return; - } - - Conversable conversable = (Conversable) sender; - Conversation conversation = new ConversationFactory(this.plugin) - .withFirstPrompt(new GroupControlPrompt(plugin, sender)) - .withEscapeSequence("##") - .withModality(false).buildConversation(conversable); - conversation.begin(); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java deleted file mode 100644 index 1831351b..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.migration.DataImporter; -import com.onarandombox.multiverseinventories.migration.MigrationException; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ImportCommand extends InventoriesCommand { - - public ImportCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Import from MultiInv/WorldInventories"); - this.setCommandUsage("/mvinv import " + ChatColor.GREEN + "{MultiInv|WorldInventories}"); - this.setArgRange(1, 1); - this.addKey("mvinv import"); - this.addKey("mvinvim"); - this.addKey("mvinvimport"); - this.setPermission(Perm.COMMAND_IMPORT.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - DataImporter importer = null; - if (args.get(0).equalsIgnoreCase("MultiInv")) { - importer = this.plugin.getImportManager().getMultiInvImporter(); - } else if (args.get(0).equalsIgnoreCase("WorldInventories")) { - importer = this.plugin.getImportManager().getWorldInventoriesImporter(); - } else { - this.messager.bad(Message.ERROR_PLUGIN_NOT_ENABLED, - sender, args.get(0)); - return; - } - if (importer == null) { - this.messager.bad(Message.ERROR_PLUGIN_NOT_ENABLED, - sender, args.get(0)); - } else { - try { - importer.importData(); - } catch (MigrationException e) { - Logging.severe(e.getMessage()); - Logging.severe("Cause: " + e.getCauseException().getMessage()); - } - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java deleted file mode 100644 index 8567b3a6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.List; -import java.util.Set; - -/** - * The /mvi info Command. - */ -public class InfoCommand extends InventoriesCommand { - - public InfoCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("World and Group Information"); - this.setCommandUsage("/mvinv info " + ChatColor.GREEN + "[WORLD|GROUP]"); - this.setArgRange(0, 1); - this.addKey("mvinv info"); - this.addKey("mvinvi"); - this.addKey("mvinvinfo"); - this.setPermission(Perm.COMMAND_INFO.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - String name; - if (args.isEmpty()) { - if (!(sender instanceof Player)) { - this.messager.normal(Message.INFO_ZERO_ARG, sender); - return; - } - name = ((Player) sender).getWorld().getName(); - } else { - name = args.get(0); - } - - ProfileContainer worldProfileContainer = this.plugin.getWorldProfileContainerStore().getContainer(name); - messager.normal(Message.INFO_WORLD, sender, name); - if (worldProfileContainer != null && Bukkit.getWorld(worldProfileContainer.getContainerName()) != null) { - worldInfo(sender, worldProfileContainer); - } else { - messager.normal(Message.ERROR_NO_WORLD_PROFILE, sender, name); - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(name); - this.messager.normal(Message.INFO_GROUP, sender, name); - if (worldGroup != null) { - this.groupInfo(sender, worldGroup); - } else { - this.messager.normal(Message.ERROR_NO_GROUP, sender, name); - } - } - - private void groupInfo(CommandSender sender, WorldGroup worldGroup) { - StringBuilder worldsString = new StringBuilder(); - Set worlds = worldGroup.getWorlds(); - if (worlds.isEmpty()) { - worldsString.append("N/A"); - } else { - for (String world : worlds) { - if (!worldsString.toString().isEmpty()) { - worldsString.append(", "); - } - worldsString.append(world); - } - } - this.messager.normal(Message.INFO_GROUPS_INFO, - sender, worldsString, worldGroup.getShares().toString()); - } - - private void worldInfo(CommandSender sender, ProfileContainer worldProfileContainer) { - StringBuilder groupsString = new StringBuilder(); - List worldGroups = this.plugin.getGroupManager() - .getGroupsForWorld(worldProfileContainer.getContainerName()); - - if (worldGroups.isEmpty()) { - groupsString.append("N/A"); - } else { - for (WorldGroup worldGroup : worldGroups) { - if (!groupsString.toString().isEmpty()) { - groupsString.append(", "); - } - groupsString.append(worldGroup.getName()); - } - } - - this.messager.normal(Message.INFO_WORLD_INFO, - sender, groupsString.toString()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java deleted file mode 100644 index c06378d7..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.commandhandler.Command; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Messager; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * A base command class to easily retrieve the plugin associated. - */ -public abstract class InventoriesCommand extends Command { - - /** - * Instance of MultiverseInventories. - */ - protected MultiverseInventories plugin; - /** - * Instance of messager used for Inventories. - */ - protected Messager messager; - - public InventoriesCommand(MultiverseInventories plugin) { - super(plugin); - this.plugin = plugin; - this.messager = plugin.getMessager(); - } - - @Override - public abstract void runCommand(CommandSender sender, List args); -} - - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java deleted file mode 100644 index aacb289e..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.Collection; -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ListCommand extends InventoriesCommand { - - public ListCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("World and Group Information"); - this.setCommandUsage("/mvinv list"); - this.setArgRange(0, 0); - this.addKey("mvinv list"); - this.addKey("mvinvl"); - this.addKey("mvinvlist"); - this.setPermission(Perm.COMMAND_LIST.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Collection groups = this.plugin.getGroupManager().getGroups(); - String groupsString = "N/A"; - if (!groups.isEmpty()) { - StringBuilder builder = new StringBuilder(); - for (WorldGroup group : groups) { - if (!builder.toString().isEmpty()) { - builder.append(", "); - } - builder.append(group.getName()); - } - groupsString = builder.toString(); - } - this.messager.normal(Message.LIST_GROUPS, sender, groupsString); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java deleted file mode 100644 index 70feb105..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.io.IOException; -import java.util.List; -import java.util.Set; - -/** - * The /mvi info Command. - */ -public class MigrateCommand extends InventoriesCommand { - - public MigrateCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Migrate player data from one name to another"); - this.setCommandUsage("/mvinv migrate " + ChatColor.GREEN + "{OLDNAME} {NEWNAME} [saveold]"); - this.setArgRange(2, 3); - this.addKey("mvinv migrate"); - this.addKey("mvinvmigrate"); - this.setPermission(Perm.COMMAND_INFO.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - String oldName = args.get(0); - String newName = args.get(1); - boolean deleteOld = true; - if (args.size() > 2) { - if (args.get(2).equalsIgnoreCase("saveold")) { - deleteOld = false; - } - } - try { - plugin.getData().migratePlayerData(oldName, newName, Bukkit.getOfflinePlayer(newName).getUniqueId(), deleteOld); - messager.good(Message.MIGRATE_SUCCESSFUL, sender, oldName, newName); - } catch (IOException e) { - Logging.severe("Could not migrate data from name " + oldName + " to " + newName); - e.printStackTrace(); - messager.bad(Message.MIGRATE_FAILED, sender, oldName, newName); - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java deleted file mode 100644 index 45596191..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.util.Perm; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ReloadCommand extends InventoriesCommand { - - public ReloadCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Reloads config file"); - this.setCommandUsage("/mvinv reload"); - this.setArgRange(0, 0); - this.addKey("mvinv reload"); - this.addKey("mvinvreload"); - this.setPermission(Perm.COMMAND_RELOAD.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - this.plugin.reloadConfig(); - this.messager.normal(Message.RELOAD_COMPLETE, sender); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java deleted file mode 100644 index 72c08ea1..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv rmshares Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class RemoveSharesCommand extends InventoriesCommand { - - public RemoveSharesCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Removes share(s) from a World Group."); - this.setCommandUsage("/mvinv removeshares {SHARE[,EXTRA]} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv removeshares"); - this.addKey("mvinv rmshares"); - this.addKey("mvinv removeshare"); - this.addKey("mvinv rmshare"); - this.addKey("mvinv removes"); - this.addKey("mvinv rms"); - this.addKey("mvinvrs"); - this.addKey("mvinvrms"); - this.addKey("mvinvremoves"); - this.addKey("mvinvremoveshares"); - this.addKey("mvinvrmshares"); - this.addKey("mvinvremoveshare"); - this.addKey("mvinvrmshare"); - this.setPermission(Perm.COMMAND_RMSHARES.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Shares newShares; - if (args.get(0).contains("all") || args.get(0).contains("everything") || args.get(0).contains("*")) { - newShares = Sharables.allOf(); - } else { - newShares = Sharables.noneOf(); - String[] sharesString = args.get(0).split(","); - for (String shareString : sharesString) { - Shares shares = Sharables.lookup(shareString); - if (shares == null) { - continue; - } - newShares.setSharing(shares, true); - } - } - if (newShares.isEmpty()) { - this.messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - for (Sharable sharable : newShares) { - worldGroup.getShares().setSharing(sharable, false); - } - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.NOW_SHARING, sender, worldGroup.getName(), - worldGroup.getShares().toString()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java deleted file mode 100644 index f715295a..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv rmworld Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class RemoveWorldCommand extends InventoriesCommand { - - public RemoveWorldCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Removes a World from a World Group."); - this.setCommandUsage("/mvinv removeworld {WORLD} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv removeworld"); - this.addKey("mvinv rmworld"); - this.addKey("mvinv removew"); - this.addKey("mvinv rmw"); - this.addKey("mvinvrw"); - this.addKey("mvinvrmw"); - this.addKey("mvinvremovew"); - this.addKey("mvinvremoveworld"); - this.addKey("mvinvrmworld"); - this.setPermission(Perm.COMMAND_RMWORLD.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - World world = Bukkit.getWorld(args.get(0)); - if (world == null) { - this.messager.normal(Message.ERROR_NO_WORLD, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - if (!worldGroup.containsWorld(world.getName())) { - this.messager.normal(Message.WORLD_NOT_IN_GROUP, sender, world.getName(), - worldGroup.getName()); - return; - } - worldGroup.removeWorld(world); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.WORLD_REMOVED, sender, world.getName(), - worldGroup.getName()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java deleted file mode 100644 index c7869ae1..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.MultiverseCore.api.MultiverseWorld; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.util.Perm; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class SpawnCommand extends InventoriesCommand { - - public SpawnCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Spawn"); - this.setCommandUsage("/mvinv spawn" + ChatColor.GOLD + " [PLAYER]"); - this.setArgRange(0, 1); - this.addKey("mvinv spawn"); - this.addKey("mvinvspawn"); - this.addKey("mvinvs"); - this.addKey("gspawn"); - this.addKey("ispawn"); - this.setPermission(Perm.COMMAND_SPAWN.getPermission()); - this.addAdditonalPermission(Perm.COMMAND_SPAWN_OTHER.getPermission()); - - } - - @Override - public void runCommand(CommandSender sender, List args) { - Player player = null; - if (sender instanceof Player) { - player = (Player) sender; - } - // If a persons name was passed in, you must be A. the console, or B have permissions - if (args.size() == 1) { - Perm perm = Perm.COMMAND_SPAWN_OTHER; - if (player != null && !perm.has(player)) { - this.messager.normal(Message.GENERIC_COMMAND_NO_PERMISSION, player, - perm.getPermission().getDescription(), perm.getPermission().getName()); - return; - } - Player target = Bukkit.getPlayerExact(args.get(0)); - if (target != null) { - this.messager.normal(Message.TELEPORTING, target); - spawnAccurately(target); - - if (player != null) { - this.messager.normal(Message.TELEPORTED_BY, target, - ChatColor.YELLOW + player.getName()); - } else { - this.messager.normal(Message.TELEPORTED_BY, target, - ChatColor.LIGHT_PURPLE + this.messager - .getMessage(Message.GENERIC_THE_CONSOLE)); - } - } else { - this.messager.normal(Message.GENERIC_NOT_LOGGED_IN, sender, args.get(0)); - } - } else { - Perm perm = Perm.COMMAND_SPAWN; - if (player != null && !perm.has(player)) { - this.messager.normal(Message.GENERIC_COMMAND_NO_PERMISSION, player, - perm.getPermission().getDescription(), perm.getPermission().getName()); - return; - } - if (player != null) { - this.messager.normal(Message.TELEPORTING, player); - spawnAccurately(player); - } else { - this.messager.normal(Message.TELEPORT_CONSOLE_ERROR, sender); - } - } - } - - private void spawnAccurately(Player player) { - World world = null; - for (WorldGroup group : this.plugin.getGroupManager().getGroupsForWorld(player.getWorld().getName())) { - if (group.getSpawnWorld() != null) { - world = Bukkit.getWorld(group.getSpawnWorld()); - if (world != null) { - break; - } - } - } - if (world == null) { - world = player.getWorld(); - } - MultiverseWorld mvWorld = this.plugin.getCore() - .getMVWorldManager().getMVWorld(world); - Location spawnLocation; - if (mvWorld != null) { - spawnLocation = mvWorld.getSpawnLocation(); - } else { - spawnLocation = world.getSpawnLocation(); - } - this.plugin.getCore().getSafeTTeleporter().safelyTeleport(player, player, spawnLocation, false); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java deleted file mode 100644 index f30e9401..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ToggleCommand extends InventoriesCommand { - - public ToggleCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Toggles the usage of optional sharables"); - this.setCommandUsage("/mvinv toggle {SHARE}"); - this.setArgRange(1, 1); - this.addKey("mvinv toggle"); - this.addKey("mvinv t"); - this.setPermission(Perm.COMMAND_ADDSHARES.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Shares shares = Sharables.lookup(args.get(0).toLowerCase()); - if (shares == null) { - this.messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); - return; - } - boolean foundOpt = false; - for (Sharable sharable : shares) { - if (sharable.isOptional()) { - foundOpt = true; - if (this.plugin.getMVIConfig().getOptionalShares().contains(sharable)) { - this.plugin.getMVIConfig().getOptionalShares().remove(sharable); - this.messager.normal(Message.NOW_NOT_USING_OPTIONAL, sender, sharable.getNames()[0]); - } else { - this.plugin.getMVIConfig().getOptionalShares().add(sharable); - this.messager.normal(Message.NOW_USING_OPTIONAL, sender, sharable.getNames()[0]); - } - } - } - if (foundOpt) { - this.plugin.getMVIConfig().save(); - } else { - this.messager.normal(Message.NO_OPTIONAL_SHARES, sender, args.get(0)); - } - } -} - diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java b/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java index 82fb2bc5..7552c09b 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java +++ b/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java @@ -8,12 +8,13 @@ package com.onarandombox.multiverseinventories.util; import com.onarandombox.MultiverseCore.MultiverseCore; +import com.onarandombox.MultiverseCore.api.MVWorldManager; import com.onarandombox.MultiverseCore.listeners.MVEntityListener; import com.onarandombox.MultiverseCore.listeners.MVPlayerListener; import com.onarandombox.MultiverseCore.listeners.MVWeatherListener; import com.onarandombox.MultiverseCore.utils.FileUtils; import com.onarandombox.MultiverseCore.utils.TestingMode; -import com.onarandombox.MultiverseCore.utils.WorldManager; +import com.onarandombox.MultiverseCore.world.SimpleMVWorldManager; import com.onarandombox.multiverseinventories.InventoriesListener; import com.onarandombox.multiverseinventories.MultiverseInventories; import org.bukkit.Bukkit; @@ -297,7 +298,7 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { serverfield.set(plugin, mockServer); // Set worldManager - WorldManager wm = spy(new WorldManager(core)); + MVWorldManager wm = spy(new SimpleMVWorldManager(core)); Field worldmanagerfield = MultiverseCore.class.getDeclaredField("worldManager"); worldmanagerfield.setAccessible(true); worldmanagerfield.set(core, wm); From cb50110a81a86a16d147837173bf4be384ddcf63 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:14:49 +0800 Subject: [PATCH 002/180] chore: Bump version to 5.0.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7cb01da6..eb3a00f3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.onarandombox.multiverseinventories Multiverse-Inventories - 4.2.4-SNAPSHOT + 5.0.0-SNAPSHOT Multiverse-Inventories Multiverse Multiworld Inventories Module From 315902ef168913930aef5c745fa988b860251b5b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:32:09 +0800 Subject: [PATCH 003/180] refactor: Update MultiverseInventories to core api changes --- .../MultiverseInventories.java | 111 +++++++++++------- src/main/resources/plugin.yml | 2 +- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java index 1f71d27e..d13067c0 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java @@ -2,11 +2,13 @@ import java.io.File; import java.io.IOException; +import java.util.List; import java.util.Locale; import java.util.logging.Level; import com.dumptruckman.minecraft.util.Logging; import com.onarandombox.MultiverseCore.MultiverseCore; +import com.onarandombox.MultiverseCore.api.MVCore; import com.onarandombox.MultiverseCore.api.MVPlugin; import com.onarandombox.multiverseinventories.locale.Message; import com.onarandombox.multiverseinventories.locale.Messager; @@ -33,6 +35,8 @@ */ public class MultiverseInventories extends JavaPlugin implements MVPlugin, Messaging { + private static final int PROTOCOL = 50; + private static MultiverseInventories inventoriesPlugin; public static MultiverseInventories getPlugin() { @@ -49,7 +53,7 @@ public static MultiverseInventories getPlugin() { private ProfileContainerStore groupProfileContainerStore = null; private ImportManager importManager = new ImportManager(this); - private MultiverseCore core = null; + private MVCore core = null; private InventoriesConfig config = null; private FlatFileProfileDataSource data = null; @@ -80,49 +84,43 @@ public MultiverseInventories(JavaPluginLoader loader, PluginDescriptionFile desc * {@inheritDoc} */ @Override - public void onDisable() { - for (final Player player : getServer().getOnlinePlayers()) { - final String world = player.getWorld().getName(); - //getData().updateLastWorld(player.getName(), world); - if (getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(this, player, new DefaultPersistingProfile(Sharables.allOf(), - getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); - getData().setLoadOnLogin(player.getName(), true); - } - } - - this.dupingPatch.disable(); - - Logging.shutdown(); + public void onLoad() { + Logging.init(this); + this.getDataFolder().mkdirs(); } /** * {@inheritDoc} */ @Override - public void onEnable() { - Logging.init(this); - Perm.register(this); - - MultiverseCore mvCore; - mvCore = (MultiverseCore) this.getServer().getPluginManager().getPlugin("Multiverse-Core"); - // Test if the Core was found, if not we'll disable this plugin. - if (mvCore == null) { - Logging.severe("Multiverse-Core not found, disabling..."); + public final void onEnable() { + this.core = (MVCore) this.getServer().getPluginManager().getPlugin("Multiverse-Core"); + if (this.core == null) { + Logging.severe("Core not found! You must have Multiverse-Core installed to use this plugin!"); + Logging.severe("Grab a copy at: "); + Logging.severe("https://dev.bukkit.org/projects/multiverse-core"); + Logging.severe("Disabling!"); this.getServer().getPluginManager().disablePlugin(this); return; } - this.setCore(mvCore); - - if (this.getCore().getProtocolVersion() < this.getRequiredProtocol()) { + if (this.core.getProtocolVersion() < this.getProtocolVersion()) { Logging.severe("Your Multiverse-Core is OUT OF DATE"); - Logging.severe("This version of Multiverse-Inventories requires Protocol Level: " + this.getRequiredProtocol()); - Logging.severe("Your of Core Protocol Level is: " + this.getCore().getProtocolVersion()); + Logging.severe("This version of " + this.getDescription().getName() + " requires Protocol Level: " + this.getProtocolVersion()); + Logging.severe("Your of Core Protocol Level is: " + this.core.getProtocolVersion()); Logging.severe("Grab an updated copy at: "); - Logging.severe("http://bukkit.onarandombox.com/?dir=multiverse-core"); + Logging.severe("https://dev.bukkit.org/projects/multiverse-core"); + Logging.severe("Disabling!"); this.getServer().getPluginManager().disablePlugin(this); return; } + Logging.setDebugLevel(core.getMVConfig().getGlobalDebug()); + this.core.incrementPluginCount(); + this.onMVPluginEnable(); + Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), getProtocolVersion(), getAuthors()); + } + + private void onMVPluginEnable() { + Perm.register(this); this.reloadConfig(); @@ -137,15 +135,12 @@ public void onEnable() { // Initialize data class //this.getWorldProfileContainerStore().setWorldProfiles(this.getData().getWorldProfiles()); - Logging.setDebugLevel(getCore().getMVConfig().getGlobalDebug()); - - this.getCore().incrementPluginCount(); - // Register Events Bukkit.getPluginManager().registerEvents(inventoriesListener, this); if (Bukkit.getPluginManager().getPlugin("Multiverse-Adventure") != null) { Bukkit.getPluginManager().registerEvents(adventureListener, this); } + if (getCore().getProtocolVersion() >= 24) { new CoreDebugListener(this); } @@ -159,9 +154,27 @@ public void onEnable() { Sharables.init(this); this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); + } - // Display enable message/version info - Logging.log(true, Level.INFO, "enabled."); + /** + * {@inheritDoc} + */ + @Override + public void onDisable() { + for (final Player player : getServer().getOnlinePlayers()) { + final String world = player.getWorld().getName(); + //getData().updateLastWorld(player.getName(), world); + if (getMVIConfig().usingLoggingSaveLoad()) { + ShareHandlingUpdater.updateProfile(this, player, new DefaultPersistingProfile(Sharables.allOf(), + getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); + getData().setLoadOnLogin(player.getName(), true); + } + } + + this.dupingPatch.disable(); + + this.core.decrementPluginCount(); + Logging.shutdown(); } private void registerCommands() { @@ -191,7 +204,7 @@ public ImportManager getImportManager() { * {@inheritDoc} */ @Override - public MultiverseCore getCore() { + public MVCore getCore() { return this.core; } @@ -199,16 +212,32 @@ public MultiverseCore getCore() { * {@inheritDoc} */ @Override - public void setCore(MultiverseCore core) { - this.core = core; + public int getProtocolVersion() { + return PROTOCOL; } /** * {@inheritDoc} */ @Override - public int getProtocolVersion() { - return 1; + public String getAuthors() { + List authorsList = this.getDescription().getAuthors(); + if (authorsList.size() == 0) { + return ""; + } + + StringBuilder authors = new StringBuilder(); + authors.append(authorsList.get(0)); + + for (int i = 1; i < authorsList.size(); i++) { + if (i == authorsList.size() - 1) { + authors.append(" and ").append(authorsList.get(i)); + } else { + authors.append(", ").append(authorsList.get(i)); + } + } + + return authors.toString(); } /** diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e33c2744..487c2f6b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: Multiverse-Inventories main: com.onarandombox.multiverseinventories.MultiverseInventories version: maven-version-number api-version: 1.13 -author: dumptruckman +authors: ['dumptruckman'] depend: ['Multiverse-Core'] softdepend: [MultiInv, WorldInventories, Multiverse-Adventure] From 9d6b3458e044076cab106215fa7cb173a65dde6d Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:37:18 +0800 Subject: [PATCH 004/180] chore: Remove unneeded imports --- .../multiverseinventories/MultiverseInventories.java | 2 -- .../multiverseinventories/event/ShareHandlingEvent.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java index d13067c0..b55fd239 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java @@ -4,10 +4,8 @@ import java.io.IOException; import java.util.List; import java.util.Locale; -import java.util.logging.Level; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.api.MVCore; import com.onarandombox.MultiverseCore.api.MVPlugin; import com.onarandombox.multiverseinventories.locale.Message; diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java b/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java index d65b1bca..9bc81d2f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java +++ b/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java @@ -6,8 +6,6 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; -import java.util.Collection; -import java.util.LinkedList; import java.util.List; /** From 0f53ba3ed1d3ce61d4b4548dec7736ed1bd70dd0 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Feb 2023 12:12:07 +0800 Subject: [PATCH 005/180] refactor: Remove old getRequiredProtocol --- .../multiverseinventories/MultiverseInventories.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java index b55fd239..9e860235 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java @@ -41,7 +41,6 @@ public static MultiverseInventories getPlugin() { return inventoriesPlugin; } - private final int requiresProtocol = 22; private final InventoriesListener inventoriesListener = new InventoriesListener(this); private final AdventureListener adventureListener = new AdventureListener(this); @@ -353,13 +352,6 @@ public void setMessager(Messager messager) { this.messager = messager; } - /** - * @return The required protocol version of core. - */ - public int getRequiredProtocol() { - return this.requiresProtocol; - } - /** * @return The World Group manager for this plugin. */ From 9c9093e9ed56e6187f462a5d230a50f8452aed2d Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:07:50 +0800 Subject: [PATCH 006/180] Base migration to support new mv5 core --- build.gradle | 36 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- .../AbstractWorldGroupManager.java | 5 +- .../AdventureListener.java | 33 ---- .../CoreDebugListener.java | 2 +- .../InventoriesConfig.java | 10 +- .../InventoriesListener.java | 25 ++- .../MultiverseInventories.java | 45 ++++- .../MultiverseInventoriesPluginBinder.java | 19 ++ .../command/package-info.java | 5 - .../commands/InventoriesCommand.java | 16 ++ .../commands/package-info.java | 5 + .../prompts/GroupControlPrompt.java | 66 +++---- .../prompts/GroupCreatePrompt.java | 74 ++++---- .../prompts/GroupDeletePrompt.java | 84 ++++----- .../prompts/GroupEditPrompt.java | 82 ++++----- .../prompts/GroupModifyPrompt.java | 72 ++++---- .../prompts/GroupSharesPrompt.java | 160 ++++++++-------- .../prompts/GroupWorldsPrompt.java | 174 +++++++++--------- .../prompts/InventoriesPrompt.java | 50 ++--- .../share/Sharables.java | 14 +- 21 files changed, 520 insertions(+), 459 deletions(-) delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/command/package-info.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupControlPrompt.java (92%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupCreatePrompt.java (93%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupDeletePrompt.java (93%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupEditPrompt.java (93%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupModifyPrompt.java (92%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupSharesPrompt.java (95%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/GroupWorldsPrompt.java (95%) rename src/main/java/com/onarandombox/multiverseinventories/{command => commands}/prompts/InventoriesPrompt.java (89%) diff --git a/build.gradle b/build.gradle index 2f24bf24..583f0639 100644 --- a/build.gradle +++ b/build.gradle @@ -2,14 +2,22 @@ plugins { id 'java-library' id 'maven-publish' id 'checkstyle' - id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'com.gradleup.shadow' version '8.3.5' } version = System.getenv('GITHUB_VERSION') ?: 'local' group = 'com.onarandombox.multiverseinventories' description = 'Multiverse-Inventories' -java.sourceCompatibility = JavaVersion.VERSION_11 +compileJava { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +// todo: Enable test when convert them to use mockbukkit like mv-core +compileTestJava { + enabled = false +} repositories { mavenLocal() @@ -38,9 +46,8 @@ dependencies { } // Core - implementation('com.onarandombox.multiversecore:Multiverse-Core:4.2.2') { - exclude group: 'me.main__.util', module: 'SerializationConfig' - } + // TODO update to correct version once we have it published + implementation 'org.mvplugins.multiverse.core:multiverse-core:local' // Config api 'com.dumptruckman.minecraft:JsonConfiguration:1.1' @@ -62,11 +69,6 @@ dependencies { exclude group: '*', module: '*' } - // Legacy Multiverse-Adventure - implementation('com.onarandombox.multiverseadventure:Multiverse-Adventure:2.5.0-SNAPSHOT') { - exclude group: '*', module: '*' - } - // Tests testImplementation 'com.github.MilkBowl:VaultAPI:1.7.1' testImplementation 'junit:junit:4.13.2' @@ -152,8 +154,20 @@ shadowJar { configurations = [project.configurations.api] - archiveFileName = "$baseName-$version.$extension" + archiveClassifier.set('') } build.dependsOn shadowJar jar.enabled = false + + +tasks.register('runHabitatGenerator', JavaExec) { + classpath = configurations["compileClasspath"] + mainClass.set('org.mvplugins.multiverse.external.jvnet.hk2.generator.HabitatGenerator') + + args = [ + '--file', "build/libs/multiverse-inventories-$version" + ".jar", + '--locator', 'Multiverse-Portals', + ] +} +tasks.named("build") { finalizedBy("runHabitatGenerator") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c..5c40527d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java b/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java index 59ee14f5..bcb5710e 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java +++ b/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java @@ -9,6 +9,7 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.core.world.WorldManager; import java.util.ArrayList; import java.util.Collections; @@ -25,9 +26,11 @@ abstract class AbstractWorldGroupManager implements WorldGroupManager { static final String DEFAULT_GROUP_NAME = "default"; protected final Map groupNamesMap = new LinkedHashMap<>(); protected final MultiverseInventories plugin; + protected final WorldManager worldManager; public AbstractWorldGroupManager(final MultiverseInventories plugin) { this.plugin = plugin; + this.worldManager = plugin.getServiceLocator().getService(WorldManager.class); } /** @@ -60,7 +63,7 @@ public List getGroupsForWorld(String worldName) { } // Only use the default group for worlds managed by MV-Core if (worldGroups.isEmpty() && plugin.getMVIConfig().isDefaultingUngroupedWorlds() && - plugin.getCore().getMVWorldManager().isMVWorld(worldName)) { + this.worldManager.isWorld(worldName)) { Logging.finer("Returning default group for world: " + worldName); worldGroups.add(getDefaultGroup()); } diff --git a/src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java b/src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java deleted file mode 100644 index c17b4524..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseAdventure.event.MVAResetFinishedEvent; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -/** - * Listener for Multiverse-Adventure events. - */ -public class AdventureListener implements Listener { - - private MultiverseInventories inventories; - - public AdventureListener(MultiverseInventories inventories) { - this.inventories = inventories; - } - - /** - * @param event The Multiverse-Adventure event to handle when a world has finished resetting. - */ - @EventHandler - public void worldReset(MVAResetFinishedEvent event) { - ProfileContainer container = inventories.getWorldProfileContainerStore().getContainer(event.getWorld()); - for (OfflinePlayer player : inventories.getServer().getOfflinePlayers()) { - container.removeAllPlayerData(player); - } - Logging.info("Removed all inventories for Multiverse-Adventure world."); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java b/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java index cd9685d8..0a1d4a4b 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java +++ b/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java @@ -1,7 +1,7 @@ package com.onarandombox.multiverseinventories; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.event.MVDebugModeEvent; +import org.mvplugins.multiverse.core.event.MVDebugModeEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java b/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java index 867e1a97..ad39e3b8 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java +++ b/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java @@ -6,6 +6,8 @@ import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; import io.papermc.lib.PaperLib; import org.bukkit.configuration.file.FileConfiguration; +import org.mvplugins.multiverse.core.api.MVConfig; +import org.mvplugins.multiverse.core.config.MVCoreConfig; import java.io.File; import java.io.IOException; @@ -107,9 +109,11 @@ private List getComments() { private final CommentedYamlConfiguration config; private final MultiverseInventories plugin; + private final MVCoreConfig mvCoreConfig; - InventoriesConfig(MultiverseInventories plugin) throws IOException { + InventoriesConfig(MultiverseInventories plugin, MVCoreConfig mvCoreConfig) throws IOException { this.plugin = plugin; + this.mvCoreConfig = mvCoreConfig; // Make the data folders if (plugin.getDataFolder().mkdirs()) { Logging.fine("Created data folder."); @@ -179,7 +183,7 @@ FileConfiguration getConfig() { * @param globalDebug The new value. 0 = off. */ public void setGlobalDebug(int globalDebug) { - plugin.getCore().getMVConfig().setGlobalDebug(globalDebug); + mvCoreConfig.setGlobalDebug(globalDebug); } /** @@ -188,7 +192,7 @@ public void setGlobalDebug(int globalDebug) { * @return globalDebug. */ public int getGlobalDebug() { - return plugin.getCore().getMVConfig().getGlobalDebug(); + return mvCoreConfig.getGlobalDebug(); } /** diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java b/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java index 992df2a3..b8b1cd38 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java +++ b/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java @@ -1,9 +1,8 @@ package com.onarandombox.multiverseinventories; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.api.MVWorld; -import com.onarandombox.MultiverseCore.event.MVConfigReloadEvent; -import com.onarandombox.MultiverseCore.event.MVVersionEvent; +import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; +import org.mvplugins.multiverse.core.event.MVVersionEvent; import com.onarandombox.multiverseinventories.profile.GlobalProfile; import com.onarandombox.multiverseinventories.profile.PlayerProfile; import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; @@ -32,6 +31,11 @@ import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.inventory.InventoryHolder; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; import java.io.File; @@ -42,14 +46,19 @@ /** * PlayerListener for MultiverseInventories. */ +@Service public class InventoriesListener implements Listener { - private MultiverseInventories inventories; + private final MultiverseInventories inventories; + private final WorldManager worldManager; + private List currentGroups; private Location spawnLoc = null; - public InventoriesListener(MultiverseInventories inventories) { + @Inject + InventoriesListener(@NotNull MultiverseInventories inventories, @NotNull WorldManager worldManager) { this.inventories = inventories; + this.worldManager = worldManager; } /** @@ -219,8 +228,7 @@ public void playerChangedWorld(PlayerChangedWorldEvent event) { return; } // Warn if not managed by Multiverse-Core - if (this.inventories.getCore().getMVWorldManager().getMVWorld(toWorld) == null - || this.inventories.getCore().getMVWorldManager().getMVWorld(fromWorld) == null) { + if (!this.worldManager.isLoadedWorld(toWorld) || !this.worldManager.isLoadedWorld(fromWorld)) { Logging.fine("The from or to world is not managed by Multiverse-Core!"); } @@ -389,8 +397,7 @@ private void handleRespawn(PlayerRespawnEvent event, EventPriority priority) { if (group.getSpawnPriority().equals(priority)) { String spawnWorldName = group.getSpawnWorld(); if (spawnWorldName != null) { - MVWorld mvWorld = this.inventories.getCore() - .getMVWorldManager().getMVWorld(spawnWorldName); + LoadedMultiverseWorld mvWorld = this.worldManager.getLoadedWorld(spawnWorldName).getOrNull(); if (mvWorld != null) { this.spawnLoc = mvWorld.getSpawnLocation(); event.setRespawnLocation(this.spawnLoc); diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java index 9e860235..556d8dae 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java @@ -6,8 +6,8 @@ import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.api.MVCore; -import com.onarandombox.MultiverseCore.api.MVPlugin; +import org.mvplugins.multiverse.core.api.MVCore; +import org.mvplugins.multiverse.core.api.MVPlugin; import com.onarandombox.multiverseinventories.locale.Message; import com.onarandombox.multiverseinventories.locale.Messager; import com.onarandombox.multiverseinventories.locale.Messaging; @@ -26,11 +26,17 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPluginLoader; +import org.mvplugins.multiverse.core.config.MVCoreConfig; +import org.mvplugins.multiverse.core.inject.PluginServiceLocator; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jakarta.inject.Provider; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; /** * Multiverse-Inventories plugin main class. */ +@Service public class MultiverseInventories extends JavaPlugin implements MVPlugin, Messaging { private static final int PROTOCOL = 50; @@ -41,8 +47,11 @@ public static MultiverseInventories getPlugin() { return inventoriesPlugin; } - private final InventoriesListener inventoriesListener = new InventoriesListener(this); - private final AdventureListener adventureListener = new AdventureListener(this); + private PluginServiceLocator serviceLocator; + @Inject + private Provider mvCoreConfig; + @Inject + private Provider inventoriesListener; private Messager messager = new DefaultMessager(this); private WorldGroupManager worldGroupManager = null; @@ -110,12 +119,26 @@ public final void onEnable() { this.getServer().getPluginManager().disablePlugin(this); return; } - Logging.setDebugLevel(core.getMVConfig().getGlobalDebug()); + + initializeDependencyInjection(); + + Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); this.core.incrementPluginCount(); this.onMVPluginEnable(); Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), getProtocolVersion(), getAuthors()); } + private void initializeDependencyInjection() { + serviceLocator = core.getServiceLocatorFactory() + .registerPlugin(new MultiverseInventoriesPluginBinder(this), core.getServiceLocator()) + .flatMap(PluginServiceLocator::enable) + .getOrElseThrow(exception -> { + Logging.severe("Failed to initialize dependency injection!"); + getServer().getPluginManager().disablePlugin(this); + return new RuntimeException(exception); + }); + } + private void onMVPluginEnable() { Perm.register(this); @@ -133,10 +156,7 @@ private void onMVPluginEnable() { //this.getWorldProfileContainerStore().setWorldProfiles(this.getData().getWorldProfiles()); // Register Events - Bukkit.getPluginManager().registerEvents(inventoriesListener, this); - if (Bukkit.getPluginManager().getPlugin("Multiverse-Adventure") != null) { - Bukkit.getPluginManager().registerEvents(adventureListener, this); - } + Bukkit.getPluginManager().registerEvents(inventoriesListener.get(), this); if (getCore().getProtocolVersion() >= 24) { new CoreDebugListener(this); @@ -237,6 +257,11 @@ public String getAuthors() { return authors.toString(); } + @Override + public PluginServiceLocator getServiceLocator() { + return serviceLocator; + } + /** * Builds a String containing Multiverse-Inventories' version info. * @@ -280,7 +305,7 @@ public InventoriesConfig getMVIConfig() { @Override public void reloadConfig() { try { - this.config = new InventoriesConfig(this); + this.config = new InventoriesConfig(this, mvCoreConfig.get()); this.worldGroupManager = new YamlWorldGroupManager(this, this.config.getConfig()); this.worldProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.WORLD); this.groupProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.GROUP); diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java new file mode 100644 index 00000000..c12b1f38 --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java @@ -0,0 +1,19 @@ +package com.onarandombox.multiverseinventories; + +import org.mvplugins.multiverse.core.api.MVPlugin; +import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder; +import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; + +public class MultiverseInventoriesPluginBinder extends JavaPluginBinder { + + protected MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugin) { + super(plugin); + } + + @Override + protected ScopedBindingBuilder bindPluginClass + (ScopedBindingBuilder bindingBuilder) { + return super.bindPluginClass(bindingBuilder).to(MVPlugin.class).to(MultiverseInventories.class); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/command/package-info.java deleted file mode 100644 index 988881fa..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains all Commands. - */ -package com.onarandombox.multiverseinventories.command; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java new file mode 100644 index 00000000..c0e3cd9a --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java @@ -0,0 +1,16 @@ +package com.onarandombox.multiverseinventories.commands; + +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Contract; + +/** + * Base class for all multiverse inventories commands. + */ +@Contract +public abstract class InventoriesCommand extends MultiverseCommand { + protected InventoriesCommand(@NotNull MVCommandManager commandManager) { + super(commandManager); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java new file mode 100644 index 00000000..8c8105f9 --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains all Commands. + */ +package com.onarandombox.multiverseinventories.commands; + diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupControlPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupControlPrompt.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupControlPrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupControlPrompt.java index fa517717..0078cfd7 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupControlPrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupControlPrompt.java @@ -1,33 +1,33 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -public class GroupControlPrompt extends InventoriesPrompt { - - public GroupControlPrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_COMMAND_PROMPT); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equalsIgnoreCase("delete")) { - return new GroupDeletePrompt(plugin, sender); - } else if (s.equalsIgnoreCase("create")) { - return new GroupCreatePrompt(plugin, sender); - } else if (s.equalsIgnoreCase("edit")) { - return new GroupEditPrompt(plugin, sender); - } else { - messager.normal(Message.INVALID_PROMPT_OPTION, sender); - return this; - } - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +public class GroupControlPrompt extends InventoriesPrompt { + + public GroupControlPrompt(final MultiverseInventories plugin, final CommandSender sender) { + super(plugin, sender); + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + return messager.getMessage(Message.GROUP_COMMAND_PROMPT); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + if (s.equalsIgnoreCase("delete")) { + return new GroupDeletePrompt(plugin, sender); + } else if (s.equalsIgnoreCase("create")) { + return new GroupCreatePrompt(plugin, sender); + } else if (s.equalsIgnoreCase("edit")) { + return new GroupEditPrompt(plugin, sender); + } else { + messager.normal(Message.INVALID_PROMPT_OPTION, sender); + return this; + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupCreatePrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupCreatePrompt.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupCreatePrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupCreatePrompt.java index ea03e3c1..79e1ea22 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupCreatePrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupCreatePrompt.java @@ -1,37 +1,37 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupCreatePrompt extends InventoriesPrompt { - - public GroupCreatePrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_CREATE_PROMPT); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); - if (group == null) { - if (s.isEmpty() || !s.matches("^[a-zA-Z0-9][a-zA-Z0-9_]*$")) { - messager.normal(Message.GROUP_INVALID_NAME, sender); - return this; - } - final WorldGroup newGroup = plugin.getGroupManager().newEmptyGroup(s); - return new GroupWorldsPrompt(plugin, sender, newGroup, - new GroupSharesPrompt(plugin, sender, newGroup, Prompt.END_OF_CONVERSATION, true), true); - } else { - messager.normal(Message.GROUP_EXISTS, sender, s); - } - return Prompt.END_OF_CONVERSATION; - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +class GroupCreatePrompt extends InventoriesPrompt { + + public GroupCreatePrompt(final MultiverseInventories plugin, final CommandSender sender) { + super(plugin, sender); + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + return messager.getMessage(Message.GROUP_CREATE_PROMPT); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + final WorldGroup group = plugin.getGroupManager().getGroup(s); + if (group == null) { + if (s.isEmpty() || !s.matches("^[a-zA-Z0-9][a-zA-Z0-9_]*$")) { + messager.normal(Message.GROUP_INVALID_NAME, sender); + return this; + } + final WorldGroup newGroup = plugin.getGroupManager().newEmptyGroup(s); + return new GroupWorldsPrompt(plugin, sender, newGroup, + new GroupSharesPrompt(plugin, sender, newGroup, Prompt.END_OF_CONVERSATION, true), true); + } else { + messager.normal(Message.GROUP_EXISTS, sender, s); + } + return Prompt.END_OF_CONVERSATION; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupDeletePrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupDeletePrompt.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupDeletePrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupDeletePrompt.java index 12617b81..db80b323 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupDeletePrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupDeletePrompt.java @@ -1,42 +1,42 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupDeletePrompt extends InventoriesPrompt { - - public GroupDeletePrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (WorldGroup group : plugin.getGroupManager().getGroups()) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(group.getName()); - } - return messager.getMessage(Message.GROUP_DELETE_PROMPT, builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); - if (group == null) { - messager.normal(Message.ERROR_NO_GROUP, sender, s); - } else { - plugin.getGroupManager().removeGroup(group); - messager.normal(Message.GROUP_REMOVED, sender, s); - } - return Prompt.END_OF_CONVERSATION; - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +class GroupDeletePrompt extends InventoriesPrompt { + + public GroupDeletePrompt(final MultiverseInventories plugin, final CommandSender sender) { + super(plugin, sender); + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (WorldGroup group : plugin.getGroupManager().getGroups()) { + if (builder.length() == 0) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(group.getName()); + } + return messager.getMessage(Message.GROUP_DELETE_PROMPT, builder.toString()); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + final WorldGroup group = plugin.getGroupManager().getGroup(s); + if (group == null) { + messager.normal(Message.ERROR_NO_GROUP, sender, s); + } else { + plugin.getGroupManager().removeGroup(group); + messager.normal(Message.GROUP_REMOVED, sender, s); + } + return Prompt.END_OF_CONVERSATION; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupEditPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupEditPrompt.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupEditPrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupEditPrompt.java index 395e99f3..28c0526a 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupEditPrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupEditPrompt.java @@ -1,41 +1,41 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupEditPrompt extends InventoriesPrompt { - - public GroupEditPrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (WorldGroup group : plugin.getGroupManager().getGroups()) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(group.getName()); - } - return messager.getMessage(Message.GROUP_EDIT_PROMPT, builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); - if (group == null) { - messager.normal(Message.ERROR_NO_GROUP, sender, s); - } else { - return new GroupModifyPrompt(plugin, sender, group); - } - return Prompt.END_OF_CONVERSATION; - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +class GroupEditPrompt extends InventoriesPrompt { + + public GroupEditPrompt(final MultiverseInventories plugin, final CommandSender sender) { + super(plugin, sender); + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (WorldGroup group : plugin.getGroupManager().getGroups()) { + if (builder.length() == 0) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(group.getName()); + } + return messager.getMessage(Message.GROUP_EDIT_PROMPT, builder.toString()); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + final WorldGroup group = plugin.getGroupManager().getGroup(s); + if (group == null) { + messager.normal(Message.ERROR_NO_GROUP, sender, s); + } else { + return new GroupModifyPrompt(plugin, sender, group); + } + return Prompt.END_OF_CONVERSATION; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupModifyPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupModifyPrompt.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupModifyPrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupModifyPrompt.java index 87a4abd4..abe6cedb 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupModifyPrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupModifyPrompt.java @@ -1,36 +1,36 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupModifyPrompt extends InventoriesPrompt { - - protected final WorldGroup group; - - public GroupModifyPrompt(final MultiverseInventories plugin, final CommandSender sender, - final WorldGroup group) { - super(plugin, sender); - this.group = group; - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_MODIFY_PROMPT, group.getName()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equalsIgnoreCase("worlds")) { - return new GroupWorldsPrompt(plugin, sender, group, this, false); - } else if (s.equalsIgnoreCase("shares")) { - return new GroupSharesPrompt(plugin, sender, group, this, false); - } else { - messager.normal(Message.INVALID_PROMPT_OPTION, sender); - return this; - } - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +class GroupModifyPrompt extends InventoriesPrompt { + + protected final WorldGroup group; + + public GroupModifyPrompt(final MultiverseInventories plugin, final CommandSender sender, + final WorldGroup group) { + super(plugin, sender); + this.group = group; + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + return messager.getMessage(Message.GROUP_MODIFY_PROMPT, group.getName()); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + if (s.equalsIgnoreCase("worlds")) { + return new GroupWorldsPrompt(plugin, sender, group, this, false); + } else if (s.equalsIgnoreCase("shares")) { + return new GroupSharesPrompt(plugin, sender, group, this, false); + } else { + messager.normal(Message.INVALID_PROMPT_OPTION, sender); + return this; + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupSharesPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupSharesPrompt.java similarity index 95% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupSharesPrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupSharesPrompt.java index f2071f52..d9bc36b1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupSharesPrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupSharesPrompt.java @@ -1,80 +1,80 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupSharesPrompt extends InventoriesPrompt { - - protected final WorldGroup group; - protected final Prompt nextPrompt; - protected final boolean isCreating; - protected final Shares shares; - - public GroupSharesPrompt(final MultiverseInventories plugin, final CommandSender sender, - final WorldGroup group, final Prompt nextPrompt, - final boolean creatingGroup) { - super(plugin, sender); - this.group = group; - this.nextPrompt = nextPrompt; - this.isCreating = creatingGroup; - this.shares = Sharables.fromShares(group.getShares()); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (final Sharable sharable : shares) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(sharable.toString()); - } - return messager.getMessage(Message.GROUP_SHARES_PROMPT, group.getName(), builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equals("@")) { - group.getShares().clear(); - group.getShares().addAll(this.shares); - plugin.getGroupManager().updateGroup(group); - if (isCreating) { - messager.normal(Message.GROUP_CREATION_COMPLETE, sender); - } else { - messager.normal(Message.GROUP_UPDATED, sender); - } - messager.normal(Message.INFO_GROUP, sender, group.getName()); - messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); - plugin.getGroupManager().checkForConflicts(sender); - return nextPrompt; - } - - boolean negative = false; - Shares shares = Sharables.lookup(s.toLowerCase()); - if (shares == null && s.startsWith("-") && s.length() > 1) { - negative = true; - shares = Sharables.lookup(s.toLowerCase().substring(1)); - } - - if (shares == null) { - messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); - return this; - } - if (negative) { - this.shares.removeAll(shares); - return this; - } - this.shares.addAll(shares); - return this; - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.share.Sharable; +import com.onarandombox.multiverseinventories.share.Sharables; +import com.onarandombox.multiverseinventories.share.Shares; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +class GroupSharesPrompt extends InventoriesPrompt { + + protected final WorldGroup group; + protected final Prompt nextPrompt; + protected final boolean isCreating; + protected final Shares shares; + + public GroupSharesPrompt(final MultiverseInventories plugin, final CommandSender sender, + final WorldGroup group, final Prompt nextPrompt, + final boolean creatingGroup) { + super(plugin, sender); + this.group = group; + this.nextPrompt = nextPrompt; + this.isCreating = creatingGroup; + this.shares = Sharables.fromShares(group.getShares()); + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (final Sharable sharable : shares) { + if (builder.length() == 0) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(sharable.toString()); + } + return messager.getMessage(Message.GROUP_SHARES_PROMPT, group.getName(), builder.toString()); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + if (s.equals("@")) { + group.getShares().clear(); + group.getShares().addAll(this.shares); + plugin.getGroupManager().updateGroup(group); + if (isCreating) { + messager.normal(Message.GROUP_CREATION_COMPLETE, sender); + } else { + messager.normal(Message.GROUP_UPDATED, sender); + } + messager.normal(Message.INFO_GROUP, sender, group.getName()); + messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); + plugin.getGroupManager().checkForConflicts(sender); + return nextPrompt; + } + + boolean negative = false; + Shares shares = Sharables.lookup(s.toLowerCase()); + if (shares == null && s.startsWith("-") && s.length() > 1) { + negative = true; + shares = Sharables.lookup(s.toLowerCase().substring(1)); + } + + if (shares == null) { + messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); + return this; + } + if (negative) { + this.shares.removeAll(shares); + return this; + } + this.shares.addAll(shares); + return this; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupWorldsPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupWorldsPrompt.java similarity index 95% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupWorldsPrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupWorldsPrompt.java index 6d428863..c5fe2ffa 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupWorldsPrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupWorldsPrompt.java @@ -1,87 +1,87 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -import java.util.HashSet; -import java.util.Set; - -class GroupWorldsPrompt extends InventoriesPrompt { - - protected final WorldGroup group; - protected final Prompt nextPrompt; - protected final boolean isCreating; - protected final Set worlds; - - public GroupWorldsPrompt(final MultiverseInventories plugin, final CommandSender sender, - final WorldGroup group, final Prompt nextPrompt, - final boolean creatingGroup) { - super(plugin, sender); - this.group = group; - this.nextPrompt = nextPrompt; - this.isCreating = creatingGroup; - this.worlds = new HashSet(group.getWorlds()); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (final String world : worlds) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(world); - } - return messager.getMessage(Message.GROUP_WORLDS_PROMPT, group.getName(), builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equals("@")) { - if (worlds.isEmpty()) { - messager.normal(Message.GROUP_WORLDS_EMPTY, sender); - return this; - } - group.removeAllWorlds(false); - group.addWorlds(worlds, false); - if (!isCreating) { - plugin.getGroupManager().updateGroup(group); - messager.normal(Message.GROUP_UPDATED, sender); - messager.normal(Message.INFO_GROUP, sender, group.getName()); - messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); - } - return nextPrompt; - } - - boolean negative = false; - World world = Bukkit.getWorld(s); - if (world == null && s.startsWith("-") && s.length() > 1) { - negative = true; - world = Bukkit.getWorld(s.substring(1)); - } - - if (world == null) { - messager.normal(Message.ERROR_NO_WORLD, sender, s); - return this; - } - if (negative) { - if (!worlds.contains(world.getName())) { - messager.normal(Message.WORLD_NOT_IN_GROUP, sender, world.getName(), group.getName()); - return this; - } - worlds.remove(world.getName()); - return this; - } - worlds.add(world.getName()); - return this; - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +import java.util.HashSet; +import java.util.Set; + +class GroupWorldsPrompt extends InventoriesPrompt { + + protected final WorldGroup group; + protected final Prompt nextPrompt; + protected final boolean isCreating; + protected final Set worlds; + + public GroupWorldsPrompt(final MultiverseInventories plugin, final CommandSender sender, + final WorldGroup group, final Prompt nextPrompt, + final boolean creatingGroup) { + super(plugin, sender); + this.group = group; + this.nextPrompt = nextPrompt; + this.isCreating = creatingGroup; + this.worlds = new HashSet(group.getWorlds()); + } + + @Override + public String getPromptText(final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (final String world : worlds) { + if (builder.length() == 0) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(world); + } + return messager.getMessage(Message.GROUP_WORLDS_PROMPT, group.getName(), builder.toString()); + } + + @Override + public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + if (s.equals("@")) { + if (worlds.isEmpty()) { + messager.normal(Message.GROUP_WORLDS_EMPTY, sender); + return this; + } + group.removeAllWorlds(false); + group.addWorlds(worlds, false); + if (!isCreating) { + plugin.getGroupManager().updateGroup(group); + messager.normal(Message.GROUP_UPDATED, sender); + messager.normal(Message.INFO_GROUP, sender, group.getName()); + messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); + } + return nextPrompt; + } + + boolean negative = false; + World world = Bukkit.getWorld(s); + if (world == null && s.startsWith("-") && s.length() > 1) { + negative = true; + world = Bukkit.getWorld(s.substring(1)); + } + + if (world == null) { + messager.normal(Message.ERROR_NO_WORLD, sender, s); + return this; + } + if (negative) { + if (!worlds.contains(world.getName())) { + messager.normal(Message.WORLD_NOT_IN_GROUP, sender, world.getName(), group.getName()); + return this; + } + worlds.remove(world.getName()); + return this; + } + worlds.add(world.getName()); + return this; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/InventoriesPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/InventoriesPrompt.java similarity index 89% rename from src/main/java/com/onarandombox/multiverseinventories/command/prompts/InventoriesPrompt.java rename to src/main/java/com/onarandombox/multiverseinventories/commands/prompts/InventoriesPrompt.java index 7ec9f212..2e7324b7 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/InventoriesPrompt.java +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/InventoriesPrompt.java @@ -1,25 +1,25 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Messager; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -abstract class InventoriesPrompt implements Prompt { - - protected final MultiverseInventories plugin; - protected final Messager messager; - protected final CommandSender sender; - - InventoriesPrompt(final MultiverseInventories plugin, final CommandSender sender) { - this.plugin = plugin; - this.messager = plugin.getMessager(); - this.sender = sender; - } - - @Override - public boolean blocksForInput(final ConversationContext conversationContext) { - return true; - } -} +package com.onarandombox.multiverseinventories.commands.prompts; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.locale.Messager; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; + +abstract class InventoriesPrompt implements Prompt { + + protected final MultiverseInventories plugin; + protected final Messager messager; + protected final CommandSender sender; + + InventoriesPrompt(final MultiverseInventories plugin, final CommandSender sender) { + this.plugin = plugin; + this.messager = plugin.getMessager(); + this.sender = sender; + } + + @Override + public boolean blocksForInput(final ConversationContext conversationContext) { + return true; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java b/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java index c66aec90..cef0e602 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java +++ b/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java @@ -13,6 +13,8 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; +import org.mvplugins.multiverse.core.economy.MVEconomist; + import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -37,6 +39,7 @@ public final class Sharables implements Shares { static final Map LOOKUP_MAP = new HashMap(); private static MultiverseInventories inventories = null; + private static MVEconomist economist = null; /** * Initialize this class with the instance of Inventories. @@ -47,6 +50,9 @@ public static void init(MultiverseInventories inventories) { if (Sharables.inventories == null) { Sharables.inventories = inventories; } + if (Sharables.economist == null) { + Sharables.economist = inventories.getServiceLocator().getService(MVEconomist.class); + } } /** @@ -536,7 +542,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable ECONOMY = new Sharable.Builder("economy", Double.class, new SharableHandler() { private boolean hasValidEconomyHandler() { - if (inventories.getCore().getEconomist().isUsingEconomyPlugin()) { + if (economist.isUsingEconomyPlugin()) { return true; } Logging.warning("You do not have an an economy plugin with Vault. Economy sharable will not work!"); @@ -550,7 +556,7 @@ public void updateProfile(PlayerProfile profile, Player player) { if (!hasValidEconomyHandler()) { return; } - profile.set(ECONOMY, inventories.getCore().getEconomist().getBalance(player)); + profile.set(ECONOMY, economist.getBalance(player)); } @Override @@ -560,10 +566,10 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { } Double money = profile.get(ECONOMY); if (money == null) { - inventories.getCore().getEconomist().setBalance(player, 0); + economist.setBalance(player, 0); return false; } - inventories.getCore().getEconomist().setBalance(player, money); + economist.setBalance(player, money); return true; } }).stringSerializer(new ProfileEntry(false, "balance")).optional() From e841662f476a3ebb1c8d73643b0fc10fbfeeb3cc Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:55:50 +0800 Subject: [PATCH 007/180] Fix build issue with hk2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 583f0639..6feb2906 100644 --- a/build.gradle +++ b/build.gradle @@ -167,7 +167,7 @@ tasks.register('runHabitatGenerator', JavaExec) { args = [ '--file', "build/libs/multiverse-inventories-$version" + ".jar", - '--locator', 'Multiverse-Portals', + '--locator', 'Multiverse-Inventories', ] } tasks.named("build") { finalizedBy("runHabitatGenerator") } From bb1cbb57e08db1b08cb812218605c014e08df3b2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:56:10 +0800 Subject: [PATCH 008/180] Implement group, info, list, reload and toggle commands --- .../MultiverseInventories.java | 14 ++- .../commands/GroupCommand.java | 46 +++++++ .../commands/InfoCommand.java | 113 ++++++++++++++++++ .../commands/ListCommand.java | 49 ++++++++ .../commands/ReloadCommand.java | 35 ++++++ .../commands/ToggleCommand.java | 71 +++++++++++ src/main/resources/plugin.yml | 105 +--------------- 7 files changed, 329 insertions(+), 104 deletions(-) create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java create mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java index 556d8dae..143d72a1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java @@ -6,6 +6,7 @@ import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; +import com.onarandombox.multiverseinventories.commands.InventoriesCommand; import org.mvplugins.multiverse.core.api.MVCore; import org.mvplugins.multiverse.core.api.MVPlugin; import com.onarandombox.multiverseinventories.locale.Message; @@ -26,11 +27,13 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPluginLoader; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jakarta.inject.Provider; import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.vavr.control.Try; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; /** @@ -48,6 +51,9 @@ public static MultiverseInventories getPlugin() { } private PluginServiceLocator serviceLocator; + + @Inject + private Provider commandManager; @Inject private Provider mvCoreConfig; @Inject @@ -195,7 +201,13 @@ public void onDisable() { } private void registerCommands() { - + Try.of(() -> commandManager.get()) + .andThenTry(commandManager -> serviceLocator.getAllServices(InventoriesCommand.class) + .forEach(commandManager::registerCommand)) + .onFailure(e -> { + Logging.severe("Failed to register commands"); + e.printStackTrace(); + }); } private void hookImportables() { diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java new file mode 100644 index 00000000..9bf071cc --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java @@ -0,0 +1,46 @@ +package com.onarandombox.multiverseinventories.commands; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.commands.prompts.GroupControlPrompt; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.Conversable; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationFactory; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; + +@Service +@CommandAlias("mvinv") +class GroupCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + GroupCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + super(commandManager); + this.plugin = plugin; + } + + @CommandAlias("mvinvgroup|mvinvg") + @Subcommand("group") + @CommandPermission("multiverse.inventories.group") + @Description("Manage a world group wiht prompts!") + void onGroupCommand(@NotNull CommandSender sender) { + if (!(sender instanceof Conversable conversable)) { + this.plugin.getMessager().normal(Message.NON_CONVERSABLE, sender); + return; + } + Conversation conversation = new ConversationFactory(plugin) + .withFirstPrompt(new GroupControlPrompt(plugin, sender)) + .withEscapeSequence("##") + .withModality(false).buildConversation(conversable); + conversation.begin(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java b/src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java new file mode 100644 index 00000000..e0a75853 --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java @@ -0,0 +1,113 @@ +package com.onarandombox.multiverseinventories.commands; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Optional; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; + +import java.util.List; +import java.util.Set; + +@Service +@CommandAlias("mvinv") +class InfoCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + InfoCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + super(commandManager); + this.plugin = plugin; + } + + @CommandAlias("mvinvinfo|mvinvi") + @Subcommand("info") + @CommandPermission("multiverse.inventories.info") + @CommandCompletion("@mvworlds") + @Syntax("") + @Description("World and Group Information") + void onInfoCommand( + @NotNull CommandSender sender, + + @Optional + @Single + @Syntax("") + @Description("World or Group") + @NotNull String name + ) { + if (name == null) { + if (!(sender instanceof Player)) { + this.plugin.getMessager().normal(Message.INFO_ZERO_ARG, sender); + return; + } + name = ((Player) sender).getWorld().getName(); + } + + ProfileContainer worldProfileContainer = this.plugin.getWorldProfileContainerStore().getContainer(name); + plugin.getMessager().normal(Message.INFO_WORLD, sender, name); + if (worldProfileContainer != null && Bukkit.getWorld(worldProfileContainer.getContainerName()) != null) { + worldInfo(sender, worldProfileContainer); + } else { + plugin.getMessager().normal(Message.ERROR_NO_WORLD_PROFILE, sender, name); + } + WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(name); + this.plugin.getMessager().normal(Message.INFO_GROUP, sender, name); + if (worldGroup != null) { + this.groupInfo(sender, worldGroup); + } else { + this.plugin.getMessager().normal(Message.ERROR_NO_GROUP, sender, name); + } + } + + private void groupInfo(CommandSender sender, WorldGroup worldGroup) { + StringBuilder worldsString = new StringBuilder(); + Set worlds = worldGroup.getWorlds(); + if (worlds.isEmpty()) { + worldsString.append("N/A"); + } else { + for (String world : worlds) { + if (!worldsString.toString().isEmpty()) { + worldsString.append(", "); + } + worldsString.append(world); + } + } + this.plugin.getMessager().normal(Message.INFO_GROUPS_INFO, + sender, worldsString, worldGroup.getShares().toString()); + } + + private void worldInfo(CommandSender sender, ProfileContainer worldProfileContainer) { + StringBuilder groupsString = new StringBuilder(); + List worldGroups = this.plugin.getGroupManager() + .getGroupsForWorld(worldProfileContainer.getContainerName()); + + if (worldGroups.isEmpty()) { + groupsString.append("N/A"); + } else { + for (WorldGroup worldGroup : worldGroups) { + if (!groupsString.toString().isEmpty()) { + groupsString.append(", "); + } + groupsString.append(worldGroup.getName()); + } + } + + this.plugin.getMessager().normal(Message.INFO_WORLD_INFO, + sender, groupsString.toString()); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java b/src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java new file mode 100644 index 00000000..dd42e754 --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java @@ -0,0 +1,49 @@ +package com.onarandombox.multiverseinventories.commands; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.WorldGroup; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; + +import java.util.Collection; + +@Service +@CommandAlias("mvinv") +class ListCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + ListCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + super(commandManager); + this.plugin = plugin; + } + + @CommandAlias("mvinvlist|mvinvl") + @Subcommand("list") + @CommandPermission("multiverse.inventories.list") + @Description("World and Group Information") + void onListCommand(@NotNull CommandSender sender) { + Collection groups = this.plugin.getGroupManager().getGroups(); + String groupsString = "N/A"; + if (!groups.isEmpty()) { + StringBuilder builder = new StringBuilder(); + for (WorldGroup group : groups) { + if (!builder.toString().isEmpty()) { + builder.append(", "); + } + builder.append(group.getName()); + } + groupsString = builder.toString(); + } + this.plugin.getMessager().normal(Message.LIST_GROUPS, sender, groupsString); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java b/src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java new file mode 100644 index 00000000..b9246dc9 --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java @@ -0,0 +1,35 @@ +package com.onarandombox.multiverseinventories.commands; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.locale.Message; +import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; + +@Service +@CommandAlias("mvinv") +class ReloadCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + ReloadCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + super(commandManager); + this.plugin = plugin; + } + + @CommandAlias("mvinvreload") + @Subcommand("reload") + @CommandPermission("multiverse.inventories.reload") + @Description("Reloads config file") + void onReloadCommand(@NotNull CommandSender sender) { + this.plugin.reloadConfig(); + this.plugin.getMessager().normal(Message.RELOAD_COMPLETE, sender); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java b/src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java new file mode 100644 index 00000000..9a7e66b4 --- /dev/null +++ b/src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java @@ -0,0 +1,71 @@ +package com.onarandombox.multiverseinventories.commands; + +import com.onarandombox.multiverseinventories.MultiverseInventories; +import com.onarandombox.multiverseinventories.locale.Message; +import com.onarandombox.multiverseinventories.share.Sharable; +import com.onarandombox.multiverseinventories.share.Sharables; +import com.onarandombox.multiverseinventories.share.Shares; +import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; + +@Service +@CommandAlias("mvinv") +class ToggleCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + ToggleCommand(MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + super(commandManager); + this.plugin = plugin; + } + + @CommandAlias("mvinvtoggle") + @Subcommand("toggle") + @CommandPermission("multiverse.inventories.addshares") + @CommandCompletion("economy|last_location") + @Syntax("") + @Description("Toggles the usage of optional sharables") + void onToggleCommand( + @NotNull CommandSender sender, + + @Single + @Syntax("") + @Description("Share to toggle") + @NotNull String shareName + ) { + Shares shares = Sharables.lookup(shareName.toLowerCase()); + if (shares == null) { + this.plugin.getMessager().normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); + return; + } + boolean foundOpt = false; + for (Sharable sharable : shares) { + if (sharable.isOptional()) { + foundOpt = true; + if (this.plugin.getMVIConfig().getOptionalShares().contains(sharable)) { + this.plugin.getMVIConfig().getOptionalShares().remove(sharable); + this.plugin.getMessager().normal(Message.NOW_NOT_USING_OPTIONAL, sender, sharable.getNames()[0]); + } else { + this.plugin.getMVIConfig().getOptionalShares().add(sharable); + this.plugin.getMessager().normal(Message.NOW_USING_OPTIONAL, sender, sharable.getNames()[0]); + } + } + } + if (foundOpt) { + this.plugin.getMVIConfig().save(); + } else { + this.plugin.getMessager().normal(Message.NO_OPTIONAL_SHARES, sender, shareName); + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f49dcf43..0679898b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,107 +2,6 @@ name: Multiverse-Inventories main: com.onarandombox.multiverseinventories.MultiverseInventories version: ${version} api-version: 1.13 -authors: ['dumptruckman'] +authors: ['dumptruckman', 'benwoo1110'] depend: ['Multiverse-Core'] -softdepend: [MultiInv, WorldInventories, Multiverse-Adventure] - -commands: - mvinv: - description: Generic Multiverse-Inventories Command - usage: / - mvinvinfo: - description: Generic Multiverse-Inventories Command - usage: / - mvinvi: - description: Generic Multiverse-Inventories Command - usage: / - mvinvim: - description: Generic Multiverse-Inventories Command - usage: / - mvinvimport: - description: Generic Multiverse-Inventories Command - usage: / - mvinvlist: - description: Generic Multiverse-Inventories Command - usage: / - mvinvl: - description: Generic Multiverse-Inventories Command - usage: / - mvinvreload: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaddshares: - description: Generic Multiverse-Inventories Command - usage: / - mvinvadds: - description: Generic Multiverse-Inventories Command - usage: / - mvinvas: - description: Generic Multiverse-Inventories Command - usage: / - mvinvraddshare: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaddworld: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaddw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoveshares: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoveshare: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoves: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmshares: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmshare: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrms: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrs: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoveworld: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremovew: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmworld: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvspawn: - description: Generic Multiverse-Inventories Command - usage: / - mvinvs: - description: Generic Multiverse-Inventories Command - usage: / - ispawn: - description: Generic Multiverse-Inventories Command - usage: / - gspawn: - description: Generic Multiverse-Inventories Command - usage: / - mvinvdebug: - description: Generic Multiverse-Inventories Command - usage: / - mvinvd: - description: Generic Multiverse-Inventories Command - usage: / +softdepend: [MultiInv, WorldInventories] From fde6c67ff67438c9217b60f9c52e4974328f9cc8 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:00:32 +0800 Subject: [PATCH 009/180] Refactor to new package naming --- build.gradle | 12 +- .../commands/package-info.java | 5 - .../share/package-info.java | 5 - .../util/package-info.java | 5 - .../AbstractWorldGroupManager.java | 12 +- .../inventories}/CoreDebugListener.java | 2 +- .../multiverse/inventories}/DataStrings.java | 2 +- .../inventories}/DefaultMessageProvider.java | 14 +- .../inventories}/DefaultMessager.java | 11 +- .../DefaultPersistingProfile.java | 8 +- .../FlatFileProfileDataSource.java | 1196 ++++++++--------- .../inventories}/GameModeShareHandler.java | 190 +-- .../inventories}/InventoriesConfig.java | 672 ++++----- .../inventories}/InventoriesDupingPatch.java | 2 +- .../inventories}/InventoriesListener.java | 10 +- .../inventories}/MultiverseInventories.java | 24 +- .../MultiverseInventoriesPluginBinder.java | 2 +- .../multiverse/inventories}/PlayerStats.java | 138 +- .../multiverse/inventories}/ShareHandler.java | 12 +- .../inventories}/ShareHandlingUpdater.java | 10 +- .../inventories}/WeakProfileContainer.java | 246 ++-- .../WeakProfileContainerStore.java | 82 +- .../inventories}/WorldChangeShareHandler.java | 462 +++---- .../multiverse/inventories}/WorldGroup.java | 494 +++---- .../inventories}/YamlWorldGroupManager.java | 8 +- .../inventories}/blacklist/ItemBlacklist.java | 2 +- .../blacklist/SimpleItemBlacklist.java | 2 +- .../inventories}/blacklist/package-info.java | 2 +- .../inventories}/commands/GroupCommand.java | 8 +- .../inventories}/commands/InfoCommand.java | 10 +- .../commands/InventoriesCommand.java | 2 +- .../inventories}/commands/ListCommand.java | 8 +- .../inventories}/commands/ReloadCommand.java | 6 +- .../inventories}/commands/ToggleCommand.java | 12 +- .../inventories/commands/package-info.java | 5 + .../commands/prompts/GroupControlPrompt.java | 6 +- .../commands/prompts/GroupCreatePrompt.java | 8 +- .../commands/prompts/GroupDeletePrompt.java | 8 +- .../commands/prompts/GroupEditPrompt.java | 8 +- .../commands/prompts/GroupModifyPrompt.java | 8 +- .../commands/prompts/GroupSharesPrompt.java | 14 +- .../commands/prompts/GroupWorldsPrompt.java | 8 +- .../commands/prompts/InventoriesPrompt.java | 6 +- .../GameModeChangeShareHandlingEvent.java | 4 +- .../event/ShareHandlingEvent.java | 6 +- .../event/WorldChangeShareHandlingEvent.java | 4 +- .../inventories}/event/package-info.java | 2 +- .../locale/LazyLocaleMessageProvider.java | 2 +- .../locale/LocalizationLoadingException.java | 2 +- .../inventories}/locale/Message.java | 226 ++-- .../inventories}/locale/MessageProvider.java | 2 +- .../inventories}/locale/Messager.java | 2 +- .../inventories}/locale/Messaging.java | 2 +- .../locale/NoSuchLocalizationException.java | 2 +- .../inventories}/locale/package-info.java | 2 +- .../inventories}/migration/DataImporter.java | 2 +- .../inventories}/migration/ImportManager.java | 142 +- .../migration/MigrationException.java | 2 +- .../multiinv/MIInventoryConverter.java | 4 +- .../multiinv/MIInventoryInterface.java | 2 +- .../multiinv/MIInventoryOldWrapper.java | 2 +- .../multiinv/MIInventoryWrapper.java | 2 +- .../multiinv/MIPlayerFileLoader.java | 228 ++-- .../migration/multiinv/MultiInvImporter.java | 334 ++--- .../migration/multiinv/package-info.java | 2 +- .../inventories}/migration/package-info.java | 2 +- .../WorldInventoriesImporter.java | 548 ++++---- .../worldinventories/package-info.java | 2 +- .../multiverse/inventories}/package-info.java | 2 +- .../inventories}/profile/GlobalProfile.java | 2 +- .../profile/GroupingConflict.java | 144 +- .../inventories}/profile/PlayerProfile.java | 8 +- .../profile/ProfileDataSource.java | 208 +-- .../inventories}/profile/ProfileKey.java | 224 +-- .../inventories}/profile/ProfileType.java | 78 +- .../inventories}/profile/ProfileTypes.java | 94 +- .../profile/WorldGroupManager.java | 4 +- .../profile/container/ContainerType.java | 34 +- .../profile/container/ProfileContainer.java | 6 +- .../container/ProfileContainerStore.java | 46 +- .../inventories}/profile/package-info.java | 10 +- .../inventories}/share/DefaultSerializer.java | 60 +- .../inventories}/share/DefaultSharable.java | 166 +-- .../share/DefaultStringSerializer.java | 102 +- .../share/InventorySerializer.java | 116 +- .../share/LocationSerializer.java | 72 +- .../inventories}/share/PersistingProfile.java | 4 +- .../share/PotionEffectSerializer.java | 72 +- .../inventories}/share/ProfileEntry.java | 140 +- .../inventories}/share/Sharable.java | 312 ++--- .../inventories}/share/SharableEntry.java | 40 +- .../inventories}/share/SharableGroup.java | 268 ++-- .../inventories}/share/SharableHandler.java | 4 +- .../share/SharableSerializer.java | 58 +- .../inventories}/share/Sharables.java | 14 +- .../multiverse/inventories}/share/Shares.java | 112 +- .../inventories/share/package-info.java | 5 + .../util/CommentedYamlConfiguration.java | 2 +- .../util/DeserializationException.java | 2 +- .../multiverse/inventories}/util/Font.java | 2 +- .../inventories}/util/MinecraftTools.java | 76 +- .../multiverse/inventories}/util/Perm.java | 452 +++---- .../inventories/util/package-info.java | 5 + src/main/resources/plugin.yml | 2 +- .../inventories}/FlatFileDataHelper.java | 46 +- .../multiverse/inventories}/TestCommands.java | 6 +- .../TestCommentedYamlConfiguration.java | 4 +- .../inventories}/TestPerformance.java | 12 +- .../inventories}/TestPlayerNameChange.java | 8 +- .../inventories}/TestResetWorld.java | 4 +- .../inventories}/TestWSharableAPI.java | 16 +- .../inventories}/TestWorldChanged.java | 10 +- .../inventories}/util/MVTestLogFormatter.java | 2 +- .../inventories}/util/MockItemMeta.java | 2 +- .../inventories}/util/MockPlayerFactory.java | 4 +- .../util/MockPlayerInventory.java | 4 +- .../inventories}/util/MockWorldFactory.java | 2 +- .../util/TestInstanceCreator.java | 6 +- .../multiverse/inventories}/util/Util.java | 2 +- .../util/WorldCreatorMatcher.java | 2 +- 120 files changed, 4176 insertions(+), 4175 deletions(-) delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/share/package-info.java delete mode 100644 src/main/java/com/onarandombox/multiverseinventories/util/package-info.java rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/AbstractWorldGroupManager.java (95%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/CoreDebugListener.java (91%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/DataStrings.java (99%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/DefaultMessageProvider.java (93%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/DefaultMessager.java (86%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/DefaultPersistingProfile.java (70%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/FlatFileProfileDataSource.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/GameModeShareHandler.java (82%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/InventoriesConfig.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/InventoriesDupingPatch.java (98%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/InventoriesListener.java (98%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/MultiverseInventories.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/MultiverseInventoriesPluginBinder.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/PlayerStats.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/ShareHandler.java (93%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/ShareHandlingUpdater.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/WeakProfileContainer.java (82%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/WeakProfileContainerStore.java (77%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/WorldChangeShareHandler.java (90%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/WorldGroup.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/YamlWorldGroupManager.java (97%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/blacklist/ItemBlacklist.java (52%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/blacklist/SimpleItemBlacklist.java (66%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/blacklist/package-info.java (73%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/GroupCommand.java (86%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/InfoCommand.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/InventoriesCommand.java (90%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/ListCommand.java (87%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/ReloadCommand.java (87%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/ToggleCommand.java (88%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupControlPrompt.java (84%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupCreatePrompt.java (83%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupDeletePrompt.java (84%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupEditPrompt.java (84%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupModifyPrompt.java (81%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupSharesPrompt.java (86%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/GroupWorldsPrompt.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/commands/prompts/InventoriesPrompt.java (76%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/event/GameModeChangeShareHandlingEvent.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/event/ShareHandlingEvent.java (91%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/event/WorldChangeShareHandlingEvent.java (91%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/event/package-info.java (69%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/LazyLocaleMessageProvider.java (96%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/LocalizationLoadingException.java (95%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/Message.java (96%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/MessageProvider.java (97%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/Messager.java (97%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/Messaging.java (87%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/NoSuchLocalizationException.java (91%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/locale/package-info.java (59%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/DataImporter.java (87%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/ImportManager.java (83%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/MigrationException.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/MIInventoryConverter.java (85%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/MIInventoryInterface.java (84%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/MIInventoryOldWrapper.java (91%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/MIInventoryWrapper.java (90%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/MIPlayerFileLoader.java (93%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/MultiInvImporter.java (89%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/multiinv/package-info.java (55%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/package-info.java (67%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/worldinventories/WorldInventoriesImporter.java (91%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/migration/worldinventories/package-info.java (54%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/package-info.java (54%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/GlobalProfile.java (98%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/GroupingConflict.java (87%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/PlayerProfile.java (93%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/ProfileDataSource.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/ProfileKey.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/ProfileType.java (90%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/ProfileTypes.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/WorldGroupManager.java (97%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/container/ContainerType.java (76%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/container/ProfileContainer.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/container/ProfileContainerStore.java (85%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/profile/package-info.java (64%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/DefaultSerializer.java (87%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/DefaultSharable.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/DefaultStringSerializer.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/InventorySerializer.java (90%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/LocationSerializer.java (88%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/PersistingProfile.java (82%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/PotionEffectSerializer.java (79%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/ProfileEntry.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/Sharable.java (95%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/SharableEntry.java (83%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/SharableGroup.java (94%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/SharableHandler.java (92%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/SharableSerializer.java (93%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/Sharables.java (98%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/share/Shares.java (93%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/CommentedYamlConfiguration.java (99%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/DeserializationException.java (82%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/Font.java (99%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/MinecraftTools.java (90%) rename src/main/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/Perm.java (95%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/FlatFileDataHelper.java (72%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestCommands.java (97%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestCommentedYamlConfiguration.java (97%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestPerformance.java (97%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestPlayerNameChange.java (96%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestResetWorld.java (97%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestWSharableAPI.java (95%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/TestWorldChanged.java (98%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/MVTestLogFormatter.java (96%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/MockItemMeta.java (98%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/MockPlayerFactory.java (98%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/MockPlayerInventory.java (98%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/MockWorldFactory.java (99%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/TestInstanceCreator.java (99%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/Util.java (97%) rename src/test/java/{com/onarandombox/multiverseinventories => org/mvplugins/multiverse/inventories}/util/WorldCreatorMatcher.java (97%) diff --git a/build.gradle b/build.gradle index 6feb2906..d8ecd9c0 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } version = System.getenv('GITHUB_VERSION') ?: 'local' -group = 'com.onarandombox.multiverseinventories' +group = 'org.mvplugins.multiverse.inventories' description = 'Multiverse-Inventories' compileJava { @@ -146,11 +146,11 @@ javadoc { project.configurations.api.canBeResolved = true shadowJar { - relocate 'com.dumptruckman.minecraft.util.Logging', 'com.onarandombox.multiverseinventories.utils.InvLogging' - relocate 'com.dumptruckman.minecraft.util.DebugLog', 'com.onarandombox.multiverseinventories.utils.DebugFileLogger' - relocate 'com.dumptruckman.bukkit.configuration', 'com.onarandombox.multiverseinventories.utils.configuration' - relocate 'io.papermc.lib', 'com.onarandombox.multiverseinventories.utils.paperlib' - relocate 'net.minidev.json', 'com.onarandombox.multiverseinventories.utils.json' + relocate 'com.dumptruckman.minecraft.util.Logging', 'org.mvplugins.multiverse.inventories.utils.InvLogging' + relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' + relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' + relocate 'io.papermc.lib', 'org.mvplugins.multiverse.inventories.utils.paperlib' + relocate 'net.minidev.json', 'org.mvplugins.multiverse.inventories.utils.json' configurations = [project.configurations.api] diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java deleted file mode 100644 index 8c8105f9..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains all Commands. - */ -package com.onarandombox.multiverseinventories.commands; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/share/package-info.java deleted file mode 100644 index 6f538d65..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/share/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Contains all external API classes for {@link com.onarandombox.multiverseinventories.share.Sharable}s and handling the sharing of those between worlds. - */ -package com.onarandombox.multiverseinventories.share; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/util/package-info.java deleted file mode 100644 index 0826b3ff..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/util/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains utility classes. - */ -package com.onarandombox.multiverseinventories.util; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java similarity index 95% rename from src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java index bcb5710e..545d011e 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java @@ -1,11 +1,11 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.profile.WorldGroupManager; -import com.onarandombox.multiverseinventories.profile.GroupingConflict; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.GroupingConflict; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; diff --git a/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java similarity index 91% rename from src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java rename to src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java index 0a1d4a4b..9735c2b8 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.core.event.MVDebugModeEvent; diff --git a/src/main/java/com/onarandombox/multiverseinventories/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java similarity index 99% rename from src/main/java/com/onarandombox/multiverseinventories/DataStrings.java rename to src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java index baf6bfad..0c5bfb5f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; import net.minidev.json.JSONArray; diff --git a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/DefaultMessageProvider.java rename to src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java index 71e6b397..b34e5036 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessageProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java @@ -1,10 +1,10 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.locale.LazyLocaleMessageProvider; -import com.onarandombox.multiverseinventories.locale.LocalizationLoadingException; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.locale.NoSuchLocalizationException; -import com.onarandombox.multiverseinventories.util.Font; +import org.mvplugins.multiverse.inventories.locale.LazyLocaleMessageProvider; +import org.mvplugins.multiverse.inventories.locale.LocalizationLoadingException; +import org.mvplugins.multiverse.inventories.locale.Message; +import org.mvplugins.multiverse.inventories.locale.NoSuchLocalizationException; +import org.mvplugins.multiverse.inventories.util.Font; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -50,7 +50,7 @@ public DefaultMessageProvider(JavaPlugin plugin) { * Tries to load the locale. * * @param locale Locale to try to load. - * @throws com.onarandombox.multiverseinventories.locale.LocalizationLoadingException if the Locale could not be loaded. + * @throws LocalizationLoadingException if the Locale could not be loaded. */ public void maybeLoadLocale(Locale locale) throws LocalizationLoadingException { if (!isLocaleLoaded(locale)) { diff --git a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessager.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java similarity index 86% rename from src/main/java/com/onarandombox/multiverseinventories/DefaultMessager.java rename to src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java index 57008559..35afe289 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java @@ -1,11 +1,12 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.locale.MessageProvider; -import com.onarandombox.multiverseinventories.locale.Messager; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.locale.MessageProvider; +import org.mvplugins.multiverse.inventories.locale.Messager; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.plugin.java.JavaPlugin; +import org.mvplugins.multiverse.inventories.util.Font; import java.util.List; @@ -71,7 +72,7 @@ public void help(Message message, CommandSender sender, Object... args) { */ @Override public void sendMessage(CommandSender player, String message) { - List messages = com.onarandombox.multiverseinventories.util.Font.splitString(message); + List messages = Font.splitString(message); sendMessages(player, messages); } diff --git a/src/main/java/com/onarandombox/multiverseinventories/DefaultPersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java similarity index 70% rename from src/main/java/com/onarandombox/multiverseinventories/DefaultPersistingProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java index 186b5916..9ee01ab6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/DefaultPersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.PersistingProfile; -import com.onarandombox.multiverseinventories.share.Shares; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.PersistingProfile; +import org.mvplugins.multiverse.inventories.share.Shares; /** * Simple implementation of PersistingProfile. diff --git a/src/main/java/com/onarandombox/multiverseinventories/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/FlatFileProfileDataSource.java rename to src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java index 040d31f6..2902a076 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java @@ -1,598 +1,598 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.dumptruckman.minecraft.util.Logging; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.ProfileKey; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.share.ProfileEntry; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.SharableEntry; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.GlobalProfile; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import net.minidev.json.JSONObject; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.json.simple.parser.JSONParser; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; - -class FlatFileProfileDataSource implements ProfileDataSource { - - private static final String JSON = ".json"; - - private final JSONParser JSON_PARSER = new JSONParser(); - - private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); - - // TODO these probably need configurable max sizes - private final Cache profileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(1000) - .build(); - private final Cache globalProfileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(500) - .build(); - - private final File worldFolder; - private final File groupFolder; - private final File playerFolder; - - FlatFileProfileDataSource(MultiverseInventories plugin) throws IOException { - // Make the data folders - plugin.getDataFolder().mkdirs(); - - // Check if the data file exists. If not, create it. - this.worldFolder = new File(plugin.getDataFolder(), "worlds"); - if (!this.worldFolder.exists()) { - if (!this.worldFolder.mkdirs()) { - throw new IOException("Could not create world folder!"); - } - } - this.groupFolder = new File(plugin.getDataFolder(), "groups"); - if (!this.groupFolder.exists()) { - if (!this.groupFolder.mkdirs()) { - throw new IOException("Could not create group folder!"); - } - } - this.playerFolder = new File(plugin.getDataFolder(), "players"); - if (!this.playerFolder.exists()) { - if (!this.playerFolder.mkdirs()) { - throw new IOException("Could not create player folder!"); - } - } - } - - private FileConfiguration waitForConfigHandle(File file) { - Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - while (true) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - - private static FileConfiguration getConfigHandleNow(File file) { - return JsonConfiguration.loadConfiguration(file); - } - - private static class ConfigLoader implements Callable { - private final File file; - - private ConfigLoader(File file) { - this.file = file; - } - - @Override - public FileConfiguration call() throws Exception { - return getConfigHandleNow(file); - } - } - - private File getFolder(ContainerType type, String folderName) { - File folder; - switch (type) { - case GROUP: - folder = new File(this.groupFolder, folderName); - break; - case WORLD: - folder = new File(this.worldFolder, folderName); - break; - default: - folder = new File(this.worldFolder, folderName); - break; - } - - if (!folder.exists()) { - folder.mkdirs(); - } - return folder; - } - - /** - * Retrieves the data file for a player based on a given world/group name, creating it if necessary. - * - * @param type Indicates whether data is for group or world. - * @param dataName The name of the group or world. - * @param playerName The name of the player. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); - if (!jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() - + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName - + " may not be saved.", e); - } - } - Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", - jsonPlayerFile.getPath(), type, dataName, playerName); - return jsonPlayerFile; - } - - /** - * Retrieves the data file for a player for their global data, creating it if necessary. - * - * @param fileName The name of the file (player name or UUID) without extension. - * @param createIfMissing If true, the file will be created it it does not exist. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { - File jsonPlayerFile = new File(playerFolder, fileName + JSON); - if (createIfMissing && !jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " - + "There may be issues with " + fileName + "'s metadata", e); - } - } - return jsonPlayerFile; - } - - private void queueWrite(PlayerProfile profile) { - fileIOExecutorService.submit(new FileWriter(profile.clone())); - } - - private class FileWriter implements Callable { - private final PlayerProfile profile; - - private FileWriter(PlayerProfile profile) { - this.profile = profile; - } - - @Override - public Void call() throws Exception { - processProfileWrite(profile); - return null; - } - } - - private void processProfileWrite(PlayerProfile playerProfile) { - try { - File playerFile = this.getPlayerFile(playerProfile.getContainerType(), - playerProfile.getContainerName(), playerProfile.getPlayer().getName()); - FileConfiguration playerData = getConfigHandleNow(playerFile); - playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() - + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); - Logging.severe(e.getMessage()); - } - } catch (final Exception e) { - Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); - } - } - - private Map serializePlayerProfile(PlayerProfile playerProfile) { - Map playerData = new LinkedHashMap(); - JSONObject jsonStats = new JSONObject(); - for (SharableEntry entry : playerProfile) { - if (entry.getValue() != null) { - if (entry.getSharable().getSerializer() == null) { - continue; - } - Sharable sharable = entry.getSharable(); - if (sharable.getProfileEntry().isStat()) { - jsonStats.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } else { - playerData.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } - } - } - if (!jsonStats.isEmpty()) { - playerData.put(DataStrings.PLAYER_STATS, jsonStats); - } - return playerData; - } - - /** - * {@inheritDoc} - */ - @Override - public void updatePlayerData(PlayerProfile playerProfile) { - queueWrite(playerProfile); - } - - private PlayerProfile getPlayerData(ProfileKey key) { - PlayerProfile cached = profileCache.getIfPresent(key); - if (cached != null) { - return cached; - } - File playerFile = null; - try { - playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); - } catch (IOException e) { - e.printStackTrace(); - // Return an empty profile - return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), - Bukkit.getOfflinePlayer(key.getPlayerUUID())); - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - if (convertConfig(playerData)) { - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + key.getPlayerName() - + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); - Logging.severe(e.getMessage()); - } - } - ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); - if (section == null) { - section = playerData.createSection(key.getProfileType().getName()); - } - PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); - profileCache.put(key, result); - return result; - } - - @Override - public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); - } - - private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); - for (Object keyObj : playerData.keySet()) { - String key = keyObj.toString(); - if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { - final Object statsObject = playerData.get(key); - if (statsObject instanceof String) { - parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); - } else { - if (statsObject instanceof Map) { - parsePlayerStatsIntoProfile((Map) statsObject, profile); - } else { - Logging.warning("Could not parse stats for " + pKey.getPlayerName()); - } - } - } else { - if (playerData.get(key) == null) { - Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); - continue; - } - try { - Sharable sharable = ProfileEntry.lookup(false, key); - if (sharable == null) { - Logging.fine("Player fileTag '" + key + "' is unrecognized!"); - continue; - } - profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); - } catch (Exception e) { - Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); - Logging.getLogger().log(Level.FINE, "Exception: ", e); - e.printStackTrace(); - } - } - } - Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); - return profile; - } - - private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { - for (Object key : stats.keySet()) { - Sharable sharable = ProfileEntry.lookup(true, key.toString()); - if (sharable != null) { - profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); - } else { - Logging.warning("Could not parse stat: '" + key + "' for player '" - + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" - + profile.getContainerName() + "'"); - } - } - } - - private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { - if (stats.isEmpty()) { - return; - } - org.json.simple.JSONObject jsonStats = null; - try { - jsonStats = (org.json.simple.JSONObject) JSON_PARSER.parse(stats); - } catch (org.json.simple.parser.ParseException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } catch (ClassCastException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } - if (jsonStats == null) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "'"); - return; - } - parsePlayerStatsIntoProfile(jsonStats, profile); - } - - // TODO Remove this conversion - private boolean convertConfig(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection("playerData"); - if (section != null) { - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set("playerData", null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { - if (profileType == null) { - try { - File playerFile = getPlayerFile(containerType, dataName, playerName); - return playerFile.delete(); - } catch (IOException ignore) { - Logging.warning("Attempted to delete file that did not exist for player " + playerName - + " in " + containerType.name().toLowerCase() + " " + dataName); - return false; - } - } else { - File playerFile; - try { - playerFile = getPlayerFile(containerType, dataName, playerName); - } catch (IOException e) { - Logging.warning("Attempted to delete " + playerName + "'s data for " - + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() - + " " + dataName + " but the file did not exist."); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.set(profileType.getName(), null); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not delete data for player: " + playerName - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - } - - private Map convertSection(ConfigurationSection section) { - Map resultMap = new HashMap(); - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj instanceof ConfigurationSection) { - resultMap.put(key, convertSection((ConfigurationSection) obj)); - } else { - resultMap.put(key, obj); - } - } - return resultMap; - } - - @Override - @Deprecated - public GlobalProfile getGlobalProfile(String playerName) { - return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); - } - - @Override - public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { - GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); - if (cached != null) { - return cached; - } - File playerFile; - - // Migrate old data if necessary - try { - playerFile = getGlobalFile(playerName, false); - } catch (IOException e) { - // This won't ever happen - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName); - } - if (playerFile.exists()) { - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - if (!migrateGlobalProfileToUUID(profile, playerFile)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - globalProfileCache.put(playerUUID, profile); - return profile; - } - - // Load current format - try { - playerFile = getGlobalFile(playerUUID.toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName, playerUUID); - } - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - globalProfileCache.put(playerUUID, profile); - return profile; - } - - private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { - updateGlobalProfile(profile); - return playerFile.delete(); - } - - private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - ConfigurationSection section = playerData.getConfigurationSection("playerData"); - if (section == null) { - section = playerData.createSection("playerData"); - } - return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); - } - - private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, - Map playerData) { - GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); - for (String key : playerData.keySet()) { - if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { - globalProfile.setLastWorld(playerData.get(key).toString()); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { - globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { - globalProfile.setLastKnownName(playerData.get(key).toString()); - } - } - return globalProfile; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean updateGlobalProfile(GlobalProfile globalProfile) { - File playerFile = null; - try { - playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save global data for player: " + globalProfile); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - - private Map serializeGlobalProfile(GlobalProfile profile) { - Map result = new HashMap(2); - if (profile.getLastWorld() != null) { - result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); - } - result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); - result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); - return result; - } - - @Override - @Deprecated - // TODO replace for UUID - public void updateLastWorld(String playerName, String worldName) { - GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLastWorld(worldName); - updateGlobalProfile(globalProfile); - } - - @Override - @Deprecated - // TODO replace for UUID - public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { - final GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLoadOnLogin(loadOnLogin); - updateGlobalProfile(globalProfile); - } - - @Override - public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { - File[] worldFolders = worldFolder.listFiles(File::isDirectory); - if (worldFolders == null) { - throw new IOException("Could not enumerate world folders"); - } - File[] groupFolders = groupFolder.listFiles(File::isDirectory); - if (groupFolders == null) { - throw new IOException("Could not enumerate group folders"); - } - - for (File worldFolder : worldFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - for (File groupFolder : groupFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - if (removeOldData) { - for (File worldFolder : worldFolders) { - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - for (File groupFolder : groupFolders) { - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - } - } - - @Override - public void clearProfileCache(ProfileKey key) { - profileCache.invalidate(key); - } - - void clearCache() { - globalProfileCache.invalidateAll(); - profileCache.invalidateAll(); - } -} - +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.ProfileEntry; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.SharableEntry; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import net.minidev.json.JSONObject; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.json.simple.parser.JSONParser; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +class FlatFileProfileDataSource implements ProfileDataSource { + + private static final String JSON = ".json"; + + private final JSONParser JSON_PARSER = new JSONParser(); + + private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); + + // TODO these probably need configurable max sizes + private final Cache profileCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(1000) + .build(); + private final Cache globalProfileCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(500) + .build(); + + private final File worldFolder; + private final File groupFolder; + private final File playerFolder; + + FlatFileProfileDataSource(MultiverseInventories plugin) throws IOException { + // Make the data folders + plugin.getDataFolder().mkdirs(); + + // Check if the data file exists. If not, create it. + this.worldFolder = new File(plugin.getDataFolder(), "worlds"); + if (!this.worldFolder.exists()) { + if (!this.worldFolder.mkdirs()) { + throw new IOException("Could not create world folder!"); + } + } + this.groupFolder = new File(plugin.getDataFolder(), "groups"); + if (!this.groupFolder.exists()) { + if (!this.groupFolder.mkdirs()) { + throw new IOException("Could not create group folder!"); + } + } + this.playerFolder = new File(plugin.getDataFolder(), "players"); + if (!this.playerFolder.exists()) { + if (!this.playerFolder.mkdirs()) { + throw new IOException("Could not create player folder!"); + } + } + } + + private FileConfiguration waitForConfigHandle(File file) { + Future future = fileIOExecutorService.submit(new ConfigLoader(file)); + while (true) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + } + + private static FileConfiguration getConfigHandleNow(File file) { + return JsonConfiguration.loadConfiguration(file); + } + + private static class ConfigLoader implements Callable { + private final File file; + + private ConfigLoader(File file) { + this.file = file; + } + + @Override + public FileConfiguration call() throws Exception { + return getConfigHandleNow(file); + } + } + + private File getFolder(ContainerType type, String folderName) { + File folder; + switch (type) { + case GROUP: + folder = new File(this.groupFolder, folderName); + break; + case WORLD: + folder = new File(this.worldFolder, folderName); + break; + default: + folder = new File(this.worldFolder, folderName); + break; + } + + if (!folder.exists()) { + folder.mkdirs(); + } + return folder; + } + + /** + * Retrieves the data file for a player based on a given world/group name, creating it if necessary. + * + * @param type Indicates whether data is for group or world. + * @param dataName The name of the group or world. + * @param playerName The name of the player. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { + File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); + if (!jsonPlayerFile.exists()) { + try { + jsonPlayerFile.createNewFile(); + } catch (IOException e) { + throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() + + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName + + " may not be saved.", e); + } + } + Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", + jsonPlayerFile.getPath(), type, dataName, playerName); + return jsonPlayerFile; + } + + /** + * Retrieves the data file for a player for their global data, creating it if necessary. + * + * @param fileName The name of the file (player name or UUID) without extension. + * @param createIfMissing If true, the file will be created it it does not exist. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { + File jsonPlayerFile = new File(playerFolder, fileName + JSON); + if (createIfMissing && !jsonPlayerFile.exists()) { + try { + jsonPlayerFile.createNewFile(); + } catch (IOException e) { + throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " + + "There may be issues with " + fileName + "'s metadata", e); + } + } + return jsonPlayerFile; + } + + private void queueWrite(PlayerProfile profile) { + fileIOExecutorService.submit(new FileWriter(profile.clone())); + } + + private class FileWriter implements Callable { + private final PlayerProfile profile; + + private FileWriter(PlayerProfile profile) { + this.profile = profile; + } + + @Override + public Void call() throws Exception { + processProfileWrite(profile); + return null; + } + } + + private void processProfileWrite(PlayerProfile playerProfile) { + try { + File playerFile = this.getPlayerFile(playerProfile.getContainerType(), + playerProfile.getContainerName(), playerProfile.getPlayer().getName()); + FileConfiguration playerData = getConfigHandleNow(playerFile); + playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); + Logging.severe(e.getMessage()); + } + } catch (final Exception e) { + Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); + } + } + + private Map serializePlayerProfile(PlayerProfile playerProfile) { + Map playerData = new LinkedHashMap(); + JSONObject jsonStats = new JSONObject(); + for (SharableEntry entry : playerProfile) { + if (entry.getValue() != null) { + if (entry.getSharable().getSerializer() == null) { + continue; + } + Sharable sharable = entry.getSharable(); + if (sharable.getProfileEntry().isStat()) { + jsonStats.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(entry.getValue())); + } else { + playerData.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(entry.getValue())); + } + } + } + if (!jsonStats.isEmpty()) { + playerData.put(DataStrings.PLAYER_STATS, jsonStats); + } + return playerData; + } + + /** + * {@inheritDoc} + */ + @Override + public void updatePlayerData(PlayerProfile playerProfile) { + queueWrite(playerProfile); + } + + private PlayerProfile getPlayerData(ProfileKey key) { + PlayerProfile cached = profileCache.getIfPresent(key); + if (cached != null) { + return cached; + } + File playerFile = null; + try { + playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); + } catch (IOException e) { + e.printStackTrace(); + // Return an empty profile + return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), + Bukkit.getOfflinePlayer(key.getPlayerUUID())); + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + if (convertConfig(playerData)) { + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + key.getPlayerName() + + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); + Logging.severe(e.getMessage()); + } + } + ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); + if (section == null) { + section = playerData.createSection(key.getProfileType().getName()); + } + PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); + profileCache.put(key, result); + return result; + } + + @Override + public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { + return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); + } + + private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { + PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), + pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); + for (Object keyObj : playerData.keySet()) { + String key = keyObj.toString(); + if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { + final Object statsObject = playerData.get(key); + if (statsObject instanceof String) { + parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); + } else { + if (statsObject instanceof Map) { + parsePlayerStatsIntoProfile((Map) statsObject, profile); + } else { + Logging.warning("Could not parse stats for " + pKey.getPlayerName()); + } + } + } else { + if (playerData.get(key) == null) { + Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + try { + Sharable sharable = ProfileEntry.lookup(false, key); + if (sharable == null) { + Logging.fine("Player fileTag '" + key + "' is unrecognized!"); + continue; + } + profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); + } catch (Exception e) { + Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); + Logging.getLogger().log(Level.FINE, "Exception: ", e); + e.printStackTrace(); + } + } + } + Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); + return profile; + } + + private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { + for (Object key : stats.keySet()) { + Sharable sharable = ProfileEntry.lookup(true, key.toString()); + if (sharable != null) { + profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); + } else { + Logging.warning("Could not parse stat: '" + key + "' for player '" + + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" + + profile.getContainerName() + "'"); + } + } + } + + private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { + if (stats.isEmpty()) { + return; + } + org.json.simple.JSONObject jsonStats = null; + try { + jsonStats = (org.json.simple.JSONObject) JSON_PARSER.parse(stats); + } catch (org.json.simple.parser.ParseException e) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); + } catch (ClassCastException e) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); + } + if (jsonStats == null) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "'"); + return; + } + parsePlayerStatsIntoProfile(jsonStats, profile); + } + + // TODO Remove this conversion + private boolean convertConfig(FileConfiguration config) { + ConfigurationSection section = config.getConfigurationSection("playerData"); + if (section != null) { + config.set(ProfileTypes.SURVIVAL.getName(), section); + config.set(ProfileTypes.CREATIVE.getName(), section); + config.set(ProfileTypes.ADVENTURE.getName(), section); + config.set("playerData", null); + Logging.finer("Migrated old player data to new multi-profile format"); + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { + if (profileType == null) { + try { + File playerFile = getPlayerFile(containerType, dataName, playerName); + return playerFile.delete(); + } catch (IOException ignore) { + Logging.warning("Attempted to delete file that did not exist for player " + playerName + + " in " + containerType.name().toLowerCase() + " " + dataName); + return false; + } + } else { + File playerFile; + try { + playerFile = getPlayerFile(containerType, dataName, playerName); + } catch (IOException e) { + Logging.warning("Attempted to delete " + playerName + "'s data for " + + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() + + " " + dataName + " but the file did not exist."); + return false; + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + playerData.set(profileType.getName(), null); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not delete data for player: " + playerName + + " for " + containerType.toString() + ": " + dataName); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + } + + private Map convertSection(ConfigurationSection section) { + Map resultMap = new HashMap(); + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj instanceof ConfigurationSection) { + resultMap.put(key, convertSection((ConfigurationSection) obj)); + } else { + resultMap.put(key, obj); + } + } + return resultMap; + } + + @Override + @Deprecated + public GlobalProfile getGlobalProfile(String playerName) { + return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); + } + + @Override + public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { + GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); + if (cached != null) { + return cached; + } + File playerFile; + + // Migrate old data if necessary + try { + playerFile = getGlobalFile(playerName, false); + } catch (IOException e) { + // This won't ever happen + e.printStackTrace(); + return GlobalProfile.createGlobalProfile(playerName); + } + if (playerFile.exists()) { + GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); + if (!migrateGlobalProfileToUUID(profile, playerFile)) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } + globalProfileCache.put(playerUUID, profile); + return profile; + } + + // Load current format + try { + playerFile = getGlobalFile(playerUUID.toString(), true); + } catch (IOException e) { + e.printStackTrace(); + return GlobalProfile.createGlobalProfile(playerName, playerUUID); + } + GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); + globalProfileCache.put(playerUUID, profile); + return profile; + } + + private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { + updateGlobalProfile(profile); + return playerFile.delete(); + } + + private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + ConfigurationSection section = playerData.getConfigurationSection("playerData"); + if (section == null) { + section = playerData.createSection("playerData"); + } + return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); + } + + private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, + Map playerData) { + GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); + for (String key : playerData.keySet()) { + if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { + globalProfile.setLastWorld(playerData.get(key).toString()); + } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { + globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); + } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { + globalProfile.setLastKnownName(playerData.get(key).toString()); + } + } + return globalProfile; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean updateGlobalProfile(GlobalProfile globalProfile) { + File playerFile = null; + try { + playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save global data for player: " + globalProfile); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + + private Map serializeGlobalProfile(GlobalProfile profile) { + Map result = new HashMap(2); + if (profile.getLastWorld() != null) { + result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); + } + result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); + result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); + return result; + } + + @Override + @Deprecated + // TODO replace for UUID + public void updateLastWorld(String playerName, String worldName) { + GlobalProfile globalProfile = getGlobalProfile(playerName); + globalProfile.setLastWorld(worldName); + updateGlobalProfile(globalProfile); + } + + @Override + @Deprecated + // TODO replace for UUID + public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { + final GlobalProfile globalProfile = getGlobalProfile(playerName); + globalProfile.setLoadOnLogin(loadOnLogin); + updateGlobalProfile(globalProfile); + } + + @Override + public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { + File[] worldFolders = worldFolder.listFiles(File::isDirectory); + if (worldFolders == null) { + throw new IOException("Could not enumerate world folders"); + } + File[] groupFolders = groupFolder.listFiles(File::isDirectory); + if (groupFolders == null) { + throw new IOException("Could not enumerate group folders"); + } + + for (File worldFolder : worldFolders) { + ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), + ProfileTypes.ADVENTURE, uuid, oldName); + updatePlayerData(getPlayerData(key)); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); + } + + for (File groupFolder : groupFolders) { + ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), + ProfileTypes.ADVENTURE, uuid, oldName); + updatePlayerData(getPlayerData(key)); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); + } + + if (removeOldData) { + for (File worldFolder : worldFolders) { + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); + } + for (File groupFolder : groupFolders) { + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); + } + } + } + + @Override + public void clearProfileCache(ProfileKey key) { + profileCache.invalidate(key); + } + + void clearCache() { + globalProfileCache.invalidateAll(); + profileCache.invalidateAll(); + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/GameModeShareHandler.java similarity index 82% rename from src/main/java/com/onarandombox/multiverseinventories/GameModeShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/GameModeShareHandler.java index 55434da2..c8864648 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/GameModeShareHandler.java @@ -1,95 +1,95 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.event.GameModeChangeShareHandlingEvent; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; - -import java.util.List; - -/** - * GameMode change implementation of ShareHandler. - */ -final class GameModeShareHandler extends ShareHandler { - - private final GameMode fromGameMode; - private final GameMode toGameMode; - private final ProfileType fromType; - private final ProfileType toType; - private final String world; - private final ProfileContainer worldProfileContainer; - private final List worldGroups; - - GameModeShareHandler(MultiverseInventories inventories, Player player, - GameMode fromGameMode, GameMode toGameMode) { - super(inventories, player); - this.fromGameMode = fromGameMode; - this.toGameMode = toGameMode; - this.fromType = ProfileTypes.forGameMode(fromGameMode); - this.toType = ProfileTypes.forGameMode(toGameMode); - this.world = player.getWorld().getName(); - this.worldProfileContainer = inventories.getWorldProfileContainerStore().getContainer(world); - this.worldGroups = getAffectedWorldGroups(); - - prepareProfiles(); - } - - private List getAffectedWorldGroups() { - return this.inventories.getGroupManager().getGroupsForWorld(world); - } - - @Override - protected ShareHandlingEvent createEvent() { - return new GameModeChangeShareHandlingEvent(player, affectedProfiles, fromGameMode, toGameMode); - } - - private void prepareProfiles() { - Logging.finer("=== " + player.getName() + " changing game mode from: " + fromType - + " to: " + toType + " for world: " + world + " ==="); - - setAlwaysWriteProfile(worldProfileContainer.getPlayerData(fromType, player)); - - if (isPlayerAffectedByChange()) { - addProfiles(); - } - } - - private boolean isPlayerAffectedByChange() { - if (isPlayerBypassingChange()) { - logBypass(); - return false; - } - return true; - } - - private boolean isPlayerBypassingChange() { - return Perm.BYPASS_WORLD.hasBypass(player, world) - || Perm.BYPASS_GAME_MODE.hasBypass(player, toGameMode.name().toLowerCase()); - } - - private void addProfiles() { - if (hasWorldGroups()) { - worldGroups.forEach(this::addProfilesForWorldGroup); - } else { - Logging.finer("No groups for world."); - addReadProfile(worldProfileContainer.getPlayerData(toType, player), Sharables.allOf()); - } - } - - private boolean hasWorldGroups() { - return !worldGroups.isEmpty(); - } - - private void addProfilesForWorldGroup(WorldGroup worldGroup) { - ProfileContainer container = worldGroup.getGroupProfileContainer(); - addWriteProfile(container.getPlayerData(fromType, player), Sharables.allOf()); - addReadProfile(container.getPlayerData(toType, player), Sharables.allOf()); - } -} - +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.event.GameModeChangeShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.Perm; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * GameMode change implementation of ShareHandler. + */ +final class GameModeShareHandler extends ShareHandler { + + private final GameMode fromGameMode; + private final GameMode toGameMode; + private final ProfileType fromType; + private final ProfileType toType; + private final String world; + private final ProfileContainer worldProfileContainer; + private final List worldGroups; + + GameModeShareHandler(MultiverseInventories inventories, Player player, + GameMode fromGameMode, GameMode toGameMode) { + super(inventories, player); + this.fromGameMode = fromGameMode; + this.toGameMode = toGameMode; + this.fromType = ProfileTypes.forGameMode(fromGameMode); + this.toType = ProfileTypes.forGameMode(toGameMode); + this.world = player.getWorld().getName(); + this.worldProfileContainer = inventories.getWorldProfileContainerStore().getContainer(world); + this.worldGroups = getAffectedWorldGroups(); + + prepareProfiles(); + } + + private List getAffectedWorldGroups() { + return this.inventories.getGroupManager().getGroupsForWorld(world); + } + + @Override + protected ShareHandlingEvent createEvent() { + return new GameModeChangeShareHandlingEvent(player, affectedProfiles, fromGameMode, toGameMode); + } + + private void prepareProfiles() { + Logging.finer("=== " + player.getName() + " changing game mode from: " + fromType + + " to: " + toType + " for world: " + world + " ==="); + + setAlwaysWriteProfile(worldProfileContainer.getPlayerData(fromType, player)); + + if (isPlayerAffectedByChange()) { + addProfiles(); + } + } + + private boolean isPlayerAffectedByChange() { + if (isPlayerBypassingChange()) { + logBypass(); + return false; + } + return true; + } + + private boolean isPlayerBypassingChange() { + return Perm.BYPASS_WORLD.hasBypass(player, world) + || Perm.BYPASS_GAME_MODE.hasBypass(player, toGameMode.name().toLowerCase()); + } + + private void addProfiles() { + if (hasWorldGroups()) { + worldGroups.forEach(this::addProfilesForWorldGroup); + } else { + Logging.finer("No groups for world."); + addReadProfile(worldProfileContainer.getPlayerData(toType, player), Sharables.allOf()); + } + } + + private boolean hasWorldGroups() { + return !worldGroups.isEmpty(); + } + + private void addProfilesForWorldGroup(WorldGroup worldGroup) { + ProfileContainer container = worldGroup.getGroupProfileContainer(); + addWriteProfile(container.getPlayerData(fromType, player), Sharables.allOf()); + addReadProfile(container.getPlayerData(toType, player), Sharables.allOf()); + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java rename to src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java index ad39e3b8..2db1a360 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java @@ -1,336 +1,336 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; -import io.papermc.lib.PaperLib; -import org.bukkit.configuration.file.FileConfiguration; -import org.mvplugins.multiverse.core.api.MVConfig; -import org.mvplugins.multiverse.core.config.MVCoreConfig; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Provides methods for interacting with the configuration of Multiverse-Inventories. - */ -public final class InventoriesConfig { - - /** - * Enum for easily keeping track of config paths, defaults and comments. - */ - public enum Path { - /** - * Locale name config path, default and comments. - */ - LANGUAGE_FILE_NAME("settings.locale", "en", "# This is the locale you wish to use."), - /** - * First Run flag config path, default and comments. - */ - FIRST_RUN("settings.first_run", true, "# If this is true it will generate world groups for you based on MV worlds."), - /** - * First Run flag config path, default and comments. - */ - USE_BYPASS("settings.use_bypass", false, "# If this is set to true, it will enable bypass permissions (Check the wiki for more info.)"), - - /** - * Whether or not to make ungrouped worlds use the default group. - */ - DEFAULT_UNGROUPED_WORLDS("settings.default_ungrouped_worlds", false, "# If set to true, any world not listed in a group will automatically use the settings for the default group!"), - - /** - * Whether or not to save/load player data on log out/in. - */ - LOGGING_SAVE_LOAD("settings.save_load_on_log_in_out", false, - "# The default and suggested setting for this is FALSE.", - "# False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out.", - "# That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY.", - "# Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in.", - "# The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back!"), - - USE_OPTIONALS_UNGROUPED("shares.optionals_for_ungrouped_worlds", true, - "# When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.", - "# An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.", - "# When set to false, optional shares WILL NOt be utilized in these cases, effectively disabling it for ungrouped worlds."), - /** - * First Run flag config path, default and comments. - */ - OPTIONAL_SHARES("shares.use_optionals", new ArrayList(), - "# You must specify optional shares you wish to use here or they will be ignored.", - "# The only built in optional shares are \"economy\" and \"last_location\"."), - /** - * Whether or not to split data based on game modes. - */ - USE_GAME_MODE_PROFILES("settings.use_game_mode_profiles", false, - "# If this is set to true, players will have different inventories/stats for each game mode.", - "# Please note that old data migrated to the version that has this feature will have their data copied for both game modes."); - - private String path; - private Object def; - private List comments; - - Path(String path, Object def, String... comments) { - this.path = path; - this.def = def; - this.comments = Arrays.asList(comments); - } - - /** - * Retrieves the path for a config option. - * - * @return The path for a config option. - */ - private String getPath() { - return this.path; - } - - /** - * Retrieves the default value for a config path. - * - * @return The default value for a config path. - */ - private Object getDefault() { - return this.def; - } - - /** - * Retrieves the comment for a config path. - * - * @return The comments for a config path. - */ - private List getComments() { - return this.comments; - } - } - - private final CommentedYamlConfiguration config; - private final MultiverseInventories plugin; - private final MVCoreConfig mvCoreConfig; - - InventoriesConfig(MultiverseInventories plugin, MVCoreConfig mvCoreConfig) throws IOException { - this.plugin = plugin; - this.mvCoreConfig = mvCoreConfig; - // Make the data folders - if (plugin.getDataFolder().mkdirs()) { - Logging.fine("Created data folder."); - } - - // Check if the config file exists. If not, create it. - File configFile = new File(plugin.getDataFolder(), "config.yml"); - boolean configFileExists = configFile.exists(); - if (!configFileExists) { - Logging.fine("Created config file."); - configFile.createNewFile(); - } - - // Load the configuration file into memory - boolean supportsCommentsNatively = PaperLib.getMinecraftVersion() > 17; - config = new CommentedYamlConfiguration(configFile, !configFileExists || !supportsCommentsNatively); - - // Sets defaults config values - this.setDefaults(); - - config.getConfig().options().header("Multiverse-Inventories Settings"); - - // Saves the configuration from memory to file - config.save(); - - Logging.setDebugLevel(this.getGlobalDebug()); - } - - - /** - * Loads default settings for any missing config values. - */ - private void setDefaults() { - for (InventoriesConfig.Path path : InventoriesConfig.Path.values()) { - config.addComment(path.getPath(), path.getComments()); - if (this.getConfig().get(path.getPath()) == null) { - if (path.getDefault() != null) { - Logging.fine("Config: Defaulting '" + path.getPath() + "' to " + path.getDefault()); - this.getConfig().set(path.getPath(), path.getDefault()); - } else { - this.getConfig().createSection(path.getPath()); - } - } - } - - } - - private Boolean getBoolean(Path path) { - return this.getConfig().getBoolean(path.getPath(), (Boolean) path.getDefault()); - } - - private Integer getInt(Path path) { - return this.getConfig().getInt(path.getPath(), (Integer) path.getDefault()); - } - - private String getString(Path path) { - return this.getConfig().getString(path.getPath(), (String) path.getDefault()); - } - - FileConfiguration getConfig() { - return this.config.getConfig(); - } - - /** - * Sets globalDebug level. - * - * @param globalDebug The new value. 0 = off. - */ - public void setGlobalDebug(int globalDebug) { - mvCoreConfig.setGlobalDebug(globalDebug); - } - - /** - * Gets globalDebug level. - * - * @return globalDebug. - */ - public int getGlobalDebug() { - return mvCoreConfig.getGlobalDebug(); - } - - /** - * Retrieves the locale string from the config. - * - * @return The locale string. - */ - public String getLocale() { - return this.getString(Path.LANGUAGE_FILE_NAME); - } - - /** - * Tells whether this is the first time the plugin has run as set by a config flag. - * - * @return True if first_run is set to true in config. - */ - public boolean isFirstRun() { - return this.getBoolean(Path.FIRST_RUN); - } - - /** - * Sets the first_run flag in the config so that the plugin no longer thinks it is the first run. - * - * @param firstRun What to set the flag to in the config. - */ - void setFirstRun(boolean firstRun) { - this.getConfig().set(Path.FIRST_RUN.getPath(), firstRun); - } - - /** - * @return True if we should check for bypass permissions. - */ - public boolean isUsingBypass() { - return this.getBoolean(Path.USE_BYPASS); - } - - /** - * @param useBypass Whether or not to check for bypass permissions. - */ - public void setUsingBypass(boolean useBypass) { - this.getConfig().set(Path.USE_BYPASS.getPath(), useBypass); - } - - /** - * Tells whether Multiverse-Inventories should save on player logout and load on player login. - * - * @return True if should save and load on player log out and in. - */ - public boolean usingLoggingSaveLoad() { - return this.getBoolean(Path.LOGGING_SAVE_LOAD); - } - - /** - * Sets whether Multiverse-Inventories should save on player logout and load on player login. - * - * @param useLoggingSaveLoad true if should save and load on player log out and in. - */ - public void setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { - this.getConfig().set(Path.LOGGING_SAVE_LOAD.getPath(), useLoggingSaveLoad); - } - - private Shares optionalSharables = null; - - /** - * @return A list of optional {@link com.onarandombox.multiverseinventories.share.Sharable}s to be treated as - * regular {@link com.onarandombox.multiverseinventories.share.Sharable}s throughout the code. - * A {@link com.onarandombox.multiverseinventories.share.Sharable} marked as optional is ignored if it is not - * contained in this list. - */ - public Shares getOptionalShares() { - if (this.optionalSharables == null) { - List list = this.getConfig().getList(Path.OPTIONAL_SHARES.getPath()); - if (list != null) { - this.optionalSharables = Sharables.fromList(list); - } else { - Logging.warning("'" + Path.OPTIONAL_SHARES.getPath() + "' is setup incorrectly!"); - this.optionalSharables = Sharables.noneOf(); - } - } - return this.optionalSharables; - } - - /** - * @return true if worlds with no group should be considered part of the default group. - */ - public boolean isDefaultingUngroupedWorlds() { - return this.getBoolean(Path.DEFAULT_UNGROUPED_WORLDS); - } - - /** - * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. - */ - public void setDefaultingUngroupedWorlds(boolean useDefaultGroup) { - this.getConfig().set(Path.FIRST_RUN.getPath(), useDefaultGroup); - } - - /** - * @return True if using separate data for game modes. - */ - public boolean isUsingGameModeProfiles() { - return this.getBoolean(Path.USE_GAME_MODE_PROFILES); - } - - /** - * @param useGameModeProfile whether to use separate data for game modes. - */ - public void setUsingGameModeProfiles(boolean useGameModeProfile) { - this.getConfig().set(Path.USE_GAME_MODE_PROFILES.getPath(), useGameModeProfile); - } - - /** - * Whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. - * - * @return true if should utilize optional shares in worlds that are not grouped. - */ - public boolean usingOptionalsForUngrouped() { - return this.getBoolean(Path.USE_OPTIONALS_UNGROUPED); - } - - /** - * Sets whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. - * - * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. - */ - public void setUsingOptionalsForUngrouped(final boolean usingOptionalsForUngrouped) { - this.getConfig().set(Path.USE_OPTIONALS_UNGROUPED.getPath(), usingOptionalsForUngrouped); - } - - /** - * Saves the configuration file to disk. - */ - // TODO remove need for this method. - // TODO Figure out what I meant by the above todo message... - public void save() { - if (this.optionalSharables != null) { - this.getConfig().set(Path.OPTIONAL_SHARES.getPath(), this.optionalSharables.toStringList()); - } - this.config.save(); - } -} - +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; +import io.papermc.lib.PaperLib; +import org.bukkit.configuration.file.FileConfiguration; +import org.mvplugins.multiverse.core.config.MVCoreConfig; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Provides methods for interacting with the configuration of Multiverse-Inventories. + */ +public final class InventoriesConfig { + + /** + * Enum for easily keeping track of config paths, defaults and comments. + */ + public enum Path { + /** + * Locale name config path, default and comments. + */ + LANGUAGE_FILE_NAME("settings.locale", "en", "# This is the locale you wish to use."), + /** + * First Run flag config path, default and comments. + */ + FIRST_RUN("settings.first_run", true, "# If this is true it will generate world groups for you based on MV worlds."), + /** + * First Run flag config path, default and comments. + */ + USE_BYPASS("settings.use_bypass", false, "# If this is set to true, it will enable bypass permissions (Check the wiki for more info.)"), + + /** + * Whether or not to make ungrouped worlds use the default group. + */ + DEFAULT_UNGROUPED_WORLDS("settings.default_ungrouped_worlds", false, "# If set to true, any world not listed in a group will automatically use the settings for the default group!"), + + /** + * Whether or not to save/load player data on log out/in. + */ + LOGGING_SAVE_LOAD("settings.save_load_on_log_in_out", false, + "# The default and suggested setting for this is FALSE.", + "# False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out.", + "# That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY.", + "# Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in.", + "# The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back!"), + + USE_OPTIONALS_UNGROUPED("shares.optionals_for_ungrouped_worlds", true, + "# When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.", + "# An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.", + "# When set to false, optional shares WILL NOt be utilized in these cases, effectively disabling it for ungrouped worlds."), + /** + * First Run flag config path, default and comments. + */ + OPTIONAL_SHARES("shares.use_optionals", new ArrayList(), + "# You must specify optional shares you wish to use here or they will be ignored.", + "# The only built in optional shares are \"economy\" and \"last_location\"."), + /** + * Whether or not to split data based on game modes. + */ + USE_GAME_MODE_PROFILES("settings.use_game_mode_profiles", false, + "# If this is set to true, players will have different inventories/stats for each game mode.", + "# Please note that old data migrated to the version that has this feature will have their data copied for both game modes."); + + private String path; + private Object def; + private List comments; + + Path(String path, Object def, String... comments) { + this.path = path; + this.def = def; + this.comments = Arrays.asList(comments); + } + + /** + * Retrieves the path for a config option. + * + * @return The path for a config option. + */ + private String getPath() { + return this.path; + } + + /** + * Retrieves the default value for a config path. + * + * @return The default value for a config path. + */ + private Object getDefault() { + return this.def; + } + + /** + * Retrieves the comment for a config path. + * + * @return The comments for a config path. + */ + private List getComments() { + return this.comments; + } + } + + private final CommentedYamlConfiguration config; + private final MultiverseInventories plugin; + private final MVCoreConfig mvCoreConfig; + + InventoriesConfig(MultiverseInventories plugin, MVCoreConfig mvCoreConfig) throws IOException { + this.plugin = plugin; + this.mvCoreConfig = mvCoreConfig; + // Make the data folders + if (plugin.getDataFolder().mkdirs()) { + Logging.fine("Created data folder."); + } + + // Check if the config file exists. If not, create it. + File configFile = new File(plugin.getDataFolder(), "config.yml"); + boolean configFileExists = configFile.exists(); + if (!configFileExists) { + Logging.fine("Created config file."); + configFile.createNewFile(); + } + + // Load the configuration file into memory + boolean supportsCommentsNatively = PaperLib.getMinecraftVersion() > 17; + config = new CommentedYamlConfiguration(configFile, !configFileExists || !supportsCommentsNatively); + + // Sets defaults config values + this.setDefaults(); + + config.getConfig().options().header("Multiverse-Inventories Settings"); + + // Saves the configuration from memory to file + config.save(); + + Logging.setDebugLevel(this.getGlobalDebug()); + } + + + /** + * Loads default settings for any missing config values. + */ + private void setDefaults() { + for (InventoriesConfig.Path path : InventoriesConfig.Path.values()) { + config.addComment(path.getPath(), path.getComments()); + if (this.getConfig().get(path.getPath()) == null) { + if (path.getDefault() != null) { + Logging.fine("Config: Defaulting '" + path.getPath() + "' to " + path.getDefault()); + this.getConfig().set(path.getPath(), path.getDefault()); + } else { + this.getConfig().createSection(path.getPath()); + } + } + } + + } + + private Boolean getBoolean(Path path) { + return this.getConfig().getBoolean(path.getPath(), (Boolean) path.getDefault()); + } + + private Integer getInt(Path path) { + return this.getConfig().getInt(path.getPath(), (Integer) path.getDefault()); + } + + private String getString(Path path) { + return this.getConfig().getString(path.getPath(), (String) path.getDefault()); + } + + FileConfiguration getConfig() { + return this.config.getConfig(); + } + + /** + * Sets globalDebug level. + * + * @param globalDebug The new value. 0 = off. + */ + public void setGlobalDebug(int globalDebug) { + mvCoreConfig.setGlobalDebug(globalDebug); + } + + /** + * Gets globalDebug level. + * + * @return globalDebug. + */ + public int getGlobalDebug() { + return mvCoreConfig.getGlobalDebug(); + } + + /** + * Retrieves the locale string from the config. + * + * @return The locale string. + */ + public String getLocale() { + return this.getString(Path.LANGUAGE_FILE_NAME); + } + + /** + * Tells whether this is the first time the plugin has run as set by a config flag. + * + * @return True if first_run is set to true in config. + */ + public boolean isFirstRun() { + return this.getBoolean(Path.FIRST_RUN); + } + + /** + * Sets the first_run flag in the config so that the plugin no longer thinks it is the first run. + * + * @param firstRun What to set the flag to in the config. + */ + void setFirstRun(boolean firstRun) { + this.getConfig().set(Path.FIRST_RUN.getPath(), firstRun); + } + + /** + * @return True if we should check for bypass permissions. + */ + public boolean isUsingBypass() { + return this.getBoolean(Path.USE_BYPASS); + } + + /** + * @param useBypass Whether or not to check for bypass permissions. + */ + public void setUsingBypass(boolean useBypass) { + this.getConfig().set(Path.USE_BYPASS.getPath(), useBypass); + } + + /** + * Tells whether Multiverse-Inventories should save on player logout and load on player login. + * + * @return True if should save and load on player log out and in. + */ + public boolean usingLoggingSaveLoad() { + return this.getBoolean(Path.LOGGING_SAVE_LOAD); + } + + /** + * Sets whether Multiverse-Inventories should save on player logout and load on player login. + * + * @param useLoggingSaveLoad true if should save and load on player log out and in. + */ + public void setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { + this.getConfig().set(Path.LOGGING_SAVE_LOAD.getPath(), useLoggingSaveLoad); + } + + private Shares optionalSharables = null; + + /** + * @return A list of optional {@link Sharable}s to be treated as + * regular {@link Sharable}s throughout the code. + * A {@link Sharable} marked as optional is ignored if it is not + * contained in this list. + */ + public Shares getOptionalShares() { + if (this.optionalSharables == null) { + List list = this.getConfig().getList(Path.OPTIONAL_SHARES.getPath()); + if (list != null) { + this.optionalSharables = Sharables.fromList(list); + } else { + Logging.warning("'" + Path.OPTIONAL_SHARES.getPath() + "' is setup incorrectly!"); + this.optionalSharables = Sharables.noneOf(); + } + } + return this.optionalSharables; + } + + /** + * @return true if worlds with no group should be considered part of the default group. + */ + public boolean isDefaultingUngroupedWorlds() { + return this.getBoolean(Path.DEFAULT_UNGROUPED_WORLDS); + } + + /** + * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. + */ + public void setDefaultingUngroupedWorlds(boolean useDefaultGroup) { + this.getConfig().set(Path.FIRST_RUN.getPath(), useDefaultGroup); + } + + /** + * @return True if using separate data for game modes. + */ + public boolean isUsingGameModeProfiles() { + return this.getBoolean(Path.USE_GAME_MODE_PROFILES); + } + + /** + * @param useGameModeProfile whether to use separate data for game modes. + */ + public void setUsingGameModeProfiles(boolean useGameModeProfile) { + this.getConfig().set(Path.USE_GAME_MODE_PROFILES.getPath(), useGameModeProfile); + } + + /** + * Whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. + * + * @return true if should utilize optional shares in worlds that are not grouped. + */ + public boolean usingOptionalsForUngrouped() { + return this.getBoolean(Path.USE_OPTIONALS_UNGROUPED); + } + + /** + * Sets whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. + * + * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. + */ + public void setUsingOptionalsForUngrouped(final boolean usingOptionalsForUngrouped) { + this.getConfig().set(Path.USE_OPTIONALS_UNGROUPED.getPath(), usingOptionalsForUngrouped); + } + + /** + * Saves the configuration file to disk. + */ + // TODO remove need for this method. + // TODO Figure out what I meant by the above todo message... + public void save() { + if (this.optionalSharables != null) { + this.getConfig().set(Path.OPTIONAL_SHARES.getPath(), this.optionalSharables.toStringList()); + } + this.config.save(); + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesDupingPatch.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java similarity index 98% rename from src/main/java/com/onarandombox/multiverseinventories/InventoriesDupingPatch.java rename to src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java index 13fcbeae..c1473552 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesDupingPatch.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import java.util.HashMap; import java.util.Iterator; diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java similarity index 98% rename from src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java rename to src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index b8b1cd38..1ad5f730 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -1,12 +1,12 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; import org.mvplugins.multiverse.core.event.MVVersionEvent; -import com.onarandombox.multiverseinventories.profile.GlobalProfile; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.share.Sharables; import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; import org.bukkit.Location; diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java rename to src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 143d72a1..2049b264 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import java.io.File; import java.io.IOException; @@ -6,19 +6,19 @@ import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.core.api.MVCore; import org.mvplugins.multiverse.core.api.MVPlugin; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.locale.Messager; -import com.onarandombox.multiverseinventories.locale.Messaging; -import com.onarandombox.multiverseinventories.migration.ImportManager; -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.WorldGroupManager; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.Perm; +import org.mvplugins.multiverse.inventories.locale.Message; +import org.mvplugins.multiverse.inventories.locale.Messager; +import org.mvplugins.multiverse.inventories.locale.Messaging; +import org.mvplugins.multiverse.inventories.migration.ImportManager; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.Perm; import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; import org.bukkit.entity.Player; diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java rename to src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java index c12b1f38..1fc4dbaf 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventoriesPluginBinder.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import org.mvplugins.multiverse.core.api.MVPlugin; import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder; diff --git a/src/main/java/com/onarandombox/multiverseinventories/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/PlayerStats.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/PlayerStats.java rename to src/main/java/org/mvplugins/multiverse/inventories/PlayerStats.java index 8c546a53..f5bcb332 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/PlayerStats.java @@ -1,69 +1,69 @@ -package com.onarandombox.multiverseinventories; - -/** - * A collection of values relating to a Minecraft player. - */ -public class PlayerStats { - - /** - * Number of slots in Minecraft player inventory. - */ - public static final int INVENTORY_SIZE = 36; - /** - * Number of slots for armor for player. - */ - public static final int ARMOR_SIZE = 4; - /** - * Number of slots in an ender chest. - */ - public static final int ENDER_CHEST_SIZE = 27; - /** - * Default health value. - */ - public static final int HEALTH = 20; - /** - * Default experience value. - */ - public static final float EXPERIENCE = 0F; - /** - * Default total experience value. - */ - public static final int TOTAL_EXPERIENCE = 0; - /** - * Default level value. - */ - public static final int LEVEL = 0; - /** - * Default food level value. - */ - public static final int FOOD_LEVEL = 20; - /** - * Default exhaustion value. - */ - public static final float EXHAUSTION = 0F; - /** - * Default saturation value. - */ - public static final float SATURATION = 5F; - /** - * Default fall distance value. - */ - public static final float FALL_DISTANCE = 0F; - /** - * Default fire ticks value. - */ - public static final int FIRE_TICKS = 0; - /** - * Default remaining air value. - */ - public static final int REMAINING_AIR = 300; - /** - * Default maximum air value. - */ - public static final int MAXIMUM_AIR = 300; - - private PlayerStats() { - throw new AssertionError(); - } -} - +package org.mvplugins.multiverse.inventories; + +/** + * A collection of values relating to a Minecraft player. + */ +public class PlayerStats { + + /** + * Number of slots in Minecraft player inventory. + */ + public static final int INVENTORY_SIZE = 36; + /** + * Number of slots for armor for player. + */ + public static final int ARMOR_SIZE = 4; + /** + * Number of slots in an ender chest. + */ + public static final int ENDER_CHEST_SIZE = 27; + /** + * Default health value. + */ + public static final int HEALTH = 20; + /** + * Default experience value. + */ + public static final float EXPERIENCE = 0F; + /** + * Default total experience value. + */ + public static final int TOTAL_EXPERIENCE = 0; + /** + * Default level value. + */ + public static final int LEVEL = 0; + /** + * Default food level value. + */ + public static final int FOOD_LEVEL = 20; + /** + * Default exhaustion value. + */ + public static final float EXHAUSTION = 0F; + /** + * Default saturation value. + */ + public static final float SATURATION = 5F; + /** + * Default fall distance value. + */ + public static final float FALL_DISTANCE = 0F; + /** + * Default fire ticks value. + */ + public static final int FIRE_TICKS = 0; + /** + * Default remaining air value. + */ + public static final int REMAINING_AIR = 300; + /** + * Default maximum air value. + */ + public static final int MAXIMUM_AIR = 300; + + private PlayerStats() { + throw new AssertionError(); + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/ShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java index 25008ab6..c07028a1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java @@ -1,17 +1,17 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.PersistingProfile; -import com.onarandombox.multiverseinventories.share.Shares; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.PersistingProfile; +import org.mvplugins.multiverse.inventories.share.Shares; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.LinkedList; import java.util.List; -import static com.onarandombox.multiverseinventories.share.Sharables.allOf; +import static org.mvplugins.multiverse.inventories.share.Sharables.allOf; /** * Abstract class for handling sharing of data between worlds and game modes. diff --git a/src/main/java/com/onarandombox/multiverseinventories/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/ShareHandlingUpdater.java rename to src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java index 5409a059..128c9016 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java @@ -1,10 +1,10 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.share.PersistingProfile; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.share.PersistingProfile; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; import org.apache.commons.lang.StringUtils; import org.bukkit.entity.Player; diff --git a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java similarity index 82% rename from src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainer.java rename to src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java index eb34c970..a62aed92 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java @@ -1,123 +1,123 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.ProfileKey; -import com.onarandombox.multiverseinventories.profile.WorldGroupManager; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * Implementation of ProfileContainer using WeakHashMaps to keep memory usage to a minimum. - */ -final class WeakProfileContainer implements ProfileContainer { - - private Map> playerData = new WeakHashMap<>(); - private final MultiverseInventories inventories; - private final String name; - private final ContainerType type; - - WeakProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { - this.inventories = inventories; - this.name = name; - this.type = type; - } - - /** - * Gets the stored profiles for this player, mapped by ProfileType. - * - * @param name The name of player to get profile map for. - * @return The profile map for the given player. - */ - protected Map getPlayerData(String name) { - return this.playerData.computeIfAbsent(name, k -> new HashMap<>()); - } - - protected ProfileDataSource getDataSource() { - return this.getInventories().getData(); - } - - protected WorldGroupManager getGroupManager() { - return this.getInventories().getGroupManager(); - } - - protected ProfileContainerStore getProfileManager() { - return this.getInventories().getWorldProfileContainerStore(); - } - - protected MultiverseInventories getInventories() { - return this.inventories; - } - - @Override - public PlayerProfile getPlayerData(Player player) { - ProfileType type; - if (inventories.getMVIConfig().isUsingGameModeProfiles()) { - type = ProfileTypes.forGameMode(player.getGameMode()); - } else { - type = ProfileTypes.SURVIVAL; - } - return getPlayerData(type, player); - } - - @Override - public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { - Map profileMap = this.getPlayerData(player.getName()); - PlayerProfile playerProfile = profileMap.get(profileType); - if (playerProfile == null) { - playerProfile = getDataSource().getPlayerData(getContainerType(), - getContainerName(), profileType, player.getUniqueId()); - Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", - profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); - profileMap.put(profileType, playerProfile); - } - return playerProfile; - } - - @Override - public void addPlayerData(PlayerProfile playerProfile) { - this.getPlayerData(playerProfile.getPlayer().getName()).put(playerProfile.getProfileType(), playerProfile); - } - - @Override - public void removeAllPlayerData(OfflinePlayer player) { - this.getPlayerData(player.getName()).clear(); - this.getDataSource().removePlayerData(getContainerType(), getContainerName(), null, player.getName()); - } - - @Override - public void removePlayerData(ProfileType profileType, OfflinePlayer player) { - this.getPlayerData(player.getName()).remove(profileType); - this.getDataSource().removePlayerData(getContainerType(), getContainerName(), profileType, player.getName()); - } - - @Override - public String getContainerName() { - return name; - } - - @Override - public ContainerType getContainerType() { - return type; - } - - @Override - public void clearContainer() { - for (Map profiles : playerData.values()) { - for (PlayerProfile profile : profiles.values()) { - this.getDataSource().clearProfileCache(ProfileKey.createProfileKey(profile)); - } - } - this.playerData.clear(); - } -} +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Implementation of ProfileContainer using WeakHashMaps to keep memory usage to a minimum. + */ +final class WeakProfileContainer implements ProfileContainer { + + private Map> playerData = new WeakHashMap<>(); + private final MultiverseInventories inventories; + private final String name; + private final ContainerType type; + + WeakProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { + this.inventories = inventories; + this.name = name; + this.type = type; + } + + /** + * Gets the stored profiles for this player, mapped by ProfileType. + * + * @param name The name of player to get profile map for. + * @return The profile map for the given player. + */ + protected Map getPlayerData(String name) { + return this.playerData.computeIfAbsent(name, k -> new HashMap<>()); + } + + protected ProfileDataSource getDataSource() { + return this.getInventories().getData(); + } + + protected WorldGroupManager getGroupManager() { + return this.getInventories().getGroupManager(); + } + + protected ProfileContainerStore getProfileManager() { + return this.getInventories().getWorldProfileContainerStore(); + } + + protected MultiverseInventories getInventories() { + return this.inventories; + } + + @Override + public PlayerProfile getPlayerData(Player player) { + ProfileType type; + if (inventories.getMVIConfig().isUsingGameModeProfiles()) { + type = ProfileTypes.forGameMode(player.getGameMode()); + } else { + type = ProfileTypes.SURVIVAL; + } + return getPlayerData(type, player); + } + + @Override + public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { + Map profileMap = this.getPlayerData(player.getName()); + PlayerProfile playerProfile = profileMap.get(profileType); + if (playerProfile == null) { + playerProfile = getDataSource().getPlayerData(getContainerType(), + getContainerName(), profileType, player.getUniqueId()); + Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", + profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); + profileMap.put(profileType, playerProfile); + } + return playerProfile; + } + + @Override + public void addPlayerData(PlayerProfile playerProfile) { + this.getPlayerData(playerProfile.getPlayer().getName()).put(playerProfile.getProfileType(), playerProfile); + } + + @Override + public void removeAllPlayerData(OfflinePlayer player) { + this.getPlayerData(player.getName()).clear(); + this.getDataSource().removePlayerData(getContainerType(), getContainerName(), null, player.getName()); + } + + @Override + public void removePlayerData(ProfileType profileType, OfflinePlayer player) { + this.getPlayerData(player.getName()).remove(profileType); + this.getDataSource().removePlayerData(getContainerType(), getContainerName(), profileType, player.getName()); + } + + @Override + public String getContainerName() { + return name; + } + + @Override + public ContainerType getContainerType() { + return type; + } + + @Override + public void clearContainer() { + for (Map profiles : playerData.values()) { + for (PlayerProfile profile : profiles.values()) { + this.getDataSource().clearProfileCache(ProfileKey.createProfileKey(profile)); + } + } + this.playerData.clear(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java similarity index 77% rename from src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainerStore.java rename to src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java index ad853c80..e693b63e 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainerStore.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java @@ -1,41 +1,41 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; - -import java.util.Map; -import java.util.WeakHashMap; - -final class WeakProfileContainerStore implements ProfileContainerStore { - - private final Map containers = new WeakHashMap<>(); - - private final MultiverseInventories inventories; - private final ContainerType containerType; - - WeakProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { - this.inventories = inventories; - this.containerType = containerType; - } - - private MultiverseInventories getInventories() { - return this.inventories; - } - - @Override - public void addContainer(ProfileContainer container) { - this.containers.put(container.getContainerName().toLowerCase(), container); - } - - @Override - public ProfileContainer getContainer(String containerName) { - ProfileContainer container = this.containers.get(containerName.toLowerCase()); - if (container == null) { - container = new WeakProfileContainer(this.getInventories(), containerName, containerType); - addContainer(container); - } - return container; - } -} - +package org.mvplugins.multiverse.inventories; + +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; + +import java.util.Map; +import java.util.WeakHashMap; + +final class WeakProfileContainerStore implements ProfileContainerStore { + + private final Map containers = new WeakHashMap<>(); + + private final MultiverseInventories inventories; + private final ContainerType containerType; + + WeakProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { + this.inventories = inventories; + this.containerType = containerType; + } + + private MultiverseInventories getInventories() { + return this.inventories; + } + + @Override + public void addContainer(ProfileContainer container) { + this.containers.put(container.getContainerName().toLowerCase(), container); + } + + @Override + public ProfileContainer getContainer(String containerName) { + ProfileContainer container = this.containers.get(containerName.toLowerCase()); + if (container == null) { + container = new WeakProfileContainer(this.getInventories(), containerName, containerType); + addContainer(container); + } + return container; + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/WorldChangeShareHandler.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/WorldChangeShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/WorldChangeShareHandler.java index 33662872..e2e1ea75 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/WorldChangeShareHandler.java @@ -1,231 +1,231 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.event.WorldChangeShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.entity.Player; - -import java.util.List; - -/** - * WorldChange implementation of ShareHandler. - */ -final class WorldChangeShareHandler extends ShareHandler { - - private final String fromWorld; - private final String toWorld; - private final List fromWorldGroups; - private final List toWorldGroups; - - WorldChangeShareHandler(MultiverseInventories inventories, Player player, String fromWorld, String toWorld) { - super(inventories, player); - this.fromWorld = fromWorld; - this.toWorld = toWorld; - - // Get any groups we may need to save stuff to. - this.fromWorldGroups = getAffectedWorldGroups(fromWorld); - // Get any groups we may need to load stuff from. - this.toWorldGroups = getAffectedWorldGroups(toWorld); - - prepareProfiles(); - } - - private List getAffectedWorldGroups(String world) { - return this.inventories.getGroupManager().getGroupsForWorld(world); - } - - @Override - protected ShareHandlingEvent createEvent() { - return new WorldChangeShareHandlingEvent(player, affectedProfiles, fromWorld, toWorld); - } - - private void prepareProfiles() { - Logging.finer("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); - - setAlwaysWriteWorldProfile(); - - if (isPlayerAffectedByChange()) { - addProfiles(); - } - } - - private void setAlwaysWriteWorldProfile() { - // We will always save everything to the world they come from. - PlayerProfile fromWorldProfile = getWorldPlayerProfile(fromWorld, player); - setAlwaysWriteProfile(fromWorldProfile); - } - - private PlayerProfile getWorldPlayerProfile(String world, Player player) { - return getWorldProfile(world).getPlayerData(player); - } - - private ProfileContainer getWorldProfile(String world) { - return inventories.getWorldProfileContainerStore().getContainer(world); - } - - private boolean isPlayerAffectedByChange() { - if (isPlayerBypassingChange()) { - logBypass(); - return false; - } - return true; - } - - private boolean isPlayerBypassingChange() { - return Perm.BYPASS_WORLD.hasBypass(player, fromWorld); - } - - private void addProfiles() { - addWriteProfiles(); - new ReadProfilesAggregator().addReadProfiles(); - } - - private void addWriteProfiles() { - if (hasFromWorldGroups()) { - fromWorldGroups.forEach(wg -> new WorldGroupWrapper(wg).conditionallyAddWriteProfiles()); - } else { - Logging.finer("No groups for fromWorld."); - } - } - - private boolean hasFromWorldGroups() { - return !fromWorldGroups.isEmpty(); - } - - private class ReadProfilesAggregator { - - private Shares sharesToRead; - - private void addReadProfiles() { - sharesToRead = Sharables.noneOf(); - addReadProfilesFromToWorldGroups(); - useToWorldForMissingShares(); - } - - private void addReadProfilesFromToWorldGroups() { - if (hasToWorldGroups()) { - toWorldGroups.forEach(this::conditionallyAddReadProfileForWorldGroup); - } else { - Logging.finer("No groups for toWorld."); - } - } - - private boolean hasToWorldGroups() { - return !toWorldGroups.isEmpty(); - } - - private void conditionallyAddReadProfileForWorldGroup(WorldGroup worldGroup) { - if (isPlayerAffectedByChange(worldGroup)) { - if (isFromWorldNotInToWorldGroup(worldGroup)) { - addReadProfileForWorldGroup(worldGroup); - } else { - sharesToRead.addAll(worldGroup.getShares()); - } - } - } - - private boolean isPlayerAffectedByChange(WorldGroup worldGroup) { - if (isPlayerBypassingChange(worldGroup)) { - logBypass(); - return false; - } - return true; - } - - private boolean isPlayerBypassingChange(WorldGroup worldGroup) { - return Perm.BYPASS_GROUP.hasBypass(player, worldGroup.getName()); - } - - private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { - return !worldGroup.containsWorld(fromWorld); - } - - private void addReadProfileForWorldGroup(WorldGroup worldGroup) { - PlayerProfile playerProfile = getWorldGroupPlayerData(worldGroup); - Shares sharesToAdd = getWorldGroupShares(worldGroup); - - addReadProfile(playerProfile, sharesToAdd); - sharesToRead.addAll(sharesToAdd); - } - - private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { - return getWorldGroupProfileContainer(worldGroup).getPlayerData(player); - } - - private ProfileContainer getWorldGroupProfileContainer(WorldGroup worldGroup) { - return worldGroup.getGroupProfileContainer(); - } - - private Shares getWorldGroupShares(WorldGroup worldGroup) { - return Sharables.fromShares(worldGroup.getShares()); - } - - private void useToWorldForMissingShares() { - // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. - if (hasUnhandledShares()) { - addUnhandledSharesFromToWorld(); - } - } - - private boolean hasUnhandledShares() { - return !sharesToRead.isSharing(Sharables.all()); - } - - private void addUnhandledSharesFromToWorld() { - Shares unhandledShares = Sharables.complimentOf(sharesToRead); - - Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); - - addReadProfile(getToWorldPlayerData(), unhandledShares); - } - - private PlayerProfile getToWorldPlayerData() { - return getToWorldProfileContainer().getPlayerData(player); - } - - private ProfileContainer getToWorldProfileContainer() { - return inventories.getWorldProfileContainerStore().getContainer(toWorld); - } - } - - private class WorldGroupWrapper { - private final WorldGroup worldGroup; - - public WorldGroupWrapper(WorldGroup worldGroup) { - this.worldGroup = worldGroup; - } - - private void conditionallyAddWriteProfiles() { - if (isEligibleForWrite()) { - addWriteProfiles(); - } - } - - boolean isEligibleForWrite() { - return groupDoesNotContainWorld(toWorld) || isNotSharingAll(); - } - - private boolean groupDoesNotContainWorld(String world) { - return !worldGroup.containsWorld(world); - } - - private boolean isNotSharingAll() { - return !worldGroup.getShares().isSharing(Sharables.all()); - } - - void addWriteProfiles() { - ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(player), getWorldGroupShares()); - } - - private Shares getWorldGroupShares() { - return Sharables.fromShares(worldGroup.getShares()); - } - } - -} +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.WorldChangeShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.Perm; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * WorldChange implementation of ShareHandler. + */ +final class WorldChangeShareHandler extends ShareHandler { + + private final String fromWorld; + private final String toWorld; + private final List fromWorldGroups; + private final List toWorldGroups; + + WorldChangeShareHandler(MultiverseInventories inventories, Player player, String fromWorld, String toWorld) { + super(inventories, player); + this.fromWorld = fromWorld; + this.toWorld = toWorld; + + // Get any groups we may need to save stuff to. + this.fromWorldGroups = getAffectedWorldGroups(fromWorld); + // Get any groups we may need to load stuff from. + this.toWorldGroups = getAffectedWorldGroups(toWorld); + + prepareProfiles(); + } + + private List getAffectedWorldGroups(String world) { + return this.inventories.getGroupManager().getGroupsForWorld(world); + } + + @Override + protected ShareHandlingEvent createEvent() { + return new WorldChangeShareHandlingEvent(player, affectedProfiles, fromWorld, toWorld); + } + + private void prepareProfiles() { + Logging.finer("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); + + setAlwaysWriteWorldProfile(); + + if (isPlayerAffectedByChange()) { + addProfiles(); + } + } + + private void setAlwaysWriteWorldProfile() { + // We will always save everything to the world they come from. + PlayerProfile fromWorldProfile = getWorldPlayerProfile(fromWorld, player); + setAlwaysWriteProfile(fromWorldProfile); + } + + private PlayerProfile getWorldPlayerProfile(String world, Player player) { + return getWorldProfile(world).getPlayerData(player); + } + + private ProfileContainer getWorldProfile(String world) { + return inventories.getWorldProfileContainerStore().getContainer(world); + } + + private boolean isPlayerAffectedByChange() { + if (isPlayerBypassingChange()) { + logBypass(); + return false; + } + return true; + } + + private boolean isPlayerBypassingChange() { + return Perm.BYPASS_WORLD.hasBypass(player, fromWorld); + } + + private void addProfiles() { + addWriteProfiles(); + new ReadProfilesAggregator().addReadProfiles(); + } + + private void addWriteProfiles() { + if (hasFromWorldGroups()) { + fromWorldGroups.forEach(wg -> new WorldGroupWrapper(wg).conditionallyAddWriteProfiles()); + } else { + Logging.finer("No groups for fromWorld."); + } + } + + private boolean hasFromWorldGroups() { + return !fromWorldGroups.isEmpty(); + } + + private class ReadProfilesAggregator { + + private Shares sharesToRead; + + private void addReadProfiles() { + sharesToRead = Sharables.noneOf(); + addReadProfilesFromToWorldGroups(); + useToWorldForMissingShares(); + } + + private void addReadProfilesFromToWorldGroups() { + if (hasToWorldGroups()) { + toWorldGroups.forEach(this::conditionallyAddReadProfileForWorldGroup); + } else { + Logging.finer("No groups for toWorld."); + } + } + + private boolean hasToWorldGroups() { + return !toWorldGroups.isEmpty(); + } + + private void conditionallyAddReadProfileForWorldGroup(WorldGroup worldGroup) { + if (isPlayerAffectedByChange(worldGroup)) { + if (isFromWorldNotInToWorldGroup(worldGroup)) { + addReadProfileForWorldGroup(worldGroup); + } else { + sharesToRead.addAll(worldGroup.getShares()); + } + } + } + + private boolean isPlayerAffectedByChange(WorldGroup worldGroup) { + if (isPlayerBypassingChange(worldGroup)) { + logBypass(); + return false; + } + return true; + } + + private boolean isPlayerBypassingChange(WorldGroup worldGroup) { + return Perm.BYPASS_GROUP.hasBypass(player, worldGroup.getName()); + } + + private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { + return !worldGroup.containsWorld(fromWorld); + } + + private void addReadProfileForWorldGroup(WorldGroup worldGroup) { + PlayerProfile playerProfile = getWorldGroupPlayerData(worldGroup); + Shares sharesToAdd = getWorldGroupShares(worldGroup); + + addReadProfile(playerProfile, sharesToAdd); + sharesToRead.addAll(sharesToAdd); + } + + private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { + return getWorldGroupProfileContainer(worldGroup).getPlayerData(player); + } + + private ProfileContainer getWorldGroupProfileContainer(WorldGroup worldGroup) { + return worldGroup.getGroupProfileContainer(); + } + + private Shares getWorldGroupShares(WorldGroup worldGroup) { + return Sharables.fromShares(worldGroup.getShares()); + } + + private void useToWorldForMissingShares() { + // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. + if (hasUnhandledShares()) { + addUnhandledSharesFromToWorld(); + } + } + + private boolean hasUnhandledShares() { + return !sharesToRead.isSharing(Sharables.all()); + } + + private void addUnhandledSharesFromToWorld() { + Shares unhandledShares = Sharables.complimentOf(sharesToRead); + + Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); + + addReadProfile(getToWorldPlayerData(), unhandledShares); + } + + private PlayerProfile getToWorldPlayerData() { + return getToWorldProfileContainer().getPlayerData(player); + } + + private ProfileContainer getToWorldProfileContainer() { + return inventories.getWorldProfileContainerStore().getContainer(toWorld); + } + } + + private class WorldGroupWrapper { + private final WorldGroup worldGroup; + + public WorldGroupWrapper(WorldGroup worldGroup) { + this.worldGroup = worldGroup; + } + + private void conditionallyAddWriteProfiles() { + if (isEligibleForWrite()) { + addWriteProfiles(); + } + } + + boolean isEligibleForWrite() { + return groupDoesNotContainWorld(toWorld) || isNotSharingAll(); + } + + private boolean groupDoesNotContainWorld(String world) { + return !worldGroup.containsWorld(world); + } + + private boolean isNotSharingAll() { + return !worldGroup.getShares().isSharing(Sharables.all()); + } + + void addWriteProfiles() { + ProfileContainer container = worldGroup.getGroupProfileContainer(); + affectedProfiles.addWriteProfile(container.getPlayerData(player), getWorldGroupShares()); + } + + private Shares getWorldGroupShares() { + return Sharables.fromShares(worldGroup.getShares()); + } + } + +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/WorldGroup.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/WorldGroup.java rename to src/main/java/org/mvplugins/multiverse/inventories/WorldGroup.java index ef42ac42..a0e6cdd6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/WorldGroup.java @@ -1,247 +1,247 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import org.bukkit.World; -import org.bukkit.event.EventPriority; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public final class WorldGroup { - - private final MultiverseInventories plugin; - private final String name; - private final HashSet worlds = new HashSet<>(); - private final Shares shares = Sharables.noneOf(); - - private String spawnWorld = null; - private EventPriority spawnPriority = EventPriority.NORMAL; - - WorldGroup(final MultiverseInventories inventories, final String name) { - this.plugin = inventories; - this.name = name; - } - - /** - * Get the name of this World Group. - * - * @return Name of this World Group. - */ - public String getName() { - return this.name; - } - - /** - * Adds a world to this world group and updates it in the Config. - * - * @param worldName The name of the world to add. - */ - public void addWorld(String worldName) { - this.addWorld(worldName, true); - } - - /** - * Adds a world to this world group and optionally updates it in the Config. - * - * @param worldName The name of the world to add. - * @param updateConfig True to update this group in the config. - */ - public void addWorld(String worldName, boolean updateConfig) { - this.getWorlds().add(worldName.toLowerCase()); - if (updateConfig) { - plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Convenience method to add a {@link org.bukkit.World} to this World Group. - * - * @param world The world to add. - */ - public void addWorld(World world) { - this.addWorld(world.getName()); - } - - /** - * Convenience method to add multiple worlds to this World Group and updates it in the Config. - * - * @param worlds A collections of worlds to add. - */ - public void addWorlds(Collection worlds) { - this.addWorlds(worlds, true); - } - - /** - * Convenience method to add multiple worlds to this World Group. - * - * @param worlds A collections of worlds to add. - * @param updateConfig True to update this group in the config. - */ - public void addWorlds(Collection worlds, boolean updateConfig) { - worlds.forEach(worldName -> this.addWorld(worldName, false)); - if (updateConfig) { - this.plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Removes a world from this world group and updates the group in the Config. - * - * @param worldName The name of the world to remove. - */ - public void removeWorld(String worldName) { - this.removeWorld(worldName, true); - } - - /** - * Removes a world from this world group and optionally updates it in the Config. - * - * @param worldName The name of the world to remove. - * @param updateConfig True to update this group in the config. - */ - public void removeWorld(String worldName, boolean updateConfig) { - this.getWorlds().remove(worldName.toLowerCase()); - if (updateConfig) { - plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Convenience method to remove a {@link org.bukkit.World} from this World Group. - * - * @param world The world to remove. - */ - public void removeWorld(World world) { - this.removeWorld(world.getName()); - } - - /** - * Remove all the worlds in this World Group. - */ - public void removeAllWorlds() { - this.removeAllWorlds(true); - } - - /** - * Remove all the worlds in this World Group. - * - * @param updateConfig True to update this group in the config. - */ - public void removeAllWorlds(boolean updateConfig) { - this.worlds.clear(); - if (updateConfig) { - this.plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Retrieves all of the worlds in this World Group. - * - * @return The worlds of this World Group. - */ - public Set getWorlds() { - return this.worlds; - } - - /** - * Checks if this group is sharing sharable. This will check both shares and negative shares of the group. - * This is the preferred method for checking if a group shares something as shares may contain ALL shares while - * ones indicated in negative shares means those aren't actually shared. - * - * @param sharable Sharable to check if sharing. - * @return true is the sharable is shared for this group. - */ - public boolean isSharing(Sharable sharable) { - return getShares().isSharing(sharable); - } - - /** - * Retrieves the shares for this World Group. Any changes to this group must be subsequently saved to the data - * source for the changes to be permanent. - * - * @return The shares for this World Group. - */ - public Shares getShares() { - return this.shares; - } - - /** - * @param worldName Name of world to check for. - * @return True if specified world is part of this group. - */ - public boolean containsWorld(String worldName) { - return this.getWorlds().contains(worldName.toLowerCase()); - } - - /** - * @return The name of the world that will be used as the spawn for this group. - * Or null if no world was specified as the group spawn world. - */ - public String getSpawnWorld() { - return this.spawnWorld; - } - - /** - * @param worldName The name of the world to set this groups spawn to. - */ - public void setSpawnWorld(String worldName) { - this.spawnWorld = worldName.toLowerCase(); - } - - /** - * @return The priority for the respawn event that this spawn will act on. - */ - public EventPriority getSpawnPriority() { - return this.spawnPriority; - } - - /** - * @param priority The priority that will be used for respawning the player at - * this group's spawn location if there is one set. - */ - public void setSpawnPriority(EventPriority priority) { - this.spawnPriority = priority; - } - - /** - * Is this a default group. - * - * @return true if this is the default group. - */ - public boolean isDefault() { - return AbstractWorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); - } - - /** - * Returns the profile container for this group. - * - * @return the profile container for this group. - */ - public ProfileContainer getGroupProfileContainer() { - return plugin.getGroupProfileContainerStore().getContainer(getName()); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(this.getName()).append(": {Worlds: ["); - String[] worldsString = this.getWorlds().toArray(new String[this.getWorlds().size()]); - for (int i = 0; i < worldsString.length; i++) { - if (i != 0) { - builder.append(", "); - } - builder.append(worldsString[i]); - } - builder.append("], Shares: [").append(this.getShares().toString()).append("]"); - if (this.getSpawnWorld() != null) { - builder.append(", Spawn World: ").append(this.getSpawnWorld()); - builder.append(", Spawn Priority: ").append(this.getSpawnPriority().toString()); - } - builder.append("}"); - return builder.toString(); - } -} +package org.mvplugins.multiverse.inventories; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.bukkit.World; +import org.bukkit.event.EventPriority; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public final class WorldGroup { + + private final MultiverseInventories plugin; + private final String name; + private final HashSet worlds = new HashSet<>(); + private final Shares shares = Sharables.noneOf(); + + private String spawnWorld = null; + private EventPriority spawnPriority = EventPriority.NORMAL; + + WorldGroup(final MultiverseInventories inventories, final String name) { + this.plugin = inventories; + this.name = name; + } + + /** + * Get the name of this World Group. + * + * @return Name of this World Group. + */ + public String getName() { + return this.name; + } + + /** + * Adds a world to this world group and updates it in the Config. + * + * @param worldName The name of the world to add. + */ + public void addWorld(String worldName) { + this.addWorld(worldName, true); + } + + /** + * Adds a world to this world group and optionally updates it in the Config. + * + * @param worldName The name of the world to add. + * @param updateConfig True to update this group in the config. + */ + public void addWorld(String worldName, boolean updateConfig) { + this.getWorlds().add(worldName.toLowerCase()); + if (updateConfig) { + plugin.getGroupManager().updateGroup(this); + } + } + + /** + * Convenience method to add a {@link org.bukkit.World} to this World Group. + * + * @param world The world to add. + */ + public void addWorld(World world) { + this.addWorld(world.getName()); + } + + /** + * Convenience method to add multiple worlds to this World Group and updates it in the Config. + * + * @param worlds A collections of worlds to add. + */ + public void addWorlds(Collection worlds) { + this.addWorlds(worlds, true); + } + + /** + * Convenience method to add multiple worlds to this World Group. + * + * @param worlds A collections of worlds to add. + * @param updateConfig True to update this group in the config. + */ + public void addWorlds(Collection worlds, boolean updateConfig) { + worlds.forEach(worldName -> this.addWorld(worldName, false)); + if (updateConfig) { + this.plugin.getGroupManager().updateGroup(this); + } + } + + /** + * Removes a world from this world group and updates the group in the Config. + * + * @param worldName The name of the world to remove. + */ + public void removeWorld(String worldName) { + this.removeWorld(worldName, true); + } + + /** + * Removes a world from this world group and optionally updates it in the Config. + * + * @param worldName The name of the world to remove. + * @param updateConfig True to update this group in the config. + */ + public void removeWorld(String worldName, boolean updateConfig) { + this.getWorlds().remove(worldName.toLowerCase()); + if (updateConfig) { + plugin.getGroupManager().updateGroup(this); + } + } + + /** + * Convenience method to remove a {@link org.bukkit.World} from this World Group. + * + * @param world The world to remove. + */ + public void removeWorld(World world) { + this.removeWorld(world.getName()); + } + + /** + * Remove all the worlds in this World Group. + */ + public void removeAllWorlds() { + this.removeAllWorlds(true); + } + + /** + * Remove all the worlds in this World Group. + * + * @param updateConfig True to update this group in the config. + */ + public void removeAllWorlds(boolean updateConfig) { + this.worlds.clear(); + if (updateConfig) { + this.plugin.getGroupManager().updateGroup(this); + } + } + + /** + * Retrieves all of the worlds in this World Group. + * + * @return The worlds of this World Group. + */ + public Set getWorlds() { + return this.worlds; + } + + /** + * Checks if this group is sharing sharable. This will check both shares and negative shares of the group. + * This is the preferred method for checking if a group shares something as shares may contain ALL shares while + * ones indicated in negative shares means those aren't actually shared. + * + * @param sharable Sharable to check if sharing. + * @return true is the sharable is shared for this group. + */ + public boolean isSharing(Sharable sharable) { + return getShares().isSharing(sharable); + } + + /** + * Retrieves the shares for this World Group. Any changes to this group must be subsequently saved to the data + * source for the changes to be permanent. + * + * @return The shares for this World Group. + */ + public Shares getShares() { + return this.shares; + } + + /** + * @param worldName Name of world to check for. + * @return True if specified world is part of this group. + */ + public boolean containsWorld(String worldName) { + return this.getWorlds().contains(worldName.toLowerCase()); + } + + /** + * @return The name of the world that will be used as the spawn for this group. + * Or null if no world was specified as the group spawn world. + */ + public String getSpawnWorld() { + return this.spawnWorld; + } + + /** + * @param worldName The name of the world to set this groups spawn to. + */ + public void setSpawnWorld(String worldName) { + this.spawnWorld = worldName.toLowerCase(); + } + + /** + * @return The priority for the respawn event that this spawn will act on. + */ + public EventPriority getSpawnPriority() { + return this.spawnPriority; + } + + /** + * @param priority The priority that will be used for respawning the player at + * this group's spawn location if there is one set. + */ + public void setSpawnPriority(EventPriority priority) { + this.spawnPriority = priority; + } + + /** + * Is this a default group. + * + * @return true if this is the default group. + */ + public boolean isDefault() { + return AbstractWorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); + } + + /** + * Returns the profile container for this group. + * + * @return the profile container for this group. + */ + public ProfileContainer getGroupProfileContainer() { + return plugin.getGroupProfileContainerStore().getContainer(getName()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(this.getName()).append(": {Worlds: ["); + String[] worldsString = this.getWorlds().toArray(new String[this.getWorlds().size()]); + for (int i = 0; i < worldsString.length; i++) { + if (i != 0) { + builder.append(", "); + } + builder.append(worldsString[i]); + } + builder.append("], Shares: [").append(this.getShares().toString()).append("]"); + if (this.getSpawnWorld() != null) { + builder.append(", Spawn World: ").append(this.getSpawnWorld()); + builder.append(", Spawn Priority: ").append(this.getSpawnPriority().toString()); + } + builder.append("}"); + return builder.toString(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java similarity index 97% rename from src/main/java/com/onarandombox/multiverseinventories/YamlWorldGroupManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java index a4ce12cf..32ab98db 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java @@ -1,10 +1,10 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; import com.google.common.collect.Lists; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; -import com.onarandombox.multiverseinventories.util.DeserializationException; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; +import org.mvplugins.multiverse.inventories.util.DeserializationException; import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; import org.bukkit.World; diff --git a/src/main/java/com/onarandombox/multiverseinventories/blacklist/ItemBlacklist.java b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java similarity index 52% rename from src/main/java/com/onarandombox/multiverseinventories/blacklist/ItemBlacklist.java rename to src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java index 049ca185..36d808b6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/blacklist/ItemBlacklist.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.blacklist; +package org.mvplugins.multiverse.inventories.blacklist; /** * Placeholder. diff --git a/src/main/java/com/onarandombox/multiverseinventories/blacklist/SimpleItemBlacklist.java b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java similarity index 66% rename from src/main/java/com/onarandombox/multiverseinventories/blacklist/SimpleItemBlacklist.java rename to src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java index e0395f3b..67fb00c5 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/blacklist/SimpleItemBlacklist.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.blacklist; +package org.mvplugins.multiverse.inventories.blacklist; /** * Simple Implementation of ItemBlacklist. diff --git a/src/main/java/com/onarandombox/multiverseinventories/blacklist/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java similarity index 73% rename from src/main/java/com/onarandombox/multiverseinventories/blacklist/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java index bf4177e5..8f81a034 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/blacklist/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java @@ -2,5 +2,5 @@ * This package contains classes related to Item Blacklisting per world group/world. * It is also very unfinished and likely to be refactored later. */ -package com.onarandombox.multiverseinventories.blacklist; +package org.mvplugins.multiverse.inventories.blacklist; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java similarity index 86% rename from src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index 9bf071cc..bfc587a1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands; +package org.mvplugins.multiverse.inventories.commands; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.commands.prompts.GroupControlPrompt; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.commands.prompts.GroupControlPrompt; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.Conversable; import org.bukkit.conversations.Conversation; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index e0a75853..b2945e23 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -1,9 +1,9 @@ -package com.onarandombox.multiverseinventories.commands; +package org.mvplugins.multiverse.inventories.commands; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java index c0e3cd9a..bc4c9ec3 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/InventoriesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.commands; +package org.mvplugins.multiverse.inventories.commands; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java similarity index 87% rename from src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index dd42e754..0cb68940 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands; +package org.mvplugins.multiverse.inventories.commands; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java similarity index 87% rename from src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index b9246dc9..f60e8fae 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -1,7 +1,7 @@ -package com.onarandombox.multiverseinventories.commands; +package org.mvplugins.multiverse.inventories.commands; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java similarity index 88% rename from src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 9a7e66b4..c83821ae 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -1,10 +1,10 @@ -package com.onarandombox.multiverseinventories.commands; +package org.mvplugins.multiverse.inventories.commands; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.locale.Message; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java new file mode 100644 index 00000000..513b441b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains all Commands. + */ +package org.mvplugins.multiverse.inventories.commands; + diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupControlPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java similarity index 84% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupControlPrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java index 0078cfd7..48c084fe 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupControlPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java @@ -1,7 +1,7 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupCreatePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java similarity index 83% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupCreatePrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java index 79e1ea22..c473178b 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupCreatePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupDeletePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java similarity index 84% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupDeletePrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java index db80b323..a1e64b8b 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupDeletePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupEditPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java similarity index 84% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupEditPrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java index 28c0526a..67f1b439 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupEditPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupModifyPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java similarity index 81% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupModifyPrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java index abe6cedb..17952186 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupModifyPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java similarity index 86% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupSharesPrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java index d9bc36b1..f20d8668 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupSharesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -1,11 +1,11 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupWorldsPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupWorldsPrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java index c5fe2ffa..6050ecbf 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/GroupWorldsPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; diff --git a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/InventoriesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java similarity index 76% rename from src/main/java/com/onarandombox/multiverseinventories/commands/prompts/InventoriesPrompt.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java index 2e7324b7..c3ed4c52 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/commands/prompts/InventoriesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java @@ -1,7 +1,7 @@ -package com.onarandombox.multiverseinventories.commands.prompts; +package org.mvplugins.multiverse.inventories.commands.prompts; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Messager; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.locale.Messager; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/GameModeChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/event/GameModeChangeShareHandlingEvent.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java index b785a28b..2a397f1d 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/GameModeChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; -import com.onarandombox.multiverseinventories.ShareHandler; +import org.mvplugins.multiverse.inventories.ShareHandler; import org.bukkit.GameMode; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java similarity index 91% rename from src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index 9bc81d2f..29669135 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -1,7 +1,7 @@ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; -import com.onarandombox.multiverseinventories.ShareHandler; -import com.onarandombox.multiverseinventories.share.PersistingProfile; +import org.mvplugins.multiverse.inventories.ShareHandler; +import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/WorldChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java similarity index 91% rename from src/main/java/com/onarandombox/multiverseinventories/event/WorldChangeShareHandlingEvent.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java index 87bda1e1..71266aa3 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/WorldChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; -import com.onarandombox.multiverseinventories.ShareHandler; +import org.mvplugins.multiverse.inventories.ShareHandler; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/event/package-info.java similarity index 69% rename from src/main/java/com/onarandombox/multiverseinventories/event/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/package-info.java index c9221141..deb2da33 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/package-info.java @@ -1,5 +1,5 @@ /** * This package contains event classes that are thrown by Multiverse-Inventories for handling by other plugins. */ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/LazyLocaleMessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java similarity index 96% rename from src/main/java/com/onarandombox/multiverseinventories/locale/LazyLocaleMessageProvider.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java index baf9c688..a4fb2d58 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/LazyLocaleMessageProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; import java.util.Locale; import java.util.Set; diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/LocalizationLoadingException.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java similarity index 95% rename from src/main/java/com/onarandombox/multiverseinventories/locale/LocalizationLoadingException.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java index f5bfea63..acd78fb4 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/LocalizationLoadingException.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; import java.io.IOException; import java.util.Locale; diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/Message.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java similarity index 96% rename from src/main/java/com/onarandombox/multiverseinventories/locale/Message.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java index badd749d..42f6f27d 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/Message.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java @@ -1,113 +1,113 @@ -package com.onarandombox.multiverseinventories.locale; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * An enum containing all messages/strings used by Multiverse. - */ -public enum Message { - // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc - TEST_STRING("a test-string from the enum"), - - // Generic Strings - GENERIC_SORRY("Sorry..."), - GENERIC_PAGE("Page"), - GENERIC_OF("of"), - GENERIC_UNLOADED("UNLOADED"), - GENERIC_PLUGIN_DISABLED("This plugin is Disabled!"), - GENERIC_ERROR("[Error]"), - GENERIC_SUCCESS("[Success]"), - GENERIC_INFO("[Info]"), - GENERIC_HELP("[Help]"), - GENERIC_COMMAND_NO_PERMISSION("You do not have permission to %1. (%2)"), - GENERIC_THE_CONSOLE("the console"), - GENERIC_NOT_LOGGED_IN("%1 is not logged on right now!"), - GENERIC_OFF("OFF"), - - // Errors - ERROR_CONFIG_LOAD("Encountered an error while loading the configuration file. Disabling..."), - ERROR_DATA_LOAD("Encountered an error while loading the data file. Disabling..."), - ERROR_NO_GROUP("&6There is no group with the name: &f%1"), - ERROR_NO_WORLD("&6There is no world with the name: &f%1"), - ERROR_NO_WORLD_PROFILE("&6There is no profile container for the world: &f%1"), - ERROR_PLUGIN_NOT_ENABLED("&f%1 &6is not enabled so you may not import data from it!"), - ERROR_UNSUPPORTED_IMPORT("&6Sorry, ''&f%1&6'' is not supported for importing."), - ERROR_NO_SHARES_SPECIFIED("&cYou did not specify any valid shares!"), - - // Group Conflicts - CONFLICT_RESULTS("Conflict found for groups: '%1' and '%2' because they both share: '%3' for the world(s): '%4'"), - CONFLICT_CHECKING("Checking for conflicts in groups..."), - CONFLICT_FOUND("Conflicts have been found... If these are not resolved, you may experience problems with your data."), - CONFLICT_NOT_FOUND("No group conflicts found!"), - - //// Commands - NON_CONVERSABLE("You are not allowed to access conversations (remote console?)"), - INVALID_PROMPT_OPTION("&cThat is not a valid option! Type &f##&c to stop working on groups."), - // Info Command - INFO_ZERO_ARG("You may only use the no argument version of this command in game!"), - INFO_WORLD("&b===[ Info for world: &6%1&b ]==="), - INFO_WORLD_INFO("&6Groups:&f %1"), - INFO_GROUP("&b===[ Info for group: &6%1&b ]==="), - INFO_GROUPS_INFO("&6Worlds:&f %1", "&bShares:&f %2"), - // Group Command - GROUP_COMMAND_PROMPT("&6What would you like to do? &fCreate&6, &fEdit &6or &fDelete&6. Enter &f##&6 at any time to cancel."), - GROUP_CREATE_PROMPT("&6Please name your new group: "), - GROUP_EDIT_PROMPT("&6Edit which group? %1"), - GROUP_DELETE_PROMPT("&6Delete which group? %1"), - GROUP_MODIFY_PROMPT("&6Which would you like to change for &e%1&6? &fWorlds &6or &fShares&6. Enter &f##&6 to finish."), - GROUP_WORLDS_PROMPT("&6Enter the name of a world to add to group &f%1&6 or enter &f@&6 to continue. To remove a world, precede the name with the minus symbol. (ex: &f-worldname&6). Current worlds: %2"), - GROUP_SHARES_PROMPT("&6Enter &fall&6 or a specific share to add to group &f%1&6 or enter &f@&6 to continue. To remove shares, precede the name with the minus symbol (ex: &f-inventory&6). Current shares: %2"), - GROUP_INVALID_NAME("&cThat name is not valid! May only contain letters, numbers, and underscores."), - GROUP_EXISTS("&cThat group already exists! (&f%1&c)"), - GROUP_REMOVED("&2Removed group: &f%1"), - GROUP_WORLDS_EMPTY("&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel."), - GROUP_CREATION_COMPLETE("&2You created a new group!"), - GROUP_UPDATED("&2Group has been updated!"), - // List Command - LIST_GROUPS("&b===[ Group List ]===", "&6Groups:&f %1"), - // Reload Command - RELOAD_COMPLETE("&b===[ Reload Complete! ]==="), - // AddWorld Command - WORLD_ADDED("&6World:&f %1 &6added to Group: &f%2"), - WORLD_ALREADY_EXISTS("&6World:&f %1 &6already part of Group: &f%2"), - // RemoveWorld Command - WORLD_REMOVED("&6World:&f %1 &6removed from Group: &f%2"), - WORLD_NOT_IN_GROUP("&6World:&f %1 &6is not part of Group: &f%2"), - // AddShares Command - NOW_SHARING("&6Group: &f%1 &6is now sharing: &f%2"), - // Spawn Command - TELEPORTING("Teleporting to this world group's spawn..."), - TELEPORTED_BY("You were teleported by: %1"), - TELEPORT_CONSOLE_ERROR("From the console, you must provide a PLAYER"), - // DebugCommand - INVALID_DEBUG("&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!)"), - DEBUG_SET("Debug mode is %1"), - // Toggle Command - NOW_USING_OPTIONAL("&f%1 &6will now be considered when player's change world."), - NOW_NOT_USING_OPTIONAL("&f%1 &6will no longer be considered when player's change world."), - NO_OPTIONAL_SHARES("&f%1 &6is not an optional share!"), - // Migrate Command - MIGRATE_FAILED("Failed to migrate data from %1 to %2! Check logs for error details."), - MIGRATE_SUCCESSFUL("Migrated data from %1 to %2!"); - - // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc - - private final List def; - - Message(String def, String... extra) { - this.def = new ArrayList(); - this.def.add(def); - this.def.addAll(Arrays.asList(extra)); - } - - /** - * @return This {@link Message}'s default-message - */ - public List getDefault() { - return def; - } - -} - +package org.mvplugins.multiverse.inventories.locale; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * An enum containing all messages/strings used by Multiverse. + */ +public enum Message { + // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc + TEST_STRING("a test-string from the enum"), + + // Generic Strings + GENERIC_SORRY("Sorry..."), + GENERIC_PAGE("Page"), + GENERIC_OF("of"), + GENERIC_UNLOADED("UNLOADED"), + GENERIC_PLUGIN_DISABLED("This plugin is Disabled!"), + GENERIC_ERROR("[Error]"), + GENERIC_SUCCESS("[Success]"), + GENERIC_INFO("[Info]"), + GENERIC_HELP("[Help]"), + GENERIC_COMMAND_NO_PERMISSION("You do not have permission to %1. (%2)"), + GENERIC_THE_CONSOLE("the console"), + GENERIC_NOT_LOGGED_IN("%1 is not logged on right now!"), + GENERIC_OFF("OFF"), + + // Errors + ERROR_CONFIG_LOAD("Encountered an error while loading the configuration file. Disabling..."), + ERROR_DATA_LOAD("Encountered an error while loading the data file. Disabling..."), + ERROR_NO_GROUP("&6There is no group with the name: &f%1"), + ERROR_NO_WORLD("&6There is no world with the name: &f%1"), + ERROR_NO_WORLD_PROFILE("&6There is no profile container for the world: &f%1"), + ERROR_PLUGIN_NOT_ENABLED("&f%1 &6is not enabled so you may not import data from it!"), + ERROR_UNSUPPORTED_IMPORT("&6Sorry, ''&f%1&6'' is not supported for importing."), + ERROR_NO_SHARES_SPECIFIED("&cYou did not specify any valid shares!"), + + // Group Conflicts + CONFLICT_RESULTS("Conflict found for groups: '%1' and '%2' because they both share: '%3' for the world(s): '%4'"), + CONFLICT_CHECKING("Checking for conflicts in groups..."), + CONFLICT_FOUND("Conflicts have been found... If these are not resolved, you may experience problems with your data."), + CONFLICT_NOT_FOUND("No group conflicts found!"), + + //// Commands + NON_CONVERSABLE("You are not allowed to access conversations (remote console?)"), + INVALID_PROMPT_OPTION("&cThat is not a valid option! Type &f##&c to stop working on groups."), + // Info Command + INFO_ZERO_ARG("You may only use the no argument version of this command in game!"), + INFO_WORLD("&b===[ Info for world: &6%1&b ]==="), + INFO_WORLD_INFO("&6Groups:&f %1"), + INFO_GROUP("&b===[ Info for group: &6%1&b ]==="), + INFO_GROUPS_INFO("&6Worlds:&f %1", "&bShares:&f %2"), + // Group Command + GROUP_COMMAND_PROMPT("&6What would you like to do? &fCreate&6, &fEdit &6or &fDelete&6. Enter &f##&6 at any time to cancel."), + GROUP_CREATE_PROMPT("&6Please name your new group: "), + GROUP_EDIT_PROMPT("&6Edit which group? %1"), + GROUP_DELETE_PROMPT("&6Delete which group? %1"), + GROUP_MODIFY_PROMPT("&6Which would you like to change for &e%1&6? &fWorlds &6or &fShares&6. Enter &f##&6 to finish."), + GROUP_WORLDS_PROMPT("&6Enter the name of a world to add to group &f%1&6 or enter &f@&6 to continue. To remove a world, precede the name with the minus symbol. (ex: &f-worldname&6). Current worlds: %2"), + GROUP_SHARES_PROMPT("&6Enter &fall&6 or a specific share to add to group &f%1&6 or enter &f@&6 to continue. To remove shares, precede the name with the minus symbol (ex: &f-inventory&6). Current shares: %2"), + GROUP_INVALID_NAME("&cThat name is not valid! May only contain letters, numbers, and underscores."), + GROUP_EXISTS("&cThat group already exists! (&f%1&c)"), + GROUP_REMOVED("&2Removed group: &f%1"), + GROUP_WORLDS_EMPTY("&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel."), + GROUP_CREATION_COMPLETE("&2You created a new group!"), + GROUP_UPDATED("&2Group has been updated!"), + // List Command + LIST_GROUPS("&b===[ Group List ]===", "&6Groups:&f %1"), + // Reload Command + RELOAD_COMPLETE("&b===[ Reload Complete! ]==="), + // AddWorld Command + WORLD_ADDED("&6World:&f %1 &6added to Group: &f%2"), + WORLD_ALREADY_EXISTS("&6World:&f %1 &6already part of Group: &f%2"), + // RemoveWorld Command + WORLD_REMOVED("&6World:&f %1 &6removed from Group: &f%2"), + WORLD_NOT_IN_GROUP("&6World:&f %1 &6is not part of Group: &f%2"), + // AddShares Command + NOW_SHARING("&6Group: &f%1 &6is now sharing: &f%2"), + // Spawn Command + TELEPORTING("Teleporting to this world group's spawn..."), + TELEPORTED_BY("You were teleported by: %1"), + TELEPORT_CONSOLE_ERROR("From the console, you must provide a PLAYER"), + // DebugCommand + INVALID_DEBUG("&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!)"), + DEBUG_SET("Debug mode is %1"), + // Toggle Command + NOW_USING_OPTIONAL("&f%1 &6will now be considered when player's change world."), + NOW_NOT_USING_OPTIONAL("&f%1 &6will no longer be considered when player's change world."), + NO_OPTIONAL_SHARES("&f%1 &6is not an optional share!"), + // Migrate Command + MIGRATE_FAILED("Failed to migrate data from %1 to %2! Check logs for error details."), + MIGRATE_SUCCESSFUL("Migrated data from %1 to %2!"); + + // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc + + private final List def; + + Message(String def, String... extra) { + this.def = new ArrayList(); + this.def.add(def); + this.def.addAll(Arrays.asList(extra)); + } + + /** + * @return This {@link Message}'s default-message + */ + public List getDefault() { + return def; + } + +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/MessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java similarity index 97% rename from src/main/java/com/onarandombox/multiverseinventories/locale/MessageProvider.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java index 815a8311..610d84ff 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/MessageProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; import java.util.List; import java.util.Locale; diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/Messager.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java similarity index 97% rename from src/main/java/com/onarandombox/multiverseinventories/locale/Messager.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java index 86ed0110..2f4efdc4 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/Messager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; import org.bukkit.command.CommandSender; diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/Messaging.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java similarity index 87% rename from src/main/java/com/onarandombox/multiverseinventories/locale/Messaging.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java index 971b4978..01cfb473 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/Messaging.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; /** * This interface is implemented by classes that use a {@link Messager}. diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/NoSuchLocalizationException.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java similarity index 91% rename from src/main/java/com/onarandombox/multiverseinventories/locale/NoSuchLocalizationException.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java index 31dd92ae..f586b5ad 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/NoSuchLocalizationException.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; import java.util.Locale; diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java similarity index 59% rename from src/main/java/com/onarandombox/multiverseinventories/locale/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java index 6279ccea..e13d4271 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java @@ -1,5 +1,5 @@ /** * This package contains Multiverse-Inventories localization features. */ -package com.onarandombox.multiverseinventories.locale; +package org.mvplugins.multiverse.inventories.locale; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/DataImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java similarity index 87% rename from src/main/java/com/onarandombox/multiverseinventories/migration/DataImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java index e97a5fbd..52af376f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/DataImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration; +package org.mvplugins.multiverse.inventories.migration; import org.bukkit.plugin.Plugin; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/ImportManager.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java similarity index 83% rename from src/main/java/com/onarandombox/multiverseinventories/migration/ImportManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java index b1eefaeb..1511eb8c 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/ImportManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java @@ -1,71 +1,71 @@ -package com.onarandombox.multiverseinventories.migration; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.migration.multiinv.MultiInvImporter; -import com.onarandombox.multiverseinventories.migration.worldinventories.WorldInventoriesImporter; -import me.drayshak.WorldInventories.WorldInventories; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -/** - * Manages the import heplers for other similar plugins. - */ -public class ImportManager { - - private MultiInvImporter multiInvImporter = null; - private WorldInventoriesImporter worldInventoriesImporter = null; - private MultiverseInventories inventories; - - public ImportManager(MultiverseInventories inventories) { - this.inventories = inventories; - } - - /** - * Hooks MultiInv for importing it's data. - * - * @param plugin Instance of MultiInv. - */ - public void hookMultiInv(MultiInv plugin) { - this.multiInvImporter = new MultiInvImporter(this.inventories, plugin); - Logging.info("Hooked MultiInv for importing!"); - } - - /** - * Hooks WorldInventories for importing it's data. - * - * @param plugin Instance of WorldInventories. - */ - public void hookWorldInventories(WorldInventories plugin) { - this.worldInventoriesImporter = new WorldInventoriesImporter(this.inventories, plugin); - Logging.info("Hooked WorldInventories for importing!"); - } - - /** - * @return The MultiInv importer class or null if not hooked. - */ - public MultiInvImporter getMultiInvImporter() { - return this.multiInvImporter; - } - - /** - * @return The WorldInventories importer class or null if not hooked. - */ - public WorldInventoriesImporter getWorldInventoriesImporter() { - return this.worldInventoriesImporter; - } - - /** - * Un-hooks MultiInv so we're not able to import from it anymore. - */ - public void unHookMultiInv() { - this.multiInvImporter = null; - } - - /** - * Un-hooks WorldInventories so we're not able to import from it anymore. - */ - public void unHookWorldInventories() { - this.worldInventoriesImporter = null; - } -} - +package org.mvplugins.multiverse.inventories.migration; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.migration.multiinv.MultiInvImporter; +import org.mvplugins.multiverse.inventories.migration.worldinventories.WorldInventoriesImporter; +import me.drayshak.WorldInventories.WorldInventories; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +/** + * Manages the import heplers for other similar plugins. + */ +public class ImportManager { + + private MultiInvImporter multiInvImporter = null; + private WorldInventoriesImporter worldInventoriesImporter = null; + private MultiverseInventories inventories; + + public ImportManager(MultiverseInventories inventories) { + this.inventories = inventories; + } + + /** + * Hooks MultiInv for importing it's data. + * + * @param plugin Instance of MultiInv. + */ + public void hookMultiInv(MultiInv plugin) { + this.multiInvImporter = new MultiInvImporter(this.inventories, plugin); + Logging.info("Hooked MultiInv for importing!"); + } + + /** + * Hooks WorldInventories for importing it's data. + * + * @param plugin Instance of WorldInventories. + */ + public void hookWorldInventories(WorldInventories plugin) { + this.worldInventoriesImporter = new WorldInventoriesImporter(this.inventories, plugin); + Logging.info("Hooked WorldInventories for importing!"); + } + + /** + * @return The MultiInv importer class or null if not hooked. + */ + public MultiInvImporter getMultiInvImporter() { + return this.multiInvImporter; + } + + /** + * @return The WorldInventories importer class or null if not hooked. + */ + public WorldInventoriesImporter getWorldInventoriesImporter() { + return this.worldInventoriesImporter; + } + + /** + * Un-hooks MultiInv so we're not able to import from it anymore. + */ + public void unHookMultiInv() { + this.multiInvImporter = null; + } + + /** + * Un-hooks WorldInventories so we're not able to import from it anymore. + */ + public void unHookWorldInventories() { + this.worldInventoriesImporter = null; + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/MigrationException.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/migration/MigrationException.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java index c169b378..f7319478 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/MigrationException.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration; +package org.mvplugins.multiverse.inventories.migration; /** * Exception thrown when migration doesn't go well. diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryConverter.java similarity index 85% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryConverter.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryConverter.java index 33f1d5e2..d4180de6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryConverter.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.migration.multiinv; -import com.onarandombox.multiverseinventories.util.MinecraftTools; +import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIItemStack; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryInterface.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryInterface.java similarity index 84% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryInterface.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryInterface.java index 04f46b18..9259bd90 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryInterface.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryInterface.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.migration.multiinv; import org.bukkit.inventory.ItemStack; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryOldWrapper.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryOldWrapper.java similarity index 91% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryOldWrapper.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryOldWrapper.java index d624a566..89b3b614 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryOldWrapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryOldWrapper.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.migration.multiinv; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIInventoryOld; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryWrapper.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryWrapper.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryWrapper.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryWrapper.java index 5572b3b3..7dadff75 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryWrapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryWrapper.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.migration.multiinv; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIInventory; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIPlayerFileLoader.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIPlayerFileLoader.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java index ba0e6e02..b597d12f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIPlayerFileLoader.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java @@ -1,114 +1,114 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; - -import com.onarandombox.multiverseinventories.PlayerStats; -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.file.YamlConfiguration; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -import java.io.File; - -/** - * A replacement for MultiInv's MIPlayerFile class so that it may accept an OfflinePlayer instead of Player. - */ -public class MIPlayerFileLoader { - - private YamlConfiguration playerFile; - private File file; - - public MIPlayerFileLoader(MultiInv plugin, OfflinePlayer player, String group) { - // Find and load configuration file for the player - File worldsFolder = new File(plugin.getDataFolder(), "Groups"); - file = new File(worldsFolder, group + File.separator + player.getName() + ".yml"); - - playerFile = new YamlConfiguration(); - } - - /** - * Loads the player file into memory. - * - * @return True if there was a file to load and it loaded successfully. - */ - public boolean load() { - if (file.exists()) { - try { - playerFile.load(file); - return true; - } catch (Exception ignore) { } - } - return false; - } - - /** - * Load particular inventory for specified player from specified group. - * - * @param inventoryName The gamemode for the inventory to load. - * @return An interface for retrieve the inventory/armor contents. - */ - public MIInventoryInterface getInventory(String inventoryName) { - // Get stored string from configuration file - MIInventoryInterface inventory; - String inventoryString = playerFile.getString(inventoryName, null); - // Check for old inventory save - if (inventoryString == null || inventoryString.contains(";-;")) { - inventory = new MIInventoryOldWrapper(inventoryString); - } else { - inventory = new MIInventoryWrapper(inventoryString); - } - return inventory; - } - - /** - * @return The player's health. - */ - public double getHealth() { - double health = playerFile.getDouble("health", PlayerStats.HEALTH); - if (health <= 0 || health > PlayerStats.HEALTH) { - health = PlayerStats.HEALTH; - } - return health; - } - - /** - * @return The player's hunger. - */ - public int getHunger() { - int hunger = playerFile.getInt("hunger", PlayerStats.FOOD_LEVEL); - if (hunger <= 0 || hunger > PlayerStats.FOOD_LEVEL) { - hunger = PlayerStats.FOOD_LEVEL; - } - return hunger; - } - - /** - * @return The player's saturation. - */ - public float getSaturation() { - double saturationDouble = playerFile.getDouble("saturation", 0); - float saturation = (float) saturationDouble; - return saturation; - } - - /** - * @return The player's total exp. - */ - public int getTotalExperience() { - return playerFile.getInt("experience", 0); - } - - /** - * @return The player's level. - */ - public int getLevel() { - return playerFile.getInt("level", 0); - } - - /** - * @return The player's exp. - */ - public float getExperience() { - double expDouble = playerFile.getDouble("exp", 0); - float exp = (float) expDouble; - return exp; - } -} - +package org.mvplugins.multiverse.inventories.migration.multiinv; + +import org.mvplugins.multiverse.inventories.PlayerStats; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.file.YamlConfiguration; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +import java.io.File; + +/** + * A replacement for MultiInv's MIPlayerFile class so that it may accept an OfflinePlayer instead of Player. + */ +public class MIPlayerFileLoader { + + private YamlConfiguration playerFile; + private File file; + + public MIPlayerFileLoader(MultiInv plugin, OfflinePlayer player, String group) { + // Find and load configuration file for the player + File worldsFolder = new File(plugin.getDataFolder(), "Groups"); + file = new File(worldsFolder, group + File.separator + player.getName() + ".yml"); + + playerFile = new YamlConfiguration(); + } + + /** + * Loads the player file into memory. + * + * @return True if there was a file to load and it loaded successfully. + */ + public boolean load() { + if (file.exists()) { + try { + playerFile.load(file); + return true; + } catch (Exception ignore) { } + } + return false; + } + + /** + * Load particular inventory for specified player from specified group. + * + * @param inventoryName The gamemode for the inventory to load. + * @return An interface for retrieve the inventory/armor contents. + */ + public MIInventoryInterface getInventory(String inventoryName) { + // Get stored string from configuration file + MIInventoryInterface inventory; + String inventoryString = playerFile.getString(inventoryName, null); + // Check for old inventory save + if (inventoryString == null || inventoryString.contains(";-;")) { + inventory = new MIInventoryOldWrapper(inventoryString); + } else { + inventory = new MIInventoryWrapper(inventoryString); + } + return inventory; + } + + /** + * @return The player's health. + */ + public double getHealth() { + double health = playerFile.getDouble("health", PlayerStats.HEALTH); + if (health <= 0 || health > PlayerStats.HEALTH) { + health = PlayerStats.HEALTH; + } + return health; + } + + /** + * @return The player's hunger. + */ + public int getHunger() { + int hunger = playerFile.getInt("hunger", PlayerStats.FOOD_LEVEL); + if (hunger <= 0 || hunger > PlayerStats.FOOD_LEVEL) { + hunger = PlayerStats.FOOD_LEVEL; + } + return hunger; + } + + /** + * @return The player's saturation. + */ + public float getSaturation() { + double saturationDouble = playerFile.getDouble("saturation", 0); + float saturation = (float) saturationDouble; + return saturation; + } + + /** + * @return The player's total exp. + */ + public int getTotalExperience() { + return playerFile.getInt("experience", 0); + } + + /** + * @return The player's level. + */ + public int getLevel() { + return playerFile.getInt("level", 0); + } + + /** + * @return The player's exp. + */ + public float getExperience() { + double expDouble = playerFile.getDouble("exp", 0); + float exp = (float) expDouble; + return exp; + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MultiInvImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java similarity index 89% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MultiInvImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java index c0f3ae8c..da976987 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MultiInvImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java @@ -1,167 +1,167 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.migration.DataImporter; -import com.onarandombox.multiverseinventories.migration.MigrationException; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; -import uk.co.tggl.pluckerpluck.multiinv.MIYamlFiles; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -/** - * A class to help with importing data from MultiInv. - */ -public class MultiInvImporter implements DataImporter { - - private MultiInv miPlugin; - private MultiverseInventories inventories; - - public MultiInvImporter(MultiverseInventories inventories, MultiInv miPlugin) { - this.inventories = inventories; - this.miPlugin = miPlugin; - } - - /** - * @return The MultiInv plugin hooked to the importer. - */ - public MultiInv getMIPlugin() { - return this.miPlugin; - } - - /** - * {@inheritDoc} - */ - @Override - public Plugin getPlugin() { - return this.getMIPlugin(); - } - - /** - * Imports the data from MultiInv. - * - * @throws MigrationException If there was any MAJOR issue loading the data. - */ - @Override - public void importData() throws MigrationException { - HashMap miGroupMap = this.getGroupMap(); - if (miGroupMap == null) { - throw new MigrationException("There is no data to import from MultiInv!"); - } - if (!miGroupMap.isEmpty()) { - WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); - if (defaultWorldGroup != null) { - this.inventories.getGroupManager().removeGroup(defaultWorldGroup); - Logging.info("Removed automatically created world group in favor of imported groups."); - } - } - for (Map.Entry groupEntry : miGroupMap.entrySet()) { - WorldGroup worldGroup = this.inventories.getGroupManager().getGroup(groupEntry.getValue()); - if (worldGroup == null) { - worldGroup = this.inventories.getGroupManager().newEmptyGroup(groupEntry.getValue()); - worldGroup.getShares().mergeShares(Sharables.allOf()); - Logging.info("Importing group: " + groupEntry.getValue()); - this.inventories.getGroupManager().updateGroup(worldGroup); - } - worldGroup.addWorld(groupEntry.getValue()); - } - this.inventories.getMVIConfig().save(); - - for (OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) { - Logging.info("Processing MultiInv data for player: " + player.getName()); - for (Map.Entry entry : miGroupMap.entrySet()) { - String worldName = entry.getKey(); - String groupName = entry.getValue(); - MIPlayerFileLoader playerFileLoader = - new MIPlayerFileLoader(this.getMIPlugin(), player, groupName); - if (!playerFileLoader.load()) { - continue; - } - Logging.info("Processing MultiInv data for player: " + player.getName() - + " for group: " + groupName); - mergeData(player, playerFileLoader, groupName, ContainerType.GROUP); - } - for (World world : Bukkit.getWorlds()) { - String worldName = world.getName(); - MIPlayerFileLoader playerFileLoader = - new MIPlayerFileLoader(this.getMIPlugin(), player, worldName); - if (!playerFileLoader.load()) { - continue; - } - Logging.info("Processing MultiInv data for player: " + player.getName() - + " for world only: " + worldName); - mergeData(player, playerFileLoader, worldName, ContainerType.WORLD); - } - } - - Logging.info("Import from MultiInv finished. Disabling MultiInv."); - Bukkit.getPluginManager().disablePlugin(this.getMIPlugin()); - } - - private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader, - String dataName, ContainerType type) { - PlayerProfile playerProfile; - if (type.equals(ContainerType.GROUP)) { - WorldGroup group = this.inventories.getGroupManager() - .getGroup(dataName); - if (group == null) { - Logging.warning("Could not import player data for group: " + dataName); - return; - } - playerProfile = group.getGroupProfileContainer().getPlayerData(ProfileTypes.SURVIVAL, player); - } else { - playerProfile = this.inventories.getWorldProfileContainerStore() - .getContainer(dataName).getPlayerData(ProfileTypes.SURVIVAL, player); - } - MIInventoryInterface inventoryInterface = - playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); - playerProfile.set(Sharables.INVENTORY, inventoryInterface.getInventoryContents()); - playerProfile.set(Sharables.ARMOR, inventoryInterface.getArmorContents()); - playerProfile.set(Sharables.HEALTH, playerFileLoader.getHealth()); - playerProfile.set(Sharables.SATURATION, playerFileLoader.getSaturation()); - playerProfile.set(Sharables.EXPERIENCE, playerFileLoader.getExperience()); - playerProfile.set(Sharables.TOTAL_EXPERIENCE, playerFileLoader.getTotalExperience()); - playerProfile.set(Sharables.LEVEL, playerFileLoader.getLevel()); - playerProfile.set(Sharables.FOOD_LEVEL, playerFileLoader.getHunger()); - this.inventories.getData().updatePlayerData(playerProfile); - } - - /** - * @return The group mapping from MultiInv, where worldName -> groupName. - * @throws MigrationException If there was any issues getting the data through reflection. - */ - private HashMap getGroupMap() throws MigrationException { - Field field; - try { - field = MIYamlFiles.class.getDeclaredField("groups"); - } catch (NoSuchFieldException nsfe) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(nsfe); - } - field.setAccessible(true); - HashMap miGroupMap = null; - try { - miGroupMap = (HashMap) field.get(null); - } catch (IllegalAccessException iae) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(iae); - } catch (ClassCastException cce) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(cce); - } - return miGroupMap; - } -} - +package org.mvplugins.multiverse.inventories.migration.multiinv; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.migration.DataImporter; +import org.mvplugins.multiverse.inventories.migration.MigrationException; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; +import uk.co.tggl.pluckerpluck.multiinv.MIYamlFiles; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * A class to help with importing data from MultiInv. + */ +public class MultiInvImporter implements DataImporter { + + private MultiInv miPlugin; + private MultiverseInventories inventories; + + public MultiInvImporter(MultiverseInventories inventories, MultiInv miPlugin) { + this.inventories = inventories; + this.miPlugin = miPlugin; + } + + /** + * @return The MultiInv plugin hooked to the importer. + */ + public MultiInv getMIPlugin() { + return this.miPlugin; + } + + /** + * {@inheritDoc} + */ + @Override + public Plugin getPlugin() { + return this.getMIPlugin(); + } + + /** + * Imports the data from MultiInv. + * + * @throws MigrationException If there was any MAJOR issue loading the data. + */ + @Override + public void importData() throws MigrationException { + HashMap miGroupMap = this.getGroupMap(); + if (miGroupMap == null) { + throw new MigrationException("There is no data to import from MultiInv!"); + } + if (!miGroupMap.isEmpty()) { + WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); + if (defaultWorldGroup != null) { + this.inventories.getGroupManager().removeGroup(defaultWorldGroup); + Logging.info("Removed automatically created world group in favor of imported groups."); + } + } + for (Map.Entry groupEntry : miGroupMap.entrySet()) { + WorldGroup worldGroup = this.inventories.getGroupManager().getGroup(groupEntry.getValue()); + if (worldGroup == null) { + worldGroup = this.inventories.getGroupManager().newEmptyGroup(groupEntry.getValue()); + worldGroup.getShares().mergeShares(Sharables.allOf()); + Logging.info("Importing group: " + groupEntry.getValue()); + this.inventories.getGroupManager().updateGroup(worldGroup); + } + worldGroup.addWorld(groupEntry.getValue()); + } + this.inventories.getMVIConfig().save(); + + for (OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) { + Logging.info("Processing MultiInv data for player: " + player.getName()); + for (Map.Entry entry : miGroupMap.entrySet()) { + String worldName = entry.getKey(); + String groupName = entry.getValue(); + MIPlayerFileLoader playerFileLoader = + new MIPlayerFileLoader(this.getMIPlugin(), player, groupName); + if (!playerFileLoader.load()) { + continue; + } + Logging.info("Processing MultiInv data for player: " + player.getName() + + " for group: " + groupName); + mergeData(player, playerFileLoader, groupName, ContainerType.GROUP); + } + for (World world : Bukkit.getWorlds()) { + String worldName = world.getName(); + MIPlayerFileLoader playerFileLoader = + new MIPlayerFileLoader(this.getMIPlugin(), player, worldName); + if (!playerFileLoader.load()) { + continue; + } + Logging.info("Processing MultiInv data for player: " + player.getName() + + " for world only: " + worldName); + mergeData(player, playerFileLoader, worldName, ContainerType.WORLD); + } + } + + Logging.info("Import from MultiInv finished. Disabling MultiInv."); + Bukkit.getPluginManager().disablePlugin(this.getMIPlugin()); + } + + private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader, + String dataName, ContainerType type) { + PlayerProfile playerProfile; + if (type.equals(ContainerType.GROUP)) { + WorldGroup group = this.inventories.getGroupManager() + .getGroup(dataName); + if (group == null) { + Logging.warning("Could not import player data for group: " + dataName); + return; + } + playerProfile = group.getGroupProfileContainer().getPlayerData(ProfileTypes.SURVIVAL, player); + } else { + playerProfile = this.inventories.getWorldProfileContainerStore() + .getContainer(dataName).getPlayerData(ProfileTypes.SURVIVAL, player); + } + MIInventoryInterface inventoryInterface = + playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); + playerProfile.set(Sharables.INVENTORY, inventoryInterface.getInventoryContents()); + playerProfile.set(Sharables.ARMOR, inventoryInterface.getArmorContents()); + playerProfile.set(Sharables.HEALTH, playerFileLoader.getHealth()); + playerProfile.set(Sharables.SATURATION, playerFileLoader.getSaturation()); + playerProfile.set(Sharables.EXPERIENCE, playerFileLoader.getExperience()); + playerProfile.set(Sharables.TOTAL_EXPERIENCE, playerFileLoader.getTotalExperience()); + playerProfile.set(Sharables.LEVEL, playerFileLoader.getLevel()); + playerProfile.set(Sharables.FOOD_LEVEL, playerFileLoader.getHunger()); + this.inventories.getData().updatePlayerData(playerProfile); + } + + /** + * @return The group mapping from MultiInv, where worldName -> groupName. + * @throws MigrationException If there was any issues getting the data through reflection. + */ + private HashMap getGroupMap() throws MigrationException { + Field field; + try { + field = MIYamlFiles.class.getDeclaredField("groups"); + } catch (NoSuchFieldException nsfe) { + throw new MigrationException("The running version of MultiInv is " + + "incompatible with the import feature.").setCauseException(nsfe); + } + field.setAccessible(true); + HashMap miGroupMap = null; + try { + miGroupMap = (HashMap) field.get(null); + } catch (IllegalAccessException iae) { + throw new MigrationException("The running version of MultiInv is " + + "incompatible with the import feature.").setCauseException(iae); + } catch (ClassCastException cce) { + throw new MigrationException("The running version of MultiInv is " + + "incompatible with the import feature.").setCauseException(cce); + } + return miGroupMap; + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/package-info.java similarity index 55% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/package-info.java index 62a128b2..bfbc619f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/package-info.java @@ -1,5 +1,5 @@ /** * This package contains MultiInv classes to handle importing their data. */ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.migration.multiinv; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java similarity index 67% rename from src/main/java/com/onarandombox/multiverseinventories/migration/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java index 139a8dbc..d66b1e65 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java @@ -2,5 +2,5 @@ * This package contains thigns to help with importing player stats/inventory data from * other similar plugins. */ -package com.onarandombox.multiverseinventories.migration; +package org.mvplugins.multiverse.inventories.migration; diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/WorldInventoriesImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java similarity index 91% rename from src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/WorldInventoriesImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java index 7ff07ffd..7b5ff5ea 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/WorldInventoriesImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java @@ -1,274 +1,274 @@ -package com.onarandombox.multiverseinventories.migration.worldinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.migration.DataImporter; -import com.onarandombox.multiverseinventories.migration.MigrationException; -import me.drayshak.WorldInventories.Group; -import me.drayshak.WorldInventories.WIPlayerInventory; -import me.drayshak.WorldInventories.WIPlayerStats; -import me.drayshak.WorldInventories.WorldInventories; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Handles the importing of data from WorldInventories. - */ -public class WorldInventoriesImporter implements DataImporter { - - private WorldInventories wiPlugin; - private MultiverseInventories inventories; - - public WorldInventoriesImporter(MultiverseInventories inventories, WorldInventories wiPlugin) { - this.inventories = inventories; - this.wiPlugin = wiPlugin; - } - - /** - * @return The WorldInventories plugin hooked to the importer. - */ - public WorldInventories getWIPlugin() { - return this.wiPlugin; - } - - /** - * {@inheritDoc} - */ - @Override - public Plugin getPlugin() { - return this.getWIPlugin(); - } - - /** - * Imports the data from WorldInventories into MultiverseInventories. - * - * @throws MigrationException If there was any MAJOR issues importing the data. - */ - @Override - public void importData() throws MigrationException { - List wiGroups; - try { - wiGroups = this.getWIPlugin().getGroups(); - } catch (Exception e) { - throw new MigrationException("Unable to import from this version of WorldInventories!") - .setCauseException(e); - } catch (Error e) { - throw new MigrationException("Unable to import from this version of WorldInventories!"); - } - if (wiGroups == null) { - throw new MigrationException("No data to import from WorldInventories!"); - } - - if (!wiGroups.isEmpty()) { - WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); - if (defaultWorldGroup != null) { - this.inventories.getGroupManager().removeGroup(defaultWorldGroup); - Logging.info("Removed automatically created world group in favor of imported groups."); - } - } - - this.createGroups(wiGroups); - Set noGroupWorlds = this.getWorldsWithoutGroups(); - this.inventories.getMVIConfig().save(); - - OfflinePlayer[] offlinePlayers = Bukkit.getServer().getOfflinePlayers(); - Logging.info("Processing data for " + offlinePlayers.length + " players. The larger than number, the longer" - + " this process will take. Please be patient. :) Your server will freeze for the duration."); - int playerCount = 0; - for (OfflinePlayer player : offlinePlayers) { - playerCount++; - Logging.finer("(" + playerCount + "/" + offlinePlayers.length - + ")Processing WorldInventories data for player: " + player.getName()); - for (Group wiGroup : wiGroups) { - WorldGroup worldGroup = inventories.getGroupManager().getGroup(wiGroup.getName()); - if (worldGroup == null) { - Logging.finest("Could not import player data for WorldInventories group: " + wiGroup.getName() - + " because there is no Multiverse-Inventories group by that name."); - continue; - } - this.transferData(player, wiGroup, worldGroup.getGroupProfileContainer()); - } - for (ProfileContainer container : noGroupWorlds) { - this.transferData(player, null, container); - } - } - - Logging.info("Import from WorldInventories finished. Disabling WorldInventories."); - Bukkit.getPluginManager().disablePlugin(this.getWIPlugin()); - } - - private void createGroups(List wiGroups) { - for (Group wiGroup : wiGroups) { - if (wiGroup.getWorlds().isEmpty()) { - Logging.warning("Group '" + wiGroup.getName() + "' has no worlds." - + " You may need to add these manually!"); - } - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup(wiGroup.getName()); - for (String worldName : wiGroup.getWorlds()) { - newGroup.addWorld(worldName); - } - - try { - if (WorldInventories.doStats) { - newGroup.getShares().mergeShares(Sharables.allOf()); - } else { - newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); - } - } catch (Exception ignore) { - Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); - newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); - } catch (Error e) { - Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); - newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); - } - this.inventories.getGroupManager().updateGroup(newGroup); - Logging.info("Created Multiverse-Inventories group: " + wiGroup.getName()); - } - } - - private Set getWorldsWithoutGroups() { - Set noGroupWorlds = new LinkedHashSet<>(); - for (World world : Bukkit.getWorlds()) { - if (this.inventories.getGroupManager().getGroupsForWorld(world.getName()).isEmpty()) { - Logging.fine("Added ungrouped world for importing."); - ProfileContainer container = this.inventories.getWorldProfileContainerStore().getContainer(world.getName()); - noGroupWorlds.add(container); - } - } - return noGroupWorlds; - } - - private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer profileContainer) { - PlayerProfile playerProfile = profileContainer.getPlayerData(ProfileTypes.SURVIVAL, player); - WIPlayerInventory wiInventory = this.loadPlayerInventory(player, wiGroup); - WIPlayerStats wiStats = this.loadPlayerStats(player, wiGroup); - if (wiInventory != null) { - playerProfile.set(Sharables.INVENTORY, wiInventory.getItems()); - playerProfile.set(Sharables.ARMOR, wiInventory.getArmour()); - } - if (wiStats != null) { - playerProfile.set(Sharables.HEALTH, (double) wiStats.getHealth()); - playerProfile.set(Sharables.SATURATION, wiStats.getSaturation()); - playerProfile.set(Sharables.EXPERIENCE, wiStats.getExp()); - playerProfile.set(Sharables.LEVEL, wiStats.getLevel()); - playerProfile.set(Sharables.EXHAUSTION, wiStats.getExhaustion()); - playerProfile.set(Sharables.FOOD_LEVEL, wiStats.getFoodLevel()); - } - this.inventories.getData().updatePlayerData(playerProfile); - Logging.finest("Player's data imported successfully for group: " + profileContainer.getContainerName()); - } - - private File getFile(OfflinePlayer player, Group group, DataType dataType) { - StringBuilder path = new StringBuilder(); - path.append(File.separator); - - // Use default group - if (group == null) { - path.append("default"); - } else { - path.append(group.getName()); - } - path.insert(0, this.getWIPlugin().getDataFolder().getAbsolutePath()); - path.append(File.separator).append(player.getName()).append(dataType.fileExtension); - - File file = new File(path.toString()); - if (!file.exists()) { - file = null; - } - return file; - } - - // Copied and modified from WorldInventories - private WIPlayerInventory loadPlayerInventory(OfflinePlayer player, Group group) { - File file = this.getFile(player, group, DataType.INVENTORY); - if (file == null) { - return null; - } - WIPlayerInventory playerInventory = null; - FileInputStream fIS = null; - ObjectInputStream obIn = null; - try { - fIS = new FileInputStream(file); - obIn = new ObjectInputStream(fIS); - playerInventory = (WIPlayerInventory) obIn.readObject(); - } catch (Exception ignore) { - } finally { - if (obIn != null) { - try { - obIn.close(); - } catch (IOException ignore) { - } - } - if (fIS != null) { - try { - fIS.close(); - } catch (IOException ignore) { - } - } - } - - return playerInventory; - } - - // Copied and modified from WorldInventories - private WIPlayerStats loadPlayerStats(OfflinePlayer player, Group group) { - File file = this.getFile(player, group, DataType.STATS); - if (file == null) { - return null; - } - WIPlayerStats playerstats = null; - FileInputStream fIS = null; - ObjectInputStream obIn = null; - try { - fIS = new FileInputStream(file); - obIn = new ObjectInputStream(fIS); - playerstats = (WIPlayerStats) obIn.readObject(); - } catch (Exception ignore) { - } finally { - if (obIn != null) { - try { - obIn.close(); - } catch (IOException ignore) { - } - } - if (fIS != null) { - try { - fIS.close(); - } catch (IOException ignore) { - } - } - } - - return playerstats; - } - - /** - * Indicates the type of data we're importing for. - */ - private enum DataType { - INVENTORY(".inventory"), - STATS(".stats"); - - private String fileExtension; - - DataType(String fileExtension) { - this.fileExtension = fileExtension; - } - } -} - +package org.mvplugins.multiverse.inventories.migration.worldinventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.migration.DataImporter; +import org.mvplugins.multiverse.inventories.migration.MigrationException; +import me.drayshak.WorldInventories.Group; +import me.drayshak.WorldInventories.WIPlayerInventory; +import me.drayshak.WorldInventories.WIPlayerStats; +import me.drayshak.WorldInventories.WorldInventories; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Handles the importing of data from WorldInventories. + */ +public class WorldInventoriesImporter implements DataImporter { + + private WorldInventories wiPlugin; + private MultiverseInventories inventories; + + public WorldInventoriesImporter(MultiverseInventories inventories, WorldInventories wiPlugin) { + this.inventories = inventories; + this.wiPlugin = wiPlugin; + } + + /** + * @return The WorldInventories plugin hooked to the importer. + */ + public WorldInventories getWIPlugin() { + return this.wiPlugin; + } + + /** + * {@inheritDoc} + */ + @Override + public Plugin getPlugin() { + return this.getWIPlugin(); + } + + /** + * Imports the data from WorldInventories into MultiverseInventories. + * + * @throws MigrationException If there was any MAJOR issues importing the data. + */ + @Override + public void importData() throws MigrationException { + List wiGroups; + try { + wiGroups = this.getWIPlugin().getGroups(); + } catch (Exception e) { + throw new MigrationException("Unable to import from this version of WorldInventories!") + .setCauseException(e); + } catch (Error e) { + throw new MigrationException("Unable to import from this version of WorldInventories!"); + } + if (wiGroups == null) { + throw new MigrationException("No data to import from WorldInventories!"); + } + + if (!wiGroups.isEmpty()) { + WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); + if (defaultWorldGroup != null) { + this.inventories.getGroupManager().removeGroup(defaultWorldGroup); + Logging.info("Removed automatically created world group in favor of imported groups."); + } + } + + this.createGroups(wiGroups); + Set noGroupWorlds = this.getWorldsWithoutGroups(); + this.inventories.getMVIConfig().save(); + + OfflinePlayer[] offlinePlayers = Bukkit.getServer().getOfflinePlayers(); + Logging.info("Processing data for " + offlinePlayers.length + " players. The larger than number, the longer" + + " this process will take. Please be patient. :) Your server will freeze for the duration."); + int playerCount = 0; + for (OfflinePlayer player : offlinePlayers) { + playerCount++; + Logging.finer("(" + playerCount + "/" + offlinePlayers.length + + ")Processing WorldInventories data for player: " + player.getName()); + for (Group wiGroup : wiGroups) { + WorldGroup worldGroup = inventories.getGroupManager().getGroup(wiGroup.getName()); + if (worldGroup == null) { + Logging.finest("Could not import player data for WorldInventories group: " + wiGroup.getName() + + " because there is no Multiverse-Inventories group by that name."); + continue; + } + this.transferData(player, wiGroup, worldGroup.getGroupProfileContainer()); + } + for (ProfileContainer container : noGroupWorlds) { + this.transferData(player, null, container); + } + } + + Logging.info("Import from WorldInventories finished. Disabling WorldInventories."); + Bukkit.getPluginManager().disablePlugin(this.getWIPlugin()); + } + + private void createGroups(List wiGroups) { + for (Group wiGroup : wiGroups) { + if (wiGroup.getWorlds().isEmpty()) { + Logging.warning("Group '" + wiGroup.getName() + "' has no worlds." + + " You may need to add these manually!"); + } + WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup(wiGroup.getName()); + for (String worldName : wiGroup.getWorlds()) { + newGroup.addWorld(worldName); + } + + try { + if (WorldInventories.doStats) { + newGroup.getShares().mergeShares(Sharables.allOf()); + } else { + newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); + } + } catch (Exception ignore) { + Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); + newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); + } catch (Error e) { + Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); + newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); + } + this.inventories.getGroupManager().updateGroup(newGroup); + Logging.info("Created Multiverse-Inventories group: " + wiGroup.getName()); + } + } + + private Set getWorldsWithoutGroups() { + Set noGroupWorlds = new LinkedHashSet<>(); + for (World world : Bukkit.getWorlds()) { + if (this.inventories.getGroupManager().getGroupsForWorld(world.getName()).isEmpty()) { + Logging.fine("Added ungrouped world for importing."); + ProfileContainer container = this.inventories.getWorldProfileContainerStore().getContainer(world.getName()); + noGroupWorlds.add(container); + } + } + return noGroupWorlds; + } + + private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer profileContainer) { + PlayerProfile playerProfile = profileContainer.getPlayerData(ProfileTypes.SURVIVAL, player); + WIPlayerInventory wiInventory = this.loadPlayerInventory(player, wiGroup); + WIPlayerStats wiStats = this.loadPlayerStats(player, wiGroup); + if (wiInventory != null) { + playerProfile.set(Sharables.INVENTORY, wiInventory.getItems()); + playerProfile.set(Sharables.ARMOR, wiInventory.getArmour()); + } + if (wiStats != null) { + playerProfile.set(Sharables.HEALTH, (double) wiStats.getHealth()); + playerProfile.set(Sharables.SATURATION, wiStats.getSaturation()); + playerProfile.set(Sharables.EXPERIENCE, wiStats.getExp()); + playerProfile.set(Sharables.LEVEL, wiStats.getLevel()); + playerProfile.set(Sharables.EXHAUSTION, wiStats.getExhaustion()); + playerProfile.set(Sharables.FOOD_LEVEL, wiStats.getFoodLevel()); + } + this.inventories.getData().updatePlayerData(playerProfile); + Logging.finest("Player's data imported successfully for group: " + profileContainer.getContainerName()); + } + + private File getFile(OfflinePlayer player, Group group, DataType dataType) { + StringBuilder path = new StringBuilder(); + path.append(File.separator); + + // Use default group + if (group == null) { + path.append("default"); + } else { + path.append(group.getName()); + } + path.insert(0, this.getWIPlugin().getDataFolder().getAbsolutePath()); + path.append(File.separator).append(player.getName()).append(dataType.fileExtension); + + File file = new File(path.toString()); + if (!file.exists()) { + file = null; + } + return file; + } + + // Copied and modified from WorldInventories + private WIPlayerInventory loadPlayerInventory(OfflinePlayer player, Group group) { + File file = this.getFile(player, group, DataType.INVENTORY); + if (file == null) { + return null; + } + WIPlayerInventory playerInventory = null; + FileInputStream fIS = null; + ObjectInputStream obIn = null; + try { + fIS = new FileInputStream(file); + obIn = new ObjectInputStream(fIS); + playerInventory = (WIPlayerInventory) obIn.readObject(); + } catch (Exception ignore) { + } finally { + if (obIn != null) { + try { + obIn.close(); + } catch (IOException ignore) { + } + } + if (fIS != null) { + try { + fIS.close(); + } catch (IOException ignore) { + } + } + } + + return playerInventory; + } + + // Copied and modified from WorldInventories + private WIPlayerStats loadPlayerStats(OfflinePlayer player, Group group) { + File file = this.getFile(player, group, DataType.STATS); + if (file == null) { + return null; + } + WIPlayerStats playerstats = null; + FileInputStream fIS = null; + ObjectInputStream obIn = null; + try { + fIS = new FileInputStream(file); + obIn = new ObjectInputStream(fIS); + playerstats = (WIPlayerStats) obIn.readObject(); + } catch (Exception ignore) { + } finally { + if (obIn != null) { + try { + obIn.close(); + } catch (IOException ignore) { + } + } + if (fIS != null) { + try { + fIS.close(); + } catch (IOException ignore) { + } + } + } + + return playerstats; + } + + /** + * Indicates the type of data we're importing for. + */ + private enum DataType { + INVENTORY(".inventory"), + STATS(".stats"); + + private String fileExtension; + + DataType(String fileExtension) { + this.fileExtension = fileExtension; + } + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/package-info.java similarity index 54% rename from src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/package-info.java index f82850c7..e7666201 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/package-info.java @@ -1,5 +1,5 @@ /** * This package contains WorldInventories classes to handle importing their data. */ -package com.onarandombox.multiverseinventories.migration.worldinventories; +package org.mvplugins.multiverse.inventories.migration.worldinventories; diff --git a/src/main/java/com/onarandombox/multiverseinventories/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/package-info.java similarity index 54% rename from src/main/java/com/onarandombox/multiverseinventories/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/package-info.java index e36f6040..e0176883 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/package-info.java @@ -1,5 +1,5 @@ /** * The main package for Multiverse-Inventories. */ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java similarity index 98% rename from src/main/java/com/onarandombox/multiverseinventories/profile/GlobalProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index 77c8c58b..c95f7ddf 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.profile; +package org.mvplugins.multiverse.inventories.profile; import org.bukkit.Bukkit; diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/GroupingConflict.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GroupingConflict.java similarity index 87% rename from src/main/java/com/onarandombox/multiverseinventories/profile/GroupingConflict.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/GroupingConflict.java index cd30b4b5..cb0fc2fc 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/GroupingConflict.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GroupingConflict.java @@ -1,72 +1,72 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Shares; - -import java.util.ArrayList; -import java.util.List; - -/** - * A data class to hold information about any conflicts between world groups. - */ -public final class GroupingConflict { - - private WorldGroup groupOne; - private WorldGroup groupTwo; - private Shares conflictingShares; - - public GroupingConflict(WorldGroup groupOne, WorldGroup groupTwo, Shares conflictingShares) { - this.groupOne = groupOne; - this.groupTwo = groupTwo; - this.conflictingShares = conflictingShares; - } - - /** - * @return The first group in the conflict. - */ - public WorldGroup getFirstGroup() { - return this.groupOne; - } - - /** - * @return The second group in the conflict. - */ - public WorldGroup getSecondGroup() { - return this.groupTwo; - } - - /** - * @return The shares that are causing a conflict. - */ - public Shares getConflictingShares() { - return this.conflictingShares; - } - - /** - * @return The worlds the two groups share. - */ - public List getConflictingWorlds() { - List worlds = new ArrayList(); - for (String world : this.getFirstGroup().getWorlds()) { - if (this.getSecondGroup().getWorlds().contains(world)) { - worlds.add(world); - } - } - return worlds; - } - - /** - * @return The worlds the two groups share as a single string. - */ - public String getWorldsString() { - StringBuilder builder = new StringBuilder(); - for (String world : this.getConflictingWorlds()) { - if (!builder.toString().isEmpty()) { - builder.append(", "); - } - builder.append(world); - } - return builder.toString(); - } -} - +package org.mvplugins.multiverse.inventories.profile; + +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.ArrayList; +import java.util.List; + +/** + * A data class to hold information about any conflicts between world groups. + */ +public final class GroupingConflict { + + private WorldGroup groupOne; + private WorldGroup groupTwo; + private Shares conflictingShares; + + public GroupingConflict(WorldGroup groupOne, WorldGroup groupTwo, Shares conflictingShares) { + this.groupOne = groupOne; + this.groupTwo = groupTwo; + this.conflictingShares = conflictingShares; + } + + /** + * @return The first group in the conflict. + */ + public WorldGroup getFirstGroup() { + return this.groupOne; + } + + /** + * @return The second group in the conflict. + */ + public WorldGroup getSecondGroup() { + return this.groupTwo; + } + + /** + * @return The shares that are causing a conflict. + */ + public Shares getConflictingShares() { + return this.conflictingShares; + } + + /** + * @return The worlds the two groups share. + */ + public List getConflictingWorlds() { + List worlds = new ArrayList(); + for (String world : this.getFirstGroup().getWorlds()) { + if (this.getSecondGroup().getWorlds().contains(world)) { + worlds.add(world); + } + } + return worlds; + } + + /** + * @return The worlds the two groups share as a single string. + */ + public String getWorldsString() { + StringBuilder builder = new StringBuilder(); + for (String world : this.getConflictingWorlds()) { + if (!builder.toString().isEmpty()) { + builder.append(", "); + } + builder.append(world); + } + return builder.toString(); + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/profile/PlayerProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 2755e949..8ddc54d1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories.profile; +package org.mvplugins.multiverse.inventories.profile; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.SharableEntry; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.SharableEntry; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/profile/ProfileDataSource.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 15427986..dbf8bcef 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,104 +1,104 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.onarandombox.multiverseinventories.profile.container.ContainerType; - -import java.io.IOException; -import java.util.UUID; - -/** - * A source for updating and retrieving player profiles via persistence. - */ -public interface ProfileDataSource { - - /** - * Updates the persisted data for a player for a specific profile. - * - * - * @param playerProfile The profile for the player that is being updated. - */ - void updatePlayerData(PlayerProfile playerProfile); - - /** - * Retrieves a PlayerProfile from the data source. - * - * @param containerType The type of container this profile is part of, world or group. - * @param dataName World/Group to retrieve from. - * @param profileType The type of profile to load data for, typically based on game mode. - * @param playerUUID UUID of the player to retrieve for. - * @return The player as returned from data. If no data was found, a new PlayerProfile will be - * created. - */ - PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); - - /** - * Removes the persisted data for a player for a specific profile. - * - * @param containerType The type of container this profile is part of, world or group. - * @param dataName The name of the world/group the player's data is associated with. - * @param profileType The type of profile we're removing, as per {@link ProfileType}. If null, this will remove - * remove all profile types. - * @param playerName The name of the player whose data is being removed. - * @return True if successfully removed. - */ - boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName); - - /** - * Retrieves the global profile for a player which contains meta-data for the player. - * - * @param playerName The name of player to retrieve for. - * @return The global profile for the specified player. - * @deprecated UUID must be supported now. - */ - @Deprecated - GlobalProfile getGlobalProfile(String playerName); - - /** - * Retrieves the global profile for a player which contains meta-data for the player. - * - * @param playerName The name of the player to retrieve for. This is required for updating name last known as. - * @param playerUUID The UUID of the player. - * @return the global profile for the player with the given UUID. - */ - GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); - - /** - * Update the file for a player's global profile. - * - * @param globalProfile The GlobalProfile object to update the file for. - * @return True if data successfully saved to file. - */ - boolean updateGlobalProfile(GlobalProfile globalProfile); - - /** - * A convenience method to update the GlobalProfile of a player with a specified world. - * - * @param playerName The player whose global profile this will update. - * @param worldName The world to update the global profile with. - */ - void updateLastWorld(String playerName, String worldName); - - /** - * A convenience method for setting whether player data should be loaded on login for the specified player. - * - * @param playerName The player whose data should be loaded. - * @param loadOnLogin Whether or not to load on login. - */ - void setLoadOnLogin(String playerName, boolean loadOnLogin); - - /** - * Copies all the data belonging to oldName to newName and removes the old data. - * - * @param oldName the previous name of the player. - * @param newName the new name of the player. - * @param playerUUID the UUID of the player. - * @param removeOldData whether or not to remove the data belonging to oldName. - * @throws IOException Thrown if something goes wrong while migrating the files. - */ - void migratePlayerData(String oldName, String newName, UUID playerUUID, boolean removeOldData) throws IOException; - - /** - * Clears a single profile in cache. - */ - void clearProfileCache(ProfileKey key); -} - +package org.mvplugins.multiverse.inventories.profile; + +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; + +import java.io.IOException; +import java.util.UUID; + +/** + * A source for updating and retrieving player profiles via persistence. + */ +public interface ProfileDataSource { + + /** + * Updates the persisted data for a player for a specific profile. + * + * + * @param playerProfile The profile for the player that is being updated. + */ + void updatePlayerData(PlayerProfile playerProfile); + + /** + * Retrieves a PlayerProfile from the data source. + * + * @param containerType The type of container this profile is part of, world or group. + * @param dataName World/Group to retrieve from. + * @param profileType The type of profile to load data for, typically based on game mode. + * @param playerUUID UUID of the player to retrieve for. + * @return The player as returned from data. If no data was found, a new PlayerProfile will be + * created. + */ + PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); + + /** + * Removes the persisted data for a player for a specific profile. + * + * @param containerType The type of container this profile is part of, world or group. + * @param dataName The name of the world/group the player's data is associated with. + * @param profileType The type of profile we're removing, as per {@link ProfileType}. If null, this will remove + * remove all profile types. + * @param playerName The name of the player whose data is being removed. + * @return True if successfully removed. + */ + boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName); + + /** + * Retrieves the global profile for a player which contains meta-data for the player. + * + * @param playerName The name of player to retrieve for. + * @return The global profile for the specified player. + * @deprecated UUID must be supported now. + */ + @Deprecated + GlobalProfile getGlobalProfile(String playerName); + + /** + * Retrieves the global profile for a player which contains meta-data for the player. + * + * @param playerName The name of the player to retrieve for. This is required for updating name last known as. + * @param playerUUID The UUID of the player. + * @return the global profile for the player with the given UUID. + */ + GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); + + /** + * Update the file for a player's global profile. + * + * @param globalProfile The GlobalProfile object to update the file for. + * @return True if data successfully saved to file. + */ + boolean updateGlobalProfile(GlobalProfile globalProfile); + + /** + * A convenience method to update the GlobalProfile of a player with a specified world. + * + * @param playerName The player whose global profile this will update. + * @param worldName The world to update the global profile with. + */ + void updateLastWorld(String playerName, String worldName); + + /** + * A convenience method for setting whether player data should be loaded on login for the specified player. + * + * @param playerName The player whose data should be loaded. + * @param loadOnLogin Whether or not to load on login. + */ + void setLoadOnLogin(String playerName, boolean loadOnLogin); + + /** + * Copies all the data belonging to oldName to newName and removes the old data. + * + * @param oldName the previous name of the player. + * @param newName the new name of the player. + * @param playerUUID the UUID of the player. + * @param removeOldData whether or not to remove the data belonging to oldName. + * @throws IOException Thrown if something goes wrong while migrating the files. + */ + void migratePlayerData(String oldName, String newName, UUID playerUUID, boolean removeOldData) throws IOException; + + /** + * Clears a single profile in cache. + */ + void clearProfileCache(ProfileKey key); +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/profile/ProfileKey.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java index 0609391e..f0335e1c 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java @@ -1,112 +1,112 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.google.common.base.Objects; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import org.bukkit.Bukkit; - -import java.util.UUID; - -public final class ProfileKey { - - public static ProfileKey createProfileKey(ContainerType containerType, String dataName, - ProfileType profileType, UUID playerUUID, String playerName) { - return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); - } - - public static ProfileKey createProfileKey(ContainerType containerType, String dataName, - ProfileType profileType, UUID playerUUID) { - return new ProfileKey(containerType, dataName, profileType, playerUUID); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType) { - return new ProfileKey(containerType, copyKey.getDataName(), copyKey.getProfileType(), copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ProfileType profileType) { - return new ProfileKey(copyKey.getContainerType(), copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType, - ProfileType profileType) { - return new ProfileKey(containerType, copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(PlayerProfile profile) { - return new ProfileKey(profile.getContainerType(), profile.getContainerName(), profile.getProfileType(), - profile.getPlayer().getUniqueId(), profile.getPlayer().getName()); - } - - private final ContainerType containerType; - private final String dataName; - private final ProfileType profileType; - private final String playerName; - private final UUID playerUUID; - - private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - this.containerType = containerType; - this.dataName = dataName; - this.profileType = profileType; - this.playerUUID = playerUUID; - this.playerName = Bukkit.getOfflinePlayer(playerUUID).getName(); - } - - private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, - UUID playerUUID, String playerName) { - this.containerType = containerType; - this.dataName = dataName; - this.profileType = profileType; - this.playerUUID = playerUUID; - this.playerName = playerName; - } - - public ContainerType getContainerType() { - return containerType; - } - - public String getDataName() { - return dataName; - } - - public ProfileType getProfileType() { - return profileType; - } - - public String getPlayerName() { - return playerName; - } - - public UUID getPlayerUUID() { - return playerUUID; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ProfileKey)) return false; - final ProfileKey that = (ProfileKey) o; - return getContainerType() == that.getContainerType() && - Objects.equal(getDataName(), that.getDataName()) && - Objects.equal(getProfileType(), that.getProfileType()) && - Objects.equal(getPlayerName(), that.getPlayerName()) && - Objects.equal(getPlayerUUID(), that.getPlayerUUID()); - } - - @Override - public int hashCode() { - return Objects.hashCode(getContainerType(), getDataName(), getProfileType(), getPlayerName(), getPlayerUUID()); - } - - @Override - public String toString() { - return "ProfileKey{" + - "containerType=" + containerType + - ", dataName='" + dataName + '\'' + - ", profileType=" + profileType + - ", playerName='" + playerName + '\'' + - ", playerUUID=" + playerUUID + - '}'; - } -} +package org.mvplugins.multiverse.inventories.profile; + +import com.google.common.base.Objects; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.bukkit.Bukkit; + +import java.util.UUID; + +public final class ProfileKey { + + public static ProfileKey createProfileKey(ContainerType containerType, String dataName, + ProfileType profileType, UUID playerUUID, String playerName) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); + } + + public static ProfileKey createProfileKey(ContainerType containerType, String dataName, + ProfileType profileType, UUID playerUUID) { + return new ProfileKey(containerType, dataName, profileType, playerUUID); + } + + public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType) { + return new ProfileKey(containerType, copyKey.getDataName(), copyKey.getProfileType(), copyKey.getPlayerUUID(), + copyKey.getPlayerName()); + } + + public static ProfileKey createProfileKey(ProfileKey copyKey, ProfileType profileType) { + return new ProfileKey(copyKey.getContainerType(), copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), + copyKey.getPlayerName()); + } + + public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType, + ProfileType profileType) { + return new ProfileKey(containerType, copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), + copyKey.getPlayerName()); + } + + public static ProfileKey createProfileKey(PlayerProfile profile) { + return new ProfileKey(profile.getContainerType(), profile.getContainerName(), profile.getProfileType(), + profile.getPlayer().getUniqueId(), profile.getPlayer().getName()); + } + + private final ContainerType containerType; + private final String dataName; + private final ProfileType profileType; + private final String playerName; + private final UUID playerUUID; + + private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { + this.containerType = containerType; + this.dataName = dataName; + this.profileType = profileType; + this.playerUUID = playerUUID; + this.playerName = Bukkit.getOfflinePlayer(playerUUID).getName(); + } + + private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, + UUID playerUUID, String playerName) { + this.containerType = containerType; + this.dataName = dataName; + this.profileType = profileType; + this.playerUUID = playerUUID; + this.playerName = playerName; + } + + public ContainerType getContainerType() { + return containerType; + } + + public String getDataName() { + return dataName; + } + + public ProfileType getProfileType() { + return profileType; + } + + public String getPlayerName() { + return playerName; + } + + public UUID getPlayerUUID() { + return playerUUID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ProfileKey)) return false; + final ProfileKey that = (ProfileKey) o; + return getContainerType() == that.getContainerType() && + Objects.equal(getDataName(), that.getDataName()) && + Objects.equal(getProfileType(), that.getProfileType()) && + Objects.equal(getPlayerName(), that.getPlayerName()) && + Objects.equal(getPlayerUUID(), that.getPlayerUUID()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getContainerType(), getDataName(), getProfileType(), getPlayerName(), getPlayerUUID()); + } + + @Override + public String toString() { + return "ProfileKey{" + + "containerType=" + containerType + + ", dataName='" + dataName + '\'' + + ", profileType=" + profileType + + ", playerName='" + playerName + '\'' + + ", playerUUID=" + playerUUID + + '}'; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/profile/ProfileType.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java index 6af78391..b4198653 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java @@ -1,39 +1,39 @@ -package com.onarandombox.multiverseinventories.profile; - -/** - * Used to differentiate between profiles in the same world or world group, primarily for game modes. - */ -public final class ProfileType { - - static ProfileType createProfileType(String name) { - return new ProfileType(name); - } - - private String name; - - private ProfileType(String name) { - this.name = name; - } - - /** - * @return The name of the profile. The default profile type will return a blank string. - */ - public String getName() { - return name; - } - - @Override - public final boolean equals(Object o) { - return o instanceof ProfileType && ((ProfileType) o).getName().equals(this.getName()); - } - - @Override - public final int hashCode() { - return getName().hashCode(); - } - - @Override - public String toString() { - return "ProfileType:" + getName(); - } -} +package org.mvplugins.multiverse.inventories.profile; + +/** + * Used to differentiate between profiles in the same world or world group, primarily for game modes. + */ +public final class ProfileType { + + static ProfileType createProfileType(String name) { + return new ProfileType(name); + } + + private String name; + + private ProfileType(String name) { + this.name = name; + } + + /** + * @return The name of the profile. The default profile type will return a blank string. + */ + public String getName() { + return name; + } + + @Override + public final boolean equals(Object o) { + return o instanceof ProfileType && ((ProfileType) o).getName().equals(this.getName()); + } + + @Override + public final int hashCode() { + return getName().hashCode(); + } + + @Override + public String toString() { + return "ProfileType:" + getName(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/profile/ProfileTypes.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java index 4de6121b..cdf9c061 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java @@ -1,47 +1,47 @@ -package com.onarandombox.multiverseinventories.profile; - -import org.bukkit.GameMode; - -/** - * Static class for profile type lookup and protected registration. - */ -public final class ProfileTypes { - - /** - * The profile type for the SURVIVAL Game Mode. - */ - public static final ProfileType SURVIVAL = ProfileType.createProfileType("SURVIVAL"); - - /** - * The profile type for the CREATIVE Game Mode. - */ - public static final ProfileType CREATIVE = ProfileType.createProfileType("CREATIVE"); - - /** - * The profile type for the ADVENTURE Game Mode. - */ - public static final ProfileType ADVENTURE = ProfileType.createProfileType("ADVENTURE"); - - /** - * Returns the appropriate ProfileType for the given game mode. - * - * @param mode The game mode to get the profile type for. - * @return The profile type for the given game mode. - */ - public static ProfileType forGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: - return SURVIVAL; - case CREATIVE: - return CREATIVE; - case ADVENTURE: - return ADVENTURE; - default: - return SURVIVAL; - } - } - - private ProfileTypes() { - throw new AssertionError(); - } -} +package org.mvplugins.multiverse.inventories.profile; + +import org.bukkit.GameMode; + +/** + * Static class for profile type lookup and protected registration. + */ +public final class ProfileTypes { + + /** + * The profile type for the SURVIVAL Game Mode. + */ + public static final ProfileType SURVIVAL = ProfileType.createProfileType("SURVIVAL"); + + /** + * The profile type for the CREATIVE Game Mode. + */ + public static final ProfileType CREATIVE = ProfileType.createProfileType("CREATIVE"); + + /** + * The profile type for the ADVENTURE Game Mode. + */ + public static final ProfileType ADVENTURE = ProfileType.createProfileType("ADVENTURE"); + + /** + * Returns the appropriate ProfileType for the given game mode. + * + * @param mode The game mode to get the profile type for. + * @return The profile type for the given game mode. + */ + public static ProfileType forGameMode(GameMode mode) { + switch (mode) { + case SURVIVAL: + return SURVIVAL; + case CREATIVE: + return CREATIVE; + case ADVENTURE: + return ADVENTURE; + default: + return SURVIVAL; + } + } + + private ProfileTypes() { + throw new AssertionError(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java similarity index 97% rename from src/main/java/com/onarandombox/multiverseinventories/profile/WorldGroupManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java index 251a2b1d..a79ca089 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.profile; +package org.mvplugins.multiverse.inventories.profile; -import com.onarandombox.multiverseinventories.WorldGroup; +import org.mvplugins.multiverse.inventories.WorldGroup; import org.bukkit.command.CommandSender; import java.util.List; diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ContainerType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ContainerType.java similarity index 76% rename from src/main/java/com/onarandombox/multiverseinventories/profile/container/ContainerType.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/container/ContainerType.java index 4cb56a28..8659e22a 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ContainerType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ContainerType.java @@ -1,17 +1,17 @@ -package com.onarandombox.multiverseinventories.profile.container; - -/** - * Used to describe whether a {@link ProfileContainer} represents a single world or a group of worlds. - */ -public enum ContainerType { - - /** - * Indicates World type profiles. - */ - WORLD, - /** - * Indicates Group type profiles. - */ - GROUP; -} - +package org.mvplugins.multiverse.inventories.profile.container; + +/** + * Used to describe whether a {@link ProfileContainer} represents a single world or a group of worlds. + */ +public enum ContainerType { + + /** + * Indicates World type profiles. + */ + WORLD, + /** + * Indicates Group type profiles. + */ + GROUP; +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainer.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index e6ffc560..2cd4fb93 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -1,7 +1,7 @@ -package com.onarandombox.multiverseinventories.profile.container; +package org.mvplugins.multiverse.inventories.profile.container; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java similarity index 85% rename from src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainerStore.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java index b7b4d9e2..91f5c873 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainerStore.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java @@ -1,23 +1,23 @@ -package com.onarandombox.multiverseinventories.profile.container; - -/** - * A utility for storing and retrieving profile containers. - */ -public interface ProfileContainerStore { - - /** - * Adds a profile container to the store. - * - * @param container profile container to add. - */ - void addContainer(ProfileContainer container); - - /** - * Returns the profile container for the given name. - * - * @param containerName Name of the profile container to retrieve. - * @return the profile container for given name. - */ - ProfileContainer getContainer(String containerName); -} - +package org.mvplugins.multiverse.inventories.profile.container; + +/** + * A utility for storing and retrieving profile containers. + */ +public interface ProfileContainerStore { + + /** + * Adds a profile container to the store. + * + * @param container profile container to add. + */ + void addContainer(ProfileContainer container); + + /** + * Returns the profile container for the given name. + * + * @param containerName Name of the profile container to retrieve. + * @return the profile container for given name. + */ + ProfileContainer getContainer(String containerName); +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/package-info.java similarity index 64% rename from src/main/java/com/onarandombox/multiverseinventories/profile/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/package-info.java index 38c8b5f9..9480ae1f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/package-info.java @@ -1,5 +1,5 @@ -/** - * This package contains classes related to groups and worlds and the player profiles they contain. - */ -package com.onarandombox.multiverseinventories.profile; - +/** + * This package contains classes related to groups and worlds and the player profiles they contain. + */ +package org.mvplugins.multiverse.inventories.profile; + diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java similarity index 87% rename from src/main/java/com/onarandombox/multiverseinventories/share/DefaultSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java index 1888dc38..ef524d28 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java @@ -1,30 +1,30 @@ -package com.onarandombox.multiverseinventories.share; - -/** - * The default Sharable serializer. It performs no special tasks on the objects being sent to persistence, they are - * sent as is. - * - * @param The type of data this serializer serializes. - */ -final class DefaultSerializer implements SharableSerializer { - - private Class type; - - public DefaultSerializer(Class type) { - this.type = type; - } - - private Class getType() { - return this.type; - } - - @Override - public T deserialize(Object obj) { - return getType().cast(obj); - } - - @Override - public Object serialize(T t) { - return t; - } -} +package org.mvplugins.multiverse.inventories.share; + +/** + * The default Sharable serializer. It performs no special tasks on the objects being sent to persistence, they are + * sent as is. + * + * @param The type of data this serializer serializes. + */ +final class DefaultSerializer implements SharableSerializer { + + private Class type; + + public DefaultSerializer(Class type) { + this.type = type; + } + + private Class getType() { + return this.type; + } + + @Override + public T deserialize(Object obj) { + return getType().cast(obj); + } + + @Override + public Object serialize(T t) { + return t; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSharable.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/share/DefaultSharable.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSharable.java index 306616e9..5d3c61ba 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSharable.java @@ -1,83 +1,83 @@ -package com.onarandombox.multiverseinventories.share; - -/** - * A class used to define a value that can be shared between worlds and world groups in Multiverse-Inventories. - * - * @param The type of data this Sharable represents. - */ -final class DefaultSharable implements Sharable { - - private final String[] names; - private final SharableHandler handler; - private final SharableSerializer serializer; - private final ProfileEntry profileEntry; - private final boolean optional; - private final Class type; - - DefaultSharable(final String[] names, final Class type, final SharableHandler handler, - final SharableSerializer serializer, final ProfileEntry entry, final boolean optional) { - this.names = names; - this.handler = handler; - this.serializer = serializer; - this.profileEntry = entry; - this.optional = optional; - this.type = type; - Sharables.register(this); - } - - /** - * {@inheritDoc} - */ - @Override - public String[] getNames() { - return names; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getType() { - return this.type; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return this.names[0]; - } - - /** - * {@inheritDoc} - */ - @Override - public SharableHandler getHandler() { - return this.handler; - } - - /** - * {@inheritDoc} - */ - @Override - public SharableSerializer getSerializer() { - return this.serializer; - } - - /** - * {@inheritDoc} - */ - @Override - public ProfileEntry getProfileEntry() { - return this.profileEntry; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isOptional() { - return this.optional; - } -} +package org.mvplugins.multiverse.inventories.share; + +/** + * A class used to define a value that can be shared between worlds and world groups in Multiverse-Inventories. + * + * @param The type of data this Sharable represents. + */ +final class DefaultSharable implements Sharable { + + private final String[] names; + private final SharableHandler handler; + private final SharableSerializer serializer; + private final ProfileEntry profileEntry; + private final boolean optional; + private final Class type; + + DefaultSharable(final String[] names, final Class type, final SharableHandler handler, + final SharableSerializer serializer, final ProfileEntry entry, final boolean optional) { + this.names = names; + this.handler = handler; + this.serializer = serializer; + this.profileEntry = entry; + this.optional = optional; + this.type = type; + Sharables.register(this); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getNames() { + return names; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getType() { + return this.type; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return this.names[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public SharableHandler getHandler() { + return this.handler; + } + + /** + * {@inheritDoc} + */ + @Override + public SharableSerializer getSerializer() { + return this.serializer; + } + + /** + * {@inheritDoc} + */ + @Override + public ProfileEntry getProfileEntry() { + return this.profileEntry; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOptional() { + return this.optional; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultStringSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/share/DefaultStringSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java index c18f2374..f2c2c329 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultStringSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java @@ -1,51 +1,51 @@ -package com.onarandombox.multiverseinventories.share; - -import com.dumptruckman.minecraft.util.Logging; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * This Sharable serializer attempts to deserialize the string form of objects passed to it through use of - * T.valueOf(String). Likewise, it serializes data simply by calling Object.toString() on the value passed in. - * - * @param The type of data this serializer serializes. This class MUST have a static valueOf(String) method that - * returns it's type. - */ -final class DefaultStringSerializer implements SharableSerializer { - - private Method valueOfMethod; - private Class clazz; - - DefaultStringSerializer(Class clazz) { - this.clazz = clazz; - try { - valueOfMethod = clazz.getMethod("valueOf", String.class); - valueOfMethod.setAccessible(true); - if (!valueOfMethod.getReturnType().equals(clazz) || !Modifier.isStatic(valueOfMethod.getModifiers())) { - throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); - } - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); - } - } - - @Override - public T deserialize(Object obj) { - try { - return clazz.cast(valueOfMethod.invoke(null, obj.toString())); - } catch (IllegalAccessException e) { - Logging.severe(this.clazz.getName() + " has no accessible static valueOf(String) method!"); - } catch (InvocationTargetException e) { - Logging.severe(this.clazz.getName() + ".valueOf(String) is throwing an exception:"); - e.printStackTrace(); - } - throw new IllegalStateException(this.getClass().getName() + " was used illegally! Contact dumptruckman!"); - } - - @Override - public Object serialize(T t) { - return t.toString(); - } -} +package org.mvplugins.multiverse.inventories.share; + +import com.dumptruckman.minecraft.util.Logging; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * This Sharable serializer attempts to deserialize the string form of objects passed to it through use of + * T.valueOf(String). Likewise, it serializes data simply by calling Object.toString() on the value passed in. + * + * @param The type of data this serializer serializes. This class MUST have a static valueOf(String) method that + * returns it's type. + */ +final class DefaultStringSerializer implements SharableSerializer { + + private Method valueOfMethod; + private Class clazz; + + DefaultStringSerializer(Class clazz) { + this.clazz = clazz; + try { + valueOfMethod = clazz.getMethod("valueOf", String.class); + valueOfMethod.setAccessible(true); + if (!valueOfMethod.getReturnType().equals(clazz) || !Modifier.isStatic(valueOfMethod.getModifiers())) { + throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); + } + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); + } + } + + @Override + public T deserialize(Object obj) { + try { + return clazz.cast(valueOfMethod.invoke(null, obj.toString())); + } catch (IllegalAccessException e) { + Logging.severe(this.clazz.getName() + " has no accessible static valueOf(String) method!"); + } catch (InvocationTargetException e) { + Logging.severe(this.clazz.getName() + ".valueOf(String) is throwing an exception:"); + e.printStackTrace(); + } + throw new IllegalStateException(this.getClass().getName() + " was used illegally! Contact dumptruckman!"); + } + + @Override + public Object serialize(T t) { + return t.toString(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/share/InventorySerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index e1baa956..f4cca144 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -1,58 +1,58 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.util.MinecraftTools; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -/** - * A simple {@link SharableSerializer} usable with ItemStack[] which converts the ItemStack[] to the string format - * that is used by default in Multiverse-Inventories. - */ -public final class InventorySerializer implements SharableSerializer { - - private int inventorySize; - - public InventorySerializer(final int inventorySize) { - this.inventorySize = inventorySize; - } - - @Override - public ItemStack[] deserialize(Object obj) { - return unmapSlots(obj); - } - - @Override - public Object serialize(ItemStack[] itemStacks) { - return mapSlots(itemStacks); - } - - private Map mapSlots(ItemStack[] itemStacks) { - Map result = new HashMap<>(itemStacks.length); - for (int i = 0; i < itemStacks.length; i++) { - if (itemStacks[i] != null && itemStacks[i].getType() != Material.AIR) { - result.put(Integer.toString(i), itemStacks[i]); - } - } - return result; - } - - private ItemStack[] unmapSlots(Object obj) { - ItemStack[] result = new ItemStack[inventorySize]; - if (obj instanceof Map) { - Map invMap = (Map) obj; - for (int i = 0; i < result.length; i++) { - Object value = invMap.get(Integer.toString(i)); - if (value != null && value instanceof ItemStack) { - result[i] = (ItemStack) value; - } else { - result[i] = new ItemStack(Material.AIR); - } - } - return result; - } - return MinecraftTools.fillWithAir(result); - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.mvplugins.multiverse.inventories.util.MinecraftTools; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; + +/** + * A simple {@link SharableSerializer} usable with ItemStack[] which converts the ItemStack[] to the string format + * that is used by default in Multiverse-Inventories. + */ +public final class InventorySerializer implements SharableSerializer { + + private int inventorySize; + + public InventorySerializer(final int inventorySize) { + this.inventorySize = inventorySize; + } + + @Override + public ItemStack[] deserialize(Object obj) { + return unmapSlots(obj); + } + + @Override + public Object serialize(ItemStack[] itemStacks) { + return mapSlots(itemStacks); + } + + private Map mapSlots(ItemStack[] itemStacks) { + Map result = new HashMap<>(itemStacks.length); + for (int i = 0; i < itemStacks.length; i++) { + if (itemStacks[i] != null && itemStacks[i].getType() != Material.AIR) { + result.put(Integer.toString(i), itemStacks[i]); + } + } + return result; + } + + private ItemStack[] unmapSlots(Object obj) { + ItemStack[] result = new ItemStack[inventorySize]; + if (obj instanceof Map) { + Map invMap = (Map) obj; + for (int i = 0; i < result.length; i++) { + Object value = invMap.get(Integer.toString(i)); + if (value != null && value instanceof ItemStack) { + result[i] = (ItemStack) value; + } else { + result[i] = new ItemStack(Material.AIR); + } + } + return result; + } + return MinecraftTools.fillWithAir(result); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/LocationSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java similarity index 88% rename from src/main/java/com/onarandombox/multiverseinventories/share/LocationSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java index 8284c7f0..98554ddf 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/LocationSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java @@ -1,36 +1,36 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.DataStrings; -import org.bukkit.Location; - -import java.util.Map; - -/** - * A simple {@link SharableSerializer} usable with {@link Location} which converts the {@link Location} to the string - * format that is used by default in Multiverse-Inventories. - * @deprecated Locations no longer need a special serializer because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. This remains to convert legacy data. - */ -@Deprecated -public final class LocationSerializer implements SharableSerializer { - - @Override - public Location deserialize(Object obj) { - if (obj instanceof Location) { - return (Location) obj; - } else if (obj instanceof String) { - return DataStrings.parseLocation(obj.toString()); - } else { - if (obj instanceof Map) { - return DataStrings.parseLocation((Map) obj); - } else { - return DataStrings.parseLocation(obj.toString()); - } - } - } - - @Override - public Object serialize(Location location) { - return location; - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.mvplugins.multiverse.inventories.DataStrings; +import org.bukkit.Location; + +import java.util.Map; + +/** + * A simple {@link SharableSerializer} usable with {@link Location} which converts the {@link Location} to the string + * format that is used by default in Multiverse-Inventories. + * @deprecated Locations no longer need a special serializer because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. This remains to convert legacy data. + */ +@Deprecated +public final class LocationSerializer implements SharableSerializer { + + @Override + public Location deserialize(Object obj) { + if (obj instanceof Location) { + return (Location) obj; + } else if (obj instanceof String) { + return DataStrings.parseLocation(obj.toString()); + } else { + if (obj instanceof Map) { + return DataStrings.parseLocation((Map) obj); + } else { + return DataStrings.parseLocation(obj.toString()); + } + } + } + + @Override + public Object serialize(Location location) { + return location; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java similarity index 82% rename from src/main/java/com/onarandombox/multiverseinventories/share/PersistingProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java index 20fc431b..555015d1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.share; +package org.mvplugins.multiverse.inventories.share; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; /** * Simple interface for groups that are going to be saved/loaded. This is used specifically for when a user's world diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/PotionEffectSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java similarity index 79% rename from src/main/java/com/onarandombox/multiverseinventories/share/PotionEffectSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java index f16c0bff..753170b6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/PotionEffectSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java @@ -1,36 +1,36 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.DataStrings; -import org.bukkit.potion.PotionEffect; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * A simple {@link com.onarandombox.multiverseinventories.share.SharableSerializer} usable with PotionEffect[] - * which converts the PotionEffect[] to the string format that is used by default in Multiverse-Inventories. - */ -public final class PotionEffectSerializer implements SharableSerializer { - - @Override - public PotionEffect[] deserialize(Object obj) { - if (obj instanceof List) { - List list = (List) obj; - List resultList = new ArrayList<>(list.size()); - for (Object o : list) { - if (o instanceof PotionEffect) { - resultList.add((PotionEffect) o); - } - } - return resultList.toArray(new PotionEffect[resultList.size()]); - } else { - return DataStrings.parsePotionEffects(obj.toString()); - } - } - - @Override - public Object serialize(PotionEffect[] potionEffects) { - return Arrays.asList(potionEffects); - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.mvplugins.multiverse.inventories.DataStrings; +import org.bukkit.potion.PotionEffect; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A simple {@link SharableSerializer} usable with PotionEffect[] + * which converts the PotionEffect[] to the string format that is used by default in Multiverse-Inventories. + */ +public final class PotionEffectSerializer implements SharableSerializer { + + @Override + public PotionEffect[] deserialize(Object obj) { + if (obj instanceof List) { + List list = (List) obj; + List resultList = new ArrayList<>(list.size()); + for (Object o : list) { + if (o instanceof PotionEffect) { + resultList.add((PotionEffect) o); + } + } + return resultList.toArray(new PotionEffect[resultList.size()]); + } else { + return DataStrings.parsePotionEffects(obj.toString()); + } + } + + @Override + public Object serialize(PotionEffect[] potionEffects) { + return Arrays.asList(potionEffects); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/ProfileEntry.java b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/share/ProfileEntry.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java index d3cf5a65..7145eb07 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/ProfileEntry.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java @@ -1,70 +1,70 @@ -package com.onarandombox.multiverseinventories.share; - -import java.util.HashMap; -import java.util.Map; - -/** - * Indicates how a Sharable should be stored in the profile file. Serves as a lookup for finding a sharable based on - * it's file tag. - */ -public final class ProfileEntry { - - private static final Map STATS_MAP = new HashMap(); - private static final Map OTHERS_MAP = new HashMap(); - - private boolean isStat; - private String fileTag; - - public ProfileEntry(boolean isStat, String fileTag) { - this.isStat = isStat; - this.fileTag = fileTag; - } - - /** - * @return True if this indicates a {@link Sharable} whose data will be stored in the stats string of the player - * file. - */ - public boolean isStat() { - return this.isStat; - } - - /** - * @return The String that represents where this file is stored in the player profile. - */ - public String getFileTag() { - return this.fileTag; - } - - /** - * Registers a {@link Sharable} with it's respective ProfileEntry. - * - * @param sharable The sharable to register. - */ - static void register(Sharable sharable) { - ProfileEntry entry = sharable.getProfileEntry(); - if (entry == null) { - // This would mean the sharable is not intended for saving in profile files. - return; - } - if (entry.isStat()) { - STATS_MAP.put(entry.getFileTag(), sharable); - } else { - OTHERS_MAP.put(entry.getFileTag(), sharable); - } - } - - /** - * Used to look up a {@link Sharable} by it's file tag. - * - * @param stat True means this sharable is in the stats section of the player file. - * @param fileTag The string representing where the sharable is stored in the player file. - * @return A sharable if one has been registered with it's ProfileEntry otherwise null. - */ - public static Sharable lookup(boolean stat, String fileTag) { - if (stat) { - return STATS_MAP.get(fileTag); - } else { - return OTHERS_MAP.get(fileTag); - } - } -} +package org.mvplugins.multiverse.inventories.share; + +import java.util.HashMap; +import java.util.Map; + +/** + * Indicates how a Sharable should be stored in the profile file. Serves as a lookup for finding a sharable based on + * it's file tag. + */ +public final class ProfileEntry { + + private static final Map STATS_MAP = new HashMap(); + private static final Map OTHERS_MAP = new HashMap(); + + private boolean isStat; + private String fileTag; + + public ProfileEntry(boolean isStat, String fileTag) { + this.isStat = isStat; + this.fileTag = fileTag; + } + + /** + * @return True if this indicates a {@link Sharable} whose data will be stored in the stats string of the player + * file. + */ + public boolean isStat() { + return this.isStat; + } + + /** + * @return The String that represents where this file is stored in the player profile. + */ + public String getFileTag() { + return this.fileTag; + } + + /** + * Registers a {@link Sharable} with it's respective ProfileEntry. + * + * @param sharable The sharable to register. + */ + static void register(Sharable sharable) { + ProfileEntry entry = sharable.getProfileEntry(); + if (entry == null) { + // This would mean the sharable is not intended for saving in profile files. + return; + } + if (entry.isStat()) { + STATS_MAP.put(entry.getFileTag(), sharable); + } else { + OTHERS_MAP.put(entry.getFileTag(), sharable); + } + } + + /** + * Used to look up a {@link Sharable} by it's file tag. + * + * @param stat True means this sharable is in the stats section of the player file. + * @param fileTag The string representing where the sharable is stored in the player file. + * @return A sharable if one has been registered with it's ProfileEntry otherwise null. + */ + public static Sharable lookup(boolean stat, String fileTag) { + if (stat) { + return STATS_MAP.get(fileTag); + } else { + return OTHERS_MAP.get(fileTag); + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Sharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java similarity index 95% rename from src/main/java/com/onarandombox/multiverseinventories/share/Sharable.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java index 64dca20a..e48e2c1a 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Sharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java @@ -1,156 +1,156 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.InventoriesConfig; - -import java.util.ArrayList; -import java.util.List; - -/** - * An interface for any attribute that can be shared between worlds in a world group. These objects are intended to - * be used as constants and may not function properly otherwise. - * - * @param The type of data that this sharable represents. - */ -public interface Sharable { - - /** - * @return The names of this Sharable for setting as shared in the config. There should ALWAYS be a index-0 which - * represents the main name, and the one that will be used for storing the sharable in a groups shares list in - * the config file. All names in this array may be used to set a group as sharing this Sharable. - */ - String[] getNames(); - - /** - * @return The object that will handle changing out the player's data with the profile's data and vice versa when - * a player changes worlds. - */ - SharableHandler getHandler(); - - /** - * @return The object that will handle serializing a profile's data for this sharable for saving/loading in the - * profile's data file. If this is null it means that persistence is not handled by Multiverse-Inventories for - * this Sharable. - */ - SharableSerializer getSerializer(); - - /** - * @return The profile entry that describes how to store this Sharable in a profile's data file. This may NOT be - * null if this Sharable getSerializer() is not null. If getSerializer() IS null, this method is never called. - */ - ProfileEntry getProfileEntry(); - - /** - * @return The type of data this Sharable represents. Used primarily for casting. - */ - Class getType(); - - /** - * @return True if this Sharable is optional. That is to say that it is completely ignored when share handling - * takes place UNLESS it is present in {@link InventoriesConfig#getOptionalShares()}. - */ - boolean isOptional(); - - /** - * This class is used to build new {@link Sharable}s. Simply instantiate this and use method chaining to set - * all the options for your Sharable. - * - * @param The type of data the new Sharable will represent. - */ - class Builder { - - private List names = new ArrayList(); - private ProfileEntry profileEntry = null; - private SharableHandler handler; - private SharableSerializer serializer = null; - private boolean optional = false; - private Class type; - - /** - * @param name The primary name of the new Sharable. - * @param type The type of data the Sharable represents. - * @param handler The object that will handle switching the Sharable data between player and profile. - */ - public Builder(String name, Class type, SharableHandler handler) { - this.names.add(name); - this.handler = handler; - this.type = type; - } - - /** - * Indicates that the new Sharable is optional as described in {@link Sharable#isOptional()}. - * - * @return This builder object for method chaining. - */ - public Builder optional() { - this.optional = true; - return this; - } - - /** - * @param name An alternate name for this Sharable which can be used to indicate a group is sharing this - * Sharable. - * @return This builder object for method chaining. - */ - public Builder altName(String name) { - this.names.add(name); - return this; - } - - /** - * Sets this sharable to be serialized as a string in the profile data file. To use this, the class type - * indicates in the Builder's constructor MUST have a static .valueOf(String) method that returns it's type. - * - * @param entry The profile entry describing where this Sharable is located in the profile file. - * @return This builder object for method chaining. - * @throws IllegalArgumentException This is thrown if the type indicated in the Builder's constructor does not - * fit the constraints indicated above. - */ - public Builder stringSerializer(ProfileEntry entry) { - this.serializer = new DefaultStringSerializer(this.type); - this.profileEntry = entry; - return this; - } - - /** - * This will make the Sharable use the default serializer which simply passes the data as is to the persistence - * object for persistence. This will only work depending on the data type this Sharable represents and further - * depending on the types the persistence methods accept. Generally, boxed primitives are okay as well as - * Lists of boxed primitives and {@link java.util.Map}<{@link String}, {@link Object}>. All other types - * will likely require a custom {@link SharableSerializer} indicated with - * {@link #serializer(ProfileEntry, SharableSerializer)}. - * - * @param entry The profile entry describing where this Sharable is located in the profile file. - * @return This builder object for method chaining. - */ - public Builder defaultSerializer(ProfileEntry entry) { - this.serializer = new DefaultSerializer(this.type); - this.profileEntry = entry; - return this; - } - - /** - * This allows you to specify a custom {@link SharableSerializer} to use to convert the data represented by - * this Sharable into something acceptable by persistence. - * - * @param entry The profile entry describing where this Sharable is located in the profile file. - * @param serializer A custom serializer describing how to handle the data in order for it to be persisted in - * the profile. - * @return This builder object for method chaining. - */ - public Builder serializer(ProfileEntry entry, SharableSerializer serializer) { - this.serializer = serializer; - this.profileEntry = entry; - return this; - } - - /** - * @return The new Sharable object built by this Builder. - */ - public Sharable build() { - Sharable sharable = new DefaultSharable(names.toArray(new String[names.size()]), type, - handler, serializer, profileEntry, optional); - ProfileEntry.register(sharable); - return sharable; - } - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.mvplugins.multiverse.inventories.InventoriesConfig; + +import java.util.ArrayList; +import java.util.List; + +/** + * An interface for any attribute that can be shared between worlds in a world group. These objects are intended to + * be used as constants and may not function properly otherwise. + * + * @param The type of data that this sharable represents. + */ +public interface Sharable { + + /** + * @return The names of this Sharable for setting as shared in the config. There should ALWAYS be a index-0 which + * represents the main name, and the one that will be used for storing the sharable in a groups shares list in + * the config file. All names in this array may be used to set a group as sharing this Sharable. + */ + String[] getNames(); + + /** + * @return The object that will handle changing out the player's data with the profile's data and vice versa when + * a player changes worlds. + */ + SharableHandler getHandler(); + + /** + * @return The object that will handle serializing a profile's data for this sharable for saving/loading in the + * profile's data file. If this is null it means that persistence is not handled by Multiverse-Inventories for + * this Sharable. + */ + SharableSerializer getSerializer(); + + /** + * @return The profile entry that describes how to store this Sharable in a profile's data file. This may NOT be + * null if this Sharable getSerializer() is not null. If getSerializer() IS null, this method is never called. + */ + ProfileEntry getProfileEntry(); + + /** + * @return The type of data this Sharable represents. Used primarily for casting. + */ + Class getType(); + + /** + * @return True if this Sharable is optional. That is to say that it is completely ignored when share handling + * takes place UNLESS it is present in {@link InventoriesConfig#getOptionalShares()}. + */ + boolean isOptional(); + + /** + * This class is used to build new {@link Sharable}s. Simply instantiate this and use method chaining to set + * all the options for your Sharable. + * + * @param The type of data the new Sharable will represent. + */ + class Builder { + + private List names = new ArrayList(); + private ProfileEntry profileEntry = null; + private SharableHandler handler; + private SharableSerializer serializer = null; + private boolean optional = false; + private Class type; + + /** + * @param name The primary name of the new Sharable. + * @param type The type of data the Sharable represents. + * @param handler The object that will handle switching the Sharable data between player and profile. + */ + public Builder(String name, Class type, SharableHandler handler) { + this.names.add(name); + this.handler = handler; + this.type = type; + } + + /** + * Indicates that the new Sharable is optional as described in {@link Sharable#isOptional()}. + * + * @return This builder object for method chaining. + */ + public Builder optional() { + this.optional = true; + return this; + } + + /** + * @param name An alternate name for this Sharable which can be used to indicate a group is sharing this + * Sharable. + * @return This builder object for method chaining. + */ + public Builder altName(String name) { + this.names.add(name); + return this; + } + + /** + * Sets this sharable to be serialized as a string in the profile data file. To use this, the class type + * indicates in the Builder's constructor MUST have a static .valueOf(String) method that returns it's type. + * + * @param entry The profile entry describing where this Sharable is located in the profile file. + * @return This builder object for method chaining. + * @throws IllegalArgumentException This is thrown if the type indicated in the Builder's constructor does not + * fit the constraints indicated above. + */ + public Builder stringSerializer(ProfileEntry entry) { + this.serializer = new DefaultStringSerializer(this.type); + this.profileEntry = entry; + return this; + } + + /** + * This will make the Sharable use the default serializer which simply passes the data as is to the persistence + * object for persistence. This will only work depending on the data type this Sharable represents and further + * depending on the types the persistence methods accept. Generally, boxed primitives are okay as well as + * Lists of boxed primitives and {@link java.util.Map}<{@link String}, {@link Object}>. All other types + * will likely require a custom {@link SharableSerializer} indicated with + * {@link #serializer(ProfileEntry, SharableSerializer)}. + * + * @param entry The profile entry describing where this Sharable is located in the profile file. + * @return This builder object for method chaining. + */ + public Builder defaultSerializer(ProfileEntry entry) { + this.serializer = new DefaultSerializer(this.type); + this.profileEntry = entry; + return this; + } + + /** + * This allows you to specify a custom {@link SharableSerializer} to use to convert the data represented by + * this Sharable into something acceptable by persistence. + * + * @param entry The profile entry describing where this Sharable is located in the profile file. + * @param serializer A custom serializer describing how to handle the data in order for it to be persisted in + * the profile. + * @return This builder object for method chaining. + */ + public Builder serializer(ProfileEntry entry, SharableSerializer serializer) { + this.serializer = serializer; + this.profileEntry = entry; + return this; + } + + /** + * @return The new Sharable object built by this Builder. + */ + public Sharable build() { + Sharable sharable = new DefaultSharable(names.toArray(new String[names.size()]), type, + handler, serializer, profileEntry, optional); + ProfileEntry.register(sharable); + return sharable; + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableEntry.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java similarity index 83% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableEntry.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java index 418c1fdc..9f13d1a6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableEntry.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java @@ -1,20 +1,20 @@ -package com.onarandombox.multiverseinventories.share; - -public final class SharableEntry { - - private final Sharable sharable; - private final T value; - - public SharableEntry(Sharable sharable, T initialValue) { - this.sharable = sharable; - this.value = initialValue; - } - - public Sharable getSharable() { - return sharable; - } - - public T getValue() { - return value; - } -} +package org.mvplugins.multiverse.inventories.share; + +public final class SharableEntry { + + private final Sharable sharable; + private final T value; + + public SharableEntry(Sharable sharable, T initialValue) { + this.sharable = sharable; + this.value = initialValue; + } + + public Sharable getSharable() { + return sharable; + } + + public T getValue() { + return value; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableGroup.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java index c030cd8f..80724ddf 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java @@ -1,134 +1,134 @@ -package com.onarandombox.multiverseinventories.share; - -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * This class represents a grouping of Sharable objects for the sole purpose of being able to use one keyword in - * group setups to indicate multiple {@link Sharable}s. - */ -public final class SharableGroup implements Shares { - - private String[] names; - private Shares shares; - - public SharableGroup(String name, Shares shares, String... alternateNames) { - this.names = new String[alternateNames.length + 1]; - this.names[0] = name; - System.arraycopy(alternateNames, 0, this.names, 1, alternateNames.length); - this.shares = shares; - for (String lookupName : this.names) { - Sharables.LOOKUP_MAP.put(lookupName, this); - } - } - - /** - * @return The names of this SharableGroup for setting as shared in the config. - * All names in this array may be used to set a group as sharing this SharableGroup. - */ - public String[] getNames() { - return this.names; - } - - @Override - public void mergeShares(Shares newShares) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean isSharing(Sharable sharable) { - return shares.isSharing(sharable); - } - - @Override - public boolean isSharing(Shares shares) { - return this.shares.isSharing(shares); - } - - @Override - public Shares compare(Shares shares) { - return this.shares.compare(shares); - } - - @Override - public void setSharing(Sharable sharable, boolean sharing) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public void setSharing(Shares sharables, boolean sharing) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public List toStringList() { - return shares.toStringList(); - } - - @Override - public Iterator iterator() { - return shares.iterator(); - } - - @Override - public int size() { - return shares.size(); - } - - @Override - public boolean isEmpty() { - return shares.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return shares.contains(o); - } - - @Override - public Object[] toArray() { - return Collections.unmodifiableCollection(this).toArray(); - } - - @Override - public T[] toArray(T[] a) { - return Collections.unmodifiableCollection(this).toArray(a); - } - - @Override - public boolean add(Sharable sharable) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean remove(Object o) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean containsAll(Collection c) { - return shares.containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean removeAll(Collection c) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean retainAll(Collection c) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public void clear() { - throw new IllegalStateException("May not alter SharableGroup!"); - } -} +package org.mvplugins.multiverse.inventories.share; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * This class represents a grouping of Sharable objects for the sole purpose of being able to use one keyword in + * group setups to indicate multiple {@link Sharable}s. + */ +public final class SharableGroup implements Shares { + + private String[] names; + private Shares shares; + + public SharableGroup(String name, Shares shares, String... alternateNames) { + this.names = new String[alternateNames.length + 1]; + this.names[0] = name; + System.arraycopy(alternateNames, 0, this.names, 1, alternateNames.length); + this.shares = shares; + for (String lookupName : this.names) { + Sharables.LOOKUP_MAP.put(lookupName, this); + } + } + + /** + * @return The names of this SharableGroup for setting as shared in the config. + * All names in this array may be used to set a group as sharing this SharableGroup. + */ + public String[] getNames() { + return this.names; + } + + @Override + public void mergeShares(Shares newShares) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean isSharing(Sharable sharable) { + return shares.isSharing(sharable); + } + + @Override + public boolean isSharing(Shares shares) { + return this.shares.isSharing(shares); + } + + @Override + public Shares compare(Shares shares) { + return this.shares.compare(shares); + } + + @Override + public void setSharing(Sharable sharable, boolean sharing) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public void setSharing(Shares sharables, boolean sharing) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public List toStringList() { + return shares.toStringList(); + } + + @Override + public Iterator iterator() { + return shares.iterator(); + } + + @Override + public int size() { + return shares.size(); + } + + @Override + public boolean isEmpty() { + return shares.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return shares.contains(o); + } + + @Override + public Object[] toArray() { + return Collections.unmodifiableCollection(this).toArray(); + } + + @Override + public T[] toArray(T[] a) { + return Collections.unmodifiableCollection(this).toArray(a); + } + + @Override + public boolean add(Sharable sharable) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean remove(Object o) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean containsAll(Collection c) { + return shares.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean removeAll(Collection c) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean retainAll(Collection c) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public void clear() { + throw new IllegalStateException("May not alter SharableGroup!"); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java index fe853755..30b3fd16 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.share; +package org.mvplugins.multiverse.inventories.share; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.bukkit.entity.Player; /** diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableSerializer.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableSerializer.java index 4c078b1c..d42fe40f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableSerializer.java @@ -1,29 +1,29 @@ -package com.onarandombox.multiverseinventories.share; - -/** - * This represents how a Sharable's data will be serialized/deserialized. - * - * @param The type of data the {@link Sharable} this belongs to represents. - */ -public interface SharableSerializer { - - /** - * This deserializes the data for a Sharable. You must be expecting the type of data coming in in order to process - * this. That type will generally be the type that this serializes as with {@link #serialize(Object)}. - * - * @param obj The incoming (serialized) data to be deserialized. - * @return The data represented by the Sharable this object represents in deserialized form. - */ - T deserialize(Object obj); - - /** - * This serializes the data for a Sharable. The output is an Object but what you return is up to you, however, - * this is limited by the constraints of the persistence method. Generally, returning a String is the safest way - * to serialize your data. Most boxed primitives are accepted as well as Lists of boxed primitives and - * {@link java.util.Map}<{@link String}, {@link Object}>. - * - * @param t The value of the data represented by the Sharable. - * @return The serialized form of the data. - */ - Object serialize(T t); -} +package org.mvplugins.multiverse.inventories.share; + +/** + * This represents how a Sharable's data will be serialized/deserialized. + * + * @param The type of data the {@link Sharable} this belongs to represents. + */ +public interface SharableSerializer { + + /** + * This deserializes the data for a Sharable. You must be expecting the type of data coming in in order to process + * this. That type will generally be the type that this serializes as with {@link #serialize(Object)}. + * + * @param obj The incoming (serialized) data to be deserialized. + * @return The data represented by the Sharable this object represents in deserialized form. + */ + T deserialize(Object obj); + + /** + * This serializes the data for a Sharable. The output is an Object but what you return is up to you, however, + * this is limited by the constraints of the persistence method. Generally, returning a String is the safest way + * to serialize your data. Most boxed primitives are accepted as well as Lists of boxed primitives and + * {@link java.util.Map}<{@link String}, {@link Object}>. + * + * @param t The value of the data represented by the Sharable. + * @return The serialized form of the data. + */ + Object serialize(T t); +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java similarity index 98% rename from src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index cef0e602..8621d127 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1,12 +1,12 @@ -package com.onarandombox.multiverseinventories.share; +package org.mvplugins.multiverse.inventories.share; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.DataStrings; -import com.onarandombox.multiverseinventories.PlayerStats; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.util.MinecraftTools; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.DataStrings; +import org.mvplugins.multiverse.inventories.PlayerStats; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.attribute.Attribute; diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Shares.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/share/Shares.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java index a523bf27..7f289126 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Shares.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java @@ -1,56 +1,56 @@ -package com.onarandombox.multiverseinventories.share; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -/** - * Interface for getting what is shared in a world player. - */ -public interface Shares extends Cloneable, Iterable, Collection, Set { - - /** - * Merges what is shared with another share. Only the false items should be merged. - * - * @param newShares The set of shares to merge into this set of shares. - */ - void mergeShares(Shares newShares); - - /** - * @param sharable The Sharable you want to check for. - * @return True if it is sharing the sharable. - */ - boolean isSharing(Sharable sharable); - - /** - * @param shares Shares to compare with. - * @return True if it is sharing the same sharables. - */ - boolean isSharing(Shares shares); - - /** - * Checks to see if any of the sharables passed in are shared by this Shares. - * - * @param shares Shares to check for. - * @return A Set containing all of the Sharables both sets contain. - */ - Shares compare(Shares shares); - - /** - * @param sharable The Sharable you wish to set sharing for. - * @param sharing Whether to share or not. - */ - void setSharing(Sharable sharable, boolean sharing); - - /** - * @param sharables a Set of Sharables you wish to set sharing for. - * @param sharing Whether to share or not. - */ - void setSharing(Shares sharables, boolean sharing); - - /** - * @return These shares as a string list. - */ - List toStringList(); -} - +package org.mvplugins.multiverse.inventories.share; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Interface for getting what is shared in a world player. + */ +public interface Shares extends Cloneable, Iterable, Collection, Set { + + /** + * Merges what is shared with another share. Only the false items should be merged. + * + * @param newShares The set of shares to merge into this set of shares. + */ + void mergeShares(Shares newShares); + + /** + * @param sharable The Sharable you want to check for. + * @return True if it is sharing the sharable. + */ + boolean isSharing(Sharable sharable); + + /** + * @param shares Shares to compare with. + * @return True if it is sharing the same sharables. + */ + boolean isSharing(Shares shares); + + /** + * Checks to see if any of the sharables passed in are shared by this Shares. + * + * @param shares Shares to check for. + * @return A Set containing all of the Sharables both sets contain. + */ + Shares compare(Shares shares); + + /** + * @param sharable The Sharable you wish to set sharing for. + * @param sharing Whether to share or not. + */ + void setSharing(Sharable sharable, boolean sharing); + + /** + * @param sharables a Set of Sharables you wish to set sharing for. + * @param sharing Whether to share or not. + */ + void setSharing(Shares sharables, boolean sharing); + + /** + * @return These shares as a string list. + */ + List toStringList(); +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java new file mode 100644 index 00000000..b4eb4791 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains all external API classes for {@link org.mvplugins.multiverse.inventories.share.Sharable}s and handling the sharing of those between worlds. + */ +package org.mvplugins.multiverse.inventories.share; + diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java b/src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java similarity index 99% rename from src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java index 718b7da6..78993d16 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/DeserializationException.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DeserializationException.java similarity index 82% rename from src/main/java/com/onarandombox/multiverseinventories/util/DeserializationException.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/DeserializationException.java index 98a978e6..64c6f695 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/DeserializationException.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DeserializationException.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; /** * Exception thrown when something goes wrong while deserializing this plugin's objects. diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/Font.java b/src/main/java/org/mvplugins/multiverse/inventories/util/Font.java similarity index 99% rename from src/main/java/com/onarandombox/multiverseinventories/util/Font.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/Font.java index 71779d20..494b211d 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/Font.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/Font.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/MinecraftTools.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/util/MinecraftTools.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java index a79e49cb..79181301 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/MinecraftTools.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java @@ -1,38 +1,38 @@ -package com.onarandombox.multiverseinventories.util; - -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -/** - * General tools to help with minecraftian things. - */ -public class MinecraftTools { - - private static final int TICKS_PER_SECOND = 20; - - private MinecraftTools() { } - - /** - * Converts an amount of seconds to the appropriate amount of ticks. - * - * @param seconds Amount of seconds to convert - * @return Ticks converted from seconds. - */ - public static long convertSecondsToTicks(long seconds) { - return seconds * TICKS_PER_SECOND; - } - - /** - * Fills an ItemStack array with air. - * - * @param items The ItemStack array to fill. - * @return The air filled ItemStack array. - */ - public static ItemStack[] fillWithAir(ItemStack[] items) { - for (int i = 0; i < items.length; i++) { - items[i] = new ItemStack(Material.AIR); - } - return items; - } -} - +package org.mvplugins.multiverse.inventories.util; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +/** + * General tools to help with minecraftian things. + */ +public class MinecraftTools { + + private static final int TICKS_PER_SECOND = 20; + + private MinecraftTools() { } + + /** + * Converts an amount of seconds to the appropriate amount of ticks. + * + * @param seconds Amount of seconds to convert + * @return Ticks converted from seconds. + */ + public static long convertSecondsToTicks(long seconds) { + return seconds * TICKS_PER_SECOND; + } + + /** + * Fills an ItemStack array with air. + * + * @param items The ItemStack array to fill. + * @return The air filled ItemStack array. + */ + public static ItemStack[] fillWithAir(ItemStack[] items) { + for (int i = 0; i < items.length; i++) { + items[i] = new ItemStack(Material.AIR); + } + return items; + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/Perm.java b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java similarity index 95% rename from src/main/java/com/onarandombox/multiverseinventories/util/Perm.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java index 42c1be28..19a57595 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/Perm.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java @@ -1,226 +1,226 @@ -package com.onarandombox.multiverseinventories.util; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import org.bukkit.plugin.PluginManager; - -/** - * @author dumptruckman - */ -public enum Perm { - /** - * Permission for /mvinv info. - */ - COMMAND_INFO(new Permission("multiverse.inventories.info", "Displays information about a world or group.", PermissionDefault.OP)), - /** - * Permission for /mvinv list. - */ - COMMAND_LIST(new Permission("multiverse.inventories.list", "Displays a list of groups.", PermissionDefault.OP)), - /** - * Permission for /mvinv reload. - */ - COMMAND_RELOAD(new Permission("multiverse.inventories.reload", "Reloads config file.", PermissionDefault.OP)), - /** - * Permission for /mvinv import. - */ - COMMAND_IMPORT(new Permission("multiverse.inventories.import", "Imports data from MultiInv/WorldInventories", PermissionDefault.OP)), - /** - * Permission for /mvinv group. - */ - COMMAND_GROUP(new Permission("multiverse.inventories.group", "Begins a conversation about groups.", - PermissionDefault.OP)), - /** - * Permission for /mvinv addworld. - */ - COMMAND_ADDWORLD(new Permission("multiverse.inventories.addworld", "Adds a world to a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv remvoveworld. - */ - COMMAND_RMWORLD(new Permission("multiverse.inventories.removeworld", "Removes a world from a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv addshare. - */ - COMMAND_ADDSHARES(new Permission("multiverse.inventories.addshares", "Adds share(s) to a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv rmshare. - */ - COMMAND_RMSHARES(new Permission("multiverse.inventories.removeshares", "Removes share(s) from a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv creategroup. - */ - COMMAND_CREATEGROUP(new Permission("multiverse.inventories.creategroup", "Creates a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv deletegroup. - */ - COMMAND_DELETEGROUP(new Permission("multiverse.inventories.deletegroup", "Deletes a world group", - PermissionDefault.OP)), - /** - * Permissions for /mvinv spawn. - */ - COMMAND_SPAWN(new Permission("multiverse.inventories.spawn.self", "teleport yourself to group spawn", - PermissionDefault.OP)), - /** - * Permissions for /mvinv spawn. - */ - COMMAND_SPAWN_OTHER(new Permission("multiverse.inventories.spawn.other", "teleport other to group spawn", - PermissionDefault.OP)), - /** - * Permission for debug command. - */ - COMMAND_DEBUG(new Permission("multiverse.inventories.debug", "Spams the console a bunch.", PermissionDefault.OP)), - /** - * Permission for bypassing all groups. - */ - BYPASS_GROUP_ALL(new Permission("mvinv.bypass.group.*", "", PermissionDefault.FALSE)), - /** - * Permission prefix for bypassing groups. - */ - BYPASS_GROUP("mvinv.bypass.group.") { - private String getBypassMessage(Player player, String name) { - return "Player: " + player.getName() + " has bypass perms for group: " + name; - } - }, - /** - * Permission for bypassing all worlds. - */ - BYPASS_WORLD_ALL(new Permission("mvinv.bypass.world.*", "", PermissionDefault.FALSE)), - /** - * Permission prefix for bypassing worlds. - */ - BYPASS_WORLD("mvinv.bypass.world.") { - private String getBypassMessage(Player player, String name) { - return "Player: " + player.getName() + " has bypass perms for world: " + name; - } - }, - /** - * Permission for bypassing all worlds. - */ - BYPASS_GAME_MODE_ALL(new Permission("mvinv.bypass.gamemode.*", "", PermissionDefault.FALSE)), - /** - * Permission prefix for bypassing worlds. - */ - BYPASS_GAME_MODE("mvinv.bypass.gamemode.") { - private String getBypassMessage(Player player, String name) { - return "Player: " + player.getName() + " has bypass perms for game mode: " + name; - } - }, - /** - * Permissions for bypassing all world/groups inventory handling. - */ - BYPASS_ALL(new Permission("mvinv.bypass.*", "Allows bypassing all of your groups/worlds and constantly use " - + "the same inventory", PermissionDefault.FALSE)); - - private Permission perm = null; - private String permNode = ""; - - Perm(Permission perm) { - this.perm = perm; - } - - Perm(String permNode) { - this.permNode = permNode; - } - - /** - * @return the Permission. - */ - public Permission getPermission() { - return this.perm; - } - - /** - * @return the Permission node string. - */ - public String getNode() { - return this.permNode; - } - - /** - * @param finalNode String to add to the bypass prefix. - * @return The full permission node for bypass. - */ - public Permission getBypassPermission(String finalNode) { - String bypassNode = this.getNode() + finalNode; - Logging.finer("Checking node " + bypassNode + "..."); - - Permission permission = Bukkit.getPluginManager().getPermission(bypassNode); - if (permission == null) { - permission = new Permission(bypassNode, PermissionDefault.FALSE); - switch (this) { - case BYPASS_GROUP: - permission.addParent(BYPASS_GROUP_ALL.getPermission(), true); - break; - case BYPASS_WORLD: - permission.addParent(BYPASS_WORLD_ALL.getPermission(), true); - break; - case BYPASS_GAME_MODE: - permission.addParent(BYPASS_GAME_MODE_ALL.getPermission(), true); - break; - default: - } - Bukkit.getPluginManager().addPermission(permission); - } - return permission; - } - - /** - * Checks if a player has permission to bypass something which requires a name of an object to be bypassed. - * A World name for example. - * - * @param player Player to check permission for. - * @param name Name of object to bypass. - * @return True if player is allowed to bypass. - */ - public boolean hasBypass(Player player, String name) { - if (inventories != null && !inventories.getMVIConfig().isUsingBypass()) { - return false; - } - Permission bypassPerm = this.getBypassPermission(name); - boolean hasBypass = player.hasPermission(bypassPerm); - if (hasBypass) { - Logging.fine("Player: " + player.getName() + " in World: " + player.getWorld().getName() - + " has permission: " + bypassPerm.getName() + "(Default: " - + bypassPerm.getDefault().toString() + ")!"); - } - return hasBypass; - } - - /** - * Checks if the sender has the node in question. - * - * @param sender CommandSender to check permission for. - * @return True if sender has the permission. - */ - public boolean has(CommandSender sender) { - return sender.hasPermission(perm); - } - - private static MultiverseInventories inventories = null; - - /** - * Registers all Permission to the plugin. - * - * @param plugin Plugin to register permissions to. - */ - public static void register(MultiverseInventories plugin) { - inventories = plugin; - BYPASS_WORLD_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); - BYPASS_GROUP_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); - PluginManager pm = plugin.getServer().getPluginManager(); - for (Perm perm : Perm.values()) { - if (perm.getPermission() != null) { - pm.addPermission(perm.getPermission()); - } - } - } -} +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; + +/** + * @author dumptruckman + */ +public enum Perm { + /** + * Permission for /mvinv info. + */ + COMMAND_INFO(new Permission("multiverse.inventories.info", "Displays information about a world or group.", PermissionDefault.OP)), + /** + * Permission for /mvinv list. + */ + COMMAND_LIST(new Permission("multiverse.inventories.list", "Displays a list of groups.", PermissionDefault.OP)), + /** + * Permission for /mvinv reload. + */ + COMMAND_RELOAD(new Permission("multiverse.inventories.reload", "Reloads config file.", PermissionDefault.OP)), + /** + * Permission for /mvinv import. + */ + COMMAND_IMPORT(new Permission("multiverse.inventories.import", "Imports data from MultiInv/WorldInventories", PermissionDefault.OP)), + /** + * Permission for /mvinv group. + */ + COMMAND_GROUP(new Permission("multiverse.inventories.group", "Begins a conversation about groups.", + PermissionDefault.OP)), + /** + * Permission for /mvinv addworld. + */ + COMMAND_ADDWORLD(new Permission("multiverse.inventories.addworld", "Adds a world to a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv remvoveworld. + */ + COMMAND_RMWORLD(new Permission("multiverse.inventories.removeworld", "Removes a world from a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv addshare. + */ + COMMAND_ADDSHARES(new Permission("multiverse.inventories.addshares", "Adds share(s) to a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv rmshare. + */ + COMMAND_RMSHARES(new Permission("multiverse.inventories.removeshares", "Removes share(s) from a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv creategroup. + */ + COMMAND_CREATEGROUP(new Permission("multiverse.inventories.creategroup", "Creates a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv deletegroup. + */ + COMMAND_DELETEGROUP(new Permission("multiverse.inventories.deletegroup", "Deletes a world group", + PermissionDefault.OP)), + /** + * Permissions for /mvinv spawn. + */ + COMMAND_SPAWN(new Permission("multiverse.inventories.spawn.self", "teleport yourself to group spawn", + PermissionDefault.OP)), + /** + * Permissions for /mvinv spawn. + */ + COMMAND_SPAWN_OTHER(new Permission("multiverse.inventories.spawn.other", "teleport other to group spawn", + PermissionDefault.OP)), + /** + * Permission for debug command. + */ + COMMAND_DEBUG(new Permission("multiverse.inventories.debug", "Spams the console a bunch.", PermissionDefault.OP)), + /** + * Permission for bypassing all groups. + */ + BYPASS_GROUP_ALL(new Permission("mvinv.bypass.group.*", "", PermissionDefault.FALSE)), + /** + * Permission prefix for bypassing groups. + */ + BYPASS_GROUP("mvinv.bypass.group.") { + private String getBypassMessage(Player player, String name) { + return "Player: " + player.getName() + " has bypass perms for group: " + name; + } + }, + /** + * Permission for bypassing all worlds. + */ + BYPASS_WORLD_ALL(new Permission("mvinv.bypass.world.*", "", PermissionDefault.FALSE)), + /** + * Permission prefix for bypassing worlds. + */ + BYPASS_WORLD("mvinv.bypass.world.") { + private String getBypassMessage(Player player, String name) { + return "Player: " + player.getName() + " has bypass perms for world: " + name; + } + }, + /** + * Permission for bypassing all worlds. + */ + BYPASS_GAME_MODE_ALL(new Permission("mvinv.bypass.gamemode.*", "", PermissionDefault.FALSE)), + /** + * Permission prefix for bypassing worlds. + */ + BYPASS_GAME_MODE("mvinv.bypass.gamemode.") { + private String getBypassMessage(Player player, String name) { + return "Player: " + player.getName() + " has bypass perms for game mode: " + name; + } + }, + /** + * Permissions for bypassing all world/groups inventory handling. + */ + BYPASS_ALL(new Permission("mvinv.bypass.*", "Allows bypassing all of your groups/worlds and constantly use " + + "the same inventory", PermissionDefault.FALSE)); + + private Permission perm = null; + private String permNode = ""; + + Perm(Permission perm) { + this.perm = perm; + } + + Perm(String permNode) { + this.permNode = permNode; + } + + /** + * @return the Permission. + */ + public Permission getPermission() { + return this.perm; + } + + /** + * @return the Permission node string. + */ + public String getNode() { + return this.permNode; + } + + /** + * @param finalNode String to add to the bypass prefix. + * @return The full permission node for bypass. + */ + public Permission getBypassPermission(String finalNode) { + String bypassNode = this.getNode() + finalNode; + Logging.finer("Checking node " + bypassNode + "..."); + + Permission permission = Bukkit.getPluginManager().getPermission(bypassNode); + if (permission == null) { + permission = new Permission(bypassNode, PermissionDefault.FALSE); + switch (this) { + case BYPASS_GROUP: + permission.addParent(BYPASS_GROUP_ALL.getPermission(), true); + break; + case BYPASS_WORLD: + permission.addParent(BYPASS_WORLD_ALL.getPermission(), true); + break; + case BYPASS_GAME_MODE: + permission.addParent(BYPASS_GAME_MODE_ALL.getPermission(), true); + break; + default: + } + Bukkit.getPluginManager().addPermission(permission); + } + return permission; + } + + /** + * Checks if a player has permission to bypass something which requires a name of an object to be bypassed. + * A World name for example. + * + * @param player Player to check permission for. + * @param name Name of object to bypass. + * @return True if player is allowed to bypass. + */ + public boolean hasBypass(Player player, String name) { + if (inventories != null && !inventories.getMVIConfig().isUsingBypass()) { + return false; + } + Permission bypassPerm = this.getBypassPermission(name); + boolean hasBypass = player.hasPermission(bypassPerm); + if (hasBypass) { + Logging.fine("Player: " + player.getName() + " in World: " + player.getWorld().getName() + + " has permission: " + bypassPerm.getName() + "(Default: " + + bypassPerm.getDefault().toString() + ")!"); + } + return hasBypass; + } + + /** + * Checks if the sender has the node in question. + * + * @param sender CommandSender to check permission for. + * @return True if sender has the permission. + */ + public boolean has(CommandSender sender) { + return sender.hasPermission(perm); + } + + private static MultiverseInventories inventories = null; + + /** + * Registers all Permission to the plugin. + * + * @param plugin Plugin to register permissions to. + */ + public static void register(MultiverseInventories plugin) { + inventories = plugin; + BYPASS_WORLD_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); + BYPASS_GROUP_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); + PluginManager pm = plugin.getServer().getPluginManager(); + for (Perm perm : Perm.values()) { + if (perm.getPermission() != null) { + pm.addPermission(perm.getPermission()); + } + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java new file mode 100644 index 00000000..ec546d9e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains utility classes. + */ +package org.mvplugins.multiverse.inventories.util; + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0679898b..936ac5a2 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: Multiverse-Inventories -main: com.onarandombox.multiverseinventories.MultiverseInventories +main: org.mvplugins.multiverse.inventories.MultiverseInventories version: ${version} api-version: 1.13 authors: ['dumptruckman', 'benwoo1110'] diff --git a/src/test/java/com/onarandombox/multiverseinventories/FlatFileDataHelper.java b/src/test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java similarity index 72% rename from src/test/java/com/onarandombox/multiverseinventories/FlatFileDataHelper.java rename to src/test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java index 85f1ba43..07f8e31c 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/FlatFileDataHelper.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java @@ -1,23 +1,23 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; - -import java.io.File; -import java.io.IOException; - -public class FlatFileDataHelper { - - private final FlatFileProfileDataSource data; - - public FlatFileDataHelper(ProfileDataSource data) { - if (!(data instanceof FlatFileProfileDataSource)) { - throw new ClassCastException("Must be instance of FlatFilePlayerData"); - } - this.data = (FlatFileProfileDataSource) data; - } - - public File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - return data.getPlayerFile(type, dataName, playerName); - } -} +package org.mvplugins.multiverse.inventories; + +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; + +import java.io.File; +import java.io.IOException; + +public class FlatFileDataHelper { + + private final FlatFileProfileDataSource data; + + public FlatFileDataHelper(ProfileDataSource data) { + if (!(data instanceof FlatFileProfileDataSource)) { + throw new ClassCastException("Must be instance of FlatFilePlayerData"); + } + this.data = (FlatFileProfileDataSource) data; + } + + public File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { + return data.getPlayerFile(type, dataName, playerName); + } +} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestCommands.java b/src/test/java/org/mvplugins/multiverse/inventories/TestCommands.java similarity index 97% rename from src/test/java/com/onarandombox/multiverseinventories/TestCommands.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestCommands.java index 5be18d5e..1218cb3f 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestCommands.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestCommands.java @@ -5,10 +5,10 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestCommentedYamlConfiguration.java b/src/test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java similarity index 97% rename from src/test/java/com/onarandombox/multiverseinventories/TestCommentedYamlConfiguration.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java index 04b489f5..5a10dc48 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestCommentedYamlConfiguration.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; +import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; import org.junit.Test; import java.io.File; diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestPerformance.java b/src/test/java/org/mvplugins/multiverse/inventories/TestPerformance.java similarity index 97% rename from src/test/java/com/onarandombox/multiverseinventories/TestPerformance.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestPerformance.java index 60afbc57..3612b514 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestPerformance.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestPerformance.java @@ -1,10 +1,10 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestPlayerNameChange.java b/src/test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java similarity index 96% rename from src/test/java/com/onarandombox/multiverseinventories/TestPlayerNameChange.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java index f33bdf31..1c16ff52 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestPlayerNameChange.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java @@ -1,8 +1,8 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; -import com.onarandombox.multiverseinventories.profile.GlobalProfile; -import com.onarandombox.multiverseinventories.util.MockPlayerFactory; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.util.MockPlayerFactory; +import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestResetWorld.java b/src/test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java similarity index 97% rename from src/test/java/com/onarandombox/multiverseinventories/TestResetWorld.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java index a55e38ed..2beb4a78 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestResetWorld.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java @@ -1,7 +1,7 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.onarandombox.MultiverseAdventure.event.MVAResetFinishedEvent; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; +import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestWSharableAPI.java b/src/test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java similarity index 95% rename from src/test/java/com/onarandombox/multiverseinventories/TestWSharableAPI.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java index ac44ae38..3dbd0338 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestWSharableAPI.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java @@ -1,11 +1,11 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.ProfileEntry; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.SharableHandler; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; +package org.mvplugins.multiverse.inventories; + +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.ProfileEntry; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.SharableHandler; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.command.Command; diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestWorldChanged.java b/src/test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java similarity index 98% rename from src/test/java/com/onarandombox/multiverseinventories/TestWorldChanged.java rename to src/test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java index 4c17bbf6..026ea1c4 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/TestWorldChanged.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java @@ -1,10 +1,10 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MVTestLogFormatter.java b/src/test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java similarity index 96% rename from src/test/java/com/onarandombox/multiverseinventories/util/MVTestLogFormatter.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java index b568f14d..7a234a98 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MVTestLogFormatter.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java @@ -5,7 +5,7 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockItemMeta.java b/src/test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java similarity index 98% rename from src/test/java/com/onarandombox/multiverseinventories/util/MockItemMeta.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java index b8e0b3ab..76c50097 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockItemMeta.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java @@ -4,7 +4,7 @@ * For more information please check the README.md file included * * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import org.bukkit.Material; import org.bukkit.inventory.ItemFactory; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerFactory.java b/src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java similarity index 98% rename from src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerFactory.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java index cd838ffe..a113d0ff 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerFactory.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java @@ -5,9 +5,9 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; -import com.onarandombox.multiverseinventories.PlayerStats; +import org.mvplugins.multiverse.inventories.PlayerStats; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.entity.Player; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerInventory.java b/src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java similarity index 98% rename from src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerInventory.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java index e178feab..55d06252 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerInventory.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java @@ -1,6 +1,6 @@ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; -import com.onarandombox.multiverseinventories.PlayerStats; +import org.mvplugins.multiverse.inventories.PlayerStats; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.HumanEntity; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockWorldFactory.java b/src/test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java similarity index 99% rename from src/test/java/com/onarandombox/multiverseinventories/util/MockWorldFactory.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java index 3dee7cbb..6356fe87 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockWorldFactory.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java @@ -5,7 +5,7 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import org.bukkit.Location; import org.bukkit.Material; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java b/src/test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java similarity index 99% rename from src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java index 7552c09b..3740c644 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java @@ -5,7 +5,7 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.api.MVWorldManager; @@ -15,8 +15,8 @@ import com.onarandombox.MultiverseCore.utils.FileUtils; import com.onarandombox.MultiverseCore.utils.TestingMode; import com.onarandombox.MultiverseCore.world.SimpleMVWorldManager; -import com.onarandombox.multiverseinventories.InventoriesListener; -import com.onarandombox.multiverseinventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.InventoriesListener; +import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/Util.java b/src/test/java/org/mvplugins/multiverse/inventories/util/Util.java similarity index 97% rename from src/test/java/com/onarandombox/multiverseinventories/util/Util.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/Util.java index 90f4fea0..e690c05e 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/Util.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/Util.java @@ -5,7 +5,7 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import com.dumptruckman.minecraft.util.Logging; diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/WorldCreatorMatcher.java b/src/test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java similarity index 97% rename from src/test/java/com/onarandombox/multiverseinventories/util/WorldCreatorMatcher.java rename to src/test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java index e60c1476..0b91bf3b 100644 --- a/src/test/java/com/onarandombox/multiverseinventories/util/WorldCreatorMatcher.java +++ b/src/test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java @@ -5,7 +5,7 @@ * with this project. * ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; import org.bukkit.WorldCreator; import org.mockito.ArgumentMatcher; From ded0d99076c89215636508ea61a0c29c2282c54b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:30:24 +0800 Subject: [PATCH 010/180] Use temp repo for mv5 to build --- build.gradle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d8ecd9c0..4994209a 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,11 @@ repositories { name = 'jitpack.io' url = uri('https://jitpack.io/') } + + maven { + name = 'benwoo1110' + url = uri('https://repo.c0ding.party/multiverse-beta') + } } dependencies { @@ -47,7 +52,7 @@ dependencies { // Core // TODO update to correct version once we have it published - implementation 'org.mvplugins.multiverse.core:multiverse-core:local' + implementation 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' // Config api 'com.dumptruckman.minecraft:JsonConfiguration:1.1' From db81d6af4a0c1ec0696dd267ad95005dccc0b8e7 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:32:17 +0800 Subject: [PATCH 011/180] Use pr test workflow from MV5 instead of main --- .github/workflows/pr.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.test.yml b/.github/workflows/pr.test.yml index b21190d5..6fa231e5 100644 --- a/.github/workflows/pr.test.yml +++ b/.github/workflows/pr.test.yml @@ -6,6 +6,6 @@ on: jobs: test: - uses: Multiverse/Multiverse-Core/.github/workflows/generic.test.yml@main + uses: Multiverse/Multiverse-Core/.github/workflows/generic.test.yml@MV5 # todo: Change back to main before release with: plugin_name: multiverse-inventories From d86eff6121fe30b5577caae443e91bd690140538 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:06:37 +0800 Subject: [PATCH 012/180] Update from MVVersionEvent to MVDumpsDebugInfoEvent --- .../multiverse/inventories/InventoriesListener.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index 1ad5f730..c318e8c6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -2,7 +2,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; -import org.mvplugins.multiverse.core.event.MVVersionEvent; +import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; @@ -67,12 +67,12 @@ public class InventoriesListener implements Listener { * @param event The MVVersionEvent that this plugin will listen for. */ @EventHandler - public void versionRequest(MVVersionEvent event) { - event.appendVersionInfo(this.inventories.getVersionInfo()); + public void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { + event.appendDebugInfo(this.inventories.getVersionInfo()); File configFile = new File(this.inventories.getDataFolder(), "config.yml"); File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); - event.putDetailedVersionInfo("multiverse-inventories/config.yml", configFile); - event.putDetailedVersionInfo("multiverse-inventories/groups.yml", groupsFile); + event.putDetailedDebugInfo("multiverse-inventories/config.yml", configFile); + event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); } /** From 275181e17d9e6bb5021d620e036b875d4e266e92 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 3 Jan 2025 22:21:00 +0800 Subject: [PATCH 013/180] Update async teleport for last_location --- .../mvplugins/multiverse/inventories/share/Sharables.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 8621d127..e04cb41c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.share; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.WorldGroup; import org.mvplugins.multiverse.inventories.DataStrings; @@ -40,6 +41,7 @@ public final class Sharables implements Shares { private static MultiverseInventories inventories = null; private static MVEconomist economist = null; + private static AsyncSafetyTeleporter safetyTeleporter = null; /** * Initialize this class with the instance of Inventories. @@ -53,6 +55,9 @@ public static void init(MultiverseInventories inventories) { if (Sharables.economist == null) { Sharables.economist = inventories.getServiceLocator().getService(MVEconomist.class); } + if (Sharables.safetyTeleporter == null) { + Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); + } } /** @@ -530,7 +535,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { if (loc == null) { return false; } - player.teleport(loc); + safetyTeleporter.teleport(player, loc); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_LAST_LOCATION), new LocationSerializer()) From 1a0616c896edbba6a1fdb1632bdca97be1fd78f7 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 3 Jan 2025 23:26:01 +0800 Subject: [PATCH 014/180] Fix respawn location not deemed as a bed location --- build.gradle | 2 +- .../multiverse/inventories/share/Sharables.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4994209a..192da1b3 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,7 @@ repositories { dependencies { // Spigot - implementation('org.bukkit:bukkit:1.14.4-R0.1-SNAPSHOT') { + implementation('org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT') { exclude group: 'junit', module: 'junit' } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index e04cb41c..4a118728 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.share; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.block.data.type.Bed; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.WorldGroup; @@ -490,7 +491,20 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public void updateProfile(PlayerProfile profile, Player player) { Location bedSpawnLocation = null; try { + Logging.finer("profile bed: " + player.getBedSpawnLocation()); bedSpawnLocation = player.getBedSpawnLocation(); + + var blockBedSpawnLocation = bedSpawnLocation.getBlock().getLocation(); + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + var newBedLoc = blockBedSpawnLocation.clone().add(x, 0, z); + Logging.finest("new bed: " + newBedLoc); + if (newBedLoc.getBlock().getBlockData() instanceof Bed) { + bedSpawnLocation = newBedLoc; + break; + } + } + } } catch (NullPointerException e) { // TODO this is a temporary fix for the bug occurring in 1.16.X CB/Spigot/Paper StackTraceElement[] stackTrace = e.getStackTrace(); @@ -513,6 +527,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { return false; } player.setBedSpawnLocation(loc, true); + Logging.finer("update bed: " + player.getBedSpawnLocation()); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_BED_SPAWN_LOCATION), new LocationSerializer()) From 9aae32ea8de7864dc07ef9e7239840001e9c91cb Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 4 Jan 2025 09:44:40 +0800 Subject: [PATCH 015/180] Refactor and check respawn location for null before finding bed block --- .../inventories/share/Sharables.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 4a118728..498bfb18 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -17,6 +17,7 @@ import org.bukkit.potion.PotionEffect; import org.mvplugins.multiverse.core.economy.MVEconomist; +import javax.annotation.Nullable; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -492,19 +493,7 @@ public void updateProfile(PlayerProfile profile, Player player) { Location bedSpawnLocation = null; try { Logging.finer("profile bed: " + player.getBedSpawnLocation()); - bedSpawnLocation = player.getBedSpawnLocation(); - - var blockBedSpawnLocation = bedSpawnLocation.getBlock().getLocation(); - for(int x = -1; x <= 1; x++) { - for(int z = -1; z <= 1; z++) { - var newBedLoc = blockBedSpawnLocation.clone().add(x, 0, z); - Logging.finest("new bed: " + newBedLoc); - if (newBedLoc.getBlock().getBlockData() instanceof Bed) { - bedSpawnLocation = newBedLoc; - break; - } - } - } + bedSpawnLocation = findBedFromRespawnLocation(player.getBedSpawnLocation()); } catch (NullPointerException e) { // TODO this is a temporary fix for the bug occurring in 1.16.X CB/Spigot/Paper StackTraceElement[] stackTrace = e.getStackTrace(); @@ -527,12 +516,29 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { return false; } player.setBedSpawnLocation(loc, true); - Logging.finer("update bed: " + player.getBedSpawnLocation()); + Logging.finer("updating bed: " + player.getBedSpawnLocation()); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_BED_SPAWN_LOCATION), new LocationSerializer()) .altName("bedspawn").altName("bed").altName("beds").altName("bedspawns").build(); + private static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var blockBedSpawnLocation = respawnLocation.getBlock().getLocation(); + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + var newBedLoc = blockBedSpawnLocation.clone().add(x, 0, z); + Logging.finest("Finding bed at: " + newBedLoc); + if (newBedLoc.getBlock().getBlockData() instanceof Bed) { + return newBedLoc; + } + } + } + return respawnLocation; + } + /** * Sharing Last Location. */ From 9a644178d9283f06c1a3a92befab5cd0972ee11c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 10 Jan 2025 20:33:58 +0800 Subject: [PATCH 016/180] Update to use new AsyncSafetyTeleporter api --- .../org/mvplugins/multiverse/inventories/share/Sharables.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 498bfb18..08bb796d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -556,7 +556,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { if (loc == null) { return false; } - safetyTeleporter.teleport(player, loc); + safetyTeleporter.to(loc).checkSafety(false).teleport(player); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_LAST_LOCATION), new LocationSerializer()) From 546a6d74294ec118a02bdf76d3c3d6a93a65180e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:51:47 +0800 Subject: [PATCH 017/180] Attempt to use PlayerSpawnChangeEvent --- build.gradle | 12 ++- .../inventories/InventoriesListener.java | 6 ++ .../inventories/MultiverseInventories.java | 15 ++++ .../inventories/ShareHandlingUpdater.java | 6 +- .../inventories/SpawnChangeListener.java | 88 +++++++++++++++++++ .../inventories/share/Sharables.java | 17 +++- 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java diff --git a/build.gradle b/build.gradle index 192da1b3..27a8f644 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,15 @@ repositories { url = uri('https://repo.onarandombox.com/content/groups/public') } + maven { + name = 'spigot' + url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' + content { + includeGroup 'org.bukkit' + includeGroup 'org.spigotmc' + } + } + maven { name ='papermc' url = uri('https://papermc.io/repo/repository/maven-public/') @@ -46,7 +55,7 @@ repositories { dependencies { // Spigot - implementation('org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT') { + implementation('org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT') { exclude group: 'junit', module: 'junit' } @@ -165,7 +174,6 @@ shadowJar { build.dependsOn shadowJar jar.enabled = false - tasks.register('runHabitatGenerator', JavaExec) { classpath = configurations["compileClasspath"] mainClass.set('org.mvplugins.multiverse.external.jvnet.hk2.generator.HabitatGenerator') diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index c318e8c6..a860f26a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -2,6 +2,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; +import org.mvplugins.multiverse.core.event.MVDebugModeEvent; import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; @@ -75,6 +76,11 @@ public void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); } + @EventHandler + public void onDebugModeChange(MVDebugModeEvent event) { + Logging.setDebugLevel(event.getLevel()); + } + /** * Hooks Multiverse-Inventories into the Multiverse reload command. * diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 2049b264..7c4512b6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -73,6 +73,8 @@ public static MultiverseInventories getPlugin() { private File serverFolder = new File(System.getProperty("user.dir")); + private boolean usingSpawnChangeEvent = false; + { inventoriesPlugin = this; } @@ -163,6 +165,15 @@ private void onMVPluginEnable() { // Register Events Bukkit.getPluginManager().registerEvents(inventoriesListener.get(), this); + try { + Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); + Bukkit.getPluginManager().registerEvents(new SpawnChangeListener(this), this); + usingSpawnChangeEvent = true; + Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); + } catch (ClassNotFoundException e) { + Logging.fine("PlayerSpawnChangeEvent will not be used!"); + usingSpawnChangeEvent = false; + } if (getCore().getProtocolVersion() >= 24) { new CoreDebugListener(this); @@ -436,5 +447,9 @@ public void setServerFolder(File newServerFolder) { } this.serverFolder = newServerFolder; } + + public boolean isUsingSpawnChangeEvent() { + return usingSpawnChangeEvent; + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java index 128c9016..2ef9683e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java @@ -5,11 +5,12 @@ import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; -import org.apache.commons.lang.StringUtils; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; class ShareHandlingUpdater { @@ -47,7 +48,8 @@ private void updateProfile() { } } if (saved.size() > 0) { - Logging.finer("Persisted: " + StringUtils.join(saved, ", ") + " to " + Logging.finer("Persisted: " + + saved.stream().map(Objects::toString).collect(Collectors.joining(", ")) + " to " + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() + " (" + profile.getProfile().getProfileType() + ")" + " for player " + profile.getProfile().getPlayer().getName()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java new file mode 100644 index 00000000..d68fe2b2 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java @@ -0,0 +1,88 @@ +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Location; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerSpawnChangeEvent; +import org.bukkit.event.player.PlayerSpawnChangeEvent.Cause; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import javax.annotation.Nullable; + +public class SpawnChangeListener implements Listener { + + private final MultiverseInventories inventories; + + public SpawnChangeListener(MultiverseInventories inventories) { + this.inventories = inventories; + } + + @EventHandler(priority = EventPriority.MONITOR) + void onSpawnChange(PlayerSpawnChangeEvent event) { + Player player = event.getPlayer(); + PlayerProfile playerData = inventories.getWorldProfileContainerStore() + .getContainer(player.getWorld().getName()) + .getPlayerData(player); + + Logging.fine("Respawn cause: %s", event.getCause()); + + if (event.getCause() == Cause.BED) { + playerData.set(Sharables.BED_SPAWN, findBedFromRespawnLocation(event.getNewSpawn())); + return; + } + if (event.getCause() == Cause.RESPAWN_ANCHOR) { + playerData.set(Sharables.BED_SPAWN, findAnchorFromRespawnLocation(event.getNewSpawn())); + return; + } + playerData.set(Sharables.BED_SPAWN, event.getNewSpawn()); + inventories.getData().updatePlayerData(playerData); + } + + public static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var bedSpawnBlock = respawnLocation.getBlock(); + for(int x = -2; x <= 2; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -2; z <= 2; z++) { + var newBedBlock = bedSpawnBlock.getRelative(x, y, z); + Logging.finest("Finding bed at: " + newBedBlock); + if (newBedBlock.getBlockData() instanceof Bed) { + Logging.finer("Found bed!"); + return newBedBlock.getLocation(); + } + } + } + } + Logging.warning("Unable to anchor, respawn may not work as expected!"); + return respawnLocation; + } + + public static @Nullable Location findAnchorFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var bedSpawnBlock = respawnLocation.getBlock(); + for(int x = -2; x <= 2; x++) { + for (int y = -2; y <= 2; y++) { + for (int z = -2; z <= 2; z++) { + var newBedBlock = bedSpawnBlock.getRelative(x, y, z); + Logging.finest("Finding anchor at: " + newBedBlock); + if (newBedBlock.getBlockData() instanceof RespawnAnchor) { + Logging.finer("Found anchor!"); + return newBedBlock.getLocation(); + } + } + } + } + Logging.warning("Unable to anchor, respawn may not work as expected!"); + return respawnLocation; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 08bb796d..5eb0294c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1,6 +1,8 @@ package org.mvplugins.multiverse.inventories.share; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.block.data.type.Bed; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.inventories.MultiverseInventories; @@ -44,6 +46,7 @@ public final class Sharables implements Shares { private static MultiverseInventories inventories = null; private static MVEconomist economist = null; private static AsyncSafetyTeleporter safetyTeleporter = null; + private static Attribute maxHealthAttr = null; /** * Initialize this class with the instance of Inventories. @@ -60,6 +63,7 @@ public static void init(MultiverseInventories inventories) { if (Sharables.safetyTeleporter == null) { Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); } + Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max-health")); } /** @@ -172,8 +176,8 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public void updateProfile(PlayerProfile profile, Player player) { double health = player.getHealth(); // Player is dead, so health should be regained to full. - if (health <= 0) { - health = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); + if (health <= 0 && maxHealthAttr != null) { + health = player.getAttribute(maxHealthAttr).getValue(); } profile.set(HEALTH, health); } @@ -189,7 +193,9 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { player.setHealth(value); } catch (IllegalArgumentException e) { Logging.fine("Invalid value '" + value + "': " + e.getMessage()); - player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + if (maxHealthAttr != null) { + player.setHealth(player.getAttribute(maxHealthAttr).getValue()); + } return false; } return true; @@ -490,6 +496,10 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { new SharableHandler() { @Override public void updateProfile(PlayerProfile profile, Player player) { + if (inventories.isUsingSpawnChangeEvent()) { + // Bed spawn location already updated during PlayerSpawnChangeEvent + return; + } Location bedSpawnLocation = null; try { Logging.finer("profile bed: " + player.getBedSpawnLocation()); @@ -512,6 +522,7 @@ public void updateProfile(PlayerProfile profile, Player player) { public boolean updatePlayer(Player player, PlayerProfile profile) { Location loc = profile.get(BED_SPAWN); if (loc == null) { + Logging.finer("No bed location saved"); player.setBedSpawnLocation(player.getWorld().getSpawnLocation()); return false; } From aacd50450b351097b9c0c08edd9e9b9ca61aa583 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:08:41 +0800 Subject: [PATCH 018/180] Properly update spawn change for group --- .../inventories/SpawnChangeListener.java | 26 ++++++++++++++----- .../inventories/share/Sharables.java | 22 ++-------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java index d68fe2b2..5f0397f0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java @@ -14,6 +14,7 @@ import org.mvplugins.multiverse.inventories.share.Sharables; import javax.annotation.Nullable; +import java.util.List; public class SpawnChangeListener implements Listener { @@ -26,22 +27,33 @@ public SpawnChangeListener(MultiverseInventories inventories) { @EventHandler(priority = EventPriority.MONITOR) void onSpawnChange(PlayerSpawnChangeEvent event) { Player player = event.getPlayer(); - PlayerProfile playerData = inventories.getWorldProfileContainerStore() - .getContainer(player.getWorld().getName()) - .getPlayerData(player); Logging.fine("Respawn cause: %s", event.getCause()); if (event.getCause() == Cause.BED) { - playerData.set(Sharables.BED_SPAWN, findBedFromRespawnLocation(event.getNewSpawn())); + updatePlayerSpawn(player, findBedFromRespawnLocation(event.getNewSpawn())); return; } if (event.getCause() == Cause.RESPAWN_ANCHOR) { - playerData.set(Sharables.BED_SPAWN, findAnchorFromRespawnLocation(event.getNewSpawn())); + updatePlayerSpawn(player, findAnchorFromRespawnLocation(event.getNewSpawn())); return; } - playerData.set(Sharables.BED_SPAWN, event.getNewSpawn()); - inventories.getData().updatePlayerData(playerData); + updatePlayerSpawn(player, event.getNewSpawn()); + } + + private void updatePlayerSpawn(Player player, Location location) { + PlayerProfile playerProfile = inventories.getWorldProfileContainerStore() + .getContainer(player.getWorld().getName()) + .getPlayerData(player); + playerProfile.set(Sharables.BED_SPAWN, location); + + List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(player.getWorld().getName()); + for (WorldGroup fromGroup : fromGroups) { + playerProfile = inventories.getGroupProfileContainerStore() + .getContainer(fromGroup.getName()) + .getPlayerData(player); + playerProfile.set(Sharables.BED_SPAWN, location); + } } public static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 5eb0294c..4e9ede08 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -3,9 +3,9 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.NamespacedKey; import org.bukkit.Registry; -import org.bukkit.block.data.type.Bed; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.SpawnChangeListener; import org.mvplugins.multiverse.inventories.WorldGroup; import org.mvplugins.multiverse.inventories.DataStrings; import org.mvplugins.multiverse.inventories.PlayerStats; @@ -19,7 +19,6 @@ import org.bukkit.potion.PotionEffect; import org.mvplugins.multiverse.core.economy.MVEconomist; -import javax.annotation.Nullable; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -503,7 +502,7 @@ public void updateProfile(PlayerProfile profile, Player player) { Location bedSpawnLocation = null; try { Logging.finer("profile bed: " + player.getBedSpawnLocation()); - bedSpawnLocation = findBedFromRespawnLocation(player.getBedSpawnLocation()); + bedSpawnLocation = SpawnChangeListener.findBedFromRespawnLocation(player.getBedSpawnLocation()); } catch (NullPointerException e) { // TODO this is a temporary fix for the bug occurring in 1.16.X CB/Spigot/Paper StackTraceElement[] stackTrace = e.getStackTrace(); @@ -533,23 +532,6 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { }).serializer(new ProfileEntry(false, DataStrings.PLAYER_BED_SPAWN_LOCATION), new LocationSerializer()) .altName("bedspawn").altName("bed").altName("beds").altName("bedspawns").build(); - private static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { - if (respawnLocation == null) { - return null; - } - var blockBedSpawnLocation = respawnLocation.getBlock().getLocation(); - for(int x = -1; x <= 1; x++) { - for(int z = -1; z <= 1; z++) { - var newBedLoc = blockBedSpawnLocation.clone().add(x, 0, z); - Logging.finest("Finding bed at: " + newBedLoc); - if (newBedLoc.getBlock().getBlockData() instanceof Bed) { - return newBedLoc; - } - } - } - return respawnLocation; - } - /** * Sharing Last Location. */ From 4d1a0f6941ac5bc00555740482e5a48e93ff2590 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Jan 2025 22:53:19 +0800 Subject: [PATCH 019/180] Force setBedSpawnLocation --- .../org/mvplugins/multiverse/inventories/share/Sharables.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 4e9ede08..4cbba286 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -522,7 +522,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { Location loc = profile.get(BED_SPAWN); if (loc == null) { Logging.finer("No bed location saved"); - player.setBedSpawnLocation(player.getWorld().getSpawnLocation()); + player.setBedSpawnLocation(player.getWorld().getSpawnLocation(), true); return false; } player.setBedSpawnLocation(loc, true); From 9fa16f114893058e0b09ec442bafc2c1b869158a Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:38:15 +0800 Subject: [PATCH 020/180] Dont set if group is not sharing bed_spawn --- .../mvplugins/multiverse/inventories/SpawnChangeListener.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java index 5f0397f0..86b26649 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java @@ -49,6 +49,9 @@ private void updatePlayerSpawn(Player player, Location location) { List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(player.getWorld().getName()); for (WorldGroup fromGroup : fromGroups) { + if (!fromGroup.isSharing(Sharables.BED_SPAWN)) { + continue; + } playerProfile = inventories.getGroupProfileContainerStore() .getContainer(fromGroup.getName()) .getPlayerData(player); From c35d6cc8e277e68eed57cecae8623b598c758e5c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 18 Jan 2025 16:43:17 +0800 Subject: [PATCH 021/180] Update to align with new core api --- build.gradle | 4 +--- .../inventories/AbstractWorldGroupManager.java | 2 +- .../multiverse/inventories/CoreDebugListener.java | 2 +- .../multiverse/inventories/DataStrings.java | 5 +---- .../inventories/FlatFileProfileDataSource.java | 14 ++++++-------- .../multiverse/inventories/InventoriesConfig.java | 2 +- .../inventories/InventoriesListener.java | 10 +++++----- .../inventories/MultiverseInventories.java | 6 +++--- .../MultiverseInventoriesPluginBinder.java | 2 +- .../inventories/commands/InventoriesCommand.java | 2 +- 10 files changed, 21 insertions(+), 28 deletions(-) diff --git a/build.gradle b/build.gradle index 27a8f644..77728232 100644 --- a/build.gradle +++ b/build.gradle @@ -65,9 +65,7 @@ dependencies { // Config api 'com.dumptruckman.minecraft:JsonConfiguration:1.1' - api ('com.googlecode.json-simple:json-simple:1.1.1') { - exclude group: 'junit', module: 'junit' - } + api 'net.minidev:json-smart:2.5.1' // Utils api 'io.papermc:paperlib:1.0.7' diff --git a/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java index 545d011e..53e18014 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.core.api.world.WorldManager; import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.GroupingConflict; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -9,7 +10,6 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; -import org.mvplugins.multiverse.core.world.WorldManager; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java index 9735c2b8..e862613c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java @@ -1,9 +1,9 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.core.event.MVDebugModeEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.mvplugins.multiverse.core.api.event.MVDebugModeEvent; public class CoreDebugListener implements Listener { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java index 0c5bfb5f..2952a9a3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java @@ -167,10 +167,7 @@ public static Location parseLocation(String locString) { JSONObject jsonLoc; try { jsonLoc = (JSONObject) JSON_PARSER.parse(locString); - } catch (ParseException e) { - Logging.warning("Could not parse location! " + e.getMessage()); - return null; - } catch (ClassCastException e) { + } catch (ParseException | ClassCastException e) { Logging.warning("Could not parse location! " + e.getMessage()); return null; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java index 2902a076..9b8bc87e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java @@ -4,6 +4,8 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.ProfileKey; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; @@ -18,7 +20,6 @@ import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; -import org.json.simple.parser.JSONParser; import java.io.File; import java.io.IOException; @@ -38,7 +39,7 @@ class FlatFileProfileDataSource implements ProfileDataSource { private static final String JSON = ".json"; - private final JSONParser JSON_PARSER = new JSONParser(); + private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE); private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); @@ -337,13 +338,10 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile if (stats.isEmpty()) { return; } - org.json.simple.JSONObject jsonStats = null; + JSONObject jsonStats = null; try { - jsonStats = (org.json.simple.JSONObject) JSON_PARSER.parse(stats); - } catch (org.json.simple.parser.ParseException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } catch (ClassCastException e) { + jsonStats = (JSONObject) JSON_PARSER.parse(stats); + } catch (ParseException | ClassCastException e) { Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java index 2db1a360..f7b18720 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java @@ -1,13 +1,13 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.core.api.config.MVCoreConfig; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; import io.papermc.lib.PaperLib; import org.bukkit.configuration.file.FileConfiguration; -import org.mvplugins.multiverse.core.config.MVCoreConfig; import java.io.File; import java.io.IOException; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index a860f26a..b43672cb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -1,9 +1,11 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; -import org.mvplugins.multiverse.core.event.MVDebugModeEvent; -import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; +import org.mvplugins.multiverse.core.api.event.MVConfigReloadEvent; +import org.mvplugins.multiverse.core.api.event.MVDebugModeEvent; +import org.mvplugins.multiverse.core.api.event.MVDumpsDebugInfoEvent; +import org.mvplugins.multiverse.core.api.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.api.world.WorldManager; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; @@ -32,8 +34,6 @@ import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.inventory.InventoryHolder; -import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; -import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 7c4512b6..5cb5d99b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -6,9 +6,10 @@ import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.core.api.config.MVCoreConfig; +import org.mvplugins.multiverse.core.submodules.MVCore; +import org.mvplugins.multiverse.core.submodules.MVPlugin; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; -import org.mvplugins.multiverse.core.api.MVCore; -import org.mvplugins.multiverse.core.api.MVPlugin; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Messaging; @@ -28,7 +29,6 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPluginLoader; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jakarta.inject.Provider; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java index 1fc4dbaf..9f00d4e1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories; -import org.mvplugins.multiverse.core.api.MVPlugin; import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder; +import org.mvplugins.multiverse.core.submodules.MVPlugin; import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java index bc4c9ec3..19aee515 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -11,6 +11,6 @@ @Contract public abstract class InventoriesCommand extends MultiverseCommand { protected InventoriesCommand(@NotNull MVCommandManager commandManager) { - super(commandManager); + super(commandManager, "mvinv"); } } From 4f7dc758427f79812bd2cd0043973e207da3c423 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 18 Jan 2025 16:46:01 +0800 Subject: [PATCH 022/180] Set continueOnSerializationError for json configuration --- build.gradle | 2 +- .../multiverse/inventories/FlatFileProfileDataSource.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 77728232..399b3f4c 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ dependencies { implementation 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' // Config - api 'com.dumptruckman.minecraft:JsonConfiguration:1.1' + api 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' api 'net.minidev:json-smart:2.5.1' // Utils diff --git a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java index 9b8bc87e..33f9076f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java @@ -6,6 +6,7 @@ import com.google.common.cache.CacheBuilder; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; +import org.bukkit.configuration.InvalidConfigurationException; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.ProfileKey; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; @@ -93,8 +94,11 @@ private FileConfiguration waitForConfigHandle(File file) { } } - private static FileConfiguration getConfigHandleNow(File file) { - return JsonConfiguration.loadConfiguration(file); + private static FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(true); + jsonConfiguration.load(file); + return jsonConfiguration; } private static class ConfigLoader implements Callable { From e41641ccf1d143f615ae35c6b0be9258bef443d2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:04:46 +0800 Subject: [PATCH 023/180] Add ACCEPT_TAILLING_SPACE flag to JSONParser --- .../java/org/mvplugins/multiverse/inventories/DataStrings.java | 2 +- .../multiverse/inventories/FlatFileProfileDataSource.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java index 2952a9a3..5c0fe74c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java @@ -294,6 +294,6 @@ public static PotionEffect[] parsePotionEffects(String potionsString) { return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); } - private static final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE); + private static final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java index 33f9076f..85ea8b61 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java @@ -40,7 +40,7 @@ class FlatFileProfileDataSource implements ProfileDataSource { private static final String JSON = ".json"; - private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE); + private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); From c096041f0f911fad8026530222ef62806a703130 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:19:48 +0800 Subject: [PATCH 024/180] Implement helper class SingleShareWriter --- .../inventories/SingleShareWriter.java | 43 +++++++++++++++++++ .../inventories/SpawnChangeListener.java | 16 +------ 2 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java new file mode 100644 index 00000000..4acbba00 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java @@ -0,0 +1,43 @@ +package org.mvplugins.multiverse.inventories; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.share.Sharable; + +/** + * Write a single share to the relevant world and group profiles. + * + * @param The sharable type. + */ +public class SingleShareWriter { + + public static SingleShareWriter of(MultiverseInventories inventories, Player player, Sharable sharable) { + return new SingleShareWriter(inventories, player, sharable); + } + + private final MultiverseInventories inventories; + private final Player player; + private final Sharable sharable; + + private SingleShareWriter(MultiverseInventories inventories, Player player, Sharable sharable) { + this.inventories = inventories; + this.player = player; + this.sharable = sharable; + } + + public void write(T value) { + if (sharable.isOptional() && !this.inventories.getMVIConfig().getOptionalShares().contains(sharable)) { + return; + } + + String worldName = this.player.getWorld().getName(); + this.inventories.getWorldProfileContainerStore() + .getContainer(worldName) + .getPlayerData(this.player) + .set(this.sharable, value); + + this.inventories.getGroupManager().getGroupsForWorld(worldName).forEach(worldGroup -> { + worldGroup.getGroupProfileContainer().getPlayerData(this.player) + .set(this.sharable, value); + }); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java index 86b26649..64cf3eb5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java @@ -42,21 +42,7 @@ void onSpawnChange(PlayerSpawnChangeEvent event) { } private void updatePlayerSpawn(Player player, Location location) { - PlayerProfile playerProfile = inventories.getWorldProfileContainerStore() - .getContainer(player.getWorld().getName()) - .getPlayerData(player); - playerProfile.set(Sharables.BED_SPAWN, location); - - List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(player.getWorld().getName()); - for (WorldGroup fromGroup : fromGroups) { - if (!fromGroup.isSharing(Sharables.BED_SPAWN)) { - continue; - } - playerProfile = inventories.getGroupProfileContainerStore() - .getContainer(fromGroup.getName()) - .getPlayerData(player); - playerProfile.set(Sharables.BED_SPAWN, location); - } + SingleShareWriter.of(this.inventories, player, Sharables.BED_SPAWN).write(location); } public static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { From f764e31376170f87c22b4fd9ae22728125d25b40 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:20:04 +0800 Subject: [PATCH 025/180] Write last location when player quits --- .../inventories/InventoriesListener.java | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index b43672cb..017fe18b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -187,6 +187,7 @@ public void playerQuit(final PlayerQuitEvent event) { inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); inventories.getData().setLoadOnLogin(player.getName(), true); } + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); } private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { @@ -256,26 +257,7 @@ public void playerTeleport(PlayerTeleportEvent event) { } Player player = event.getPlayer(); - - String fromWorldName = event.getFrom().getWorld().getName(); - String toWorldName = event.getTo().getWorld().getName(); - - ProfileContainer fromWorldProfileContainer = this.inventories.getWorldProfileContainerStore().getContainer(fromWorldName); - PlayerProfile playerProfile = fromWorldProfileContainer.getPlayerData(player); - playerProfile.set(Sharables.LAST_LOCATION, event.getFrom()); - - List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(fromWorldName); - for (WorldGroup fromGroup : fromGroups) { - playerProfile = fromGroup.getGroupProfileContainer().getPlayerData(event.getPlayer()); - - if (fromGroup.containsWorld(toWorldName)) { - if (!fromGroup.isSharing(Sharables.LAST_LOCATION)) { - playerProfile.set(Sharables.LAST_LOCATION, event.getFrom()); - } - } else { - playerProfile.set(Sharables.LAST_LOCATION, event.getFrom()); - } - } + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(event.getFrom()); // Possibly prevents item duping exploit player.closeInventory(); From ea8ebd8957d3ae73216a8499890759d7bb949918 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:24:39 +0800 Subject: [PATCH 026/180] Debug log in SingleShareWriter --- .../mvplugins/multiverse/inventories/SingleShareWriter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java index 4acbba00..b83de310 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories; +import com.dumptruckman.minecraft.util.Logging; import org.bukkit.entity.Player; import org.mvplugins.multiverse.inventories.share.Sharable; @@ -26,9 +27,10 @@ private SingleShareWriter(MultiverseInventories inventories, Player player, Shar public void write(T value) { if (sharable.isOptional() && !this.inventories.getMVIConfig().getOptionalShares().contains(sharable)) { + Logging.finer("Skipping write for optional share: " + sharable); return; } - + Logging.finer("Writing single share: " + sharable.getNames()[0]); String worldName = this.player.getWorld().getName(); this.inventories.getWorldProfileContainerStore() .getContainer(worldName) From f7a82c9e38c3861dc0870f01cb155b18a6ef391c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:13:28 +0800 Subject: [PATCH 027/180] Rework hk2 to support mockbukkit tests --- build.gradle | 74 ++++++++++++----- .../inventories/InventoriesListener.java | 2 +- .../inventories/MultiverseInventories.java | 2 +- .../MultiverseInventoriesPluginBinder.java | 3 +- .../inventories/commands/GroupCommand.java | 2 +- .../inventories/commands/InfoCommand.java | 2 +- .../inventories/commands/ListCommand.java | 2 +- .../inventories/commands/ReloadCommand.java | 2 +- .../inventories/commands/ToggleCommand.java | 2 +- .../inventories/FlatFileDataHelper.java | 0 .../multiverse/inventories/TestCommands.java | 0 .../TestCommentedYamlConfiguration.java | 0 .../inventories/TestPerformance.java | 0 .../inventories/TestPlayerNameChange.java | 0 .../inventories/TestResetWorld.java | 0 .../inventories/TestWSharableAPI.java | 0 .../inventories/TestWorldChanged.java | 0 .../inventories/util/MVTestLogFormatter.java | 0 .../inventories/util/MockItemMeta.java | 0 .../inventories/util/MockPlayerFactory.java | 0 .../inventories/util/MockPlayerInventory.java | 0 .../inventories/util/MockWorldFactory.java | 0 .../inventories/util/TestInstanceCreator.java | 0 .../multiverse/inventories/util/Util.java | 0 .../inventories/util/WorldCreatorMatcher.java | 0 .../org.mockito.plugins.MockMaker | 0 .../multiverse/inventories/MockBukkitTest.kt | 12 +++ .../inventories/TestWithMockBukkit.kt | 79 +++++++++++++++++++ .../inventories/mock/MVServerMock.java | 53 +++++++++++++ .../inventories/mock/MVWorldMock.java | 28 +++++++ 30 files changed, 237 insertions(+), 26 deletions(-) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestCommands.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestPerformance.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestResetWorld.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/Util.java (100%) rename src/{test => old-test}/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java (100%) rename src/{test => old-test}/resources/mockito-extensions/org.mockito.plugins.MockMaker (100%) create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java diff --git a/build.gradle b/build.gradle index 399b3f4c..e3096158 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,11 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id 'java-library' id 'maven-publish' id 'checkstyle' id 'com.gradleup.shadow' version '8.3.5' + id "org.jetbrains.kotlin.jvm" version "2.0.21" } version = System.getenv('GITHUB_VERSION') ?: 'local' @@ -14,9 +17,16 @@ compileJava { targetCompatibility = JavaVersion.VERSION_17 } -// todo: Enable test when convert them to use mockbukkit like mv-core compileTestJava { - enabled = false + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +compileTestKotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + javaParameters.set(true) + } } repositories { @@ -55,7 +65,7 @@ repositories { dependencies { // Spigot - implementation('org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT') { + compileOnly('org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT') { exclude group: 'junit', module: 'junit' } @@ -63,6 +73,11 @@ dependencies { // TODO update to correct version once we have it published implementation 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' + // hk2 for annotation processing only + compileOnly('org.glassfish.hk2:hk2-api:3.0.3') { + exclude group: '*', module: '*' + } + // Config api 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' api 'net.minidev:json-smart:2.5.1' @@ -82,9 +97,15 @@ dependencies { } // Tests - testImplementation 'com.github.MilkBowl:VaultAPI:1.7.1' - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:3.11.2' + testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21' + testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.24.1' + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'com.natpryce:hamkrest:1.8.0.1' + testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0' + + // Annotation Processors + annotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' + testAnnotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' } @@ -97,6 +118,12 @@ tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } +tasks.withType(JavaCompile) { + configure(options) { + options.compilerArgs << '-Aorg.glassfish.hk2.metadata.location=META-INF/hk2-locator/Multiverse-Inventories' + } +} + tasks.withType(Javadoc).configureEach { options.encoding = 'UTF-8' } @@ -109,6 +136,7 @@ configurations { } } + publishing { publications { maven(MavenPublication) { @@ -128,6 +156,15 @@ publishing { } +compileKotlin { + // We're not using Kotlin in the plugin itself, just tests! + enabled = false +} +configurations.findAll { !it.name.startsWith('test') }.each { + it.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' +} + + processResources { def props = [version: "${project.version}"] inputs.properties props @@ -162,23 +199,24 @@ shadowJar { relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' relocate 'io.papermc.lib', 'org.mvplugins.multiverse.inventories.utils.paperlib' - relocate 'net.minidev.json', 'org.mvplugins.multiverse.inventories.utils.json' + relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' configurations = [project.configurations.api] archiveClassifier.set('') + + dependencies { + exclude(dependency { + it.moduleGroup == 'org.jetbrains.kotlin' + }) + exclude(dependency { + it.moduleGroup == 'org.jetbrains' + }) + exclude(dependency { + it.moduleGroup == 'org.ow2.asm' + }) + } } build.dependsOn shadowJar jar.enabled = false - -tasks.register('runHabitatGenerator', JavaExec) { - classpath = configurations["compileClasspath"] - mainClass.set('org.mvplugins.multiverse.external.jvnet.hk2.generator.HabitatGenerator') - - args = [ - '--file', "build/libs/multiverse-inventories-$version" + ".jar", - '--locator', 'Multiverse-Inventories', - ] -} -tasks.named("build") { finalizedBy("runHabitatGenerator") } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index b43672cb..0368614a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.event.MVConfigReloadEvent; import org.mvplugins.multiverse.core.api.event.MVDebugModeEvent; import org.mvplugins.multiverse.core.api.event.MVDumpsDebugInfoEvent; @@ -36,7 +37,6 @@ import org.bukkit.inventory.InventoryHolder; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; import java.io.File; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 5cb5d99b..78901866 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -32,7 +32,7 @@ import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jakarta.inject.Provider; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.vavr.control.Try; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java index 9f00d4e1..69dfbed1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories; +import org.bukkit.plugin.Plugin; import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder; import org.mvplugins.multiverse.core.submodules.MVPlugin; import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; @@ -14,6 +15,6 @@ protected MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugi @Override protected ScopedBindingBuilder bindPluginClass (ScopedBindingBuilder bindingBuilder) { - return super.bindPluginClass(bindingBuilder).to(MVPlugin.class).to(MultiverseInventories.class); + return super.bindPluginClass(bindingBuilder).to(Plugin.class).to(MVPlugin.class).to(MultiverseInventories.class); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index bfc587a1..e3eb632a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -14,7 +14,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.jvnet.hk2.annotations.Service; @Service @CommandAlias("mvinv") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index b2945e23..8d548a38 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -18,7 +18,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.jvnet.hk2.annotations.Service; import java.util.List; import java.util.Set; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 0cb68940..3c7a5b47 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -11,7 +11,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.jvnet.hk2.annotations.Service; import java.util.Collection; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index f60e8fae..0d6b6483 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -10,7 +10,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.jvnet.hk2.annotations.Service; @Service @CommandAlias("mvinv") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index c83821ae..afd70136 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -16,7 +16,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.jvnet.hk2.annotations.Service; @Service @CommandAlias("mvinv") diff --git a/src/test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java b/src/old-test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestCommands.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommands.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestCommands.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestCommands.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestPerformance.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestPerformance.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestPerformance.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestPerformance.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/Util.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/Util.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/Util.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/Util.java diff --git a/src/test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java similarity index 100% rename from src/test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java rename to src/old-test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/old-test/resources/mockito-extensions/org.mockito.plugins.MockMaker similarity index 100% rename from src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to src/old-test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt new file mode 100644 index 00000000..b2bc5c84 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt @@ -0,0 +1,12 @@ +package org.mvplugins.multiverse.inventories + +import kotlin.test.Test +import kotlin.test.assertNotNull + +open class MockBukkitTest : TestWithMockBukkit() { + + @Test + fun `MockBukkit loads the plugin`() { + assertNotNull(multiverseInventories) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt new file mode 100644 index 00000000..329e60a9 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -0,0 +1,79 @@ +package org.mvplugins.multiverse.inventories + +import com.dumptruckman.minecraft.util.Logging +import org.bukkit.Location +import org.bukkit.configuration.MemorySection +import org.bukkit.configuration.file.YamlConfiguration +import org.mockbukkit.mockbukkit.MockBukkit +import org.mvplugins.multiverse.core.MultiverseCore +import org.mvplugins.multiverse.core.inject.PluginServiceLocator +import org.mvplugins.multiverse.core.utils.TestingMode +import org.mvplugins.multiverse.inventories.mock.MVServerMock +import kotlin.test.* + +/** + * Basic abstract test class that sets up MockBukkit and MultiverseCore. + */ +abstract class TestWithMockBukkit { + + protected lateinit var server: MVServerMock + protected lateinit var multiverseCore: MultiverseCore + protected lateinit var multiverseInventories: MultiverseInventories + protected lateinit var serviceLocator : PluginServiceLocator + + @BeforeTest + fun setUpMockBukkit() { + TestingMode.enable() + server = MockBukkit.mock(MVServerMock()) + multiverseCore = MockBukkit.load(MultiverseCore::class.java) + multiverseInventories = MockBukkit.load(MultiverseInventories::class.java) + Logging.setDebugLevel(3) + serviceLocator = multiverseInventories.serviceLocator + assertNotNull(server.commandMap) + } + + @AfterTest + fun tearDownMockBukkit() { + MockBukkit.unmock() + } + + fun getResourceAsText(path: String): String? = object {}.javaClass.getResource(path)?.readText() + + fun assertConfigEquals(expectedPath: String, actualPath: String) { + val actualString = multiverseInventories.dataFolder.toPath().resolve(actualPath).toFile().readText() + val expectedString = getResourceAsText(expectedPath) + assertNotNull(expectedString) + + val actualYaml = YamlConfiguration() + actualYaml.loadFromString(actualString) + val actualYamlKeys = HashSet(actualYaml.getKeys(true)) + + val expectedYaml = YamlConfiguration() + expectedYaml.loadFromString(expectedString) + val expectedYamlKeys = HashSet(expectedYaml.getKeys(true)) + + for (key in expectedYamlKeys) { + assertNotNull(actualYamlKeys.remove(key), "Key $key is missing in actual config") + val actualValue = actualYaml.get(key) + if (actualValue is MemorySection) { + continue + } + assertEquals(expectedYaml.get(key), actualYaml.get(key), "Value for $key is different.") + } + for (key in actualYamlKeys) { + assertNull(actualYaml.get(key), "Key $key is present in actual config when it should be empty.") + } + + assertEquals(0, actualYamlKeys.size, + "Actual config has more keys than expected config. The following keys are missing: $actualYamlKeys") + } + + fun assertLocationEquals(expected: Location?, actual: Location?) { + assertEquals(expected?.world, actual?.world, "Worlds don't match for location comparison ($expected, $actual)") + assertEquals(expected?.x, actual?.x, "X values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.y, actual?.y, "Y values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.z, actual?.z, "Z values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.yaw, actual?.yaw, "Yaw values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.pitch, actual?.pitch, "Pitch values don't match for location comparison ($expected, $actual)") + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java new file mode 100644 index 00000000..de3c872f --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.mock; + +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.jetbrains.annotations.NotNull; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockbukkit.mockbukkit.command.CommandMapMock; +import org.mockbukkit.mockbukkit.world.WorldMock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class MVServerMock extends ServerMock { + + private final File worldContainer; + + public MVServerMock() throws IOException { + super(); + this.worldContainer = Files.createTempDirectory("world-container").toFile(); + this.worldContainer.deleteOnExit(); + System.out.println("Created test world folder: " + this.worldContainer.getAbsolutePath()); + } + + // This is required for acf reflection to work + @Override + public @NotNull CommandMapMock getCommandMap() { + return super.getCommandMap(); + } + + @Override + public @NotNull File getWorldContainer() { + return this.worldContainer; + } + + @Override + public World createWorld(@NotNull WorldCreator creator) { + WorldMock world = new MVWorldMock(creator); + world.getWorldFolder().mkdirs(); + createFile(new File(world.getWorldFolder(), "uid.dat")); + createFile(new File(world.getWorldFolder(), "level.dat")); + addWorld(world); + return world; + } + + private void createFile(File file) { + try { + file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java new file mode 100644 index 00000000..2082645e --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.mock; + +import org.bukkit.WorldCreator; +import org.jetbrains.annotations.NotNull; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.world.WorldMock; + +import java.io.File; + +public class MVWorldMock extends WorldMock { + + private final File worldFolder; + + public MVWorldMock(@NotNull WorldCreator creator) { + super(creator); + this.worldFolder = new File(MockBukkit.getMock().getWorldContainer(), getName()); + } + + @Override + public @NotNull File getWorldFolder() { + return this.worldFolder; + } + + @Override + public String toString() { + return "MVWorldMock{'name': '" + this.getName() + "'}"; + } +} From 604a62d2f69ff1c86c188b62ce161ead22441f8f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 11:59:34 +0800 Subject: [PATCH 028/180] Refactor gradle to make it more organised --- build.gradle | 61 +++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/build.gradle b/build.gradle index e3096158..0cb8cc8d 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,11 @@ compileJava { targetCompatibility = JavaVersion.VERSION_17 } +compileKotlin { + // We're not using Kotlin in the plugin itself, just tests! + enabled = false +} + compileTestJava { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 @@ -73,10 +78,6 @@ dependencies { // TODO update to correct version once we have it published implementation 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' - // hk2 for annotation processing only - compileOnly('org.glassfish.hk2:hk2-api:3.0.3') { - exclude group: '*', module: '*' - } // Config api 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' @@ -103,7 +104,10 @@ dependencies { testImplementation 'com.natpryce:hamkrest:1.8.0.1' testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0' - // Annotation Processors + // hk2 for annotation processing only + compileOnly('org.glassfish.hk2:hk2-api:3.0.3') { + exclude group: '*', module: '*' + } annotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' testAnnotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' } @@ -128,7 +132,6 @@ tasks.withType(Javadoc).configureEach { options.encoding = 'UTF-8' } - configurations { [apiElements, runtimeElements].each { it.outgoing.artifacts.removeIf { it.buildDependencies.getDependencies(null).contains(jar) } @@ -136,35 +139,10 @@ configurations { } } - -publishing { - publications { - maven(MavenPublication) { - from components.java - } - } - repositories { - maven { - name = "GitHubPackages" - url = "https://maven.pkg.github.com/Multiverse/Multiverse-Inventories" - credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") - } - } - } -} - - -compileKotlin { - // We're not using Kotlin in the plugin itself, just tests! - enabled = false -} configurations.findAll { !it.name.startsWith('test') }.each { it.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' } - processResources { def props = [version: "${project.version}"] inputs.properties props @@ -178,20 +156,17 @@ processResources { outputs.upToDateWhen { false } } - checkstyle { toolVersion = '6.1.1' configFile file('config/mv_checks.xml') ignoreFailures = true } - javadoc { source = sourceSets.main.allJava classpath = configurations.compileClasspath } - project.configurations.api.canBeResolved = true shadowJar { @@ -220,3 +195,21 @@ shadowJar { build.dependsOn shadowJar jar.enabled = false + +publishing { + publications { + maven(MavenPublication) { + from components.java + } + } + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/Multiverse/Multiverse-Inventories" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} From d4d05a011fa420c306fbe5d28ff14558b59d078b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:34:27 +0800 Subject: [PATCH 029/180] Update api to support latest core --- .../AbstractWorldGroupManager.java | 2 +- .../inventories/CoreDebugListener.java | 2 +- .../inventories/InventoriesConfig.java | 2 +- .../inventories/InventoriesListener.java | 10 +- .../inventories/MultiverseInventories.java | 118 +++--------------- .../MultiverseInventoriesPluginBinder.java | 5 +- src/main/resources/plugin.yml | 1 + .../inventories/TestWithMockBukkit.kt | 2 - 8 files changed, 26 insertions(+), 116 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java index 53e18014..a487454f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.core.api.world.WorldManager; +import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.GroupingConflict; import org.mvplugins.multiverse.inventories.share.Sharables; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java index e862613c..3e8e8394 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java @@ -3,7 +3,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.mvplugins.multiverse.core.api.event.MVDebugModeEvent; +import org.mvplugins.multiverse.core.event.MVDebugModeEvent; public class CoreDebugListener implements Listener { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java index f7b18720..ded9e252 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.core.api.config.MVCoreConfig; +import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index 0368614a..14cdb888 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -2,11 +2,11 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.api.event.MVConfigReloadEvent; -import org.mvplugins.multiverse.core.api.event.MVDebugModeEvent; -import org.mvplugins.multiverse.core.api.event.MVDumpsDebugInfoEvent; -import org.mvplugins.multiverse.core.api.world.LoadedMultiverseWorld; -import org.mvplugins.multiverse.core.api.world.WorldManager; +import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; +import org.mvplugins.multiverse.core.event.MVDebugModeEvent; +import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 78901866..e138b93e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -6,9 +6,11 @@ import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.core.api.config.MVCoreConfig; -import org.mvplugins.multiverse.core.submodules.MVCore; -import org.mvplugins.multiverse.core.submodules.MVPlugin; +import org.mvplugins.multiverse.core.MultiverseCoreApi; +import org.mvplugins.multiverse.core.MultiversePlugin; +import org.mvplugins.multiverse.core.config.MVCoreConfig; +import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; +import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.locale.Messager; @@ -24,10 +26,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.java.JavaPluginLoader; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -40,7 +39,7 @@ * Multiverse-Inventories plugin main class. */ @Service -public class MultiverseInventories extends JavaPlugin implements MVPlugin, Messaging { +public class MultiverseInventories extends MultiversePlugin implements Messaging { private static final int PROTOCOL = 50; @@ -63,16 +62,13 @@ public static MultiverseInventories getPlugin() { private WorldGroupManager worldGroupManager = null; private ProfileContainerStore worldProfileContainerStore = null; private ProfileContainerStore groupProfileContainerStore = null; - private ImportManager importManager = new ImportManager(this); + private final ImportManager importManager = new ImportManager(this); - private MVCore core = null; private InventoriesConfig config = null; private FlatFileProfileDataSource data = null; private InventoriesDupingPatch dupingPatch; - private File serverFolder = new File(System.getProperty("user.dir")); - private boolean usingSpawnChangeEvent = false; { @@ -83,17 +79,6 @@ public MultiverseInventories() { super(); } - /** - * This is for unit testing. - * @param loader The PluginLoader to use. - * @param description The Description file to use. - * @param dataFolder The folder that other datafiles can be found in. - * @param file The location of the plugin. - */ - public MultiverseInventories(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { - super(loader, description, dataFolder, file); - } - /** * {@inheritDoc} */ @@ -108,37 +93,18 @@ public void onLoad() { */ @Override public final void onEnable() { - this.core = (MVCore) this.getServer().getPluginManager().getPlugin("Multiverse-Core"); - if (this.core == null) { - Logging.severe("Core not found! You must have Multiverse-Core installed to use this plugin!"); - Logging.severe("Grab a copy at: "); - Logging.severe("https://dev.bukkit.org/projects/multiverse-core"); - Logging.severe("Disabling!"); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - if (this.core.getProtocolVersion() < this.getProtocolVersion()) { - Logging.severe("Your Multiverse-Core is OUT OF DATE"); - Logging.severe("This version of " + this.getDescription().getName() + " requires Protocol Level: " + this.getProtocolVersion()); - Logging.severe("Your of Core Protocol Level is: " + this.core.getProtocolVersion()); - Logging.severe("Grab an updated copy at: "); - Logging.severe("https://dev.bukkit.org/projects/multiverse-core"); - Logging.severe("Disabling!"); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - + super.onEnable(); initializeDependencyInjection(); Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); - this.core.incrementPluginCount(); this.onMVPluginEnable(); - Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), getProtocolVersion(), getAuthors()); + Logging.config("Version %s (API v%s) Enabled - By %s", + this.getDescription().getVersion(), getTargetCoreProtocolVersion(), StringFormatter.joinAnd(this.getDescription().getAuthors())); } private void initializeDependencyInjection() { - serviceLocator = core.getServiceLocatorFactory() - .registerPlugin(new MultiverseInventoriesPluginBinder(this), core.getServiceLocator()) + serviceLocator = PluginServiceLocatorFactory.get() + .registerPlugin(new MultiverseInventoriesPluginBinder(this), MultiverseCoreApi.get().getServiceLocator()) .flatMap(PluginServiceLocator::enable) .getOrElseThrow(exception -> { Logging.severe("Failed to initialize dependency injection!"); @@ -175,9 +141,7 @@ private void onMVPluginEnable() { usingSpawnChangeEvent = false; } - if (getCore().getProtocolVersion() >= 24) { - new CoreDebugListener(this); - } + new CoreDebugListener(this); // Register Commands this.registerCommands(); @@ -195,6 +159,7 @@ private void onMVPluginEnable() { */ @Override public void onDisable() { + super.onDisable(); for (final Player player : getServer().getOnlinePlayers()) { final String world = player.getWorld().getName(); //getData().updateLastWorld(player.getName(), world); @@ -206,8 +171,6 @@ public void onDisable() { } this.dupingPatch.disable(); - - this.core.decrementPluginCount(); Logging.shutdown(); } @@ -244,42 +207,13 @@ public ImportManager getImportManager() { * {@inheritDoc} */ @Override - public MVCore getCore() { - return this.core; - } - - /** - * {@inheritDoc} - */ - @Override - public int getProtocolVersion() { + public int getTargetCoreProtocolVersion() { return PROTOCOL; } /** * {@inheritDoc} */ - @Override - public String getAuthors() { - List authorsList = this.getDescription().getAuthors(); - if (authorsList.size() == 0) { - return ""; - } - - StringBuilder authors = new StringBuilder(); - authors.append(authorsList.get(0)); - - for (int i = 1; i < authorsList.size(); i++) { - if (i == authorsList.size() - 1) { - authors.append(" and ").append(authorsList.get(i)); - } else { - authors.append(", ").append(authorsList.get(i)); - } - } - - return authors.toString(); - } - @Override public PluginServiceLocator getServiceLocator() { return serviceLocator; @@ -427,29 +361,7 @@ public ProfileContainerStore getGroupProfileContainerStore() { return groupProfileContainerStore; } - /** - * Gets the server's root-folder as {@link File}. - * - * @return The server's root-folder - */ - public File getServerFolder() { - return serverFolder; - } - - /** - * Sets this server's root-folder. - * - * @param newServerFolder The new server-root - */ - public void setServerFolder(File newServerFolder) { - if (!newServerFolder.isDirectory()) { - throw new IllegalArgumentException("That's not a folder!"); - } - this.serverFolder = newServerFolder; - } - public boolean isUsingSpawnChangeEvent() { return usingSpawnChangeEvent; } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java index 69dfbed1..dffbe4fd 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -1,8 +1,7 @@ package org.mvplugins.multiverse.inventories; -import org.bukkit.plugin.Plugin; +import org.mvplugins.multiverse.core.MultiversePlugin; import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder; -import org.mvplugins.multiverse.core.submodules.MVPlugin; import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; @@ -15,6 +14,6 @@ protected MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugi @Override protected ScopedBindingBuilder bindPluginClass (ScopedBindingBuilder bindingBuilder) { - return super.bindPluginClass(bindingBuilder).to(Plugin.class).to(MVPlugin.class).to(MultiverseInventories.class); + return super.bindPluginClass(bindingBuilder).to(MultiversePlugin.class).to(MultiverseInventories.class); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 936ac5a2..7dc9b458 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,5 +3,6 @@ main: org.mvplugins.multiverse.inventories.MultiverseInventories version: ${version} api-version: 1.13 authors: ['dumptruckman', 'benwoo1110'] +website: 'https://dev.bukkit.org/projects/multiverse-inventories' depend: ['Multiverse-Core'] softdepend: [MultiInv, WorldInventories] diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt index 329e60a9..fe74d5f6 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -7,7 +7,6 @@ import org.bukkit.configuration.file.YamlConfiguration import org.mockbukkit.mockbukkit.MockBukkit import org.mvplugins.multiverse.core.MultiverseCore import org.mvplugins.multiverse.core.inject.PluginServiceLocator -import org.mvplugins.multiverse.core.utils.TestingMode import org.mvplugins.multiverse.inventories.mock.MVServerMock import kotlin.test.* @@ -23,7 +22,6 @@ abstract class TestWithMockBukkit { @BeforeTest fun setUpMockBukkit() { - TestingMode.enable() server = MockBukkit.mock(MVServerMock()) multiverseCore = MockBukkit.load(MultiverseCore::class.java) multiverseInventories = MockBukkit.load(MultiverseInventories::class.java) From a599ecec577011095922d898410b9194a913ba89 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:48:09 +0800 Subject: [PATCH 030/180] Some cleanup to PersistingProfile and codestyle --- .../inventories/DefaultPersistingProfile.java | 36 ----------------- .../inventories/InventoriesListener.java | 4 +- .../inventories/MultiverseInventories.java | 4 +- .../inventories/PersistingProfile.java | 39 +++++++++++++++++++ .../multiverse/inventories/ShareHandler.java | 7 ++-- .../inventories/ShareHandlingUpdater.java | 3 +- .../inventories/blacklist/ItemBlacklist.java | 10 ----- .../blacklist/SimpleItemBlacklist.java | 10 ----- .../inventories/blacklist/package-info.java | 6 --- .../inventories/event/ShareHandlingEvent.java | 2 +- .../inventories/share/PersistingProfile.java | 22 ----------- .../inventories/share/Sharable.java | 2 +- 12 files changed, 48 insertions(+), 97 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java deleted file mode 100644 index 9ee01ab6..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/DefaultPersistingProfile.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.share.PersistingProfile; -import org.mvplugins.multiverse.inventories.share.Shares; - -/** - * Simple implementation of PersistingProfile. - */ -final class DefaultPersistingProfile implements PersistingProfile { - - private Shares shares; - private PlayerProfile profile; - - public DefaultPersistingProfile(Shares shares, PlayerProfile profile) { - this.shares = shares; - this.profile = profile; - } - - /** - * {@inheritDoc} - */ - @Override - public Shares getShares() { - return this.shares; - } - - /** - * {@inheritDoc} - */ - @Override - public PlayerProfile getProfile() { - return this.profile; - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java index 14cdb888..63e77942 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java @@ -165,7 +165,7 @@ public void playerJoin(final PlayerJoinEvent event) { final GlobalProfile globalProfile = inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId()); final String world = globalProfile.getLastWorld(); if (inventories.getMVIConfig().usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { - ShareHandlingUpdater.updatePlayer(inventories, player, new DefaultPersistingProfile(Sharables.allOf(), + ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile(Sharables.allOf(), inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); } inventories.getData().setLoadOnLogin(player.getName(), false); @@ -183,7 +183,7 @@ public void playerQuit(final PlayerQuitEvent event) { final String world = event.getPlayer().getWorld().getName(); inventories.getData().updateLastWorld(player.getName(), world); if (inventories.getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(inventories, player, new DefaultPersistingProfile(Sharables.allOf(), + ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile(Sharables.allOf(), inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); inventories.getData().setLoadOnLogin(player.getName(), true); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index e138b93e..d7a298de 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -1,8 +1,6 @@ package org.mvplugins.multiverse.inventories; -import java.io.File; import java.io.IOException; -import java.util.List; import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; @@ -164,7 +162,7 @@ public void onDisable() { final String world = player.getWorld().getName(); //getData().updateLastWorld(player.getName(), world); if (getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(this, player, new DefaultPersistingProfile(Sharables.allOf(), + ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile(Sharables.allOf(), getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); getData().setLoadOnLogin(player.getName(), true); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java new file mode 100644 index 00000000..dc920fcf --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories; + +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.Shares; + +/** + * Simple class for groups that are going to be saved/loaded. This is used specifically for when a user's world + * change is being handled. + */ +public final class PersistingProfile { + + private final Shares shares; + private final PlayerProfile profile; + + public PersistingProfile(Shares shares, PlayerProfile profile) { + this.shares = shares; + this.profile = profile; + } + + /** + * Gets the shares that will be saved/loaded for the profile. + * + * @return The shares that will be saved/loaded for the profile. This is the set of all Sharables that will be acted + * upon when passed through the ShareHandler class, or any of its subclasses. + */ + public Shares getShares() { + return this.shares; + } + + /** + * Gets the player profile for the world/group that will be saved/loaded for. + * + * @return The player profile for the world/group that will be saved/loaded for. + */ + public PlayerProfile getProfile() { + return this.profile; + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java index c07028a1..b7e28fe0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java @@ -3,7 +3,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.mvplugins.multiverse.inventories.share.Shares; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -127,7 +126,7 @@ public static class AffectedProfiles { AffectedProfiles() { } protected final void setAlwaysWriteProfile(PlayerProfile profile) { - alwaysWriteProfile = new DefaultPersistingProfile(allOf(), profile); + alwaysWriteProfile = new PersistingProfile(allOf(), profile); } /** @@ -135,7 +134,7 @@ protected final void setAlwaysWriteProfile(PlayerProfile profile) { * @param shares What from this group needs to be saved. */ protected final void addWriteProfile(PlayerProfile profile, Shares shares) { - writeProfiles.add(new DefaultPersistingProfile(shares, profile)); + writeProfiles.add(new PersistingProfile(shares, profile)); } /** @@ -143,7 +142,7 @@ protected final void addWriteProfile(PlayerProfile profile, Shares shares) { * @param shares What from this group needs to be loaded. */ protected final void addReadProfile(PlayerProfile profile, Shares shares) { - readProfiles.add(new DefaultPersistingProfile(shares, profile)); + readProfiles.add(new PersistingProfile(shares, profile)); } public PersistingProfile getAlwaysWriteProfile() { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java index 2ef9683e..555f3aed 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java @@ -2,7 +2,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.bukkit.entity.Player; @@ -47,7 +46,7 @@ private void updateProfile() { sharable.getHandler().updateProfile(profile.getProfile(), player); } } - if (saved.size() > 0) { + if (!saved.isEmpty()) { Logging.finer("Persisted: " + saved.stream().map(Objects::toString).collect(Collectors.joining(", ")) + " to " + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() diff --git a/src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java deleted file mode 100644 index 36d808b6..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/blacklist/ItemBlacklist.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.mvplugins.multiverse.inventories.blacklist; - -/** - * Placeholder. - */ -public interface ItemBlacklist { - - -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java deleted file mode 100644 index 67fb00c5..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/blacklist/SimpleItemBlacklist.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.mvplugins.multiverse.inventories.blacklist; - -/** - * Simple Implementation of ItemBlacklist. - */ -public class SimpleItemBlacklist implements ItemBlacklist { - - -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java deleted file mode 100644 index 8f81a034..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/blacklist/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * This package contains classes related to Item Blacklisting per world group/world. - * It is also very unfinished and likely to be refactored later. - */ -package org.mvplugins.multiverse.inventories.blacklist; - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index 29669135..caea8b93 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.event; +import org.mvplugins.multiverse.inventories.PersistingProfile; import org.mvplugins.multiverse.inventories.ShareHandler; -import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java deleted file mode 100644 index 555015d1..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/PersistingProfile.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.mvplugins.multiverse.inventories.share; - -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; - -/** - * Simple interface for groups that are going to be saved/loaded. This is used specifically for when a user's world - * change is being handled. - */ -public interface PersistingProfile { - - /** - * @return The shares that will be saved/loaded for the profile. This is the set of all Sharables that will be acted - * upon when passed through the ShareHandler class, or any of its subclasses. - */ - Shares getShares(); - - /** - * @return The player profile for the world/group that will be saved/loaded for. - */ - PlayerProfile getProfile(); -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java index e48e2c1a..d3e77f4a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java @@ -147,7 +147,7 @@ public Builder serializer(ProfileEntry entry, SharableSerializer serialize * @return The new Sharable object built by this Builder. */ public Sharable build() { - Sharable sharable = new DefaultSharable(names.toArray(new String[names.size()]), type, + Sharable sharable = new DefaultSharable(names.toArray(new String[0]), type, handler, serializer, profileEntry, optional); ProfileEntry.register(sharable); return sharable; From 82d61f0b3d01b6187a1bc5e416c620ed46cd5c3c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:52:52 +0800 Subject: [PATCH 031/180] Convert to use core's CommentedConfiguration --- .../inventories/MultiverseInventories.java | 11 +- .../inventories/YamlWorldGroupManager.java | 23 +- .../{ => config}/InventoriesConfig.java | 25 +- .../inventories/share/Sharable.java | 2 +- .../util/CommentedYamlConfiguration.java | 278 ------------------ .../TestCommentedYamlConfiguration.java | 1 - 6 files changed, 35 insertions(+), 305 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/{ => config}/InventoriesConfig.java (94%) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index d7a298de..602e20b5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -10,6 +10,7 @@ import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Messaging; @@ -18,6 +19,7 @@ import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.util.Perm; import me.drayshak.WorldInventories.WorldInventories; @@ -49,6 +51,8 @@ public static MultiverseInventories getPlugin() { private PluginServiceLocator serviceLocator; + @Inject + private Provider configProvider; @Inject private Provider commandManager; @Inject @@ -62,7 +66,6 @@ public static MultiverseInventories getPlugin() { private ProfileContainerStore groupProfileContainerStore = null; private final ImportManager importManager = new ImportManager(this); - private InventoriesConfig config = null; private FlatFileProfileDataSource data = null; private InventoriesDupingPatch dupingPatch; @@ -251,7 +254,7 @@ private String logAndAddToPasteBinBuffer(String string) { * @return the Config object which contains settings for this plugin. */ public InventoriesConfig getMVIConfig() { - return this.config; + return this.configProvider.get(); } /** @@ -260,8 +263,7 @@ public InventoriesConfig getMVIConfig() { @Override public void reloadConfig() { try { - this.config = new InventoriesConfig(this, mvCoreConfig.get()); - this.worldGroupManager = new YamlWorldGroupManager(this, this.config.getConfig()); + this.worldGroupManager = new YamlWorldGroupManager(this, this.configProvider.get().getConfig()); this.worldProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.WORLD); this.groupProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.GROUP); @@ -363,3 +365,4 @@ public boolean isUsingSpawnChangeEvent() { return usingSpawnChangeEvent; } } + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java index 32ab98db..9ef34daa 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java @@ -2,8 +2,8 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.collect.Lists; +import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; import org.mvplugins.multiverse.inventories.util.DeserializationException; import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; @@ -24,12 +24,14 @@ final class YamlWorldGroupManager extends AbstractWorldGroupManager { - private final List groupSectionComments = Collections.unmodifiableList(new ArrayList() {{ - add("# To ADD, DELETE, and EDIT groups use the command /mvinv group."); - add("# No support will be given for those who manually edit these groups."); - }}); + private final String[] groupSectionComments = { + "# Multiverse-Inventories Groups", + "", + "# To ADD, DELETE, and EDIT groups use the command /mvinv group.", + "# No support will be given for those who manually edit these groups." + }; - private final CommentedYamlConfiguration groupsConfig; + private final CommentedConfiguration groupsConfig; YamlWorldGroupManager(final MultiverseInventories inventories, final Configuration config) throws IOException { super(inventories); @@ -44,19 +46,18 @@ final class YamlWorldGroupManager extends AbstractWorldGroupManager { migrateGroups = true; } // Load the configuration file into memory - boolean supportsCommentsNatively = PaperLib.getMinecraftVersion() > 17; - groupsConfig = new CommentedYamlConfiguration(groupsConfigFile, !groupsConfigFileExists || !supportsCommentsNatively); + groupsConfig = new CommentedConfiguration(groupsConfigFile.toPath()); + groupsConfig.load(); if (migrateGroups) { migrateGroups(config); } groupsConfig.addComment("groups", groupSectionComments); - if (groupsConfig.getConfig().get("groups") == null) { + if (groupsConfig.get("groups") == null) { this.getConfig().createSection("groups"); } - groupsConfig.getConfig().options().header("Multiverse-Inventories Groups"); // Saves the configuration from memory to file groupsConfig.save(); @@ -86,7 +87,7 @@ private void migrateGroups(final Configuration config) { } private FileConfiguration getConfig() { - return this.groupsConfig.getConfig(); + return this.groupsConfig; } private List getGroupsFromConfig() { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java similarity index 94% rename from src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java rename to src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index ded9e252..8f56765c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -1,11 +1,14 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.config; import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.core.config.MVCoreConfig; +import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; import io.papermc.lib.PaperLib; import org.bukkit.configuration.file.FileConfiguration; @@ -18,6 +21,7 @@ /** * Provides methods for interacting with the configuration of Multiverse-Inventories. */ +@Service public final class InventoriesConfig { /** @@ -107,10 +111,11 @@ private List getComments() { } } - private final CommentedYamlConfiguration config; + private final CommentedConfiguration config; private final MultiverseInventories plugin; private final MVCoreConfig mvCoreConfig; + @Inject InventoriesConfig(MultiverseInventories plugin, MVCoreConfig mvCoreConfig) throws IOException { this.plugin = plugin; this.mvCoreConfig = mvCoreConfig; @@ -128,13 +133,13 @@ private List getComments() { } // Load the configuration file into memory - boolean supportsCommentsNatively = PaperLib.getMinecraftVersion() > 17; - config = new CommentedYamlConfiguration(configFile, !configFileExists || !supportsCommentsNatively); + config = new CommentedConfiguration(configFile.toPath()); + config.load(); // Sets defaults config values this.setDefaults(); - config.getConfig().options().header("Multiverse-Inventories Settings"); + config.addComment("settings", "# Multiverse-Inventories Settings", ""); // Saves the configuration from memory to file config.save(); @@ -148,7 +153,7 @@ private List getComments() { */ private void setDefaults() { for (InventoriesConfig.Path path : InventoriesConfig.Path.values()) { - config.addComment(path.getPath(), path.getComments()); + config.addComment(path.getPath(), path.getComments().toArray(new String[0])); if (this.getConfig().get(path.getPath()) == null) { if (path.getDefault() != null) { Logging.fine("Config: Defaulting '" + path.getPath() + "' to " + path.getDefault()); @@ -173,8 +178,8 @@ private String getString(Path path) { return this.getConfig().getString(path.getPath(), (String) path.getDefault()); } - FileConfiguration getConfig() { - return this.config.getConfig(); + public FileConfiguration getConfig() { + return this.config; } /** @@ -218,7 +223,7 @@ public boolean isFirstRun() { * * @param firstRun What to set the flag to in the config. */ - void setFirstRun(boolean firstRun) { + public void setFirstRun(boolean firstRun) { this.getConfig().set(Path.FIRST_RUN.getPath(), firstRun); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java index d3e77f4a..f0b9c5a3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.share; -import org.mvplugins.multiverse.inventories.InventoriesConfig; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java b/src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java deleted file mode 100644 index 78993d16..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/CommentedYamlConfiguration.java +++ /dev/null @@ -1,278 +0,0 @@ -package org.mvplugins.multiverse.inventories.util; - -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A Configuration wrapper class that allows for comments to be applied to the config paths. - */ -public final class CommentedYamlConfiguration { - - private final File file; - private final FileConfiguration config; - private final boolean doComments; - private final HashMap comments; - - private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\r?\n"); - - public CommentedYamlConfiguration(File file, boolean doComments) { - this.file = file; - this.doComments = doComments; - this.comments = new HashMap(); - this.config = new YamlConfiguration(); - - try { - this.config.load(file); - } catch (Exception ignored) {} - } - - /** - * @return The underlying configuration object. - */ - public FileConfiguration getConfig() { - return this.config; - } - - /** - * Saves the file as per normal for YamlConfiguration and then parses the file and inserts - * comments where necessary. - * - * @return True if successful. - */ - public boolean save() { - // try to save the config file, return false on failure - try { - config.save(file); - } catch (Exception e) { - return false; - } - - // if we're not supposed to add comments, or there aren't any to add, we're done - if (!doComments || comments.isEmpty()) { - return true; - } - - // convert config file to String - String stringConfig = this.convertFileToString(file); - - // figure out where the header ends - int indexAfterHeader = 0; - Matcher newline = NEW_LINE_PATTERN.matcher(stringConfig); - - while (newline.find() && stringConfig.charAt(newline.end()) == '#') { - indexAfterHeader = newline.end(); - } - - // convert stringConfig to array, ignoring the header - String[] arrayConfig = stringConfig.substring(indexAfterHeader).split(newline.group()); - - // begin building the new config, starting with the header - StringBuilder newContents = new StringBuilder(); - newContents.append(stringConfig, 0, indexAfterHeader); - - // This holds the current path the lines are at in the config - StringBuilder currentPath = new StringBuilder(); - - // This flags if the line is a node or unknown text. - boolean node; - // The depth of the path. (number of words separated by periods - 1) - int depth = 0; - - // Loop through the config lines - for (final String line : arrayConfig) { - // If the line is a node (and not something like a list value) - if (line.contains(": ") || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) { - // This is a node so flag it as one - node = true; - - // Grab the index of the end of the node name - int index; - index = line.indexOf(": "); - if (index < 0) { - index = line.length() - 1; - } - // If currentPath is empty, store the node name as the currentPath. (this is only on the first iteration, i think) - if (currentPath.toString().isEmpty()) { - currentPath = new StringBuilder(line.substring(0, index)); - } else { - // Calculate the whitespace preceding the node name - int whiteSpace = 0; - for (int n = 0; n < line.length(); n++) { - if (line.charAt(n) == ' ') { - whiteSpace++; - } else { - break; - } - } - - int whiteSpaceDividedByTwo = whiteSpace / 2; - // Find out if the current depth (whitespace * 2) is greater/lesser/equal to the previous depth - if (whiteSpaceDividedByTwo > depth) { - // Path is deeper. Add a dot and the node name - currentPath.append(".").append(line, whiteSpace, index); - depth++; - } else if (whiteSpaceDividedByTwo < depth) { - // Path is shallower, calculate current depth from whitespace (whitespace / 2) and subtract that many levels from the currentPath - for (int i = 0; i < depth - whiteSpaceDividedByTwo; i++) { - currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), ""); - } - // Grab the index of the final period - int lastIndex = currentPath.lastIndexOf("."); - if (lastIndex < 0) { - // if there isn't a final period, set the current path to nothing because we're at root - currentPath = new StringBuilder(); - } else { - // If there is a final period, replace everything after it with nothing - currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "").append("."); - } - // Add the new node name to the path - currentPath.append(line, whiteSpace, index); - // Reset the depth - depth = whiteSpaceDividedByTwo; - } else { - // Path is same depth, replace the last path node name to the current node name - int lastIndex = currentPath.lastIndexOf("."); - if (lastIndex < 0) { - // if there isn't a final period, set the current path to nothing because we're at root - currentPath = new StringBuilder(); - } else { - // If there is a final period, replace everything after it with nothing - currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "").append("."); - } - - currentPath.append(line, whiteSpace, index); - } - } - } else { - node = false; - } - - StringBuilder newLine = new StringBuilder(); - - if (node) { - // get the comment for the current node - String comment = comments.get(currentPath.toString()); - if (comment != null && !comment.isEmpty()) { - // if the previous line doesn't end in a colon - // and there's not already a newline character, - // add a newline before we add the comment - char previousChar = newContents.charAt(newContents.length() - 2); - if (previousChar != ':' && previousChar != '\n') { - newLine.append("\n"); - } - - // add the comment - newLine.append(comment).append("\n"); - } - } - - // add the config line - newLine.append(line).append("\n"); - - // Add the (modified) line to the total config String - newContents.append(newLine); - } - - // try to save the config file, returning whether it saved successfully - return this.stringToFile(newContents.toString(), file); - } - - /** - * Adds a comment just before the specified path. The comment can be multiple lines. An empty string will indicate - * a blank line. - * - * @param path Configuration path to add comment. - * @param commentLines Comments to add. One String per line. - */ - public void addComment(String path, List commentLines) { - StringBuilder commentString = new StringBuilder(); - StringBuilder leadingSpaces = new StringBuilder(); - for (int n = 0; n < path.length(); n++) { - if (path.charAt(n) == '.') { - leadingSpaces.append(" "); - } - } - for (String line : commentLines) { - if (commentString.length() > 0) { - commentString.append("\n"); - } - if (!line.isEmpty()) { - commentString.append(leadingSpaces).append(line); - } - } - comments.put(path, commentString.toString()); - } - - /** - * Pass a file and it will return it's contents as a string. - * - * @param file File to read. - * @return Contents of file. String will be empty in case of any errors. - */ - private String convertFileToString(File file) { - final int bufferSize = 1024; - if (file != null && file.exists() && file.canRead() && !file.isDirectory()) { - Writer writer = new StringWriter(); - char[] buffer = new char[bufferSize]; - - try (InputStream is = new FileInputStream(file)) { - int n; - Reader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); - while ((n = reader.read(buffer)) != -1) { - writer.write(buffer, 0, n); - } - } catch (IOException e) { - e.printStackTrace(); - } - return writer.toString(); - } else { - return ""; - } - } - - /** - * Writes the contents of a string to a file. - * - * @param source String to write. - * @param file File to write to. - * @return True on success. - */ - private boolean stringToFile(String source, File file) { - OutputStreamWriter out = null; - try { - out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8); - out.write(source); - out.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } -} - diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java index 5a10dc48..600c8ab7 100644 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java +++ b/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java @@ -1,6 +1,5 @@ package org.mvplugins.multiverse.inventories; -import org.mvplugins.multiverse.inventories.util.CommentedYamlConfiguration; import org.junit.Test; import java.io.File; From 676f991210ff6f38d098906f648df8bdc798917b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:51:47 +0800 Subject: [PATCH 032/180] Fix PersistingProfile import --- .../mvplugins/multiverse/inventories/MultiverseInventories.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 602e20b5..3b2c2f29 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -19,7 +19,6 @@ import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; -import org.mvplugins.multiverse.inventories.share.PersistingProfile; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.util.Perm; import me.drayshak.WorldInventories.WorldInventories; From 6a6d940312830156474643c2cf8d3ee7b8cff8d8 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:58:40 +0800 Subject: [PATCH 033/180] Fix @Service annotation import --- .../multiverse/inventories/config/InventoriesConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 8f56765c..eb045d4b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -1,10 +1,10 @@ package org.mvplugins.multiverse.inventories.config; import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; import org.mvplugins.multiverse.external.jakarta.inject.Inject; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; From 274aca9f686f3f6988ebdf224de24369779ff81a Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 15:51:14 +0800 Subject: [PATCH 034/180] Move everything to hk2 injection --- .../AbstractWorldGroupManager.java | 252 -------- .../inventories/CoreDebugListener.java | 18 - .../inventories/DefaultMessageProvider.java | 2 + .../inventories/DefaultMessager.java | 15 +- .../FlatFileProfileDataSource.java | 600 ------------------ .../inventories/InventoriesDupingPatch.java | 3 +- .../inventories/MultiverseInventories.java | 176 ++--- .../MultiverseInventoriesPluginBinder.java | 4 +- .../inventories/WeakProfileContainer.java | 123 ---- .../WeakProfileContainerStore.java | 41 -- .../inventories/YamlWorldGroupManager.java | 237 ------- .../inventories/commands/InfoCommand.java | 24 +- .../inventories/commands/ListCommand.java | 12 +- .../inventories/commands/ToggleCommand.java | 16 +- .../commands/prompts/GroupCreatePrompt.java | 6 +- .../commands/prompts/GroupDeletePrompt.java | 8 +- .../commands/prompts/GroupEditPrompt.java | 6 +- .../commands/prompts/GroupModifyPrompt.java | 2 +- .../commands/prompts/GroupSharesPrompt.java | 6 +- .../commands/prompts/GroupWorldsPrompt.java | 4 +- .../commands/prompts/InventoriesPrompt.java | 3 + .../commands/prompts/package-info.java | 1 + .../inventories/config/package-info.java | 1 + .../GameModeChangeShareHandlingEvent.java | 2 +- .../inventories/event/ShareHandlingEvent.java | 4 +- .../event/WorldChangeShareHandlingEvent.java | 2 +- .../{ => listeners}/GameModeShareHandler.java | 15 +- .../{ => listeners}/InventoriesListener.java | 130 ++-- .../{ => listeners}/ShareHandler.java | 14 +- .../{ => listeners}/ShareHandlingUpdater.java | 13 +- .../{ => listeners}/SingleShareWriter.java | 24 +- .../{ => listeners}/SpawnChangeListener.java | 7 +- .../WorldChangeShareHandler.java | 10 +- .../locale/LazyLocaleMessageProvider.java | 3 + .../inventories/locale/MessageProvider.java | 3 + .../inventories/locale/Messager.java | 2 + .../inventories/migration/ImportManager.java | 9 +- .../multiinv/MIPlayerFileLoader.java | 6 +- .../migration/multiinv/MultiInvImporter.java | 42 +- .../WorldInventoriesImporter.java | 40 +- .../{ => profile}/PersistingProfile.java | 3 +- .../profile/ProfileDataSource.java | 578 ++++++++++++++++- .../inventories/profile/ProfileType.java | 2 +- .../inventories/profile/ProfileTypes.java | 16 +- .../profile/WorldGroupManager.java | 125 ---- .../profile/container/ProfileContainer.java | 117 +++- .../container/ProfileContainerStore.java | 30 +- .../ProfileContainerStoreProvider.java | 39 ++ .../profile/{ => group}/GroupingConflict.java | 9 +- .../{ => profile/group}/WorldGroup.java | 30 +- .../profile/group/WorldGroupManager.java | 488 ++++++++++++++ .../inventories/share/DefaultSerializer.java | 2 +- .../share/DefaultStringSerializer.java | 4 +- .../share/InventorySerializer.java | 2 +- .../inventories/share/LocationSerializer.java | 2 +- .../share/PotionEffectSerializer.java | 2 +- .../inventories/share/ProfileEntry.java | 8 +- .../inventories/share/SharableGroup.java | 4 +- .../inventories/share/Sharables.java | 13 +- .../inventories/{ => util}/DataStrings.java | 2 +- .../multiverse/inventories/util/Perm.java | 6 +- .../inventories/{ => util}/PlayerStats.java | 2 +- 62 files changed, 1604 insertions(+), 1766 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/GameModeShareHandler.java (83%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/InventoriesListener.java (72%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/ShareHandler.java (86%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/ShareHandlingUpdater.java (85%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/SingleShareWriter.java (51%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/SpawnChangeListener.java (94%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => listeners}/WorldChangeShareHandler.java (95%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => profile}/PersistingProfile.java (90%) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ => group}/GroupingConflict.java (88%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => profile/group}/WorldGroup.java (86%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java rename src/main/java/org/mvplugins/multiverse/inventories/{ => util}/DataStrings.java (99%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => util}/PlayerStats.java (96%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java deleted file mode 100644 index a487454f..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/AbstractWorldGroupManager.java +++ /dev/null @@ -1,252 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.core.world.WorldManager; -import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; -import org.mvplugins.multiverse.inventories.profile.GroupingConflict; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Abstract implementation of GroupManager with no persistence of groups. - */ -abstract class AbstractWorldGroupManager implements WorldGroupManager { - - static final String DEFAULT_GROUP_NAME = "default"; - protected final Map groupNamesMap = new LinkedHashMap<>(); - protected final MultiverseInventories plugin; - protected final WorldManager worldManager; - - public AbstractWorldGroupManager(final MultiverseInventories plugin) { - this.plugin = plugin; - this.worldManager = plugin.getServiceLocator().getService(WorldManager.class); - } - - /** - * {@inheritDoc} - */ - @Override - public WorldGroup getGroup(String groupName) { - return groupNamesMap.get(groupName.toLowerCase()); - } - - /** - * {@inheritDoc} - */ - @Override - public List getGroups() { - return Collections.unmodifiableList(new ArrayList(getGroupNames().values())); - } - - /** - * {@inheritDoc} - */ - @Override - public List getGroupsForWorld(String worldName) { - worldName = worldName.toLowerCase(); - List worldGroups = new ArrayList<>(); - for (WorldGroup worldGroup : getGroupNames().values()) { - if (worldGroup.containsWorld(worldName)) { - worldGroups.add(worldGroup); - } - } - // Only use the default group for worlds managed by MV-Core - if (worldGroups.isEmpty() && plugin.getMVIConfig().isDefaultingUngroupedWorlds() && - this.worldManager.isWorld(worldName)) { - Logging.finer("Returning default group for world: " + worldName); - worldGroups.add(getDefaultGroup()); - } - return worldGroups; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean hasGroup(String worldName) { - return !getGroupsForWorld(worldName).isEmpty(); - } - - /** - * Retrieves all of the World Groups mapped to their names. - * - * @return Map of Group Name -> World Group - */ - protected Map getGroupNames() { - return groupNamesMap; - } - - /** - * {@inheritDoc} - */ - @Override - @Deprecated - public void addGroup(final WorldGroup worldGroup, final boolean persist) { - updateGroup(worldGroup); - } - - @Override - public void updateGroup(final WorldGroup worldGroup) { - getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); - } - - protected void persistGroup(final WorldGroup worldGroup) { - } - - /** - * {@inheritDoc} - */ - @Override - public boolean removeGroup(final WorldGroup worldGroup) { - return getGroupNames().remove(worldGroup.getName().toLowerCase()) != null; - } - - /** - * {@inheritDoc} - */ - @Override - public WorldGroup newEmptyGroup(String name) { - if (getGroup(name) != null) { - return null; - } - return new WorldGroup(plugin, name); - } - - /** - * {@inheritDoc} - */ - @Override - @Deprecated - public void setGroups(List worldGroups) { - } - - /** - * {@inheritDoc} - */ - @Override - public void createDefaultGroup() { - if (getGroup(DEFAULT_GROUP_NAME) != null) { - return; - } - World defaultWorld = Bukkit.getWorlds().get(0); - World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); - World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); - WorldGroup worldGroup = new WorldGroup(plugin, DEFAULT_GROUP_NAME); - worldGroup.getShares().mergeShares(Sharables.allOf()); - worldGroup.addWorld(defaultWorld); - StringBuilder worlds = new StringBuilder().append(defaultWorld.getName()); - if (defaultNether != null) { - worldGroup.addWorld(defaultNether); - worlds.append(", ").append(defaultNether.getName()); - } - if (defaultEnd != null) { - worldGroup.addWorld(defaultEnd); - worlds.append(", ").append(defaultEnd.getName()); - } - updateGroup(worldGroup); - plugin.getMVIConfig().save(); - Logging.info("Created a default group for you containing all of your default worlds: " + worlds.toString()); - } - - /** - * {@inheritDoc} - */ - @Override - public WorldGroup getDefaultGroup() { - WorldGroup group = getGroupNames().get(DEFAULT_GROUP_NAME); - if (group == null) { - group = newEmptyGroup(DEFAULT_GROUP_NAME); - group.getShares().setSharing(Sharables.allOf(), true); - updateGroup(group); - } - return group; - } - - /** - * {@inheritDoc} - */ - @Override - public List checkGroups() { - List conflicts = new ArrayList(); - Map previousConflicts = new HashMap<>(); - for (WorldGroup checkingGroup : getGroupNames().values()) { - for (String worldName : checkingGroup.getWorlds()) { - for (WorldGroup worldGroup : getGroupsForWorld(worldName)) { - if (checkingGroup.equals(worldGroup)) { - continue; - } - if (previousConflicts.containsKey(checkingGroup)) { - if (previousConflicts.get(checkingGroup).equals(worldGroup)) { - continue; - } - } - if (previousConflicts.containsKey(worldGroup)) { - if (previousConflicts.get(worldGroup).equals(checkingGroup)) { - continue; - } - } - previousConflicts.put(checkingGroup, worldGroup); - Shares conflictingShares = worldGroup.getShares() - .compare(checkingGroup.getShares()); - if (!conflictingShares.isEmpty()) { - if (checkingGroup.getWorlds().containsAll(worldGroup.getWorlds()) - || worldGroup.getWorlds().containsAll(checkingGroup.getWorlds())) { - continue; - } - conflicts.add(new GroupingConflict(checkingGroup, worldGroup, - Sharables.fromShares(conflictingShares))); - } - } - } - } - - return conflicts; - } - - /** - * {@inheritDoc} - */ - @Override - public void checkForConflicts(CommandSender sender) { - String message = plugin.getMessager().getMessage(Message.CONFLICT_CHECKING); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.fine(message); - List conflicts = plugin.getGroupManager().checkGroups(); - for (GroupingConflict conflict : conflicts) { - message = plugin.getMessager().getMessage(Message.CONFLICT_RESULTS, - conflict.getFirstGroup().getName(), conflict.getSecondGroup().getName(), - conflict.getConflictingShares().toString(), conflict.getWorldsString()); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.info(message); - } - if (!conflicts.isEmpty()) { - message = plugin.getMessager().getMessage(Message.CONFLICT_FOUND); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.info(message); - } else { - message = plugin.getMessager().getMessage(Message.CONFLICT_NOT_FOUND); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.fine(message); - } - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java b/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java deleted file mode 100644 index 3e8e8394..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/CoreDebugListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.dumptruckman.minecraft.util.Logging; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.mvplugins.multiverse.core.event.MVDebugModeEvent; - -public class CoreDebugListener implements Listener { - - CoreDebugListener(MultiverseInventories plugin) { - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - @EventHandler - public void onDebugModeChange(MVDebugModeEvent event) { - Logging.setDebugLevel(event.getLevel()); - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java index b34e5036..77ca906a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories; +import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.inventories.locale.LazyLocaleMessageProvider; import org.mvplugins.multiverse.inventories.locale.LocalizationLoadingException; import org.mvplugins.multiverse.inventories.locale.Message; @@ -23,6 +24,7 @@ /** * Implementation of MessageProvider. */ +@Contract class DefaultMessageProvider implements LazyLocaleMessageProvider { /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java index 35afe289..7cee3a00 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java @@ -1,5 +1,8 @@ package org.mvplugins.multiverse.inventories; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.locale.MessageProvider; import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Message; @@ -13,9 +16,11 @@ /** * Implementation of a Messager and MessageProvider using DefaultMessageProvider to implement the latter. */ +@Service final class DefaultMessager extends DefaultMessageProvider implements Messager, MessageProvider { - public DefaultMessager(JavaPlugin plugin) { + @Inject + public DefaultMessager(@NotNull MultiverseInventories plugin) { super(plugin); } @@ -32,7 +37,7 @@ private void send(Message message, String prefix, CommandSender sender, Object.. */ @Override public void bad(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.RED.toString() + this.getMessage(Message.GENERIC_ERROR), sender, args); + send(message, ChatColor.RED + this.getMessage(Message.GENERIC_ERROR), sender, args); } /** @@ -48,7 +53,7 @@ public void normal(Message message, CommandSender sender, Object... args) { */ @Override public void good(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.GREEN.toString() + this.getMessage(Message.GENERIC_SUCCESS), sender, args); + send(message, ChatColor.GREEN + this.getMessage(Message.GENERIC_SUCCESS), sender, args); } /** @@ -56,7 +61,7 @@ public void good(Message message, CommandSender sender, Object... args) { */ @Override public void info(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.YELLOW.toString() + this.getMessage(Message.GENERIC_INFO), sender, args); + send(message, ChatColor.YELLOW + this.getMessage(Message.GENERIC_INFO), sender, args); } /** @@ -64,7 +69,7 @@ public void info(Message message, CommandSender sender, Object... args) { */ @Override public void help(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.GRAY.toString() + this.getMessage(Message.GENERIC_HELP), sender, args); + send(message, ChatColor.GRAY + this.getMessage(Message.GENERIC_HELP), sender, args); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java deleted file mode 100644 index 85ea8b61..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/FlatFileProfileDataSource.java +++ /dev/null @@ -1,600 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.dumptruckman.minecraft.util.Logging; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import org.bukkit.configuration.InvalidConfigurationException; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileKey; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.share.ProfileEntry; -import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.SharableEntry; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.profile.GlobalProfile; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import net.minidev.json.JSONObject; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; - -class FlatFileProfileDataSource implements ProfileDataSource { - - private static final String JSON = ".json"; - - private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); - - private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); - - // TODO these probably need configurable max sizes - private final Cache profileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(1000) - .build(); - private final Cache globalProfileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(500) - .build(); - - private final File worldFolder; - private final File groupFolder; - private final File playerFolder; - - FlatFileProfileDataSource(MultiverseInventories plugin) throws IOException { - // Make the data folders - plugin.getDataFolder().mkdirs(); - - // Check if the data file exists. If not, create it. - this.worldFolder = new File(plugin.getDataFolder(), "worlds"); - if (!this.worldFolder.exists()) { - if (!this.worldFolder.mkdirs()) { - throw new IOException("Could not create world folder!"); - } - } - this.groupFolder = new File(plugin.getDataFolder(), "groups"); - if (!this.groupFolder.exists()) { - if (!this.groupFolder.mkdirs()) { - throw new IOException("Could not create group folder!"); - } - } - this.playerFolder = new File(plugin.getDataFolder(), "players"); - if (!this.playerFolder.exists()) { - if (!this.playerFolder.mkdirs()) { - throw new IOException("Could not create player folder!"); - } - } - } - - private FileConfiguration waitForConfigHandle(File file) { - Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - while (true) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - - private static FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { - JsonConfiguration jsonConfiguration = new JsonConfiguration(); - jsonConfiguration.options().continueOnSerializationError(true); - jsonConfiguration.load(file); - return jsonConfiguration; - } - - private static class ConfigLoader implements Callable { - private final File file; - - private ConfigLoader(File file) { - this.file = file; - } - - @Override - public FileConfiguration call() throws Exception { - return getConfigHandleNow(file); - } - } - - private File getFolder(ContainerType type, String folderName) { - File folder; - switch (type) { - case GROUP: - folder = new File(this.groupFolder, folderName); - break; - case WORLD: - folder = new File(this.worldFolder, folderName); - break; - default: - folder = new File(this.worldFolder, folderName); - break; - } - - if (!folder.exists()) { - folder.mkdirs(); - } - return folder; - } - - /** - * Retrieves the data file for a player based on a given world/group name, creating it if necessary. - * - * @param type Indicates whether data is for group or world. - * @param dataName The name of the group or world. - * @param playerName The name of the player. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); - if (!jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() - + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName - + " may not be saved.", e); - } - } - Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", - jsonPlayerFile.getPath(), type, dataName, playerName); - return jsonPlayerFile; - } - - /** - * Retrieves the data file for a player for their global data, creating it if necessary. - * - * @param fileName The name of the file (player name or UUID) without extension. - * @param createIfMissing If true, the file will be created it it does not exist. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { - File jsonPlayerFile = new File(playerFolder, fileName + JSON); - if (createIfMissing && !jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " - + "There may be issues with " + fileName + "'s metadata", e); - } - } - return jsonPlayerFile; - } - - private void queueWrite(PlayerProfile profile) { - fileIOExecutorService.submit(new FileWriter(profile.clone())); - } - - private class FileWriter implements Callable { - private final PlayerProfile profile; - - private FileWriter(PlayerProfile profile) { - this.profile = profile; - } - - @Override - public Void call() throws Exception { - processProfileWrite(profile); - return null; - } - } - - private void processProfileWrite(PlayerProfile playerProfile) { - try { - File playerFile = this.getPlayerFile(playerProfile.getContainerType(), - playerProfile.getContainerName(), playerProfile.getPlayer().getName()); - FileConfiguration playerData = getConfigHandleNow(playerFile); - playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() - + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); - Logging.severe(e.getMessage()); - } - } catch (final Exception e) { - Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); - } - } - - private Map serializePlayerProfile(PlayerProfile playerProfile) { - Map playerData = new LinkedHashMap(); - JSONObject jsonStats = new JSONObject(); - for (SharableEntry entry : playerProfile) { - if (entry.getValue() != null) { - if (entry.getSharable().getSerializer() == null) { - continue; - } - Sharable sharable = entry.getSharable(); - if (sharable.getProfileEntry().isStat()) { - jsonStats.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } else { - playerData.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } - } - } - if (!jsonStats.isEmpty()) { - playerData.put(DataStrings.PLAYER_STATS, jsonStats); - } - return playerData; - } - - /** - * {@inheritDoc} - */ - @Override - public void updatePlayerData(PlayerProfile playerProfile) { - queueWrite(playerProfile); - } - - private PlayerProfile getPlayerData(ProfileKey key) { - PlayerProfile cached = profileCache.getIfPresent(key); - if (cached != null) { - return cached; - } - File playerFile = null; - try { - playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); - } catch (IOException e) { - e.printStackTrace(); - // Return an empty profile - return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), - Bukkit.getOfflinePlayer(key.getPlayerUUID())); - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - if (convertConfig(playerData)) { - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + key.getPlayerName() - + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); - Logging.severe(e.getMessage()); - } - } - ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); - if (section == null) { - section = playerData.createSection(key.getProfileType().getName()); - } - PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); - profileCache.put(key, result); - return result; - } - - @Override - public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); - } - - private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); - for (Object keyObj : playerData.keySet()) { - String key = keyObj.toString(); - if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { - final Object statsObject = playerData.get(key); - if (statsObject instanceof String) { - parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); - } else { - if (statsObject instanceof Map) { - parsePlayerStatsIntoProfile((Map) statsObject, profile); - } else { - Logging.warning("Could not parse stats for " + pKey.getPlayerName()); - } - } - } else { - if (playerData.get(key) == null) { - Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); - continue; - } - try { - Sharable sharable = ProfileEntry.lookup(false, key); - if (sharable == null) { - Logging.fine("Player fileTag '" + key + "' is unrecognized!"); - continue; - } - profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); - } catch (Exception e) { - Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); - Logging.getLogger().log(Level.FINE, "Exception: ", e); - e.printStackTrace(); - } - } - } - Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); - return profile; - } - - private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { - for (Object key : stats.keySet()) { - Sharable sharable = ProfileEntry.lookup(true, key.toString()); - if (sharable != null) { - profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); - } else { - Logging.warning("Could not parse stat: '" + key + "' for player '" - + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" - + profile.getContainerName() + "'"); - } - } - } - - private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { - if (stats.isEmpty()) { - return; - } - JSONObject jsonStats = null; - try { - jsonStats = (JSONObject) JSON_PARSER.parse(stats); - } catch (ParseException | ClassCastException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } - if (jsonStats == null) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "'"); - return; - } - parsePlayerStatsIntoProfile(jsonStats, profile); - } - - // TODO Remove this conversion - private boolean convertConfig(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection("playerData"); - if (section != null) { - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set("playerData", null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { - if (profileType == null) { - try { - File playerFile = getPlayerFile(containerType, dataName, playerName); - return playerFile.delete(); - } catch (IOException ignore) { - Logging.warning("Attempted to delete file that did not exist for player " + playerName - + " in " + containerType.name().toLowerCase() + " " + dataName); - return false; - } - } else { - File playerFile; - try { - playerFile = getPlayerFile(containerType, dataName, playerName); - } catch (IOException e) { - Logging.warning("Attempted to delete " + playerName + "'s data for " - + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() - + " " + dataName + " but the file did not exist."); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.set(profileType.getName(), null); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not delete data for player: " + playerName - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - } - - private Map convertSection(ConfigurationSection section) { - Map resultMap = new HashMap(); - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj instanceof ConfigurationSection) { - resultMap.put(key, convertSection((ConfigurationSection) obj)); - } else { - resultMap.put(key, obj); - } - } - return resultMap; - } - - @Override - @Deprecated - public GlobalProfile getGlobalProfile(String playerName) { - return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); - } - - @Override - public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { - GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); - if (cached != null) { - return cached; - } - File playerFile; - - // Migrate old data if necessary - try { - playerFile = getGlobalFile(playerName, false); - } catch (IOException e) { - // This won't ever happen - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName); - } - if (playerFile.exists()) { - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - if (!migrateGlobalProfileToUUID(profile, playerFile)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - globalProfileCache.put(playerUUID, profile); - return profile; - } - - // Load current format - try { - playerFile = getGlobalFile(playerUUID.toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName, playerUUID); - } - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - globalProfileCache.put(playerUUID, profile); - return profile; - } - - private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { - updateGlobalProfile(profile); - return playerFile.delete(); - } - - private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - ConfigurationSection section = playerData.getConfigurationSection("playerData"); - if (section == null) { - section = playerData.createSection("playerData"); - } - return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); - } - - private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, - Map playerData) { - GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); - for (String key : playerData.keySet()) { - if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { - globalProfile.setLastWorld(playerData.get(key).toString()); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { - globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { - globalProfile.setLastKnownName(playerData.get(key).toString()); - } - } - return globalProfile; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean updateGlobalProfile(GlobalProfile globalProfile) { - File playerFile = null; - try { - playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save global data for player: " + globalProfile); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - - private Map serializeGlobalProfile(GlobalProfile profile) { - Map result = new HashMap(2); - if (profile.getLastWorld() != null) { - result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); - } - result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); - result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); - return result; - } - - @Override - @Deprecated - // TODO replace for UUID - public void updateLastWorld(String playerName, String worldName) { - GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLastWorld(worldName); - updateGlobalProfile(globalProfile); - } - - @Override - @Deprecated - // TODO replace for UUID - public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { - final GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLoadOnLogin(loadOnLogin); - updateGlobalProfile(globalProfile); - } - - @Override - public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { - File[] worldFolders = worldFolder.listFiles(File::isDirectory); - if (worldFolders == null) { - throw new IOException("Could not enumerate world folders"); - } - File[] groupFolders = groupFolder.listFiles(File::isDirectory); - if (groupFolders == null) { - throw new IOException("Could not enumerate group folders"); - } - - for (File worldFolder : worldFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - for (File groupFolder : groupFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - if (removeOldData) { - for (File worldFolder : worldFolders) { - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - for (File groupFolder : groupFolders) { - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - } - } - - @Override - public void clearProfileCache(ProfileKey key) { - profileCache.invalidate(key); - } - - void clearCache() { - globalProfileCache.invalidateAll(); - profileCache.invalidateAll(); - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java index c1473552..080ee1b6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java @@ -17,6 +17,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.plugin.Plugin; +import org.jvnet.hk2.annotations.Service; /** * This is a simple patch that fixes a @@ -40,7 +41,7 @@ * @author Irmo van den Berge (bergerkiller) * @version 1.0 */ -class InventoriesDupingPatch { +final class InventoriesDupingPatch { private static final int SLOT_TIMEOUT = 5; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 3b2c2f29..2e221951 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -11,19 +11,19 @@ import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.listeners.InventoriesListener; +import org.mvplugins.multiverse.inventories.listeners.SpawnChangeListener; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Messaging; import org.mvplugins.multiverse.inventories.migration.ImportManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.util.Perm; import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; @@ -38,43 +38,32 @@ * Multiverse-Inventories plugin main class. */ @Service -public class MultiverseInventories extends MultiversePlugin implements Messaging { +public final class MultiverseInventories extends MultiversePlugin implements Messaging { private static final int PROTOCOL = 50; - private static MultiverseInventories inventoriesPlugin; - - public static MultiverseInventories getPlugin() { - return inventoriesPlugin; - } - - private PluginServiceLocator serviceLocator; - @Inject - private Provider configProvider; + private Provider inventoriesConfig; @Inject private Provider commandManager; @Inject private Provider mvCoreConfig; @Inject private Provider inventoriesListener; + @Inject + private Provider worldGroupManager; + @Inject + private Provider profileDataSource; + @Inject + private Provider profileContainerStoreProvider; + @Inject + private Provider importManager; + private PluginServiceLocator serviceLocator; private Messager messager = new DefaultMessager(this); - private WorldGroupManager worldGroupManager = null; - private ProfileContainerStore worldProfileContainerStore = null; - private ProfileContainerStore groupProfileContainerStore = null; - private final ImportManager importManager = new ImportManager(this); - - private FlatFileProfileDataSource data = null; - private InventoriesDupingPatch dupingPatch; - private boolean usingSpawnChangeEvent = false; - { - inventoriesPlugin = this; - } - public MultiverseInventories() { super(); } @@ -119,7 +108,7 @@ private void onMVPluginEnable() { this.reloadConfig(); try { - this.getMessager().setLocale(new Locale(this.getMVIConfig().getLocale())); + this.getMessager().setLocale(new Locale(inventoriesConfig.get().getLocale())); } catch (IllegalArgumentException e) { Logging.severe(e.getMessage()); this.getServer().getPluginManager().disablePlugin(this); @@ -141,8 +130,6 @@ private void onMVPluginEnable() { usingSpawnChangeEvent = false; } - new CoreDebugListener(this); - // Register Commands this.registerCommands(); @@ -160,15 +147,15 @@ private void onMVPluginEnable() { @Override public void onDisable() { super.onDisable(); - for (final Player player : getServer().getOnlinePlayers()) { - final String world = player.getWorld().getName(); - //getData().updateLastWorld(player.getName(), world); - if (getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile(Sharables.allOf(), - getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); - getData().setLoadOnLogin(player.getName(), true); - } - } +// for (final Player player : getServer().getOnlinePlayers()) { +// final String world = player.getWorld().getName(); +// //getData().updateLastWorld(player.getName(), world); +// if (getMVIConfig().usingLoggingSaveLoad()) { +// ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile(Sharables.allOf(), +// getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); +// getData().setLoadOnLogin(player.getName(), true); +// } +// } this.dupingPatch.disable(); Logging.shutdown(); @@ -188,21 +175,14 @@ private void hookImportables() { final PluginManager pm = Bukkit.getPluginManager(); Plugin plugin = pm.getPlugin("MultiInv"); if (plugin != null) { - this.getImportManager().hookMultiInv((MultiInv) plugin); + importManager.get().hookMultiInv((MultiInv) plugin); } plugin = pm.getPlugin("WorldInventories"); if (plugin != null) { - this.getImportManager().hookWorldInventories((WorldInventories) plugin); + importManager.get().hookWorldInventories((WorldInventories) plugin); } } - /** - * @return A class used for managing importing data from other similar plugins. - */ - public ImportManager getImportManager() { - return this.importManager; - } - /** * {@inheritDoc} */ @@ -219,58 +199,19 @@ public PluginServiceLocator getServiceLocator() { return serviceLocator; } - /** - * Builds a String containing Multiverse-Inventories' version info. - * - * @return The version info. - */ - public String getVersionInfo() { - StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + this.getDescription().getVersion() + '\n' - + "[Multiverse-Inventories] === Settings ===" + '\n' - + "[Multiverse-Inventories] First Run: " + this.getMVIConfig().isFirstRun() + '\n' - + "[Multiverse-Inventories] Using Bypass: " + this.getMVIConfig().isUsingBypass() + '\n' - + "[Multiverse-Inventories] Default Ungrouped Worlds: " + this.getMVIConfig().isDefaultingUngroupedWorlds() + '\n' - + "[Multiverse-Inventories] Save and Load on Log In and Out: " + this.getMVIConfig().usingLoggingSaveLoad() + '\n' - + "[Multiverse-Inventories] Using GameMode Profiles: " + this.getMVIConfig().isUsingGameModeProfiles() + '\n' - + "[Multiverse-Inventories] === Shares ===" + '\n' - + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + this.getMVIConfig().usingOptionalsForUngrouped() + '\n' - + "[Multiverse-Inventories] Enabled Optionals: " + this.getMVIConfig().getOptionalShares() + '\n' - + "[Multiverse-Inventories] === Groups ===" + '\n'); - - for (WorldGroup group : this.getGroupManager().getGroups()) { - versionInfo.append("[Multiverse-Inventories] ").append(group.toString()).append('\n'); - } - - return versionInfo.toString(); - } - - private String logAndAddToPasteBinBuffer(String string) { - Logging.info(string); - return Logging.getPrefixedMessage(string + '\n', false); - } - - /** - * @return the Config object which contains settings for this plugin. - */ - public InventoriesConfig getMVIConfig() { - return this.configProvider.get(); - } - /** * Nulls the config object and reloads a new one, also resetting the world groups in memory. */ @Override public void reloadConfig() { try { - this.worldGroupManager = new YamlWorldGroupManager(this, this.configProvider.get().getConfig()); - this.worldProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.WORLD); - this.groupProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.GROUP); + worldGroupManager.get().load(); + profileContainerStoreProvider.get().clearCache(); - if (data != null) { - this.data.clearCache(); + if (profileDataSource.get() != null) { + profileDataSource.get().clearCache(); } - //this.data = null; Logging.fine("Loaded config file!"); } catch (IOException e) { // Catch errors loading the config file and exit out if found. Logging.severe(this.getMessager().getMessage(Message.ERROR_CONFIG_LOAD)); @@ -283,37 +224,19 @@ public void reloadConfig() { @Override public void run() { // Create initial World Group for first run IF NO GROUPS EXIST - if (getMVIConfig().isFirstRun()) { + if (inventoriesConfig.get().isFirstRun()) { Logging.info("First run!"); - if (getGroupManager().getGroups().isEmpty()) { - getGroupManager().createDefaultGroup(); + if (worldGroupManager.get().getGroups().isEmpty()) { + worldGroupManager.get().createDefaultGroup(); } - getMVIConfig().setFirstRun(false); + inventoriesConfig.get().setFirstRun(false); } - getGroupManager().checkForConflicts(null); + worldGroupManager.get().checkForConflicts(null); } }, 1L); } - /** - * @return the PlayerData object which contains data for this plugin. - */ - public ProfileDataSource getData() { - if (this.data == null) { - // Loads the data - try { - this.data = new FlatFileProfileDataSource(this); - } catch (IOException e) { // Catch errors loading the language file and exit out if found. - Logging.severe(this.getMessager().getMessage(Message.ERROR_DATA_LOAD)); - Logging.severe(e.getMessage()); - Bukkit.getPluginManager().disablePlugin(this); - return null; - } - } - return this.data; - } - /** * {@inheritDoc} */ @@ -333,33 +256,6 @@ public void setMessager(Messager messager) { this.messager = messager; } - /** - * @return The World Group manager for this plugin. - */ - public WorldGroupManager getGroupManager() { - return this.worldGroupManager; - } - - /** - * Returns the world profile container store for this plugin. - *

Player profiles for an individual world can be found here.

- * - * @return the world profile container store for this plugin. - */ - public ProfileContainerStore getWorldProfileContainerStore() { - return worldProfileContainerStore; - } - - /** - * Returns the group profile container store for this plugin. - *

Player profiles for a world group can be found here.

- * - * @return the group profile container store for this plugin. - */ - public ProfileContainerStore getGroupProfileContainerStore() { - return groupProfileContainerStore; - } - public boolean isUsingSpawnChangeEvent() { return usingSpawnChangeEvent; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java index dffbe4fd..18f7000f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -5,9 +5,9 @@ import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -public class MultiverseInventoriesPluginBinder extends JavaPluginBinder { +final class MultiverseInventoriesPluginBinder extends JavaPluginBinder { - protected MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugin) { + MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugin) { super(plugin); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java deleted file mode 100644 index a62aed92..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainer.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileKey; -import org.mvplugins.multiverse.inventories.profile.WorldGroupManager; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * Implementation of ProfileContainer using WeakHashMaps to keep memory usage to a minimum. - */ -final class WeakProfileContainer implements ProfileContainer { - - private Map> playerData = new WeakHashMap<>(); - private final MultiverseInventories inventories; - private final String name; - private final ContainerType type; - - WeakProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { - this.inventories = inventories; - this.name = name; - this.type = type; - } - - /** - * Gets the stored profiles for this player, mapped by ProfileType. - * - * @param name The name of player to get profile map for. - * @return The profile map for the given player. - */ - protected Map getPlayerData(String name) { - return this.playerData.computeIfAbsent(name, k -> new HashMap<>()); - } - - protected ProfileDataSource getDataSource() { - return this.getInventories().getData(); - } - - protected WorldGroupManager getGroupManager() { - return this.getInventories().getGroupManager(); - } - - protected ProfileContainerStore getProfileManager() { - return this.getInventories().getWorldProfileContainerStore(); - } - - protected MultiverseInventories getInventories() { - return this.inventories; - } - - @Override - public PlayerProfile getPlayerData(Player player) { - ProfileType type; - if (inventories.getMVIConfig().isUsingGameModeProfiles()) { - type = ProfileTypes.forGameMode(player.getGameMode()); - } else { - type = ProfileTypes.SURVIVAL; - } - return getPlayerData(type, player); - } - - @Override - public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { - Map profileMap = this.getPlayerData(player.getName()); - PlayerProfile playerProfile = profileMap.get(profileType); - if (playerProfile == null) { - playerProfile = getDataSource().getPlayerData(getContainerType(), - getContainerName(), profileType, player.getUniqueId()); - Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", - profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); - profileMap.put(profileType, playerProfile); - } - return playerProfile; - } - - @Override - public void addPlayerData(PlayerProfile playerProfile) { - this.getPlayerData(playerProfile.getPlayer().getName()).put(playerProfile.getProfileType(), playerProfile); - } - - @Override - public void removeAllPlayerData(OfflinePlayer player) { - this.getPlayerData(player.getName()).clear(); - this.getDataSource().removePlayerData(getContainerType(), getContainerName(), null, player.getName()); - } - - @Override - public void removePlayerData(ProfileType profileType, OfflinePlayer player) { - this.getPlayerData(player.getName()).remove(profileType); - this.getDataSource().removePlayerData(getContainerType(), getContainerName(), profileType, player.getName()); - } - - @Override - public String getContainerName() { - return name; - } - - @Override - public ContainerType getContainerType() { - return type; - } - - @Override - public void clearContainer() { - for (Map profiles : playerData.values()) { - for (PlayerProfile profile : profiles.values()) { - this.getDataSource().clearProfileCache(ProfileKey.createProfileKey(profile)); - } - } - this.playerData.clear(); - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java deleted file mode 100644 index e693b63e..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/WeakProfileContainerStore.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; - -import java.util.Map; -import java.util.WeakHashMap; - -final class WeakProfileContainerStore implements ProfileContainerStore { - - private final Map containers = new WeakHashMap<>(); - - private final MultiverseInventories inventories; - private final ContainerType containerType; - - WeakProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { - this.inventories = inventories; - this.containerType = containerType; - } - - private MultiverseInventories getInventories() { - return this.inventories; - } - - @Override - public void addContainer(ProfileContainer container) { - this.containers.put(container.getContainerName().toLowerCase(), container); - } - - @Override - public ProfileContainer getContainer(String containerName) { - ProfileContainer container = this.containers.get(containerName.toLowerCase()); - if (container == null) { - container = new WeakProfileContainer(this.getInventories(), containerName, containerType); - addContainer(container); - } - return container; - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java deleted file mode 100644 index 9ef34daa..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/YamlWorldGroupManager.java +++ /dev/null @@ -1,237 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.google.common.collect.Lists; -import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.util.DeserializationException; -import io.papermc.lib.PaperLib; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.event.EventPriority; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -final class YamlWorldGroupManager extends AbstractWorldGroupManager { - - private final String[] groupSectionComments = { - "# Multiverse-Inventories Groups", - "", - "# To ADD, DELETE, and EDIT groups use the command /mvinv group.", - "# No support will be given for those who manually edit these groups." - }; - - private final CommentedConfiguration groupsConfig; - - YamlWorldGroupManager(final MultiverseInventories inventories, final Configuration config) throws IOException { - super(inventories); - - // Check if the group config file exists. If not, create it and migrate group data. - File groupsConfigFile = new File(plugin.getDataFolder(), "groups.yml"); - boolean groupsConfigFileExists = groupsConfigFile.exists(); - boolean migrateGroups = false; - if (!groupsConfigFile.exists()) { - Logging.fine("Created groups file."); - groupsConfigFile.createNewFile(); - migrateGroups = true; - } - // Load the configuration file into memory - groupsConfig = new CommentedConfiguration(groupsConfigFile.toPath()); - groupsConfig.load(); - - if (migrateGroups) { - migrateGroups(config); - } - - groupsConfig.addComment("groups", groupSectionComments); - if (groupsConfig.get("groups") == null) { - this.getConfig().createSection("groups"); - } - - // Saves the configuration from memory to file - groupsConfig.save(); - - // Setup groups in memory - final List worldGroups = getGroupsFromConfig(); - if (worldGroups == null) { - Logging.info("No world groups have been configured!"); - Logging.info("This will cause all worlds configured for Multiverse to have separate player statistics/inventories."); - return; - } - - for (final WorldGroup worldGroup : worldGroups) { - getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); - } - } - - private void migrateGroups(final Configuration config) { - if (config == null) { - return; - } - ConfigurationSection section = config.getConfigurationSection("groups"); - if (section != null) { - getConfig().set("groups", section); - config.set("groups", null); - Logging.fine("Migrated groups to groups.yml"); - } - } - - private FileConfiguration getConfig() { - return this.groupsConfig; - } - - private List getGroupsFromConfig() { - Logging.finer("Getting world groups from config file"); - ConfigurationSection groupsSection = getConfig().getConfigurationSection("groups"); - if (groupsSection == null) { - Logging.finer("Could not find a 'groups' section in config!"); - return null; - } - Set groupNames = groupsSection.getKeys(false); - Logging.finer("Loading groups: " + groupNames.toString()); - List worldGroups = new ArrayList<>(groupNames.size()); - for (String groupName : groupNames) { - Logging.finer("Attempting to load group: " + groupName + "..."); - WorldGroup worldGroup; - try { - ConfigurationSection groupSection = - getConfig().getConfigurationSection("groups." + groupName); - if (groupSection == null) { - Logging.warning("Group: '" + groupName + "' is not formatted correctly!"); - continue; - } - worldGroup = deserializeGroup(groupName, groupSection.getValues(true)); - } catch (DeserializationException e) { - Logging.warning("Unable to load world group: " + groupName); - Logging.warning("Reason: " + e.getMessage()); - continue; - } - worldGroups.add(worldGroup); - Logging.finer("Group: " + worldGroup.getName() + " added to memory"); - } - return worldGroups; - } - - private WorldGroup deserializeGroup(final String name, final Map dataMap) - throws DeserializationException { - WorldGroup profile = new WorldGroup(this.plugin, name); - if (dataMap.containsKey("worlds")) { - Object worldListObj = dataMap.get("worlds"); - if (worldListObj == null) { - Logging.fine("No worlds for group: " + name); - } else { - if (!(worldListObj instanceof List)) { - Logging.fine("World list formatted incorrectly for world group: " + name); - } else { - final StringBuilder builder = new StringBuilder(); - for (Object worldNameObj : (List) worldListObj) { - if (worldNameObj == null) { - Logging.fine("Error with a world listed in group: " + name); - continue; - } - profile.addWorld(worldNameObj.toString(), false); - World world = Bukkit.getWorld(worldNameObj.toString()); - if (world == null) { - if (builder.length() != 0) { - builder.append(", "); - } - builder.append(worldNameObj.toString()); - } - } - if (builder.length() > 0) { - Logging.config("The following worlds for group '%s' are not loaded: %s", name, builder.toString()); - } - } - } - } - if (dataMap.containsKey("shares")) { - Object sharesListObj = dataMap.get("shares"); - if (sharesListObj instanceof List) { - profile.getShares().mergeShares(Sharables.fromList((List) sharesListObj)); - profile.getShares().removeAll(Sharables.negativeFromList((List) sharesListObj)); - } else { - Logging.warning("Shares formatted incorrectly for group: " + name); - } - } - if (dataMap.containsKey("spawn")) { - Object spawnPropsObj = dataMap.get("spawn"); - if (spawnPropsObj instanceof ConfigurationSection) { - // Le sigh, bukkit. - spawnPropsObj = ((ConfigurationSection) spawnPropsObj).getValues(true); - } - if (spawnPropsObj instanceof Map) { - Map spawnProps = (Map) spawnPropsObj; - if (spawnProps.containsKey("world")) { - profile.setSpawnWorld(spawnProps.get("world").toString()); - } - if (spawnProps.containsKey("priority")) { - EventPriority priority = EventPriority.valueOf( - spawnProps.get("priority").toString().toUpperCase()); - if (priority != null) { - profile.setSpawnPriority(priority); - } - } - } else { - Logging.warning("Spawn settings for group formatted incorrectly"); - } - } - return profile; - } - - private void updateWorldGroup(WorldGroup worldGroup) { - Logging.finer("Updating group in config: " + worldGroup.getName()); - getConfig().createSection("groups." + worldGroup.getName(), serializeWorldGroupProfile(worldGroup)); - } - - private Map serializeWorldGroupProfile(WorldGroup profile) { - Map results = new LinkedHashMap<>(); - results.put("worlds", Lists.newArrayList(profile.getWorlds())); - List sharesList = profile.getShares().toStringList(); - if (!sharesList.isEmpty()) { - results.put("shares", sharesList); - } - Map spawnProps = new LinkedHashMap(); - if (profile.getSpawnWorld() != null) { - spawnProps.put("world", profile.getSpawnWorld()); - spawnProps.put("priority", profile.getSpawnPriority().toString()); - results.put("spawn", spawnProps); - } - return results; - } - - private void removeWorldGroup(WorldGroup worldGroup) { - Logging.finer("Removing group from config: " + worldGroup.getName()); - getConfig().set("groups." + worldGroup.getName(), null); - } - - private void save() { - groupsConfig.save(); - } - - @Override - public void updateGroup(final WorldGroup worldGroup) { - super.updateGroup(worldGroup); - updateWorldGroup(worldGroup); - save(); - } - - @Override - public boolean removeGroup(WorldGroup worldGroup) { - if (super.removeGroup(worldGroup)) { - removeWorldGroup(worldGroup); - save(); - return true; - } - return false; - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index 8d548a38..c0f55b1c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -1,9 +1,10 @@ package org.mvplugins.multiverse.inventories.commands; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -19,6 +20,8 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import java.util.List; import java.util.Set; @@ -28,11 +31,19 @@ class InfoCommand extends InventoriesCommand { private final MultiverseInventories plugin; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final WorldGroupManager worldGroupManager; @Inject - InfoCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + InfoCommand( + @NotNull MVCommandManager commandManager, + @NotNull MultiverseInventories plugin, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull WorldGroupManager worldGroupManager) { super(commandManager); this.plugin = plugin; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.worldGroupManager = worldGroupManager; } @CommandAlias("mvinvinfo|mvinvi") @@ -58,14 +69,14 @@ void onInfoCommand( name = ((Player) sender).getWorld().getName(); } - ProfileContainer worldProfileContainer = this.plugin.getWorldProfileContainerStore().getContainer(name); + ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(name); plugin.getMessager().normal(Message.INFO_WORLD, sender, name); if (worldProfileContainer != null && Bukkit.getWorld(worldProfileContainer.getContainerName()) != null) { worldInfo(sender, worldProfileContainer); } else { plugin.getMessager().normal(Message.ERROR_NO_WORLD_PROFILE, sender, name); } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(name); + WorldGroup worldGroup = worldGroupManager.getGroup(name); this.plugin.getMessager().normal(Message.INFO_GROUP, sender, name); if (worldGroup != null) { this.groupInfo(sender, worldGroup); @@ -93,8 +104,7 @@ private void groupInfo(CommandSender sender, WorldGroup worldGroup) { private void worldInfo(CommandSender sender, ProfileContainer worldProfileContainer) { StringBuilder groupsString = new StringBuilder(); - List worldGroups = this.plugin.getGroupManager() - .getGroupsForWorld(worldProfileContainer.getContainerName()); + List worldGroups = worldGroupManager.getGroupsForWorld(worldProfileContainer.getContainerName()); if (worldGroups.isEmpty()) { groupsString.append("N/A"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 3c7a5b47..a86c013d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; @@ -12,6 +12,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import java.util.Collection; @@ -20,11 +21,16 @@ class ListCommand extends InventoriesCommand { private final MultiverseInventories plugin; + private final WorldGroupManager worldGroupManager; @Inject - ListCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + ListCommand( + @NotNull MVCommandManager commandManager, + @NotNull MultiverseInventories plugin, + @NotNull WorldGroupManager worldGroupManager) { super(commandManager); this.plugin = plugin; + this.worldGroupManager = worldGroupManager; } @CommandAlias("mvinvlist|mvinvl") @@ -32,7 +38,7 @@ class ListCommand extends InventoriesCommand { @CommandPermission("multiverse.inventories.list") @Description("World and Group Information") void onListCommand(@NotNull CommandSender sender) { - Collection groups = this.plugin.getGroupManager().getGroups(); + Collection groups = worldGroupManager.getGroups(); String groupsString = "N/A"; if (!groups.isEmpty()) { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index afd70136..1348a895 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.commands; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -23,11 +24,16 @@ class ToggleCommand extends InventoriesCommand { private final MultiverseInventories plugin; + private final InventoriesConfig inventoriesConfig; @Inject - ToggleCommand(MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { + ToggleCommand( + @NotNull MVCommandManager commandManager, + @NotNull MultiverseInventories plugin, + @NotNull InventoriesConfig inventoriesConfig) { super(commandManager); this.plugin = plugin; + this.inventoriesConfig = inventoriesConfig; } @CommandAlias("mvinvtoggle") @@ -53,17 +59,17 @@ void onToggleCommand( for (Sharable sharable : shares) { if (sharable.isOptional()) { foundOpt = true; - if (this.plugin.getMVIConfig().getOptionalShares().contains(sharable)) { - this.plugin.getMVIConfig().getOptionalShares().remove(sharable); + if (inventoriesConfig.getOptionalShares().contains(sharable)) { + inventoriesConfig.getOptionalShares().remove(sharable); this.plugin.getMessager().normal(Message.NOW_NOT_USING_OPTIONAL, sender, sharable.getNames()[0]); } else { - this.plugin.getMVIConfig().getOptionalShares().add(sharable); + inventoriesConfig.getOptionalShares().add(sharable); this.plugin.getMessager().normal(Message.NOW_USING_OPTIONAL, sender, sharable.getNames()[0]); } } } if (foundOpt) { - this.plugin.getMVIConfig().save(); + inventoriesConfig.save(); } else { this.plugin.getMessager().normal(Message.NO_OPTIONAL_SHARES, sender, shareName); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java index c473178b..017e9a5c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; @@ -20,13 +20,13 @@ public String getPromptText(final ConversationContext conversationContext) { @Override public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); + final WorldGroup group = worldGroupManager.getGroup(s); if (group == null) { if (s.isEmpty() || !s.matches("^[a-zA-Z0-9][a-zA-Z0-9_]*$")) { messager.normal(Message.GROUP_INVALID_NAME, sender); return this; } - final WorldGroup newGroup = plugin.getGroupManager().newEmptyGroup(s); + final WorldGroup newGroup = worldGroupManager.newEmptyGroup(s); return new GroupWorldsPrompt(plugin, sender, newGroup, new GroupSharesPrompt(plugin, sender, newGroup, Prompt.END_OF_CONVERSATION, true), true); } else { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java index a1e64b8b..ff7ba782 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -17,7 +17,7 @@ public GroupDeletePrompt(final MultiverseInventories plugin, final CommandSender @Override public String getPromptText(final ConversationContext conversationContext) { final StringBuilder builder = new StringBuilder(); - for (WorldGroup group : plugin.getGroupManager().getGroups()) { + for (WorldGroup group : worldGroupManager.getGroups()) { if (builder.length() == 0) { builder.append(ChatColor.WHITE); } else { @@ -30,11 +30,11 @@ public String getPromptText(final ConversationContext conversationContext) { @Override public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); + final WorldGroup group = worldGroupManager.getGroup(s); if (group == null) { messager.normal(Message.ERROR_NO_GROUP, sender, s); } else { - plugin.getGroupManager().removeGroup(group); + worldGroupManager.removeGroup(group); messager.normal(Message.GROUP_REMOVED, sender, s); } return Prompt.END_OF_CONVERSATION; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java index 67f1b439..0bae766e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -17,7 +17,7 @@ public GroupEditPrompt(final MultiverseInventories plugin, final CommandSender s @Override public String getPromptText(final ConversationContext conversationContext) { final StringBuilder builder = new StringBuilder(); - for (WorldGroup group : plugin.getGroupManager().getGroups()) { + for (WorldGroup group : worldGroupManager.getGroups()) { if (builder.length() == 0) { builder.append(ChatColor.WHITE); } else { @@ -30,7 +30,7 @@ public String getPromptText(final ConversationContext conversationContext) { @Override public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); + final WorldGroup group = worldGroupManager.getGroup(s); if (group == null) { messager.normal(Message.ERROR_NO_GROUP, sender, s); } else { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java index 17952186..1308957c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java index f20d8668..b6ecb217 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; @@ -47,7 +47,7 @@ public Prompt acceptInput(final ConversationContext conversationContext, final S if (s.equals("@")) { group.getShares().clear(); group.getShares().addAll(this.shares); - plugin.getGroupManager().updateGroup(group); + worldGroupManager.updateGroup(group); if (isCreating) { messager.normal(Message.GROUP_CREATION_COMPLETE, sender); } else { @@ -55,7 +55,7 @@ public Prompt acceptInput(final ConversationContext conversationContext, final S } messager.normal(Message.INFO_GROUP, sender, group.getName()); messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); - plugin.getGroupManager().checkForConflicts(sender); + worldGroupManager.checkForConflicts(sender); return nextPrompt; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java index 6050ecbf..bcb1bcaf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -54,7 +54,7 @@ public Prompt acceptInput(final ConversationContext conversationContext, final S group.removeAllWorlds(false); group.addWorlds(worlds, false); if (!isCreating) { - plugin.getGroupManager().updateGroup(group); + worldGroupManager.updateGroup(group); messager.normal(Message.GROUP_UPDATED, sender); messager.normal(Message.INFO_GROUP, sender, group.getName()); messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java index c3ed4c52..b1a00c7e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java @@ -5,10 +5,12 @@ import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; abstract class InventoriesPrompt implements Prompt { protected final MultiverseInventories plugin; + protected final WorldGroupManager worldGroupManager; protected final Messager messager; protected final CommandSender sender; @@ -16,6 +18,7 @@ abstract class InventoriesPrompt implements Prompt { this.plugin = plugin; this.messager = plugin.getMessager(); this.sender = sender; + this.worldGroupManager = this.plugin.getServiceLocator().getService(WorldGroupManager.class); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java new file mode 100644 index 00000000..474860af --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java new file mode 100644 index 00000000..e8e69681 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.config; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java index 2a397f1d..26c43d77 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.event; -import org.mvplugins.multiverse.inventories.ShareHandler; +import org.mvplugins.multiverse.inventories.listeners.ShareHandler; import org.bukkit.GameMode; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index caea8b93..ef07ad7b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.event; -import org.mvplugins.multiverse.inventories.PersistingProfile; -import org.mvplugins.multiverse.inventories.ShareHandler; +import org.mvplugins.multiverse.inventories.profile.PersistingProfile; +import org.mvplugins.multiverse.inventories.listeners.ShareHandler; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java index 71266aa3..f2a715a0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.event; -import org.mvplugins.multiverse.inventories.ShareHandler; +import org.mvplugins.multiverse.inventories.listeners.ShareHandler; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/GameModeShareHandler.java similarity index 83% rename from src/main/java/org/mvplugins/multiverse/inventories/GameModeShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/GameModeShareHandler.java index c8864648..39cfd96d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/GameModeShareHandler.java @@ -1,6 +1,8 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.event.GameModeChangeShareHandlingEvent; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.ProfileType; @@ -23,25 +25,23 @@ final class GameModeShareHandler extends ShareHandler { private final ProfileType fromType; private final ProfileType toType; private final String world; - private final ProfileContainer worldProfileContainer; private final List worldGroups; GameModeShareHandler(MultiverseInventories inventories, Player player, - GameMode fromGameMode, GameMode toGameMode) { + GameMode fromGameMode, GameMode toGameMode) { super(inventories, player); this.fromGameMode = fromGameMode; this.toGameMode = toGameMode; this.fromType = ProfileTypes.forGameMode(fromGameMode); this.toType = ProfileTypes.forGameMode(toGameMode); this.world = player.getWorld().getName(); - this.worldProfileContainer = inventories.getWorldProfileContainerStore().getContainer(world); this.worldGroups = getAffectedWorldGroups(); prepareProfiles(); } private List getAffectedWorldGroups() { - return this.inventories.getGroupManager().getGroupsForWorld(world); + return worldGroupManager.getGroupsForWorld(world); } @Override @@ -53,7 +53,7 @@ private void prepareProfiles() { Logging.finer("=== " + player.getName() + " changing game mode from: " + fromType + " to: " + toType + " for world: " + world + " ==="); - setAlwaysWriteProfile(worldProfileContainer.getPlayerData(fromType, player)); + setAlwaysWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player)); if (isPlayerAffectedByChange()) { addProfiles(); @@ -78,7 +78,8 @@ private void addProfiles() { worldGroups.forEach(this::addProfilesForWorldGroup); } else { Logging.finer("No groups for world."); - addReadProfile(worldProfileContainer.getPlayerData(toType, player), Sharables.allOf()); + addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), + Sharables.allOf()); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java similarity index 72% rename from src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index 70999bc1..66875ef5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; @@ -7,9 +7,18 @@ import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.migration.ImportManager; +import org.mvplugins.multiverse.inventories.profile.PersistingProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; @@ -51,15 +60,31 @@ public class InventoriesListener implements Listener { private final MultiverseInventories inventories; + private final InventoriesConfig config; private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ImportManager importManager; private List currentGroups; private Location spawnLoc = null; @Inject - InventoriesListener(@NotNull MultiverseInventories inventories, @NotNull WorldManager worldManager) { + InventoriesListener( + @NotNull MultiverseInventories inventories, InventoriesConfig config, + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ImportManager importManager) { this.inventories = inventories; + this.config = config; this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.importManager = importManager; } /** @@ -69,13 +94,38 @@ public class InventoriesListener implements Listener { */ @EventHandler public void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { - event.appendDebugInfo(this.inventories.getVersionInfo()); + event.appendDebugInfo(getDebugInfo()); File configFile = new File(this.inventories.getDataFolder(), "config.yml"); File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); event.putDetailedDebugInfo("multiverse-inventories/config.yml", configFile); event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); } + /** + * Builds a String containing Multiverse-Inventories' version info. + * + * @return The version info. + */ + public String getDebugInfo() { + StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + inventories.getDescription().getVersion() + '\n' + + "[Multiverse-Inventories] === Settings ===" + '\n' + + "[Multiverse-Inventories] First Run: " + config.isFirstRun() + '\n' + + "[Multiverse-Inventories] Using Bypass: " + config.isUsingBypass() + '\n' + + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.isDefaultingUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.usingLoggingSaveLoad() + '\n' + + "[Multiverse-Inventories] Using GameMode Profiles: " + config.isUsingGameModeProfiles() + '\n' + + "[Multiverse-Inventories] === Shares ===" + '\n' + + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.usingOptionalsForUngrouped() + '\n' + + "[Multiverse-Inventories] Enabled Optionals: " + config.getOptionalShares() + '\n' + + "[Multiverse-Inventories] === Groups ===" + '\n'); + + for (WorldGroup group : worldGroupManager.getGroups()) { + versionInfo.append("[Multiverse-Inventories] ").append(group.toString()).append('\n'); + } + + return versionInfo.toString(); + } + @EventHandler public void onDebugModeChange(MVDebugModeEvent event) { Logging.setDebugLevel(event.getLevel()); @@ -101,9 +151,9 @@ public void configReload(MVConfigReloadEvent event) { public void pluginEnable(PluginEnableEvent event) { try { if (event.getPlugin() instanceof MultiInv) { - this.inventories.getImportManager().hookMultiInv((MultiInv) event.getPlugin()); + importManager.hookMultiInv((MultiInv) event.getPlugin()); } else if (event.getPlugin() instanceof WorldInventories) { - this.inventories.getImportManager().hookWorldInventories((WorldInventories) event.getPlugin()); + importManager.hookWorldInventories((WorldInventories) event.getPlugin()); } } catch (NoClassDefFoundError ignore) { } @@ -118,9 +168,9 @@ public void pluginEnable(PluginEnableEvent event) { public void pluginDisable(PluginDisableEvent event) { try { if (event.getPlugin() instanceof MultiInv) { - this.inventories.getImportManager().unHookMultiInv(); + importManager.unHookMultiInv(); } else if (event.getPlugin() instanceof WorldInventories) { - this.inventories.getImportManager().unHookWorldInventories(); + importManager.unHookWorldInventories(); } } catch (NoClassDefFoundError ignore) { } @@ -135,13 +185,13 @@ public void playerPreLogin(AsyncPlayerPreLoginEvent event) { Logging.finer("Loading global profile for Player{name:'%s', uuid:'%s'}.", event.getName(), event.getUniqueId()); - GlobalProfile globalProfile = inventories.getData().getGlobalProfile(event.getName(), event.getUniqueId()); + GlobalProfile globalProfile = profileDataSource.getGlobalProfile(event.getName(), event.getUniqueId()); if (!globalProfile.getLastKnownName().equalsIgnoreCase(event.getName())) { // Data must be migrated Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", event.getUniqueId(), globalProfile.getLastKnownName(), event.getName()); try { - inventories.getData().migratePlayerData(globalProfile.getLastKnownName(), event.getName(), + profileDataSource.migratePlayerData(globalProfile.getLastKnownName(), event.getName(), event.getUniqueId(), true); } catch (IOException e) { Logging.severe("An error occurred while trying to migrate playerdata."); @@ -149,7 +199,7 @@ public void playerPreLogin(AsyncPlayerPreLoginEvent event) { } globalProfile.setLastKnownName(event.getName()); - inventories.getData().updateGlobalProfile(globalProfile); + profileDataSource.updateGlobalProfile(globalProfile); Logging.info("Migration complete!"); } } @@ -162,13 +212,17 @@ public void playerPreLogin(AsyncPlayerPreLoginEvent event) { @EventHandler public void playerJoin(final PlayerJoinEvent event) { final Player player = event.getPlayer(); - final GlobalProfile globalProfile = inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId()); + final GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId()); final String world = globalProfile.getLastWorld(); - if (inventories.getMVIConfig().usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { - ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile(Sharables.allOf(), - inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); + if (config.usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { + ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile( + Sharables.allOf(), + profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(world) + .getPlayerData(player) + )); } - inventories.getData().setLoadOnLogin(player.getName(), false); + profileDataSource.setLoadOnLogin(player.getName(), false); verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); } @@ -181,24 +235,28 @@ public void playerJoin(final PlayerJoinEvent event) { public void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); - inventories.getData().updateLastWorld(player.getName(), world); - if (inventories.getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile(Sharables.allOf(), - inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); - inventories.getData().setLoadOnLogin(player.getName(), true); + profileDataSource.updateLastWorld(player.getName(), world); + if (config.usingLoggingSaveLoad()) { + ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile( + Sharables.allOf(), + profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(world) + .getPlayerData(player) + )); + profileDataSource.setLoadOnLogin(player.getName(), true); } SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); } private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { if (globalProfile.getLastWorld() == null) { - inventories.getData().updateLastWorld(player.getName(), world); + profileDataSource.updateLastWorld(player.getName(), world); } else { if (!world.equals(globalProfile.getLastWorld())) { Logging.fine("Player did not spawn in the world they were last reported to be in!"); new WorldChangeShareHandler(this.inventories, player, globalProfile.getLastWorld(), world).handleSharing(); - inventories.getData().updateLastWorld(player.getName(), world); + profileDataSource.updateLastWorld(player.getName(), world); } } } @@ -210,7 +268,7 @@ private void verifyCorrectWorld(Player player, String world, GlobalProfile globa */ @EventHandler(priority = EventPriority.MONITOR) public void playerGameModeChange(PlayerGameModeChangeEvent event) { - if (event.isCancelled() || !inventories.getMVIConfig().isUsingGameModeProfiles()) { + if (event.isCancelled() || !config.isUsingGameModeProfiles()) { return; } Player player = event.getPlayer(); @@ -240,7 +298,7 @@ public void playerChangedWorld(PlayerChangedWorldEvent event) { } new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); - inventories.getData().updateLastWorld(player.getName(), toWorld.getName()); + profileDataSource.updateLastWorld(player.getName(), toWorld.getName()); } /** @@ -252,7 +310,7 @@ public void playerChangedWorld(PlayerChangedWorldEvent event) { public void playerTeleport(PlayerTeleportEvent event) { if (event.isCancelled() || event.getFrom().getWorld().equals(event.getTo().getWorld()) - || !this.inventories.getMVIConfig().getOptionalShares().contains(Sharables.LAST_LOCATION)) { + || !config.getOptionalShares().contains(Sharables.LAST_LOCATION)) { return; } @@ -272,18 +330,18 @@ public void playerTeleport(PlayerTeleportEvent event) { public void playerDeath(PlayerDeathEvent event) { Logging.finer("=== Handling PlayerDeathEvent for: " + event.getEntity().getName() + " ==="); String deathWorld = event.getEntity().getWorld().getName(); - ProfileContainer worldProfileContainer = this.inventories.getWorldProfileContainerStore().getContainer(deathWorld); + ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(deathWorld); PlayerProfile profile = worldProfileContainer.getPlayerData(event.getEntity()); profile.set(Sharables.LEVEL, event.getNewLevel()); profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); - this.inventories.getData().updatePlayerData(profile); - for (WorldGroup worldGroup : this.inventories.getGroupManager().getGroupsForWorld(deathWorld)) { + profileDataSource.updatePlayerData(profile); + for (WorldGroup worldGroup : worldGroupManager.getGroupsForWorld(deathWorld)) { profile = worldGroup.getGroupProfileContainer().getPlayerData(event.getEntity()); profile.set(Sharables.LEVEL, event.getNewLevel()); profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); - this.inventories.getData().updatePlayerData(profile); + profileDataSource.updatePlayerData(profile); } Logging.finer("=== Finished handling PlayerDeathEvent for: " + event.getEntity().getName() + "! ==="); } @@ -299,7 +357,7 @@ public void playerRespawn(PlayerRespawnEvent event) { Bukkit.getScheduler().scheduleSyncDelayedTask(inventories, new Runnable() { public void run() { verifyCorrectWorld(player, player.getWorld().getName(), - inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId())); + profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId())); } }, 2L); } @@ -313,8 +371,7 @@ public void run() { public void lowestPriorityRespawn(PlayerRespawnEvent event) { if (!event.isBedSpawn()) { World world = event.getPlayer().getWorld(); - this.currentGroups = this.inventories.getGroupManager() - .getGroupsForWorld(world.getName()); + this.currentGroups = worldGroupManager.getGroupsForWorld(world.getName()); this.handleRespawn(event, EventPriority.LOWEST); } } @@ -427,8 +484,8 @@ public void entityPortal(EntityPortalEvent event) { return; } - List fromGroups = inventories.getGroupManager().getGroupsForWorld(fromWorld.getName()); - List toGroups = inventories.getGroupManager().getGroupsForWorld(toWorld.getName()); + List fromGroups = worldGroupManager.getGroupsForWorld(fromWorld.getName()); + List toGroups = worldGroupManager.getGroupsForWorld(toWorld.getName()); // We only care about the groups that have the inventory sharable fromGroups = fromGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).collect(Collectors.toList()); toGroups = toGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).collect(Collectors.toList()); @@ -452,10 +509,11 @@ public void worldUnload(WorldUnloadEvent event) { Logging.finer("Clearing data for world/groups container with '%s' world.", unloadWorldName); - ProfileContainer fromWorldProfileContainer = this.inventories.getWorldProfileContainerStore().getContainer(unloadWorldName); + ProfileContainer fromWorldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(unloadWorldName); fromWorldProfileContainer.clearContainer(); - List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(unloadWorldName); + List fromGroups = worldGroupManager.getGroupsForWorld(unloadWorldName); for (WorldGroup fromGroup : fromGroups) { fromGroup.getGroupProfileContainer().clearContainer(); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java similarity index 86% rename from src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java index b7e28fe0..e84df0b4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java @@ -1,8 +1,14 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Shares; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -19,12 +25,18 @@ public abstract class ShareHandler { protected final MultiverseInventories inventories; protected final Player player; + protected final WorldGroupManager worldGroupManager; + protected final ProfileContainerStore worldProfileContainerStore; final AffectedProfiles affectedProfiles; ShareHandler(MultiverseInventories inventories, Player player) { this.inventories = inventories; this.player = player; this.affectedProfiles = new AffectedProfiles(); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.worldProfileContainerStore = inventories.getServiceLocator() + .getService(ProfileContainerStoreProvider.class) + .getStore(ContainerType.WORLD); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandlingUpdater.java similarity index 85% rename from src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandlingUpdater.java index 555f3aed..f9880759 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandlingUpdater.java @@ -1,6 +1,10 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.PersistingProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -53,7 +57,7 @@ private void updateProfile() { + " (" + profile.getProfile().getProfileType() + ")" + " for player " + profile.getProfile().getPlayer().getName()); } - inventories.getData().updatePlayerData(profile.getProfile()); + inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(profile.getProfile()); } private void updatePlayer() { @@ -82,13 +86,14 @@ private void updatePlayer() { } private boolean isSharableUsed(Sharable sharable) { + var config = inventories.getServiceLocator().getService(InventoriesConfig.class); if (sharable.isOptional()) { - if (!inventories.getMVIConfig().getOptionalShares().contains(sharable)) { + if (!config.getOptionalShares().contains(sharable)) { Logging.finest("Ignoring optional share: " + sharable.getNames()[0]); return false; } if (profile.getProfile().getContainerType() == ContainerType.WORLD - && !inventories.getMVIConfig().usingOptionalsForUngrouped()) { + && !config.usingOptionalsForUngrouped()) { Logging.finest("Ignoring optional share '" + sharable.getNames()[0] + "' for ungrouped world!"); return false; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/SingleShareWriter.java similarity index 51% rename from src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/SingleShareWriter.java index b83de310..0a98f111 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/SingleShareWriter.java @@ -1,7 +1,12 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharable; /** @@ -9,7 +14,7 @@ * * @param The sharable type. */ -public class SingleShareWriter { +final class SingleShareWriter { public static SingleShareWriter of(MultiverseInventories inventories, Player player, Sharable sharable) { return new SingleShareWriter(inventories, player, sharable); @@ -26,20 +31,23 @@ private SingleShareWriter(MultiverseInventories inventories, Player player, Shar } public void write(T value) { - if (sharable.isOptional() && !this.inventories.getMVIConfig().getOptionalShares().contains(sharable)) { + if (sharable.isOptional() && + !inventories.getServiceLocator().getService(InventoriesConfig.class).getOptionalShares().contains(sharable)) { Logging.finer("Skipping write for optional share: " + sharable); return; } Logging.finer("Writing single share: " + sharable.getNames()[0]); String worldName = this.player.getWorld().getName(); - this.inventories.getWorldProfileContainerStore() + var profileContainerStoreProvider = this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class); + profileContainerStoreProvider.getStore(ContainerType.WORLD) .getContainer(worldName) .getPlayerData(this.player) .set(this.sharable, value); - this.inventories.getGroupManager().getGroupsForWorld(worldName).forEach(worldGroup -> { - worldGroup.getGroupProfileContainer().getPlayerData(this.player) - .set(this.sharable, value); - }); + this.inventories.getServiceLocator().getService(WorldGroupManager.class) + .getGroupsForWorld(worldName) + .forEach(worldGroup -> + worldGroup.getGroupProfileContainer().getPlayerData(this.player) + .set(this.sharable, value)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java similarity index 94% rename from src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java index 64cf3eb5..0e72763c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; import org.bukkit.Location; @@ -10,13 +10,12 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerSpawnChangeEvent; import org.bukkit.event.player.PlayerSpawnChangeEvent.Cause; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.Sharables; import javax.annotation.Nullable; -import java.util.List; -public class SpawnChangeListener implements Listener { +public final class SpawnChangeListener implements Listener { private final MultiverseInventories inventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java similarity index 95% rename from src/main/java/org/mvplugins/multiverse/inventories/WorldChangeShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java index e2e1ea75..7f3a4ae3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java @@ -1,6 +1,8 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.event.WorldChangeShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; @@ -36,7 +38,7 @@ final class WorldChangeShareHandler extends ShareHandler { } private List getAffectedWorldGroups(String world) { - return this.inventories.getGroupManager().getGroupsForWorld(world); + return worldGroupManager.getGroupsForWorld(world); } @Override @@ -65,7 +67,7 @@ private PlayerProfile getWorldPlayerProfile(String world, Player player) { } private ProfileContainer getWorldProfile(String world) { - return inventories.getWorldProfileContainerStore().getContainer(world); + return worldProfileContainerStore.getContainer(world); } private boolean isPlayerAffectedByChange() { @@ -189,7 +191,7 @@ private PlayerProfile getToWorldPlayerData() { } private ProfileContainer getToWorldProfileContainer() { - return inventories.getWorldProfileContainerStore().getContainer(toWorld); + return worldProfileContainerStore.getContainer(toWorld); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java index a4fb2d58..40d78e89 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java @@ -1,5 +1,7 @@ package org.mvplugins.multiverse.inventories.locale; +import org.jvnet.hk2.annotations.Contract; + import java.util.Locale; import java.util.Set; @@ -8,6 +10,7 @@ * * This interface describes a Multiverse-MessageProvider that only loads locales when they're needed. */ +@Contract public interface LazyLocaleMessageProvider extends MessageProvider { /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java index 610d84ff..dc32aa43 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java @@ -1,5 +1,7 @@ package org.mvplugins.multiverse.inventories.locale; +import org.jvnet.hk2.annotations.Contract; + import java.util.List; import java.util.Locale; @@ -8,6 +10,7 @@ * * This interface describes a Multiverse-MessageProvider. */ +@Contract public interface MessageProvider { /** * The default locale. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java index 2f4efdc4..5f6c2e0a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java @@ -1,12 +1,14 @@ package org.mvplugins.multiverse.inventories.locale; import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Contract; import java.util.List; /** * This interface describes a Messager which sends messages to CommandSenders. */ +@Contract public interface Messager extends MessageProvider { /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java index 1511eb8c..8b01736b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java @@ -1,6 +1,9 @@ package org.mvplugins.multiverse.inventories.migration; import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.migration.multiinv.MultiInvImporter; import org.mvplugins.multiverse.inventories.migration.worldinventories.WorldInventoriesImporter; @@ -10,13 +13,15 @@ /** * Manages the import heplers for other similar plugins. */ +@Service public class ImportManager { private MultiInvImporter multiInvImporter = null; private WorldInventoriesImporter worldInventoriesImporter = null; - private MultiverseInventories inventories; + private final MultiverseInventories inventories; - public ImportManager(MultiverseInventories inventories) { + @Inject + public ImportManager(@NotNull MultiverseInventories inventories) { this.inventories = inventories; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java index b597d12f..03988746 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.migration.multiinv; -import org.mvplugins.multiverse.inventories.PlayerStats; +import org.mvplugins.multiverse.inventories.util.PlayerStats; import org.bukkit.OfflinePlayer; import org.bukkit.configuration.file.YamlConfiguration; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; @@ -12,8 +12,8 @@ */ public class MIPlayerFileLoader { - private YamlConfiguration playerFile; - private File file; + private final YamlConfiguration playerFile; + private final File file; public MIPlayerFileLoader(MultiInv plugin, OfflinePlayer player, String group) { // Find and load configuration file for the player diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java index da976987..8770201a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java @@ -2,10 +2,15 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.migration.DataImporter; import org.mvplugins.multiverse.inventories.migration.MigrationException; @@ -26,12 +31,20 @@ */ public class MultiInvImporter implements DataImporter { - private MultiInv miPlugin; - private MultiverseInventories inventories; + private final MultiInv miPlugin; + private final InventoriesConfig config; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + private final ProfileContainerStore worldProfileContainerStore; public MultiInvImporter(MultiverseInventories inventories, MultiInv miPlugin) { - this.inventories = inventories; this.miPlugin = miPlugin; + this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.worldProfileContainerStore = inventories.getServiceLocator() + .getService(ProfileContainerStoreProvider.class) + .getStore(ContainerType.WORLD); } /** @@ -61,23 +74,23 @@ public void importData() throws MigrationException { throw new MigrationException("There is no data to import from MultiInv!"); } if (!miGroupMap.isEmpty()) { - WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); + WorldGroup defaultWorldGroup = worldGroupManager.getDefaultGroup(); if (defaultWorldGroup != null) { - this.inventories.getGroupManager().removeGroup(defaultWorldGroup); + worldGroupManager.removeGroup(defaultWorldGroup); Logging.info("Removed automatically created world group in favor of imported groups."); } } for (Map.Entry groupEntry : miGroupMap.entrySet()) { - WorldGroup worldGroup = this.inventories.getGroupManager().getGroup(groupEntry.getValue()); + WorldGroup worldGroup = worldGroupManager.getGroup(groupEntry.getValue()); if (worldGroup == null) { - worldGroup = this.inventories.getGroupManager().newEmptyGroup(groupEntry.getValue()); + worldGroup = worldGroupManager.newEmptyGroup(groupEntry.getValue()); worldGroup.getShares().mergeShares(Sharables.allOf()); Logging.info("Importing group: " + groupEntry.getValue()); - this.inventories.getGroupManager().updateGroup(worldGroup); + worldGroupManager.updateGroup(worldGroup); } worldGroup.addWorld(groupEntry.getValue()); } - this.inventories.getMVIConfig().save(); + config.save(); for (OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) { Logging.info("Processing MultiInv data for player: " + player.getName()); @@ -114,16 +127,15 @@ private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader String dataName, ContainerType type) { PlayerProfile playerProfile; if (type.equals(ContainerType.GROUP)) { - WorldGroup group = this.inventories.getGroupManager() - .getGroup(dataName); + WorldGroup group = worldGroupManager.getGroup(dataName); if (group == null) { Logging.warning("Could not import player data for group: " + dataName); return; } playerProfile = group.getGroupProfileContainer().getPlayerData(ProfileTypes.SURVIVAL, player); } else { - playerProfile = this.inventories.getWorldProfileContainerStore() - .getContainer(dataName).getPlayerData(ProfileTypes.SURVIVAL, player); + playerProfile = worldProfileContainerStore.getContainer(dataName) + .getPlayerData(ProfileTypes.SURVIVAL, player); } MIInventoryInterface inventoryInterface = playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); @@ -135,7 +147,7 @@ private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader playerProfile.set(Sharables.TOTAL_EXPERIENCE, playerFileLoader.getTotalExperience()); playerProfile.set(Sharables.LEVEL, playerFileLoader.getLevel()); playerProfile.set(Sharables.FOOD_LEVEL, playerFileLoader.getHunger()); - this.inventories.getData().updatePlayerData(playerProfile); + profileDataSource.updatePlayerData(playerProfile); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java index 7b5ff5ea..a97d9306 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java @@ -2,10 +2,16 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.WorldGroup; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.migration.DataImporter; import org.mvplugins.multiverse.inventories.migration.MigrationException; @@ -31,12 +37,20 @@ */ public class WorldInventoriesImporter implements DataImporter { - private WorldInventories wiPlugin; - private MultiverseInventories inventories; + private final WorldInventories wiPlugin; + private final InventoriesConfig config; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + private final ProfileContainerStore worldProfileContainerStore; public WorldInventoriesImporter(MultiverseInventories inventories, WorldInventories wiPlugin) { - this.inventories = inventories; this.wiPlugin = wiPlugin; + this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.worldProfileContainerStore = inventories.getServiceLocator() + .getService(ProfileContainerStoreProvider.class) + .getStore(ContainerType.WORLD); } /** @@ -75,16 +89,16 @@ public void importData() throws MigrationException { } if (!wiGroups.isEmpty()) { - WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); + WorldGroup defaultWorldGroup = worldGroupManager.getDefaultGroup(); if (defaultWorldGroup != null) { - this.inventories.getGroupManager().removeGroup(defaultWorldGroup); + worldGroupManager.removeGroup(defaultWorldGroup); Logging.info("Removed automatically created world group in favor of imported groups."); } } this.createGroups(wiGroups); Set noGroupWorlds = this.getWorldsWithoutGroups(); - this.inventories.getMVIConfig().save(); + config.save(); OfflinePlayer[] offlinePlayers = Bukkit.getServer().getOfflinePlayers(); Logging.info("Processing data for " + offlinePlayers.length + " players. The larger than number, the longer" @@ -95,7 +109,7 @@ public void importData() throws MigrationException { Logging.finer("(" + playerCount + "/" + offlinePlayers.length + ")Processing WorldInventories data for player: " + player.getName()); for (Group wiGroup : wiGroups) { - WorldGroup worldGroup = inventories.getGroupManager().getGroup(wiGroup.getName()); + WorldGroup worldGroup = worldGroupManager.getGroup(wiGroup.getName()); if (worldGroup == null) { Logging.finest("Could not import player data for WorldInventories group: " + wiGroup.getName() + " because there is no Multiverse-Inventories group by that name."); @@ -118,7 +132,7 @@ private void createGroups(List wiGroups) { Logging.warning("Group '" + wiGroup.getName() + "' has no worlds." + " You may need to add these manually!"); } - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup(wiGroup.getName()); + WorldGroup newGroup = worldGroupManager.newEmptyGroup(wiGroup.getName()); for (String worldName : wiGroup.getWorlds()) { newGroup.addWorld(worldName); } @@ -136,7 +150,7 @@ private void createGroups(List wiGroups) { Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); } - this.inventories.getGroupManager().updateGroup(newGroup); + worldGroupManager.updateGroup(newGroup); Logging.info("Created Multiverse-Inventories group: " + wiGroup.getName()); } } @@ -144,9 +158,9 @@ private void createGroups(List wiGroups) { private Set getWorldsWithoutGroups() { Set noGroupWorlds = new LinkedHashSet<>(); for (World world : Bukkit.getWorlds()) { - if (this.inventories.getGroupManager().getGroupsForWorld(world.getName()).isEmpty()) { + if (worldGroupManager.getGroupsForWorld(world.getName()).isEmpty()) { Logging.fine("Added ungrouped world for importing."); - ProfileContainer container = this.inventories.getWorldProfileContainerStore().getContainer(world.getName()); + ProfileContainer container = worldProfileContainerStore.getContainer(world.getName()); noGroupWorlds.add(container); } } @@ -169,7 +183,7 @@ private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer playerProfile.set(Sharables.EXHAUSTION, wiStats.getExhaustion()); playerProfile.set(Sharables.FOOD_LEVEL, wiStats.getFoodLevel()); } - this.inventories.getData().updatePlayerData(playerProfile); + profileDataSource.updatePlayerData(playerProfile); Logging.finest("Player's data imported successfully for group: " + profileContainer.getContainerName()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PersistingProfile.java similarity index 90% rename from src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/PersistingProfile.java index dc920fcf..ee543114 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PersistingProfile.java @@ -1,6 +1,5 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.profile; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.share.Shares; /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index dbf8bcef..8fa7ff81 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,14 +1,247 @@ package org.mvplugins.multiverse.inventories.profile; +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.bukkit.configuration.InvalidConfigurationException; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.util.DataStrings; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.share.ProfileEntry; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.SharableEntry; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import net.minidev.json.JSONObject; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import java.io.File; import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; /** * A source for updating and retrieving player profiles via persistence. */ -public interface ProfileDataSource { +@Service +public final class ProfileDataSource { + + private static final String JSON = ".json"; + + private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); + + private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); + + // TODO these probably need configurable max sizes + private final Cache profileCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(1000) + .build(); + private final Cache globalProfileCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(500) + .build(); + + private final File worldFolder; + private final File groupFolder; + private final File playerFolder; + + @Inject + ProfileDataSource(MultiverseInventories plugin) throws IOException { + // Make the data folders + plugin.getDataFolder().mkdirs(); + + // Check if the data file exists. If not, create it. + this.worldFolder = new File(plugin.getDataFolder(), "worlds"); + if (!this.worldFolder.exists()) { + if (!this.worldFolder.mkdirs()) { + throw new IOException("Could not create world folder!"); + } + } + this.groupFolder = new File(plugin.getDataFolder(), "groups"); + if (!this.groupFolder.exists()) { + if (!this.groupFolder.mkdirs()) { + throw new IOException("Could not create group folder!"); + } + } + this.playerFolder = new File(plugin.getDataFolder(), "players"); + if (!this.playerFolder.exists()) { + if (!this.playerFolder.mkdirs()) { + throw new IOException("Could not create player folder!"); + } + } + } + + private FileConfiguration waitForConfigHandle(File file) { + Future future = fileIOExecutorService.submit(new ConfigLoader(file)); + while (true) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + } + + private static FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(true); + jsonConfiguration.load(file); + return jsonConfiguration; + } + + private static class ConfigLoader implements Callable { + private final File file; + + private ConfigLoader(File file) { + this.file = file; + } + + @Override + public FileConfiguration call() throws Exception { + return getConfigHandleNow(file); + } + } + + private File getFolder(ContainerType type, String folderName) { + File folder; + switch (type) { + case GROUP: + folder = new File(this.groupFolder, folderName); + break; + case WORLD: + folder = new File(this.worldFolder, folderName); + break; + default: + folder = new File(this.worldFolder, folderName); + break; + } + + if (!folder.exists()) { + folder.mkdirs(); + } + return folder; + } + + /** + * Retrieves the data file for a player based on a given world/group name, creating it if necessary. + * + * @param type Indicates whether data is for group or world. + * @param dataName The name of the group or world. + * @param playerName The name of the player. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + private File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { + File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); + if (!jsonPlayerFile.exists()) { + try { + jsonPlayerFile.createNewFile(); + } catch (IOException e) { + throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() + + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName + + " may not be saved.", e); + } + } + Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", + jsonPlayerFile.getPath(), type, dataName, playerName); + return jsonPlayerFile; + } + + /** + * Retrieves the data file for a player for their global data, creating it if necessary. + * + * @param fileName The name of the file (player name or UUID) without extension. + * @param createIfMissing If true, the file will be created it it does not exist. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + private File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { + File jsonPlayerFile = new File(playerFolder, fileName + JSON); + if (createIfMissing && !jsonPlayerFile.exists()) { + try { + jsonPlayerFile.createNewFile(); + } catch (IOException e) { + throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " + + "There may be issues with " + fileName + "'s metadata", e); + } + } + return jsonPlayerFile; + } + + private void queueWrite(PlayerProfile profile) { + fileIOExecutorService.submit(new FileWriter(profile.clone())); + } + + private class FileWriter implements Callable { + private final PlayerProfile profile; + + private FileWriter(PlayerProfile profile) { + this.profile = profile; + } + + @Override + public Void call() throws Exception { + processProfileWrite(profile); + return null; + } + } + + private void processProfileWrite(PlayerProfile playerProfile) { + try { + File playerFile = this.getPlayerFile(playerProfile.getContainerType(), + playerProfile.getContainerName(), playerProfile.getPlayer().getName()); + FileConfiguration playerData = getConfigHandleNow(playerFile); + playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); + Logging.severe(e.getMessage()); + } + } catch (final Exception e) { + Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); + } + } + + private Map serializePlayerProfile(PlayerProfile playerProfile) { + Map playerData = new LinkedHashMap(); + JSONObject jsonStats = new JSONObject(); + for (SharableEntry entry : playerProfile) { + if (entry.getValue() != null) { + if (entry.getSharable().getSerializer() == null) { + continue; + } + Sharable sharable = entry.getSharable(); + if (sharable.getProfileEntry().isStat()) { + jsonStats.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(entry.getValue())); + } else { + playerData.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(entry.getValue())); + } + } + } + if (!jsonStats.isEmpty()) { + playerData.put(DataStrings.PLAYER_STATS, jsonStats); + } + return playerData; + } /** * Updates the persisted data for a player for a specific profile. @@ -16,7 +249,42 @@ public interface ProfileDataSource { * * @param playerProfile The profile for the player that is being updated. */ - void updatePlayerData(PlayerProfile playerProfile); + public void updatePlayerData(PlayerProfile playerProfile) { + queueWrite(playerProfile); + } + + private PlayerProfile getPlayerData(ProfileKey key) { + PlayerProfile cached = profileCache.getIfPresent(key); + if (cached != null) { + return cached; + } + File playerFile = null; + try { + playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); + } catch (IOException e) { + e.printStackTrace(); + // Return an empty profile + return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), + Bukkit.getOfflinePlayer(key.getPlayerUUID())); + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + if (convertConfig(playerData)) { + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + key.getPlayerName() + + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); + Logging.severe(e.getMessage()); + } + } + ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); + if (section == null) { + section = playerData.createSection(key.getProfileType().getName()); + } + PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); + profileCache.put(key, result); + return result; + } /** * Retrieves a PlayerProfile from the data source. @@ -28,7 +296,94 @@ public interface ProfileDataSource { * @return The player as returned from data. If no data was found, a new PlayerProfile will be * created. */ - PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); + public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { + return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); + } + + private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { + PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), + pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); + for (Object keyObj : playerData.keySet()) { + String key = keyObj.toString(); + if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { + final Object statsObject = playerData.get(key); + if (statsObject instanceof String) { + parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); + } else { + if (statsObject instanceof Map) { + parsePlayerStatsIntoProfile((Map) statsObject, profile); + } else { + Logging.warning("Could not parse stats for " + pKey.getPlayerName()); + } + } + } else { + if (playerData.get(key) == null) { + Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + try { + Sharable sharable = ProfileEntry.lookup(false, key); + if (sharable == null) { + Logging.fine("Player fileTag '" + key + "' is unrecognized!"); + continue; + } + profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); + } catch (Exception e) { + Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); + Logging.getLogger().log(Level.FINE, "Exception: ", e); + e.printStackTrace(); + } + } + } + Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); + return profile; + } + + private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { + for (Object key : stats.keySet()) { + Sharable sharable = ProfileEntry.lookup(true, key.toString()); + if (sharable != null) { + profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); + } else { + Logging.warning("Could not parse stat: '" + key + "' for player '" + + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" + + profile.getContainerName() + "'"); + } + } + } + + private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { + if (stats.isEmpty()) { + return; + } + JSONObject jsonStats = null; + try { + jsonStats = (JSONObject) JSON_PARSER.parse(stats); + } catch (ParseException | ClassCastException e) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); + } + if (jsonStats == null) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "'"); + return; + } + parsePlayerStatsIntoProfile(jsonStats, profile); + } + + // TODO Remove this conversion + private boolean convertConfig(FileConfiguration config) { + ConfigurationSection section = config.getConfigurationSection("playerData"); + if (section != null) { + config.set(ProfileTypes.SURVIVAL.getName(), section); + config.set(ProfileTypes.CREATIVE.getName(), section); + config.set(ProfileTypes.ADVENTURE.getName(), section); + config.set("playerData", null); + Logging.finer("Migrated old player data to new multi-profile format"); + return true; + } + return false; + } /** * Removes the persisted data for a player for a specific profile. @@ -40,7 +395,52 @@ public interface ProfileDataSource { * @param playerName The name of the player whose data is being removed. * @return True if successfully removed. */ - boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName); + public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { + if (profileType == null) { + try { + File playerFile = getPlayerFile(containerType, dataName, playerName); + return playerFile.delete(); + } catch (IOException ignore) { + Logging.warning("Attempted to delete file that did not exist for player " + playerName + + " in " + containerType.name().toLowerCase() + " " + dataName); + return false; + } + } else { + File playerFile; + try { + playerFile = getPlayerFile(containerType, dataName, playerName); + } catch (IOException e) { + Logging.warning("Attempted to delete " + playerName + "'s data for " + + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() + + " " + dataName + " but the file did not exist."); + return false; + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + playerData.set(profileType.getName(), null); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not delete data for player: " + playerName + + " for " + containerType.toString() + ": " + dataName); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + } + + private Map convertSection(ConfigurationSection section) { + Map resultMap = new HashMap(); + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj instanceof ConfigurationSection) { + resultMap.put(key, convertSection((ConfigurationSection) obj)); + } else { + resultMap.put(key, obj); + } + } + return resultMap; + } /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -50,7 +450,9 @@ public interface ProfileDataSource { * @deprecated UUID must be supported now. */ @Deprecated - GlobalProfile getGlobalProfile(String playerName); + public GlobalProfile getGlobalProfile(String playerName) { + return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); + } /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -59,7 +461,70 @@ public interface ProfileDataSource { * @param playerUUID The UUID of the player. * @return the global profile for the player with the given UUID. */ - GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); + public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { + GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); + if (cached != null) { + return cached; + } + File playerFile; + + // Migrate old data if necessary + try { + playerFile = getGlobalFile(playerName, false); + } catch (IOException e) { + // This won't ever happen + e.printStackTrace(); + return GlobalProfile.createGlobalProfile(playerName); + } + if (playerFile.exists()) { + GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); + if (!migrateGlobalProfileToUUID(profile, playerFile)) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } + globalProfileCache.put(playerUUID, profile); + return profile; + } + + // Load current format + try { + playerFile = getGlobalFile(playerUUID.toString(), true); + } catch (IOException e) { + e.printStackTrace(); + return GlobalProfile.createGlobalProfile(playerName, playerUUID); + } + GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); + globalProfileCache.put(playerUUID, profile); + return profile; + } + + private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { + updateGlobalProfile(profile); + return playerFile.delete(); + } + + private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + ConfigurationSection section = playerData.getConfigurationSection("playerData"); + if (section == null) { + section = playerData.createSection("playerData"); + } + return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); + } + + private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, + Map playerData) { + GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); + for (String key : playerData.keySet()) { + if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { + globalProfile.setLastWorld(playerData.get(key).toString()); + } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { + globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); + } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { + globalProfile.setLastKnownName(playerData.get(key).toString()); + } + } + return globalProfile; + } /** * Update the file for a player's global profile. @@ -67,7 +532,35 @@ public interface ProfileDataSource { * @param globalProfile The GlobalProfile object to update the file for. * @return True if data successfully saved to file. */ - boolean updateGlobalProfile(GlobalProfile globalProfile); + public boolean updateGlobalProfile(GlobalProfile globalProfile) { + File playerFile = null; + try { + playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save global data for player: " + globalProfile); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + + private Map serializeGlobalProfile(GlobalProfile profile) { + Map result = new HashMap(2); + if (profile.getLastWorld() != null) { + result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); + } + result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); + result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); + return result; + } /** * A convenience method to update the GlobalProfile of a player with a specified world. @@ -75,7 +568,13 @@ public interface ProfileDataSource { * @param playerName The player whose global profile this will update. * @param worldName The world to update the global profile with. */ - void updateLastWorld(String playerName, String worldName); + @Deprecated + // TODO replace for UUID + public void updateLastWorld(String playerName, String worldName) { + GlobalProfile globalProfile = getGlobalProfile(playerName); + globalProfile.setLastWorld(worldName); + updateGlobalProfile(globalProfile); + } /** * A convenience method for setting whether player data should be loaded on login for the specified player. @@ -83,22 +582,73 @@ public interface ProfileDataSource { * @param playerName The player whose data should be loaded. * @param loadOnLogin Whether or not to load on login. */ - void setLoadOnLogin(String playerName, boolean loadOnLogin); + @Deprecated + // TODO replace for UUID + public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { + final GlobalProfile globalProfile = getGlobalProfile(playerName); + globalProfile.setLoadOnLogin(loadOnLogin); + updateGlobalProfile(globalProfile); + } /** * Copies all the data belonging to oldName to newName and removes the old data. * - * @param oldName the previous name of the player. - * @param newName the new name of the player. - * @param playerUUID the UUID of the player. + * @param oldName the previous name of the player. + * @param newName the new name of the player. + * @param uuid the UUID of the player. * @param removeOldData whether or not to remove the data belonging to oldName. * @throws IOException Thrown if something goes wrong while migrating the files. */ - void migratePlayerData(String oldName, String newName, UUID playerUUID, boolean removeOldData) throws IOException; + public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { + File[] worldFolders = worldFolder.listFiles(File::isDirectory); + if (worldFolders == null) { + throw new IOException("Could not enumerate world folders"); + } + File[] groupFolders = groupFolder.listFiles(File::isDirectory); + if (groupFolders == null) { + throw new IOException("Could not enumerate group folders"); + } + + for (File worldFolder : worldFolders) { + ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), + ProfileTypes.ADVENTURE, uuid, oldName); + updatePlayerData(getPlayerData(key)); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); + } + + for (File groupFolder : groupFolders) { + ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), + ProfileTypes.ADVENTURE, uuid, oldName); + updatePlayerData(getPlayerData(key)); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); + } + + if (removeOldData) { + for (File worldFolder : worldFolders) { + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); + } + for (File groupFolder : groupFolders) { + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); + } + } + } /** * Clears a single profile in cache. */ - void clearProfileCache(ProfileKey key); + public void clearProfileCache(ProfileKey key) { + profileCache.invalidate(key); + } + + public void clearCache() { + globalProfileCache.invalidateAll(); + profileCache.invalidateAll(); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java index b4198653..47134579 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java @@ -9,7 +9,7 @@ static ProfileType createProfileType(String name) { return new ProfileType(name); } - private String name; + private final String name; private ProfileType(String name) { this.name = name; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java index cdf9c061..1db9cd1a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java @@ -29,16 +29,12 @@ public final class ProfileTypes { * @return The profile type for the given game mode. */ public static ProfileType forGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: - return SURVIVAL; - case CREATIVE: - return CREATIVE; - case ADVENTURE: - return ADVENTURE; - default: - return SURVIVAL; - } + return switch (mode) { + case SURVIVAL -> SURVIVAL; + case CREATIVE -> CREATIVE; + case ADVENTURE -> ADVENTURE; + default -> SURVIVAL; + }; } private ProfileTypes() { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java deleted file mode 100644 index a79ca089..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManager.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.mvplugins.multiverse.inventories.profile; - -import org.mvplugins.multiverse.inventories.WorldGroup; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * Manager class for manipulating the groups of this plugin that are contained in the groups configuration. - */ -public interface WorldGroupManager { - - /** - *

Retrieves the world group associated with the given name.

- * - * These groups represent the groups that define a set of worlds and what they share. - * - * @param groupName Name of world group to retrieve. Casing is ignored. - * @return The world group by the name given or null if one doesn't exist by that name. - */ - WorldGroup getGroup(String groupName); - - /** - *

Returns a list of all the world groups defined in Multiverse-Inventories's groups configuration.

- * - * This list is unmodifiable. - * - * @return An unmodifiable list of all world groups. - */ - List getGroups(); - - /** - * Retrieves all of the world groups associated with the given world. - * - * @param worldName Name of the world to get groups for. - * @return List of World Groups associated with the world or null if none. - */ - List getGroupsForWorld(String worldName); - - /** - * Check if the given world has any configured groups. - * - * @param worldName Name of the world to check. - * @return true if this world has one or more groups. - */ - boolean hasGroup(String worldName); - - /** - * Sets up the World Groups in memory. - * - * @param worldGroups List of World Groups to store in memory. - * @deprecated This feature is now completely unused. - */ - @Deprecated - void setGroups(List worldGroups); - - /** - * Adds a World Group to the collection in memory, also writing it to the groups configuration. - * - * @param worldGroup World group to add. Casing is ignored. - * @param persist This parameter is unused due to deprecation of the method. - * @deprecated - */ - @Deprecated - void addGroup(WorldGroup worldGroup, boolean persist); - - /** - *

Adds or updates a world group in Multiverse-Inventories.

- * - *

This will update an existing group by persisting changes made to it in the groups configuration. - * This should be called when any of the facets of a group such as worlds or shares have been modified.

- * - *

If the group does not exist it will be added to the groups configuration.

- * - * If worldGroup's name matches the name of a different WorldGroupProfileContainer object that is already - * known, the previous object will be overwritten with worldGroup parameter. - * - * @param worldGroup the world group to add. - */ - void updateGroup(WorldGroup worldGroup); - - /** - * Removes a world group from the collection in memory AND from the groups configuration. - * - * @param worldGroup the world group to remove. - * @return true if group was removed. - */ - boolean removeGroup(WorldGroup worldGroup); - - /** - *

Creates a new empty world group.

- * - * Please note if you do not add worlds to this group it will not persist very well. - * This does not automatically persist the new group. It must bed added via {@link #updateGroup(WorldGroup)}. - * - * @param name A name for the new group. - * @return The newly created world group. - */ - WorldGroup newEmptyGroup(String name); - - /** - * Creates a default world group including all of the loaded MV worlds sharing everything. - */ - void createDefaultGroup(); - - /** - * @return The default world group which may be empty. - */ - WorldGroup getDefaultGroup(); - - /** - * Checks all the world groups to see if there are any potential issues. - * - * @return A list of all the potential conflicts. - */ - List checkGroups(); - - /** - * Runs a check for conflicts between groups and displays them to console and sender if not null. - * - * @param sender The sender to relay information to. If null, info only displayed in console. - */ - void checkForConflicts(CommandSender sender); -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 2cd4fb93..97bcd957 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -1,31 +1,51 @@ package org.mvplugins.multiverse.inventories.profile.container; -import org.mvplugins.multiverse.inventories.profile.ProfileType; +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileType; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; /** - * A container for player profiles in a given world or world group (based on {@link #getContainerType()}). - *

Players may have separate profiles per game mode within this container if game mode profiles are enabled.

+ * A container for player profiles in a given world or world group (based on {@link #getContainerType()}), + * using WeakHashMaps to keep memory usage to a minimum. + *
+ * Players may have separate profiles per game mode within this container if game mode profiles are enabled. */ -public interface ProfileContainer { +public final class ProfileContainer { - /** - * Returns the name of this profile container which is primarily used for persistence purposes. - *

The name reflects the world name if this is a world profile container, or the arbitrary group name if - * this is a world group profile container.

- * - * @return The name to use to look up Data. - */ - String getContainerName(); + private final Map> playerData = new WeakHashMap<>(); + private final MultiverseInventories inventories; + private final String name; + private final ContainerType type; + private final ProfileDataSource profileDataSource; + private final InventoriesConfig config; + + ProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { + this.inventories = inventories; + this.name = name; + this.type = type; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); + } /** - * Returns the container type for this container. + * Gets the stored profiles for this player, mapped by ProfileType. * - * @return the container type. + * @param name The name of player to get profile map for. + * @return The profile map for the given player. */ - ContainerType getContainerType(); + private Map getPlayerData(String name) { + return this.playerData.computeIfAbsent(name, k -> new HashMap<>()); + } /** * Retrieves the profile for the given player. @@ -35,7 +55,15 @@ public interface ProfileContainer { * @param player Player to get profile for. * @return The profile for the given player. */ - PlayerProfile getPlayerData(Player player); + public PlayerProfile getPlayerData(Player player) { + ProfileType type; + if (config.isUsingGameModeProfiles()) { + type = ProfileTypes.forGameMode(player.getGameMode()); + } else { + type = ProfileTypes.SURVIVAL; + } + return getPlayerData(type, player); + } /** * Retrieves the profile of the given type for the given player. @@ -44,21 +72,37 @@ public interface ProfileContainer { * @param player Player to get profile for. * @return The profile of the given type for the given player. */ - PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player); + public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { + Map profileMap = this.getPlayerData(player.getName()); + PlayerProfile playerProfile = profileMap.get(profileType); + if (playerProfile == null) { + playerProfile = profileDataSource.getPlayerData(getContainerType(), + getContainerName(), profileType, player.getUniqueId()); + Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", + profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); + profileMap.put(profileType, playerProfile); + } + return playerProfile; + } /** * Adds a player profile to this profile container. * * @param playerProfile Player player to add. */ - void addPlayerData(PlayerProfile playerProfile); + public void addPlayerData(PlayerProfile playerProfile) { + this.getPlayerData(playerProfile.getPlayer().getName()).put(playerProfile.getProfileType(), playerProfile); + } /** * Removes all of the profile data for a given player in this profile container. * * @param player Player to remove data for. */ - void removeAllPlayerData(OfflinePlayer player); + public void removeAllPlayerData(OfflinePlayer player) { + this.getPlayerData(player.getName()).clear(); + profileDataSource.removePlayerData(getContainerType(), getContainerName(), null, player.getName()); + } /** * Removes the profile data for a specific type of profile in this profile container. @@ -66,11 +110,40 @@ public interface ProfileContainer { * @param profileType The type of profile to remove data for. * @param player Player to remove data for. */ - void removePlayerData(ProfileType profileType, OfflinePlayer player); + public void removePlayerData(ProfileType profileType, OfflinePlayer player) { + this.getPlayerData(player.getName()).remove(profileType); + profileDataSource.removePlayerData(getContainerType(), getContainerName(), profileType, player.getName()); + } + + /** + * Returns the name of this profile container which is primarily used for persistence purposes. + *

The name reflects the world name if this is a world profile container, or the arbitrary group name if + * this is a world group profile container.

+ * + * @return The name to use to look up Data. + */ + public String getContainerName() { + return name; + } + + /** + * Returns the container type for this container. + * + * @return the container type. + */ + public ContainerType getContainerType() { + return type; + } /** * Clears all cached data in the container. */ - void clearContainer(); + public void clearContainer() { + for (Map profiles : playerData.values()) { + for (PlayerProfile profile : profiles.values()) { + profileDataSource.clearProfileCache(ProfileKey.createProfileKey(profile)); + } + } + this.playerData.clear(); + } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java index 91f5c873..14f83748 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java @@ -1,16 +1,33 @@ package org.mvplugins.multiverse.inventories.profile.container; +import org.mvplugins.multiverse.inventories.MultiverseInventories; + +import java.util.Map; +import java.util.WeakHashMap; + /** * A utility for storing and retrieving profile containers. */ -public interface ProfileContainerStore { +public final class ProfileContainerStore { + + private final Map containers = new WeakHashMap<>(); + + private final MultiverseInventories inventories; + private final ContainerType containerType; + + ProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { + this.inventories = inventories; + this.containerType = containerType; + } /** * Adds a profile container to the store. * * @param container profile container to add. */ - void addContainer(ProfileContainer container); + public void addContainer(ProfileContainer container) { + this.containers.put(container.getContainerName().toLowerCase(), container); + } /** * Returns the profile container for the given name. @@ -18,6 +35,13 @@ public interface ProfileContainerStore { * @param containerName Name of the profile container to retrieve. * @return the profile container for given name. */ - ProfileContainer getContainer(String containerName); + public ProfileContainer getContainer(String containerName) { + ProfileContainer container = this.containers.get(containerName.toLowerCase()); + if (container == null) { + container = new ProfileContainer(inventories, containerName, containerType); + addContainer(container); + } + return container; + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java new file mode 100644 index 00000000..f3f8bd1a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.profile.container; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.MultiverseInventories; + +import java.util.EnumMap; +import java.util.Map; + +/** + * A provider for ProfileContainerStore instances based on ContainerType. + */ +@Service +public class ProfileContainerStoreProvider { + + private final Map stores; + private final MultiverseInventories inventories; + + @Inject + ProfileContainerStoreProvider(@NotNull MultiverseInventories inventories) { + this.inventories = inventories; + stores = new EnumMap<>(ContainerType.class); + } + + /** + * Gets the store for a given container type. + * + * @param type the container type + * @return the store + */ + public ProfileContainerStore getStore(ContainerType type) { + return stores.computeIfAbsent(type, t -> new ProfileContainerStore(inventories, t)); + } + + public void clearCache() { + stores.clear(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GroupingConflict.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/GroupingConflict.java similarity index 88% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/GroupingConflict.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/GroupingConflict.java index cb0fc2fc..c42eba65 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GroupingConflict.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/GroupingConflict.java @@ -1,6 +1,5 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.profile.group; -import org.mvplugins.multiverse.inventories.WorldGroup; import org.mvplugins.multiverse.inventories.share.Shares; import java.util.ArrayList; @@ -11,9 +10,9 @@ */ public final class GroupingConflict { - private WorldGroup groupOne; - private WorldGroup groupTwo; - private Shares conflictingShares; + private final WorldGroup groupOne; + private final WorldGroup groupTwo; + private final Shares conflictingShares; public GroupingConflict(WorldGroup groupOne, WorldGroup groupTwo, Shares conflictingShares) { this.groupOne = groupOne; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java similarity index 86% rename from src/main/java/org/mvplugins/multiverse/inventories/WorldGroup.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index a0e6cdd6..c5c35d56 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -1,9 +1,12 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.profile.group; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.bukkit.World; import org.bukkit.event.EventPriority; @@ -13,7 +16,8 @@ public final class WorldGroup { - private final MultiverseInventories plugin; + private final WorldGroupManager worldGroupManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; private final String name; private final HashSet worlds = new HashSet<>(); private final Shares shares = Sharables.noneOf(); @@ -21,8 +25,12 @@ public final class WorldGroup { private String spawnWorld = null; private EventPriority spawnPriority = EventPriority.NORMAL; - WorldGroup(final MultiverseInventories inventories, final String name) { - this.plugin = inventories; + WorldGroup( + final WorldGroupManager worldGroupManager, + final ProfileContainerStoreProvider profileContainerStoreProvider, + final String name) { + this.worldGroupManager = worldGroupManager; + this.profileContainerStoreProvider = profileContainerStoreProvider; this.name = name; } @@ -53,7 +61,7 @@ public void addWorld(String worldName) { public void addWorld(String worldName, boolean updateConfig) { this.getWorlds().add(worldName.toLowerCase()); if (updateConfig) { - plugin.getGroupManager().updateGroup(this); + worldGroupManager.updateGroup(this); } } @@ -84,7 +92,7 @@ public void addWorlds(Collection worlds) { public void addWorlds(Collection worlds, boolean updateConfig) { worlds.forEach(worldName -> this.addWorld(worldName, false)); if (updateConfig) { - this.plugin.getGroupManager().updateGroup(this); + worldGroupManager.updateGroup(this); } } @@ -106,7 +114,7 @@ public void removeWorld(String worldName) { public void removeWorld(String worldName, boolean updateConfig) { this.getWorlds().remove(worldName.toLowerCase()); if (updateConfig) { - plugin.getGroupManager().updateGroup(this); + worldGroupManager.updateGroup(this); } } @@ -134,7 +142,7 @@ public void removeAllWorlds() { public void removeAllWorlds(boolean updateConfig) { this.worlds.clear(); if (updateConfig) { - this.plugin.getGroupManager().updateGroup(this); + worldGroupManager.updateGroup(this); } } @@ -213,7 +221,7 @@ public void setSpawnPriority(EventPriority priority) { * @return true if this is the default group. */ public boolean isDefault() { - return AbstractWorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); + return WorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); } /** @@ -222,7 +230,7 @@ public boolean isDefault() { * @return the profile container for this group. */ public ProfileContainer getGroupProfileContainer() { - return plugin.getGroupProfileContainerStore().getContainer(getName()); + return profileContainerStoreProvider.getStore(ContainerType.GROUP).getContainer(getName()); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java new file mode 100644 index 00000000..76985a31 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -0,0 +1,488 @@ +package org.mvplugins.multiverse.inventories.profile.group; + +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.collect.Lists; +import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.locale.Message; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.DeserializationException; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.event.EventPriority; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Manager class for manipulating the groups of this plugin that are contained in the groups configuration. + */ +@Service +public final class WorldGroupManager { + + static final String DEFAULT_GROUP_NAME = "default"; + private static final String[] groupSectionComments = { + "# Multiverse-Inventories Groups", + "", + "# To ADD, DELETE, and EDIT groups use the command /mvinv group.", + "# No support will be given for those who manually edit these groups." + }; + + private final Map groupNamesMap = new LinkedHashMap<>(); + private final MultiverseInventories inventories; + private final WorldManager worldManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final InventoriesConfig config; + + private CommentedConfiguration groupsConfig; + + @Inject + WorldGroupManager( + final MultiverseInventories inventories, + final ProfileContainerStoreProvider profileContainerStoreProvider, + final InventoriesConfig config) { + this.inventories = inventories; + this.worldManager = inventories.getServiceLocator().getService(WorldManager.class); + this.profileContainerStoreProvider = profileContainerStoreProvider; + + this.config = config; + } + + public void load() throws IOException { + // Check if the group config file exists. If not, create it and migrate group data. + File groupsConfigFile = new File(inventories.getDataFolder(), "groups.yml"); + boolean migrateGroups = false; + if (!groupsConfigFile.exists()) { + Logging.fine("Created groups file."); + groupsConfigFile.createNewFile(); + migrateGroups = true; + } + // Load the configuration file into memory + groupsConfig = new CommentedConfiguration(groupsConfigFile.toPath()); + groupsConfig.load(); + + if (migrateGroups) { + migrateGroups(config.getConfig()); + } + + groupsConfig.addComment("groups", groupSectionComments); + if (groupsConfig.get("groups") == null) { + this.getConfig().createSection("groups"); + } + + // Saves the configuration from memory to file + groupsConfig.save(); + + // Setup groups in memory + final List worldGroups = getGroupsFromConfig(); + if (worldGroups == null) { + Logging.info("No world groups have been configured!"); + Logging.info("This will cause all worlds configured for Multiverse to have separate player statistics/inventories."); + return; + } + + for (final WorldGroup worldGroup : worldGroups) { + getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); + } + } + + private void migrateGroups(final Configuration config) { + if (config == null) { + return; + } + ConfigurationSection section = config.getConfigurationSection("groups"); + if (section != null) { + getConfig().set("groups", section); + config.set("groups", null); + Logging.fine("Migrated groups to groups.yml"); + } + } + + private FileConfiguration getConfig() { + return this.groupsConfig; + } + + private List getGroupsFromConfig() { + Logging.finer("Getting world groups from config file"); + ConfigurationSection groupsSection = getConfig().getConfigurationSection("groups"); + if (groupsSection == null) { + Logging.finer("Could not find a 'groups' section in config!"); + return null; + } + Set groupNames = groupsSection.getKeys(false); + Logging.finer("Loading groups: " + groupNames.toString()); + List worldGroups = new ArrayList<>(groupNames.size()); + for (String groupName : groupNames) { + Logging.finer("Attempting to load group: " + groupName + "..."); + WorldGroup worldGroup; + try { + ConfigurationSection groupSection = + getConfig().getConfigurationSection("groups." + groupName); + if (groupSection == null) { + Logging.warning("Group: '" + groupName + "' is not formatted correctly!"); + continue; + } + worldGroup = deserializeGroup(groupName, groupSection.getValues(true)); + } catch (DeserializationException e) { + Logging.warning("Unable to load world group: " + groupName); + Logging.warning("Reason: " + e.getMessage()); + continue; + } + worldGroups.add(worldGroup); + Logging.finer("Group: " + worldGroup.getName() + " added to memory"); + } + return worldGroups; + } + + private WorldGroup deserializeGroup(final String name, final Map dataMap) + throws DeserializationException { + WorldGroup profile = new WorldGroup(this, profileContainerStoreProvider, name); + if (dataMap.containsKey("worlds")) { + Object worldListObj = dataMap.get("worlds"); + if (worldListObj == null) { + Logging.fine("No worlds for group: " + name); + } else { + if (!(worldListObj instanceof List)) { + Logging.fine("World list formatted incorrectly for world group: " + name); + } else { + final StringBuilder builder = new StringBuilder(); + for (Object worldNameObj : (List) worldListObj) { + if (worldNameObj == null) { + Logging.fine("Error with a world listed in group: " + name); + continue; + } + profile.addWorld(worldNameObj.toString(), false); + World world = Bukkit.getWorld(worldNameObj.toString()); + if (world == null) { + if (builder.length() != 0) { + builder.append(", "); + } + builder.append(worldNameObj.toString()); + } + } + if (builder.length() > 0) { + Logging.config("The following worlds for group '%s' are not loaded: %s", name, builder.toString()); + } + } + } + } + if (dataMap.containsKey("shares")) { + Object sharesListObj = dataMap.get("shares"); + if (sharesListObj instanceof List) { + profile.getShares().mergeShares(Sharables.fromList((List) sharesListObj)); + profile.getShares().removeAll(Sharables.negativeFromList((List) sharesListObj)); + } else { + Logging.warning("Shares formatted incorrectly for group: " + name); + } + } + if (dataMap.containsKey("spawn")) { + Object spawnPropsObj = dataMap.get("spawn"); + if (spawnPropsObj instanceof ConfigurationSection) { + // Le sigh, bukkit. + spawnPropsObj = ((ConfigurationSection) spawnPropsObj).getValues(true); + } + if (spawnPropsObj instanceof Map) { + Map spawnProps = (Map) spawnPropsObj; + if (spawnProps.containsKey("world")) { + profile.setSpawnWorld(spawnProps.get("world").toString()); + } + if (spawnProps.containsKey("priority")) { + EventPriority priority = EventPriority.valueOf( + spawnProps.get("priority").toString().toUpperCase()); + if (priority != null) { + profile.setSpawnPriority(priority); + } + } + } else { + Logging.warning("Spawn settings for group formatted incorrectly"); + } + } + return profile; + } + + private void updateWorldGroup(WorldGroup worldGroup) { + Logging.finer("Updating group in config: " + worldGroup.getName()); + getConfig().createSection("groups." + worldGroup.getName(), serializeWorldGroupProfile(worldGroup)); + } + + private Map serializeWorldGroupProfile(WorldGroup profile) { + Map results = new LinkedHashMap<>(); + results.put("worlds", Lists.newArrayList(profile.getWorlds())); + List sharesList = profile.getShares().toStringList(); + if (!sharesList.isEmpty()) { + results.put("shares", sharesList); + } + Map spawnProps = new LinkedHashMap(); + if (profile.getSpawnWorld() != null) { + spawnProps.put("world", profile.getSpawnWorld()); + spawnProps.put("priority", profile.getSpawnPriority().toString()); + results.put("spawn", spawnProps); + } + return results; + } + + private void removeWorldGroup(WorldGroup worldGroup) { + Logging.finer("Removing group from config: " + worldGroup.getName()); + getConfig().set("groups." + worldGroup.getName(), null); + } + + private void save() { + groupsConfig.save(); + } + + public void updateGroup(final WorldGroup worldGroup) { + getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); + updateWorldGroup(worldGroup); + save(); + } + + public boolean removeGroup(WorldGroup worldGroup) { + if (getGroupNames().remove(worldGroup.getName().toLowerCase()) != null) { + removeWorldGroup(worldGroup); + save(); + return true; + } + return false; + } + + /** + *

Retrieves the world group associated with the given name.

+ * + * These groups represent the groups that define a set of worlds and what they share. + * + * @param groupName Name of world group to retrieve. Casing is ignored. + * @return The world group by the name given or null if one doesn't exist by that name. + */ + public WorldGroup getGroup(String groupName) { + return groupNamesMap.get(groupName.toLowerCase()); + } + + /** + *

Returns a list of all the world groups defined in Multiverse-Inventories's groups configuration.

+ * + * This list is unmodifiable. + * + * @return An unmodifiable list of all world groups. + */ + public List getGroups() { + return Collections.unmodifiableList(new ArrayList(getGroupNames().values())); + } + + /** + * Retrieves all of the world groups associated with the given world. + * + * @param worldName Name of the world to get groups for. + * @return List of World Groups associated with the world or null if none. + */ + public List getGroupsForWorld(String worldName) { + worldName = worldName.toLowerCase(); + List worldGroups = new ArrayList<>(); + for (WorldGroup worldGroup : getGroupNames().values()) { + if (worldGroup.containsWorld(worldName)) { + worldGroups.add(worldGroup); + } + } + // Only use the default group for worlds managed by MV-Core + if (worldGroups.isEmpty() && config.isDefaultingUngroupedWorlds() && + this.worldManager.isWorld(worldName)) { + Logging.finer("Returning default group for world: " + worldName); + worldGroups.add(getDefaultGroup()); + } + return worldGroups; + } + + /** + * Check if the given world has any configured groups. + * + * @param worldName Name of the world to check. + * @return true if this world has one or more groups. + */ + public boolean hasGroup(String worldName) { + return !getGroupsForWorld(worldName).isEmpty(); + } + + /** + * Retrieves all of the World Groups mapped to their names. + * + * @return Map of Group Name -> World Group + */ + private Map getGroupNames() { + return groupNamesMap; + } + + /** + * Adds a World Group to the collection in memory, also writing it to the groups configuration. + * + * @param worldGroup World group to add. Casing is ignored. + * @param persist This parameter is unused due to deprecation of the method. + * @deprecated + */ + @Deprecated + public void addGroup(final WorldGroup worldGroup, final boolean persist) { + updateGroup(worldGroup); + } + + private void persistGroup(final WorldGroup worldGroup) { + } + + /** + *

Creates a new empty world group.

+ * + * Please note if you do not add worlds to this group it will not persist very well. + * This does not automatically persist the new group. It must bed added via {@link #updateGroup(WorldGroup)}. + * + * @param name A name for the new group. + * @return The newly created world group. + */ + public WorldGroup newEmptyGroup(String name) { + if (getGroup(name) != null) { + return null; + } + return new WorldGroup(this, profileContainerStoreProvider, name); + } + + /** + * Sets up the World Groups in memory. + * + * @param worldGroups List of World Groups to store in memory. + * @deprecated This feature is now completely unused. + */ + @Deprecated + public void setGroups(List worldGroups) { + } + + /** + * Creates a default world group including all of the loaded MV worlds sharing everything. + */ + public void createDefaultGroup() { + if (getGroup(DEFAULT_GROUP_NAME) != null) { + return; + } + World defaultWorld = Bukkit.getWorlds().get(0); + World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); + World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); + WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, DEFAULT_GROUP_NAME); + worldGroup.getShares().mergeShares(Sharables.allOf()); + worldGroup.addWorld(defaultWorld); + StringBuilder worlds = new StringBuilder().append(defaultWorld.getName()); + if (defaultNether != null) { + worldGroup.addWorld(defaultNether); + worlds.append(", ").append(defaultNether.getName()); + } + if (defaultEnd != null) { + worldGroup.addWorld(defaultEnd); + worlds.append(", ").append(defaultEnd.getName()); + } + updateGroup(worldGroup); + config.save(); + Logging.info("Created a default group for you containing all of your default worlds: " + worlds.toString()); + } + + /** + * @return The default world group which may be empty. + */ + public WorldGroup getDefaultGroup() { + WorldGroup group = getGroupNames().get(DEFAULT_GROUP_NAME); + if (group == null) { + group = newEmptyGroup(DEFAULT_GROUP_NAME); + group.getShares().setSharing(Sharables.allOf(), true); + updateGroup(group); + } + return group; + } + + /** + * Checks all the world groups to see if there are any potential issues. + * + * @return A list of all the potential conflicts. + */ + public List checkGroups() { + List conflicts = new ArrayList(); + Map previousConflicts = new HashMap<>(); + for (WorldGroup checkingGroup : getGroupNames().values()) { + for (String worldName : checkingGroup.getWorlds()) { + for (WorldGroup worldGroup : getGroupsForWorld(worldName)) { + if (checkingGroup.equals(worldGroup)) { + continue; + } + if (previousConflicts.containsKey(checkingGroup)) { + if (previousConflicts.get(checkingGroup).equals(worldGroup)) { + continue; + } + } + if (previousConflicts.containsKey(worldGroup)) { + if (previousConflicts.get(worldGroup).equals(checkingGroup)) { + continue; + } + } + previousConflicts.put(checkingGroup, worldGroup); + Shares conflictingShares = worldGroup.getShares() + .compare(checkingGroup.getShares()); + if (!conflictingShares.isEmpty()) { + if (checkingGroup.getWorlds().containsAll(worldGroup.getWorlds()) + || worldGroup.getWorlds().containsAll(checkingGroup.getWorlds())) { + continue; + } + conflicts.add(new GroupingConflict(checkingGroup, worldGroup, + Sharables.fromShares(conflictingShares))); + } + } + } + } + + return conflicts; + } + + /** + * Runs a check for conflicts between groups and displays them to console and sender if not null. + * + * @param sender The sender to relay information to. If null, info only displayed in console. + */ + public void checkForConflicts(CommandSender sender) { + String message = inventories.getMessager().getMessage(Message.CONFLICT_CHECKING); + if (sender != null) { + inventories.getMessager().sendMessage(sender, message); + } + Logging.fine(message); + List conflicts = this.checkGroups(); + for (GroupingConflict conflict : conflicts) { + message = inventories.getMessager().getMessage(Message.CONFLICT_RESULTS, + conflict.getFirstGroup().getName(), conflict.getSecondGroup().getName(), + conflict.getConflictingShares().toString(), conflict.getWorldsString()); + if (sender != null) { + inventories.getMessager().sendMessage(sender, message); + } + Logging.info(message); + } + if (!conflicts.isEmpty()) { + message = inventories.getMessager().getMessage(Message.CONFLICT_FOUND); + if (sender != null) { + inventories.getMessager().sendMessage(sender, message); + } + Logging.info(message); + } else { + message = inventories.getMessager().getMessage(Message.CONFLICT_NOT_FOUND); + if (sender != null) { + inventories.getMessager().sendMessage(sender, message); + } + Logging.fine(message); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java index ef524d28..1765b9db 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java @@ -8,7 +8,7 @@ */ final class DefaultSerializer implements SharableSerializer { - private Class type; + private final Class type; public DefaultSerializer(Class type) { this.type = type; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java index f2c2c329..7f5f85d3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java @@ -15,8 +15,8 @@ */ final class DefaultStringSerializer implements SharableSerializer { - private Method valueOfMethod; - private Class clazz; + private final Method valueOfMethod; + private final Class clazz; DefaultStringSerializer(Class clazz) { this.clazz = clazz; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index f4cca144..4fdd7e57 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -13,7 +13,7 @@ */ public final class InventorySerializer implements SharableSerializer { - private int inventorySize; + private final int inventorySize; public InventorySerializer(final int inventorySize) { this.inventorySize = inventorySize; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java index 98554ddf..a28e3f10 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.share; -import org.mvplugins.multiverse.inventories.DataStrings; +import org.mvplugins.multiverse.inventories.util.DataStrings; import org.bukkit.Location; import java.util.Map; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java index 753170b6..191f21f8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.share; -import org.mvplugins.multiverse.inventories.DataStrings; +import org.mvplugins.multiverse.inventories.util.DataStrings; import org.bukkit.potion.PotionEffect; import java.util.ArrayList; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java index 7145eb07..2d19d5d7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java @@ -9,11 +9,11 @@ */ public final class ProfileEntry { - private static final Map STATS_MAP = new HashMap(); - private static final Map OTHERS_MAP = new HashMap(); + private static final Map STATS_MAP = new HashMap<>(); + private static final Map OTHERS_MAP = new HashMap<>(); - private boolean isStat; - private String fileTag; + private final boolean isStat; + private final String fileTag; public ProfileEntry(boolean isStat, String fileTag) { this.isStat = isStat; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java index 80724ddf..dc50a618 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java @@ -11,8 +11,8 @@ */ public final class SharableGroup implements Shares { - private String[] names; - private Shares shares; + private final String[] names; + private final Shares shares; public SharableGroup(String name, Shares shares, String... alternateNames) { this.names = new String[alternateNames.length + 1]; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 4cbba286..32362050 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -5,10 +5,11 @@ import org.bukkit.Registry; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.SpawnChangeListener; -import org.mvplugins.multiverse.inventories.WorldGroup; -import org.mvplugins.multiverse.inventories.DataStrings; -import org.mvplugins.multiverse.inventories.PlayerStats; +import org.mvplugins.multiverse.inventories.listeners.SpawnChangeListener; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.DataStrings; +import org.mvplugins.multiverse.inventories.util.PlayerStats; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.Location; @@ -680,10 +681,10 @@ static boolean register(Sharable sharable) { if (!ALL_SHARABLES.contains(sharable)) { // If the plugin has been enabled, we need to add this sharable to the existing groups with all sharables. if (inventories != null) { - for (WorldGroup group : inventories.getGroupManager().getGroups()) { + var worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + for (WorldGroup group : worldGroupManager.getGroups()) { if (group.getShares().isSharing(Sharables.all())) { group.getShares().setSharing(sharable, true); - } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java similarity index 99% rename from src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java index 5c0fe74c..cdf2eff3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.util; import com.dumptruckman.minecraft.util.Logging; import net.minidev.json.JSONArray; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java index 19a57595..2afe533f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java @@ -8,6 +8,7 @@ import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.PluginManager; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; /** * @author dumptruckman @@ -182,7 +183,8 @@ public Permission getBypassPermission(String finalNode) { * @return True if player is allowed to bypass. */ public boolean hasBypass(Player player, String name) { - if (inventories != null && !inventories.getMVIConfig().isUsingBypass()) { + if (inventories != null && + !inventories.getServiceLocator().getService(InventoriesConfig.class).isUsingBypass()) { return false; } Permission bypassPerm = this.getBypassPermission(name); @@ -190,7 +192,7 @@ public boolean hasBypass(Player player, String name) { if (hasBypass) { Logging.fine("Player: " + player.getName() + " in World: " + player.getWorld().getName() + " has permission: " + bypassPerm.getName() + "(Default: " - + bypassPerm.getDefault().toString() + ")!"); + + bypassPerm.getDefault() + ")!"); } return hasBypass; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java similarity index 96% rename from src/main/java/org/mvplugins/multiverse/inventories/PlayerStats.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java index f5bcb332..c7e46921 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.util; /** * A collection of values relating to a Minecraft player. From d8460107e31caf85afab84c6f462bc55db34bff9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 15:56:04 +0800 Subject: [PATCH 035/180] Make ShareHandlingUpdater public to allow being called by onDisable --- .../inventories/MultiverseInventories.java | 26 ++++++++++--------- .../{listeners => }/ShareHandlingUpdater.java | 5 ++-- .../listeners/InventoriesListener.java | 1 + .../inventories/listeners/ShareHandler.java | 1 + 4 files changed, 18 insertions(+), 15 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/{listeners => }/ShareHandlingUpdater.java (96%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 2e221951..b9871035 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -4,6 +4,7 @@ import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.entity.Player; import org.mvplugins.multiverse.core.MultiverseCoreApi; import org.mvplugins.multiverse.core.MultiversePlugin; import org.mvplugins.multiverse.core.config.MVCoreConfig; @@ -17,7 +18,9 @@ import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Messaging; import org.mvplugins.multiverse.inventories.migration.ImportManager; +import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -115,9 +118,6 @@ private void onMVPluginEnable() { return; } - // Initialize data class - //this.getWorldProfileContainerStore().setWorldProfiles(this.getData().getWorldProfiles()); - // Register Events Bukkit.getPluginManager().registerEvents(inventoriesListener.get(), this); try { @@ -147,15 +147,17 @@ private void onMVPluginEnable() { @Override public void onDisable() { super.onDisable(); -// for (final Player player : getServer().getOnlinePlayers()) { -// final String world = player.getWorld().getName(); -// //getData().updateLastWorld(player.getName(), world); -// if (getMVIConfig().usingLoggingSaveLoad()) { -// ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile(Sharables.allOf(), -// getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); -// getData().setLoadOnLogin(player.getName(), true); -// } -// } + for (final Player player : getServer().getOnlinePlayers()) { + final String world = player.getWorld().getName(); + if (inventoriesConfig.get().usingLoggingSaveLoad()) { + ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile( + Sharables.allOf(), + profileContainerStoreProvider.get().getStore(ContainerType.WORLD) + .getContainer(world) + .getPlayerData(player))); + profileDataSource.get().setLoadOnLogin(player.getName(), true); + } + } this.dupingPatch.disable(); Logging.shutdown(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java similarity index 96% rename from src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandlingUpdater.java rename to src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java index f9880759..68fd0062 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java @@ -1,7 +1,6 @@ -package org.mvplugins.multiverse.inventories.listeners; +package org.mvplugins.multiverse.inventories; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; @@ -15,7 +14,7 @@ import java.util.Objects; import java.util.stream.Collectors; -class ShareHandlingUpdater { +public final class ShareHandlingUpdater { static void updateProfile(final MultiverseInventories inventories, final Player player, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index 66875ef5..b6a5adca 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -8,6 +8,7 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.migration.ImportManager; import org.mvplugins.multiverse.inventories.profile.PersistingProfile; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java index e84df0b4..995fba97 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java @@ -2,6 +2,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; From d55d5a885a2639690d3ad480da549e2d4d8aa6a4 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 15:57:55 +0800 Subject: [PATCH 036/180] Fix ShareHandlingUpdater update methods should be public --- .../multiverse/inventories/ShareHandlingUpdater.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java index 68fd0062..bf450ead 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java @@ -16,13 +16,13 @@ public final class ShareHandlingUpdater { - static void updateProfile(final MultiverseInventories inventories, + public static void updateProfile(final MultiverseInventories inventories, final Player player, final PersistingProfile profile) { new ShareHandlingUpdater(inventories, player, profile).updateProfile(); } - static void updatePlayer(final MultiverseInventories inventories, + public static void updatePlayer(final MultiverseInventories inventories, final Player player, final PersistingProfile profile) { new ShareHandlingUpdater(inventories, player, profile).updatePlayer(); From 3310d896a299e1f9d2b902b415a6dab0b74167aa Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 25 Jan 2025 15:58:05 +0800 Subject: [PATCH 037/180] Remove use of paperlib --- build.gradle | 2 -- .../multiverse/inventories/config/InventoriesConfig.java | 1 - 2 files changed, 3 deletions(-) diff --git a/build.gradle b/build.gradle index 0cb8cc8d..d6f991e5 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,6 @@ dependencies { api 'net.minidev:json-smart:2.5.1' // Utils - api 'io.papermc:paperlib:1.0.7' api('com.dumptruckman.minecraft:Logging:1.1.1') { exclude group: 'junit', module: 'junit' } @@ -173,7 +172,6 @@ shadowJar { relocate 'com.dumptruckman.minecraft.util.Logging', 'org.mvplugins.multiverse.inventories.utils.InvLogging' relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' - relocate 'io.papermc.lib', 'org.mvplugins.multiverse.inventories.utils.paperlib' relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' configurations = [project.configurations.api] diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index eb045d4b..0c0ef631 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -9,7 +9,6 @@ import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; -import io.papermc.lib.PaperLib; import org.bukkit.configuration.file.FileConfiguration; import java.io.File; From a9afa2cb27d13c28f7f19138fc5749f9984bc88f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:17:11 +0800 Subject: [PATCH 038/180] Re-add WorldGroupManager as interface and AbstractWorldGroupManager --- .../inventories/MultiverseInventories.java | 8 +- .../group/AbstractWorldGroupManager.java | 264 ++++++++++ .../inventories/profile/group/WorldGroup.java | 2 +- .../profile/group/WorldGroupManager.java | 449 ++---------------- .../profile/group/YamlWorldGroupManager.java | 254 ++++++++++ 5 files changed, 571 insertions(+), 406 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index b9871035..8d085846 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -1,6 +1,5 @@ package org.mvplugins.multiverse.inventories; -import java.io.IOException; import java.util.Locale; import com.dumptruckman.minecraft.util.Logging; @@ -207,7 +206,10 @@ public PluginServiceLocator getServiceLocator() { @Override public void reloadConfig() { try { - worldGroupManager.get().load(); + worldGroupManager.get().load().onFailure(e -> { + Logging.severe("Failed to load world groups!"); + Logging.severe(e.getMessage()); + }); profileContainerStoreProvider.get().clearCache(); if (profileDataSource.get() != null) { @@ -215,7 +217,7 @@ public void reloadConfig() { } Logging.fine("Loaded config file!"); - } catch (IOException e) { // Catch errors loading the config file and exit out if found. + } catch (Exception e) { // Catch errors loading the config file and exit out if found. Logging.severe(this.getMessager().getMessage(Message.ERROR_CONFIG_LOAD)); Logging.severe(e.getMessage()); Bukkit.getPluginManager().disablePlugin(this); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java new file mode 100644 index 00000000..b8652ec6 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -0,0 +1,264 @@ +package org.mvplugins.multiverse.inventories.profile.group; + +import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.locale.Message; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Abstract implementation of GroupManager with no persistence of groups. + */ +@Contract +abstract sealed class AbstractWorldGroupManager implements WorldGroupManager permits YamlWorldGroupManager { + + static final String DEFAULT_GROUP_NAME = "default"; + protected final Map groupNamesMap = new LinkedHashMap<>(); + protected final MultiverseInventories plugin; + protected final InventoriesConfig inventoriesConfig; + protected final ProfileContainerStoreProvider profileContainerStoreProvider; + protected final WorldManager worldManager; + + public AbstractWorldGroupManager( + @NotNull MultiverseInventories plugin, + @NotNull InventoriesConfig config, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull WorldManager worldManager) { + this.plugin = plugin; + this.inventoriesConfig = config; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.worldManager = worldManager; + } + + /** + * {@inheritDoc} + */ + @Override + public WorldGroup getGroup(String groupName) { + return groupNamesMap.get(groupName.toLowerCase()); + } + + /** + * {@inheritDoc} + */ + @Override + public List getGroups() { + return Collections.unmodifiableList(new ArrayList(getGroupNames().values())); + } + + /** + * {@inheritDoc} + */ + @Override + public List getGroupsForWorld(String worldName) { + worldName = worldName.toLowerCase(); + List worldGroups = new ArrayList<>(); + for (WorldGroup worldGroup : getGroupNames().values()) { + if (worldGroup.containsWorld(worldName)) { + worldGroups.add(worldGroup); + } + } + // Only use the default group for worlds managed by MV-Core + if (worldGroups.isEmpty() && inventoriesConfig.isDefaultingUngroupedWorlds() && + this.worldManager.isWorld(worldName)) { + Logging.finer("Returning default group for world: " + worldName); + worldGroups.add(getDefaultGroup()); + } + return worldGroups; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasGroup(String worldName) { + return !getGroupsForWorld(worldName).isEmpty(); + } + + /** + * Retrieves all of the World Groups mapped to their names. + * + * @return Map of Group Name -> World Group + */ + protected Map getGroupNames() { + return groupNamesMap; + } + + /** + * {@inheritDoc} + */ + @Override + @Deprecated + public void addGroup(final WorldGroup worldGroup, final boolean persist) { + updateGroup(worldGroup); + } + + @Override + public void updateGroup(final WorldGroup worldGroup) { + getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); + } + + protected void persistGroup(final WorldGroup worldGroup) { + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removeGroup(final WorldGroup worldGroup) { + return getGroupNames().remove(worldGroup.getName().toLowerCase()) != null; + } + + /** + * {@inheritDoc} + */ + @Override + public WorldGroup newEmptyGroup(String name) { + if (getGroup(name) != null) { + return null; + } + return new WorldGroup(this, profileContainerStoreProvider, name); + } + + /** + * {@inheritDoc} + */ + @Override + @Deprecated + public void setGroups(List worldGroups) { + } + + /** + * {@inheritDoc} + */ + @Override + public void createDefaultGroup() { + if (getGroup(DEFAULT_GROUP_NAME) != null) { + return; + } + World defaultWorld = Bukkit.getWorlds().get(0); + World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); + World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); + WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, DEFAULT_GROUP_NAME); + worldGroup.getShares().mergeShares(Sharables.allOf()); + worldGroup.addWorld(defaultWorld); + StringBuilder worlds = new StringBuilder().append(defaultWorld.getName()); + if (defaultNether != null) { + worldGroup.addWorld(defaultNether); + worlds.append(", ").append(defaultNether.getName()); + } + if (defaultEnd != null) { + worldGroup.addWorld(defaultEnd); + worlds.append(", ").append(defaultEnd.getName()); + } + updateGroup(worldGroup); + inventoriesConfig.save(); + Logging.info("Created a default group for you containing all of your default worlds: " + worlds.toString()); + } + + /** + * {@inheritDoc} + */ + @Override + public WorldGroup getDefaultGroup() { + WorldGroup group = getGroupNames().get(DEFAULT_GROUP_NAME); + if (group == null) { + group = newEmptyGroup(DEFAULT_GROUP_NAME); + group.getShares().setSharing(Sharables.allOf(), true); + updateGroup(group); + } + return group; + } + + /** + * {@inheritDoc} + */ + @Override + public List checkGroups() { + List conflicts = new ArrayList(); + Map previousConflicts = new HashMap<>(); + for (WorldGroup checkingGroup : getGroupNames().values()) { + for (String worldName : checkingGroup.getWorlds()) { + for (WorldGroup worldGroup : getGroupsForWorld(worldName)) { + if (checkingGroup.equals(worldGroup)) { + continue; + } + if (previousConflicts.containsKey(checkingGroup)) { + if (previousConflicts.get(checkingGroup).equals(worldGroup)) { + continue; + } + } + if (previousConflicts.containsKey(worldGroup)) { + if (previousConflicts.get(worldGroup).equals(checkingGroup)) { + continue; + } + } + previousConflicts.put(checkingGroup, worldGroup); + Shares conflictingShares = worldGroup.getShares() + .compare(checkingGroup.getShares()); + if (!conflictingShares.isEmpty()) { + if (checkingGroup.getWorlds().containsAll(worldGroup.getWorlds()) + || worldGroup.getWorlds().containsAll(checkingGroup.getWorlds())) { + continue; + } + conflicts.add(new GroupingConflict(checkingGroup, worldGroup, + Sharables.fromShares(conflictingShares))); + } + } + } + } + + return conflicts; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkForConflicts(CommandSender sender) { + String message = plugin.getMessager().getMessage(Message.CONFLICT_CHECKING); + if (sender != null) { + plugin.getMessager().sendMessage(sender, message); + } + Logging.fine(message); + List conflicts = checkGroups(); + for (GroupingConflict conflict : conflicts) { + message = plugin.getMessager().getMessage(Message.CONFLICT_RESULTS, + conflict.getFirstGroup().getName(), conflict.getSecondGroup().getName(), + conflict.getConflictingShares().toString(), conflict.getWorldsString()); + if (sender != null) { + plugin.getMessager().sendMessage(sender, message); + } + Logging.info(message); + } + if (!conflicts.isEmpty()) { + message = plugin.getMessager().getMessage(Message.CONFLICT_FOUND); + if (sender != null) { + plugin.getMessager().sendMessage(sender, message); + } + Logging.info(message); + } else { + message = plugin.getMessager().getMessage(Message.CONFLICT_NOT_FOUND); + if (sender != null) { + plugin.getMessager().sendMessage(sender, message); + } + Logging.fine(message); + } + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index c5c35d56..2880e98d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -221,7 +221,7 @@ public void setSpawnPriority(EventPriority priority) { * @return true if this is the default group. */ public boolean isDefault() { - return WorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); + return AbstractWorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index 76985a31..3cc7ede4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -1,266 +1,22 @@ package org.mvplugins.multiverse.inventories.profile.group; -import com.dumptruckman.minecraft.util.Logging; -import com.google.common.collect.Lists; import org.bukkit.command.CommandSender; -import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.world.WorldManager; -import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; -import org.mvplugins.multiverse.external.jakarta.inject.Inject; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.util.DeserializationException; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.event.EventPriority; +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.external.vavr.control.Try; -import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Set; /** * Manager class for manipulating the groups of this plugin that are contained in the groups configuration. */ -@Service -public final class WorldGroupManager { +@Contract +public sealed interface WorldGroupManager permits AbstractWorldGroupManager { - static final String DEFAULT_GROUP_NAME = "default"; - private static final String[] groupSectionComments = { - "# Multiverse-Inventories Groups", - "", - "# To ADD, DELETE, and EDIT groups use the command /mvinv group.", - "# No support will be given for those who manually edit these groups." - }; - - private final Map groupNamesMap = new LinkedHashMap<>(); - private final MultiverseInventories inventories; - private final WorldManager worldManager; - private final ProfileContainerStoreProvider profileContainerStoreProvider; - private final InventoriesConfig config; - - private CommentedConfiguration groupsConfig; - - @Inject - WorldGroupManager( - final MultiverseInventories inventories, - final ProfileContainerStoreProvider profileContainerStoreProvider, - final InventoriesConfig config) { - this.inventories = inventories; - this.worldManager = inventories.getServiceLocator().getService(WorldManager.class); - this.profileContainerStoreProvider = profileContainerStoreProvider; - - this.config = config; - } - - public void load() throws IOException { - // Check if the group config file exists. If not, create it and migrate group data. - File groupsConfigFile = new File(inventories.getDataFolder(), "groups.yml"); - boolean migrateGroups = false; - if (!groupsConfigFile.exists()) { - Logging.fine("Created groups file."); - groupsConfigFile.createNewFile(); - migrateGroups = true; - } - // Load the configuration file into memory - groupsConfig = new CommentedConfiguration(groupsConfigFile.toPath()); - groupsConfig.load(); - - if (migrateGroups) { - migrateGroups(config.getConfig()); - } - - groupsConfig.addComment("groups", groupSectionComments); - if (groupsConfig.get("groups") == null) { - this.getConfig().createSection("groups"); - } - - // Saves the configuration from memory to file - groupsConfig.save(); - - // Setup groups in memory - final List worldGroups = getGroupsFromConfig(); - if (worldGroups == null) { - Logging.info("No world groups have been configured!"); - Logging.info("This will cause all worlds configured for Multiverse to have separate player statistics/inventories."); - return; - } - - for (final WorldGroup worldGroup : worldGroups) { - getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); - } - } - - private void migrateGroups(final Configuration config) { - if (config == null) { - return; - } - ConfigurationSection section = config.getConfigurationSection("groups"); - if (section != null) { - getConfig().set("groups", section); - config.set("groups", null); - Logging.fine("Migrated groups to groups.yml"); - } - } - - private FileConfiguration getConfig() { - return this.groupsConfig; - } - - private List getGroupsFromConfig() { - Logging.finer("Getting world groups from config file"); - ConfigurationSection groupsSection = getConfig().getConfigurationSection("groups"); - if (groupsSection == null) { - Logging.finer("Could not find a 'groups' section in config!"); - return null; - } - Set groupNames = groupsSection.getKeys(false); - Logging.finer("Loading groups: " + groupNames.toString()); - List worldGroups = new ArrayList<>(groupNames.size()); - for (String groupName : groupNames) { - Logging.finer("Attempting to load group: " + groupName + "..."); - WorldGroup worldGroup; - try { - ConfigurationSection groupSection = - getConfig().getConfigurationSection("groups." + groupName); - if (groupSection == null) { - Logging.warning("Group: '" + groupName + "' is not formatted correctly!"); - continue; - } - worldGroup = deserializeGroup(groupName, groupSection.getValues(true)); - } catch (DeserializationException e) { - Logging.warning("Unable to load world group: " + groupName); - Logging.warning("Reason: " + e.getMessage()); - continue; - } - worldGroups.add(worldGroup); - Logging.finer("Group: " + worldGroup.getName() + " added to memory"); - } - return worldGroups; - } - - private WorldGroup deserializeGroup(final String name, final Map dataMap) - throws DeserializationException { - WorldGroup profile = new WorldGroup(this, profileContainerStoreProvider, name); - if (dataMap.containsKey("worlds")) { - Object worldListObj = dataMap.get("worlds"); - if (worldListObj == null) { - Logging.fine("No worlds for group: " + name); - } else { - if (!(worldListObj instanceof List)) { - Logging.fine("World list formatted incorrectly for world group: " + name); - } else { - final StringBuilder builder = new StringBuilder(); - for (Object worldNameObj : (List) worldListObj) { - if (worldNameObj == null) { - Logging.fine("Error with a world listed in group: " + name); - continue; - } - profile.addWorld(worldNameObj.toString(), false); - World world = Bukkit.getWorld(worldNameObj.toString()); - if (world == null) { - if (builder.length() != 0) { - builder.append(", "); - } - builder.append(worldNameObj.toString()); - } - } - if (builder.length() > 0) { - Logging.config("The following worlds for group '%s' are not loaded: %s", name, builder.toString()); - } - } - } - } - if (dataMap.containsKey("shares")) { - Object sharesListObj = dataMap.get("shares"); - if (sharesListObj instanceof List) { - profile.getShares().mergeShares(Sharables.fromList((List) sharesListObj)); - profile.getShares().removeAll(Sharables.negativeFromList((List) sharesListObj)); - } else { - Logging.warning("Shares formatted incorrectly for group: " + name); - } - } - if (dataMap.containsKey("spawn")) { - Object spawnPropsObj = dataMap.get("spawn"); - if (spawnPropsObj instanceof ConfigurationSection) { - // Le sigh, bukkit. - spawnPropsObj = ((ConfigurationSection) spawnPropsObj).getValues(true); - } - if (spawnPropsObj instanceof Map) { - Map spawnProps = (Map) spawnPropsObj; - if (spawnProps.containsKey("world")) { - profile.setSpawnWorld(spawnProps.get("world").toString()); - } - if (spawnProps.containsKey("priority")) { - EventPriority priority = EventPriority.valueOf( - spawnProps.get("priority").toString().toUpperCase()); - if (priority != null) { - profile.setSpawnPriority(priority); - } - } - } else { - Logging.warning("Spawn settings for group formatted incorrectly"); - } - } - return profile; - } - - private void updateWorldGroup(WorldGroup worldGroup) { - Logging.finer("Updating group in config: " + worldGroup.getName()); - getConfig().createSection("groups." + worldGroup.getName(), serializeWorldGroupProfile(worldGroup)); - } - - private Map serializeWorldGroupProfile(WorldGroup profile) { - Map results = new LinkedHashMap<>(); - results.put("worlds", Lists.newArrayList(profile.getWorlds())); - List sharesList = profile.getShares().toStringList(); - if (!sharesList.isEmpty()) { - results.put("shares", sharesList); - } - Map spawnProps = new LinkedHashMap(); - if (profile.getSpawnWorld() != null) { - spawnProps.put("world", profile.getSpawnWorld()); - spawnProps.put("priority", profile.getSpawnPriority().toString()); - results.put("spawn", spawnProps); - } - return results; - } - - private void removeWorldGroup(WorldGroup worldGroup) { - Logging.finer("Removing group from config: " + worldGroup.getName()); - getConfig().set("groups." + worldGroup.getName(), null); - } - - private void save() { - groupsConfig.save(); - } - - public void updateGroup(final WorldGroup worldGroup) { - getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); - updateWorldGroup(worldGroup); - save(); - } - - public boolean removeGroup(WorldGroup worldGroup) { - if (getGroupNames().remove(worldGroup.getName().toLowerCase()) != null) { - removeWorldGroup(worldGroup); - save(); - return true; - } - return false; - } + /** + *

Loads the groups from storage.

+ */ + Try load(); /** *

Retrieves the world group associated with the given name.

@@ -270,9 +26,7 @@ public boolean removeGroup(WorldGroup worldGroup) { * @param groupName Name of world group to retrieve. Casing is ignored. * @return The world group by the name given or null if one doesn't exist by that name. */ - public WorldGroup getGroup(String groupName) { - return groupNamesMap.get(groupName.toLowerCase()); - } + WorldGroup getGroup(String groupName); /** *

Returns a list of all the world groups defined in Multiverse-Inventories's groups configuration.

@@ -281,9 +35,7 @@ public WorldGroup getGroup(String groupName) { * * @return An unmodifiable list of all world groups. */ - public List getGroups() { - return Collections.unmodifiableList(new ArrayList(getGroupNames().values())); - } + List getGroups(); /** * Retrieves all of the world groups associated with the given world. @@ -291,41 +43,24 @@ public List getGroups() { * @param worldName Name of the world to get groups for. * @return List of World Groups associated with the world or null if none. */ - public List getGroupsForWorld(String worldName) { - worldName = worldName.toLowerCase(); - List worldGroups = new ArrayList<>(); - for (WorldGroup worldGroup : getGroupNames().values()) { - if (worldGroup.containsWorld(worldName)) { - worldGroups.add(worldGroup); - } - } - // Only use the default group for worlds managed by MV-Core - if (worldGroups.isEmpty() && config.isDefaultingUngroupedWorlds() && - this.worldManager.isWorld(worldName)) { - Logging.finer("Returning default group for world: " + worldName); - worldGroups.add(getDefaultGroup()); - } - return worldGroups; - } + List getGroupsForWorld(String worldName); /** * Check if the given world has any configured groups. - * + * * @param worldName Name of the world to check. * @return true if this world has one or more groups. */ - public boolean hasGroup(String worldName) { - return !getGroupsForWorld(worldName).isEmpty(); - } + boolean hasGroup(String worldName); /** - * Retrieves all of the World Groups mapped to their names. + * Sets up the World Groups in memory. * - * @return Map of Group Name -> World Group + * @param worldGroups List of World Groups to store in memory. + * @deprecated This feature is now completely unused. */ - private Map getGroupNames() { - return groupNamesMap; - } + @Deprecated + void setGroups(List worldGroups); /** * Adds a World Group to the collection in memory, also writing it to the groups configuration. @@ -335,12 +70,30 @@ private Map getGroupNames() { * @deprecated */ @Deprecated - public void addGroup(final WorldGroup worldGroup, final boolean persist) { - updateGroup(worldGroup); - } + void addGroup(WorldGroup worldGroup, boolean persist); - private void persistGroup(final WorldGroup worldGroup) { - } + /** + *

Adds or updates a world group in Multiverse-Inventories.

+ * + *

This will update an existing group by persisting changes made to it in the groups configuration. + * This should be called when any of the facets of a group such as worlds or shares have been modified.

+ * + *

If the group does not exist it will be added to the groups configuration.

+ * + * If worldGroup's name matches the name of a different WorldGroupProfileContainer object that is already + * known, the previous object will be overwritten with worldGroup parameter. + * + * @param worldGroup the world group to add. + */ + void updateGroup(WorldGroup worldGroup); + + /** + * Removes a world group from the collection in memory AND from the groups configuration. + * + * @param worldGroup the world group to remove. + * @return true if group was removed. + */ + boolean removeGroup(WorldGroup worldGroup); /** *

Creates a new empty world group.

@@ -351,138 +104,30 @@ private void persistGroup(final WorldGroup worldGroup) { * @param name A name for the new group. * @return The newly created world group. */ - public WorldGroup newEmptyGroup(String name) { - if (getGroup(name) != null) { - return null; - } - return new WorldGroup(this, profileContainerStoreProvider, name); - } - - /** - * Sets up the World Groups in memory. - * - * @param worldGroups List of World Groups to store in memory. - * @deprecated This feature is now completely unused. - */ - @Deprecated - public void setGroups(List worldGroups) { - } + WorldGroup newEmptyGroup(String name); /** * Creates a default world group including all of the loaded MV worlds sharing everything. */ - public void createDefaultGroup() { - if (getGroup(DEFAULT_GROUP_NAME) != null) { - return; - } - World defaultWorld = Bukkit.getWorlds().get(0); - World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); - World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); - WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, DEFAULT_GROUP_NAME); - worldGroup.getShares().mergeShares(Sharables.allOf()); - worldGroup.addWorld(defaultWorld); - StringBuilder worlds = new StringBuilder().append(defaultWorld.getName()); - if (defaultNether != null) { - worldGroup.addWorld(defaultNether); - worlds.append(", ").append(defaultNether.getName()); - } - if (defaultEnd != null) { - worldGroup.addWorld(defaultEnd); - worlds.append(", ").append(defaultEnd.getName()); - } - updateGroup(worldGroup); - config.save(); - Logging.info("Created a default group for you containing all of your default worlds: " + worlds.toString()); - } + void createDefaultGroup(); /** * @return The default world group which may be empty. */ - public WorldGroup getDefaultGroup() { - WorldGroup group = getGroupNames().get(DEFAULT_GROUP_NAME); - if (group == null) { - group = newEmptyGroup(DEFAULT_GROUP_NAME); - group.getShares().setSharing(Sharables.allOf(), true); - updateGroup(group); - } - return group; - } + WorldGroup getDefaultGroup(); /** * Checks all the world groups to see if there are any potential issues. * * @return A list of all the potential conflicts. */ - public List checkGroups() { - List conflicts = new ArrayList(); - Map previousConflicts = new HashMap<>(); - for (WorldGroup checkingGroup : getGroupNames().values()) { - for (String worldName : checkingGroup.getWorlds()) { - for (WorldGroup worldGroup : getGroupsForWorld(worldName)) { - if (checkingGroup.equals(worldGroup)) { - continue; - } - if (previousConflicts.containsKey(checkingGroup)) { - if (previousConflicts.get(checkingGroup).equals(worldGroup)) { - continue; - } - } - if (previousConflicts.containsKey(worldGroup)) { - if (previousConflicts.get(worldGroup).equals(checkingGroup)) { - continue; - } - } - previousConflicts.put(checkingGroup, worldGroup); - Shares conflictingShares = worldGroup.getShares() - .compare(checkingGroup.getShares()); - if (!conflictingShares.isEmpty()) { - if (checkingGroup.getWorlds().containsAll(worldGroup.getWorlds()) - || worldGroup.getWorlds().containsAll(checkingGroup.getWorlds())) { - continue; - } - conflicts.add(new GroupingConflict(checkingGroup, worldGroup, - Sharables.fromShares(conflictingShares))); - } - } - } - } - - return conflicts; - } + List checkGroups(); /** * Runs a check for conflicts between groups and displays them to console and sender if not null. * * @param sender The sender to relay information to. If null, info only displayed in console. */ - public void checkForConflicts(CommandSender sender) { - String message = inventories.getMessager().getMessage(Message.CONFLICT_CHECKING); - if (sender != null) { - inventories.getMessager().sendMessage(sender, message); - } - Logging.fine(message); - List conflicts = this.checkGroups(); - for (GroupingConflict conflict : conflicts) { - message = inventories.getMessager().getMessage(Message.CONFLICT_RESULTS, - conflict.getFirstGroup().getName(), conflict.getSecondGroup().getName(), - conflict.getConflictingShares().toString(), conflict.getWorldsString()); - if (sender != null) { - inventories.getMessager().sendMessage(sender, message); - } - Logging.info(message); - } - if (!conflicts.isEmpty()) { - message = inventories.getMessager().getMessage(Message.CONFLICT_FOUND); - if (sender != null) { - inventories.getMessager().sendMessage(sender, message); - } - Logging.info(message); - } else { - message = inventories.getMessager().getMessage(Message.CONFLICT_NOT_FOUND); - if (sender != null) { - inventories.getMessager().sendMessage(sender, message); - } - Logging.fine(message); - } - } + void checkForConflicts(CommandSender sender); } + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java new file mode 100644 index 00000000..b0854893 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -0,0 +1,254 @@ +package org.mvplugins.multiverse.inventories.profile.group; + +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.collect.Lists; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.DeserializationException; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.event.EventPriority; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +final class YamlWorldGroupManager extends AbstractWorldGroupManager { + + private final String[] groupSectionComments = { + "# Multiverse-Inventories Groups", + "", + "# To ADD, DELETE, and EDIT groups use the command /mvinv group.", + "# No support will be given for those who manually edit these groups." + }; + + private CommentedConfiguration groupsConfig; + + @Inject + YamlWorldGroupManager( + @NotNull MultiverseInventories plugin, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull WorldManager worldManager) { + super(plugin, inventoriesConfig, profileContainerStoreProvider, worldManager); + } + + @Override + public Try load() { + return Try.run(() -> { + // Check if the group config file exists. If not, create it and migrate group data. + File groupsConfigFile = new File(plugin.getDataFolder(), "groups.yml"); + boolean migrateGroups = false; + if (!groupsConfigFile.exists()) { + Logging.fine("Created groups file."); + groupsConfigFile.createNewFile(); + migrateGroups = true; + } + // Load the configuration file into memory + groupsConfig = new CommentedConfiguration(groupsConfigFile.toPath()); + groupsConfig.load(); + + if (migrateGroups) { + migrateGroups(inventoriesConfig.getConfig()); + } + + groupsConfig.addComment("groups", groupSectionComments); + if (groupsConfig.get("groups") == null) { + this.getConfig().createSection("groups"); + } + + // Saves the configuration from memory to file + groupsConfig.save(); + + // Setup groups in memory + final List worldGroups = getGroupsFromConfig(); + if (worldGroups == null) { + Logging.info("No world groups have been configured!"); + Logging.info("This will cause all worlds configured for Multiverse to have separate player " + + "statistics/inventories."); + return; + } + + for (final WorldGroup worldGroup : worldGroups) { + getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); + } + }); + } + + private void migrateGroups(final Configuration config) { + if (config == null) { + return; + } + ConfigurationSection section = config.getConfigurationSection("groups"); + if (section != null) { + getConfig().set("groups", section); + config.set("groups", null); + Logging.fine("Migrated groups to groups.yml"); + } + } + + private FileConfiguration getConfig() { + return this.groupsConfig; + } + + private List getGroupsFromConfig() { + Logging.finer("Getting world groups from config file"); + ConfigurationSection groupsSection = getConfig().getConfigurationSection("groups"); + if (groupsSection == null) { + Logging.finer("Could not find a 'groups' section in config!"); + return null; + } + Set groupNames = groupsSection.getKeys(false); + Logging.finer("Loading groups: " + groupNames.toString()); + List worldGroups = new ArrayList<>(groupNames.size()); + for (String groupName : groupNames) { + Logging.finer("Attempting to load group: " + groupName + "..."); + WorldGroup worldGroup; + try { + ConfigurationSection groupSection = + getConfig().getConfigurationSection("groups." + groupName); + if (groupSection == null) { + Logging.warning("Group: '" + groupName + "' is not formatted correctly!"); + continue; + } + worldGroup = deserializeGroup(groupName, groupSection.getValues(true)); + } catch (DeserializationException e) { + Logging.warning("Unable to load world group: " + groupName); + Logging.warning("Reason: " + e.getMessage()); + continue; + } + worldGroups.add(worldGroup); + Logging.finer("Group: " + worldGroup.getName() + " added to memory"); + } + return worldGroups; + } + + private WorldGroup deserializeGroup(final String name, final Map dataMap) + throws DeserializationException { + WorldGroup profile = new WorldGroup(this, profileContainerStoreProvider, name); + if (dataMap.containsKey("worlds")) { + Object worldListObj = dataMap.get("worlds"); + if (worldListObj == null) { + Logging.fine("No worlds for group: " + name); + } else { + if (!(worldListObj instanceof List)) { + Logging.fine("World list formatted incorrectly for world group: " + name); + } else { + final StringBuilder builder = new StringBuilder(); + for (Object worldNameObj : (List) worldListObj) { + if (worldNameObj == null) { + Logging.fine("Error with a world listed in group: " + name); + continue; + } + profile.addWorld(worldNameObj.toString(), false); + World world = Bukkit.getWorld(worldNameObj.toString()); + if (world == null) { + if (builder.length() != 0) { + builder.append(", "); + } + builder.append(worldNameObj.toString()); + } + } + if (builder.length() > 0) { + Logging.config("The following worlds for group '%s' are not loaded: %s", name, builder.toString()); + } + } + } + } + if (dataMap.containsKey("shares")) { + Object sharesListObj = dataMap.get("shares"); + if (sharesListObj instanceof List) { + profile.getShares().mergeShares(Sharables.fromList((List) sharesListObj)); + profile.getShares().removeAll(Sharables.negativeFromList((List) sharesListObj)); + } else { + Logging.warning("Shares formatted incorrectly for group: " + name); + } + } + if (dataMap.containsKey("spawn")) { + Object spawnPropsObj = dataMap.get("spawn"); + if (spawnPropsObj instanceof ConfigurationSection) { + // Le sigh, bukkit. + spawnPropsObj = ((ConfigurationSection) spawnPropsObj).getValues(true); + } + if (spawnPropsObj instanceof Map) { + Map spawnProps = (Map) spawnPropsObj; + if (spawnProps.containsKey("world")) { + profile.setSpawnWorld(spawnProps.get("world").toString()); + } + if (spawnProps.containsKey("priority")) { + EventPriority priority = EventPriority.valueOf( + spawnProps.get("priority").toString().toUpperCase()); + if (priority != null) { + profile.setSpawnPriority(priority); + } + } + } else { + Logging.warning("Spawn settings for group formatted incorrectly"); + } + } + return profile; + } + + private void updateWorldGroup(WorldGroup worldGroup) { + Logging.finer("Updating group in config: " + worldGroup.getName()); + getConfig().createSection("groups." + worldGroup.getName(), serializeWorldGroupProfile(worldGroup)); + } + + private Map serializeWorldGroupProfile(WorldGroup profile) { + Map results = new LinkedHashMap<>(); + results.put("worlds", Lists.newArrayList(profile.getWorlds())); + List sharesList = profile.getShares().toStringList(); + if (!sharesList.isEmpty()) { + results.put("shares", sharesList); + } + Map spawnProps = new LinkedHashMap(); + if (profile.getSpawnWorld() != null) { + spawnProps.put("world", profile.getSpawnWorld()); + spawnProps.put("priority", profile.getSpawnPriority().toString()); + results.put("spawn", spawnProps); + } + return results; + } + + private void removeWorldGroup(WorldGroup worldGroup) { + Logging.finer("Removing group from config: " + worldGroup.getName()); + getConfig().set("groups." + worldGroup.getName(), null); + } + + private void save() { + groupsConfig.save(); + } + + @Override + public void updateGroup(final WorldGroup worldGroup) { + super.updateGroup(worldGroup); + updateWorldGroup(worldGroup); + save(); + } + + @Override + public boolean removeGroup(WorldGroup worldGroup) { + if (super.removeGroup(worldGroup)) { + removeWorldGroup(worldGroup); + save(); + return true; + } + return false; + } +} From d178da6db654c047a448530ad041be55cf662099 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:17:26 +0800 Subject: [PATCH 039/180] Fix import manager dep error --- .../inventories/listeners/InventoriesListener.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index b6a5adca..78a26eda 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -7,6 +7,7 @@ import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Provider; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; @@ -66,7 +67,7 @@ public class InventoriesListener implements Listener { private final WorldGroupManager worldGroupManager; private final ProfileDataSource profileDataSource; private final ProfileContainerStoreProvider profileContainerStoreProvider; - private final ImportManager importManager; + private final Provider importManager; private List currentGroups; private Location spawnLoc = null; @@ -78,7 +79,7 @@ public class InventoriesListener implements Listener { @NotNull WorldGroupManager worldGroupManager, @NotNull ProfileDataSource profileDataSource, @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, - @NotNull ImportManager importManager) { + @NotNull Provider importManager) { this.inventories = inventories; this.config = config; this.worldManager = worldManager; @@ -152,9 +153,9 @@ public void configReload(MVConfigReloadEvent event) { public void pluginEnable(PluginEnableEvent event) { try { if (event.getPlugin() instanceof MultiInv) { - importManager.hookMultiInv((MultiInv) event.getPlugin()); + importManager.get().hookMultiInv((MultiInv) event.getPlugin()); } else if (event.getPlugin() instanceof WorldInventories) { - importManager.hookWorldInventories((WorldInventories) event.getPlugin()); + importManager.get().hookWorldInventories((WorldInventories) event.getPlugin()); } } catch (NoClassDefFoundError ignore) { } @@ -169,9 +170,9 @@ public void pluginEnable(PluginEnableEvent event) { public void pluginDisable(PluginDisableEvent event) { try { if (event.getPlugin() instanceof MultiInv) { - importManager.unHookMultiInv(); + importManager.get().unHookMultiInv(); } else if (event.getPlugin() instanceof WorldInventories) { - importManager.unHookWorldInventories(); + importManager.get().unHookWorldInventories(); } } catch (NoClassDefFoundError ignore) { } From 53729dbb3884402c9013be27e8948a90dd6afed3 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:24:51 +0800 Subject: [PATCH 040/180] Re-implement ProfileDataSource as an interface --- .../inventories/MultiverseInventories.java | 2 +- .../profile/FlatFileProfileDataSource.java | 601 ++++++++++++++++++ .../profile/ProfileDataSource.java | 583 +---------------- 3 files changed, 622 insertions(+), 564 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 8d085846..85812778 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -213,7 +213,7 @@ public void reloadConfig() { profileContainerStoreProvider.get().clearCache(); if (profileDataSource.get() != null) { - profileDataSource.get().clearCache(); + profileDataSource.get().clearAllCache(); } Logging.fine("Loaded config file!"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java new file mode 100644 index 00000000..666ee66b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -0,0 +1,601 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.bukkit.configuration.InvalidConfigurationException; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.share.ProfileEntry; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.SharableEntry; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import net.minidev.json.JSONObject; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.mvplugins.multiverse.inventories.util.DataStrings; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +@Service +final class FlatFileProfileDataSource implements ProfileDataSource { + + private static final String JSON = ".json"; + + private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); + + private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); + + // TODO these probably need configurable max sizes + private final Cache profileCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(1000) + .build(); + private final Cache globalProfileCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(500) + .build(); + + private final File worldFolder; + private final File groupFolder; + private final File playerFolder; + + @Inject + FlatFileProfileDataSource(MultiverseInventories plugin) throws IOException { + // Make the data folders + plugin.getDataFolder().mkdirs(); + + // Check if the data file exists. If not, create it. + this.worldFolder = new File(plugin.getDataFolder(), "worlds"); + if (!this.worldFolder.exists()) { + if (!this.worldFolder.mkdirs()) { + throw new IOException("Could not create world folder!"); + } + } + this.groupFolder = new File(plugin.getDataFolder(), "groups"); + if (!this.groupFolder.exists()) { + if (!this.groupFolder.mkdirs()) { + throw new IOException("Could not create group folder!"); + } + } + this.playerFolder = new File(plugin.getDataFolder(), "players"); + if (!this.playerFolder.exists()) { + if (!this.playerFolder.mkdirs()) { + throw new IOException("Could not create player folder!"); + } + } + } + + private FileConfiguration waitForConfigHandle(File file) { + Future future = fileIOExecutorService.submit(new ConfigLoader(file)); + while (true) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + } + + private static FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(true); + jsonConfiguration.load(file); + return jsonConfiguration; + } + + private static class ConfigLoader implements Callable { + private final File file; + + private ConfigLoader(File file) { + this.file = file; + } + + @Override + public FileConfiguration call() throws Exception { + return getConfigHandleNow(file); + } + } + + private File getFolder(ContainerType type, String folderName) { + File folder; + switch (type) { + case GROUP: + folder = new File(this.groupFolder, folderName); + break; + case WORLD: + folder = new File(this.worldFolder, folderName); + break; + default: + folder = new File(this.worldFolder, folderName); + break; + } + + if (!folder.exists()) { + folder.mkdirs(); + } + return folder; + } + + /** + * Retrieves the data file for a player based on a given world/group name, creating it if necessary. + * + * @param type Indicates whether data is for group or world. + * @param dataName The name of the group or world. + * @param playerName The name of the player. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { + File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); + if (!jsonPlayerFile.exists()) { + try { + jsonPlayerFile.createNewFile(); + } catch (IOException e) { + throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() + + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName + + " may not be saved.", e); + } + } + Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", + jsonPlayerFile.getPath(), type, dataName, playerName); + return jsonPlayerFile; + } + + /** + * Retrieves the data file for a player for their global data, creating it if necessary. + * + * @param fileName The name of the file (player name or UUID) without extension. + * @param createIfMissing If true, the file will be created it it does not exist. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { + File jsonPlayerFile = new File(playerFolder, fileName + JSON); + if (createIfMissing && !jsonPlayerFile.exists()) { + try { + jsonPlayerFile.createNewFile(); + } catch (IOException e) { + throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " + + "There may be issues with " + fileName + "'s metadata", e); + } + } + return jsonPlayerFile; + } + + private void queueWrite(PlayerProfile profile) { + fileIOExecutorService.submit(new FileWriter(profile.clone())); + } + + private class FileWriter implements Callable { + private final PlayerProfile profile; + + private FileWriter(PlayerProfile profile) { + this.profile = profile; + } + + @Override + public Void call() throws Exception { + processProfileWrite(profile); + return null; + } + } + + private void processProfileWrite(PlayerProfile playerProfile) { + try { + File playerFile = this.getPlayerFile(playerProfile.getContainerType(), + playerProfile.getContainerName(), playerProfile.getPlayer().getName()); + FileConfiguration playerData = getConfigHandleNow(playerFile); + playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); + Logging.severe(e.getMessage()); + } + } catch (final Exception e) { + Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); + } + } + + private Map serializePlayerProfile(PlayerProfile playerProfile) { + Map playerData = new LinkedHashMap(); + JSONObject jsonStats = new JSONObject(); + for (SharableEntry entry : playerProfile) { + if (entry.getValue() != null) { + if (entry.getSharable().getSerializer() == null) { + continue; + } + Sharable sharable = entry.getSharable(); + if (sharable.getProfileEntry().isStat()) { + jsonStats.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(entry.getValue())); + } else { + playerData.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(entry.getValue())); + } + } + } + if (!jsonStats.isEmpty()) { + playerData.put(DataStrings.PLAYER_STATS, jsonStats); + } + return playerData; + } + + /** + * {@inheritDoc} + */ + @Override + public void updatePlayerData(PlayerProfile playerProfile) { + queueWrite(playerProfile); + } + + private PlayerProfile getPlayerData(ProfileKey key) { + PlayerProfile cached = profileCache.getIfPresent(key); + if (cached != null) { + return cached; + } + File playerFile = null; + try { + playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); + } catch (IOException e) { + e.printStackTrace(); + // Return an empty profile + return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), + Bukkit.getOfflinePlayer(key.getPlayerUUID())); + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + if (convertConfig(playerData)) { + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + key.getPlayerName() + + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); + Logging.severe(e.getMessage()); + } + } + ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); + if (section == null) { + section = playerData.createSection(key.getProfileType().getName()); + } + PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); + profileCache.put(key, result); + return result; + } + + @Override + public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { + return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); + } + + private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { + PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), + pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); + for (Object keyObj : playerData.keySet()) { + String key = keyObj.toString(); + if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { + final Object statsObject = playerData.get(key); + if (statsObject instanceof String) { + parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); + } else { + if (statsObject instanceof Map) { + parsePlayerStatsIntoProfile((Map) statsObject, profile); + } else { + Logging.warning("Could not parse stats for " + pKey.getPlayerName()); + } + } + } else { + if (playerData.get(key) == null) { + Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + try { + Sharable sharable = ProfileEntry.lookup(false, key); + if (sharable == null) { + Logging.fine("Player fileTag '" + key + "' is unrecognized!"); + continue; + } + profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); + } catch (Exception e) { + Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); + Logging.getLogger().log(Level.FINE, "Exception: ", e); + e.printStackTrace(); + } + } + } + Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); + return profile; + } + + private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { + for (Object key : stats.keySet()) { + Sharable sharable = ProfileEntry.lookup(true, key.toString()); + if (sharable != null) { + profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); + } else { + Logging.warning("Could not parse stat: '" + key + "' for player '" + + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" + + profile.getContainerName() + "'"); + } + } + } + + private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { + if (stats.isEmpty()) { + return; + } + JSONObject jsonStats = null; + try { + jsonStats = (JSONObject) JSON_PARSER.parse(stats); + } catch (ParseException | ClassCastException e) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); + } + if (jsonStats == null) { + Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "'"); + return; + } + parsePlayerStatsIntoProfile(jsonStats, profile); + } + + // TODO Remove this conversion + private boolean convertConfig(FileConfiguration config) { + ConfigurationSection section = config.getConfigurationSection("playerData"); + if (section != null) { + config.set(ProfileTypes.SURVIVAL.getName(), section); + config.set(ProfileTypes.CREATIVE.getName(), section); + config.set(ProfileTypes.ADVENTURE.getName(), section); + config.set("playerData", null); + Logging.finer("Migrated old player data to new multi-profile format"); + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { + if (profileType == null) { + try { + File playerFile = getPlayerFile(containerType, dataName, playerName); + return playerFile.delete(); + } catch (IOException ignore) { + Logging.warning("Attempted to delete file that did not exist for player " + playerName + + " in " + containerType.name().toLowerCase() + " " + dataName); + return false; + } + } else { + File playerFile; + try { + playerFile = getPlayerFile(containerType, dataName, playerName); + } catch (IOException e) { + Logging.warning("Attempted to delete " + playerName + "'s data for " + + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() + + " " + dataName + " but the file did not exist."); + return false; + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + playerData.set(profileType.getName(), null); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not delete data for player: " + playerName + + " for " + containerType.toString() + ": " + dataName); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + } + + private Map convertSection(ConfigurationSection section) { + Map resultMap = new HashMap(); + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj instanceof ConfigurationSection) { + resultMap.put(key, convertSection((ConfigurationSection) obj)); + } else { + resultMap.put(key, obj); + } + } + return resultMap; + } + + @Override + @Deprecated + public GlobalProfile getGlobalProfile(String playerName) { + return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); + } + + @Override + public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { + GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); + if (cached != null) { + return cached; + } + File playerFile; + + // Migrate old data if necessary + try { + playerFile = getGlobalFile(playerName, false); + } catch (IOException e) { + // This won't ever happen + e.printStackTrace(); + return GlobalProfile.createGlobalProfile(playerName); + } + if (playerFile.exists()) { + GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); + if (!migrateGlobalProfileToUUID(profile, playerFile)) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } + globalProfileCache.put(playerUUID, profile); + return profile; + } + + // Load current format + try { + playerFile = getGlobalFile(playerUUID.toString(), true); + } catch (IOException e) { + e.printStackTrace(); + return GlobalProfile.createGlobalProfile(playerName, playerUUID); + } + GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); + globalProfileCache.put(playerUUID, profile); + return profile; + } + + private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { + updateGlobalProfile(profile); + return playerFile.delete(); + } + + private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + ConfigurationSection section = playerData.getConfigurationSection("playerData"); + if (section == null) { + section = playerData.createSection("playerData"); + } + return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); + } + + private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, + Map playerData) { + GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); + for (String key : playerData.keySet()) { + if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { + globalProfile.setLastWorld(playerData.get(key).toString()); + } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { + globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); + } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { + globalProfile.setLastKnownName(playerData.get(key).toString()); + } + } + return globalProfile; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean updateGlobalProfile(GlobalProfile globalProfile) { + File playerFile = null; + try { + playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + FileConfiguration playerData = this.waitForConfigHandle(playerFile); + playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); + try { + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save global data for player: " + globalProfile); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + + private Map serializeGlobalProfile(GlobalProfile profile) { + Map result = new HashMap(2); + if (profile.getLastWorld() != null) { + result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); + } + result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); + result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); + return result; + } + + @Override + @Deprecated + // TODO replace for UUID + public void updateLastWorld(String playerName, String worldName) { + GlobalProfile globalProfile = getGlobalProfile(playerName); + globalProfile.setLastWorld(worldName); + updateGlobalProfile(globalProfile); + } + + @Override + @Deprecated + // TODO replace for UUID + public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { + final GlobalProfile globalProfile = getGlobalProfile(playerName); + globalProfile.setLoadOnLogin(loadOnLogin); + updateGlobalProfile(globalProfile); + } + + @Override + public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { + File[] worldFolders = worldFolder.listFiles(File::isDirectory); + if (worldFolders == null) { + throw new IOException("Could not enumerate world folders"); + } + File[] groupFolders = groupFolder.listFiles(File::isDirectory); + if (groupFolders == null) { + throw new IOException("Could not enumerate group folders"); + } + + for (File worldFolder : worldFolders) { + ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), + ProfileTypes.ADVENTURE, uuid, oldName); + updatePlayerData(getPlayerData(key)); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); + } + + for (File groupFolder : groupFolders) { + ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), + ProfileTypes.ADVENTURE, uuid, oldName); + updatePlayerData(getPlayerData(key)); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); + updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); + } + + if (removeOldData) { + for (File worldFolder : worldFolders) { + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); + removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); + } + for (File groupFolder : groupFolders) { + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); + removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); + } + } + } + + @Override + public void clearProfileCache(ProfileKey key) { + profileCache.invalidate(key); + } + + @Override + public void clearAllCache() { + globalProfileCache.invalidateAll(); + profileCache.invalidateAll(); + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 8fa7ff81..c9922395 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,247 +1,16 @@ package org.mvplugins.multiverse.inventories.profile; -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.dumptruckman.minecraft.util.Logging; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import org.bukkit.configuration.InvalidConfigurationException; -import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.external.jakarta.inject.Inject; -import org.mvplugins.multiverse.inventories.util.DataStrings; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.share.ProfileEntry; -import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.SharableEntry; +import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import net.minidev.json.JSONObject; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; /** * A source for updating and retrieving player profiles via persistence. */ -@Service -public final class ProfileDataSource { - - private static final String JSON = ".json"; - - private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); - - private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); - - // TODO these probably need configurable max sizes - private final Cache profileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(1000) - .build(); - private final Cache globalProfileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(500) - .build(); - - private final File worldFolder; - private final File groupFolder; - private final File playerFolder; - - @Inject - ProfileDataSource(MultiverseInventories plugin) throws IOException { - // Make the data folders - plugin.getDataFolder().mkdirs(); - - // Check if the data file exists. If not, create it. - this.worldFolder = new File(plugin.getDataFolder(), "worlds"); - if (!this.worldFolder.exists()) { - if (!this.worldFolder.mkdirs()) { - throw new IOException("Could not create world folder!"); - } - } - this.groupFolder = new File(plugin.getDataFolder(), "groups"); - if (!this.groupFolder.exists()) { - if (!this.groupFolder.mkdirs()) { - throw new IOException("Could not create group folder!"); - } - } - this.playerFolder = new File(plugin.getDataFolder(), "players"); - if (!this.playerFolder.exists()) { - if (!this.playerFolder.mkdirs()) { - throw new IOException("Could not create player folder!"); - } - } - } - - private FileConfiguration waitForConfigHandle(File file) { - Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - while (true) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - - private static FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { - JsonConfiguration jsonConfiguration = new JsonConfiguration(); - jsonConfiguration.options().continueOnSerializationError(true); - jsonConfiguration.load(file); - return jsonConfiguration; - } - - private static class ConfigLoader implements Callable { - private final File file; - - private ConfigLoader(File file) { - this.file = file; - } - - @Override - public FileConfiguration call() throws Exception { - return getConfigHandleNow(file); - } - } - - private File getFolder(ContainerType type, String folderName) { - File folder; - switch (type) { - case GROUP: - folder = new File(this.groupFolder, folderName); - break; - case WORLD: - folder = new File(this.worldFolder, folderName); - break; - default: - folder = new File(this.worldFolder, folderName); - break; - } - - if (!folder.exists()) { - folder.mkdirs(); - } - return folder; - } - - /** - * Retrieves the data file for a player based on a given world/group name, creating it if necessary. - * - * @param type Indicates whether data is for group or world. - * @param dataName The name of the group or world. - * @param playerName The name of the player. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - private File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); - if (!jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() - + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName - + " may not be saved.", e); - } - } - Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", - jsonPlayerFile.getPath(), type, dataName, playerName); - return jsonPlayerFile; - } - - /** - * Retrieves the data file for a player for their global data, creating it if necessary. - * - * @param fileName The name of the file (player name or UUID) without extension. - * @param createIfMissing If true, the file will be created it it does not exist. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - private File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { - File jsonPlayerFile = new File(playerFolder, fileName + JSON); - if (createIfMissing && !jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " - + "There may be issues with " + fileName + "'s metadata", e); - } - } - return jsonPlayerFile; - } - - private void queueWrite(PlayerProfile profile) { - fileIOExecutorService.submit(new FileWriter(profile.clone())); - } - - private class FileWriter implements Callable { - private final PlayerProfile profile; - - private FileWriter(PlayerProfile profile) { - this.profile = profile; - } - - @Override - public Void call() throws Exception { - processProfileWrite(profile); - return null; - } - } - - private void processProfileWrite(PlayerProfile playerProfile) { - try { - File playerFile = this.getPlayerFile(playerProfile.getContainerType(), - playerProfile.getContainerName(), playerProfile.getPlayer().getName()); - FileConfiguration playerData = getConfigHandleNow(playerFile); - playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() - + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); - Logging.severe(e.getMessage()); - } - } catch (final Exception e) { - Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); - } - } - - private Map serializePlayerProfile(PlayerProfile playerProfile) { - Map playerData = new LinkedHashMap(); - JSONObject jsonStats = new JSONObject(); - for (SharableEntry entry : playerProfile) { - if (entry.getValue() != null) { - if (entry.getSharable().getSerializer() == null) { - continue; - } - Sharable sharable = entry.getSharable(); - if (sharable.getProfileEntry().isStat()) { - jsonStats.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } else { - playerData.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } - } - } - if (!jsonStats.isEmpty()) { - playerData.put(DataStrings.PLAYER_STATS, jsonStats); - } - return playerData; - } +@Contract +public sealed interface ProfileDataSource permits FlatFileProfileDataSource { /** * Updates the persisted data for a player for a specific profile. @@ -249,42 +18,7 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) * * @param playerProfile The profile for the player that is being updated. */ - public void updatePlayerData(PlayerProfile playerProfile) { - queueWrite(playerProfile); - } - - private PlayerProfile getPlayerData(ProfileKey key) { - PlayerProfile cached = profileCache.getIfPresent(key); - if (cached != null) { - return cached; - } - File playerFile = null; - try { - playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); - } catch (IOException e) { - e.printStackTrace(); - // Return an empty profile - return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), - Bukkit.getOfflinePlayer(key.getPlayerUUID())); - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - if (convertConfig(playerData)) { - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + key.getPlayerName() - + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); - Logging.severe(e.getMessage()); - } - } - ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); - if (section == null) { - section = playerData.createSection(key.getProfileType().getName()); - } - PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); - profileCache.put(key, result); - return result; - } + void updatePlayerData(PlayerProfile playerProfile); /** * Retrieves a PlayerProfile from the data source. @@ -296,94 +30,7 @@ private PlayerProfile getPlayerData(ProfileKey key) { * @return The player as returned from data. If no data was found, a new PlayerProfile will be * created. */ - public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); - } - - private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); - for (Object keyObj : playerData.keySet()) { - String key = keyObj.toString(); - if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { - final Object statsObject = playerData.get(key); - if (statsObject instanceof String) { - parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); - } else { - if (statsObject instanceof Map) { - parsePlayerStatsIntoProfile((Map) statsObject, profile); - } else { - Logging.warning("Could not parse stats for " + pKey.getPlayerName()); - } - } - } else { - if (playerData.get(key) == null) { - Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); - continue; - } - try { - Sharable sharable = ProfileEntry.lookup(false, key); - if (sharable == null) { - Logging.fine("Player fileTag '" + key + "' is unrecognized!"); - continue; - } - profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); - } catch (Exception e) { - Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); - Logging.getLogger().log(Level.FINE, "Exception: ", e); - e.printStackTrace(); - } - } - } - Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); - return profile; - } - - private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { - for (Object key : stats.keySet()) { - Sharable sharable = ProfileEntry.lookup(true, key.toString()); - if (sharable != null) { - profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); - } else { - Logging.warning("Could not parse stat: '" + key + "' for player '" - + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" - + profile.getContainerName() + "'"); - } - } - } - - private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { - if (stats.isEmpty()) { - return; - } - JSONObject jsonStats = null; - try { - jsonStats = (JSONObject) JSON_PARSER.parse(stats); - } catch (ParseException | ClassCastException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } - if (jsonStats == null) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "'"); - return; - } - parsePlayerStatsIntoProfile(jsonStats, profile); - } - - // TODO Remove this conversion - private boolean convertConfig(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection("playerData"); - if (section != null) { - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set("playerData", null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; - } - return false; - } + PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); /** * Removes the persisted data for a player for a specific profile. @@ -395,52 +42,7 @@ private boolean convertConfig(FileConfiguration config) { * @param playerName The name of the player whose data is being removed. * @return True if successfully removed. */ - public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { - if (profileType == null) { - try { - File playerFile = getPlayerFile(containerType, dataName, playerName); - return playerFile.delete(); - } catch (IOException ignore) { - Logging.warning("Attempted to delete file that did not exist for player " + playerName - + " in " + containerType.name().toLowerCase() + " " + dataName); - return false; - } - } else { - File playerFile; - try { - playerFile = getPlayerFile(containerType, dataName, playerName); - } catch (IOException e) { - Logging.warning("Attempted to delete " + playerName + "'s data for " - + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() - + " " + dataName + " but the file did not exist."); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.set(profileType.getName(), null); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not delete data for player: " + playerName - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - } - - private Map convertSection(ConfigurationSection section) { - Map resultMap = new HashMap(); - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj instanceof ConfigurationSection) { - resultMap.put(key, convertSection((ConfigurationSection) obj)); - } else { - resultMap.put(key, obj); - } - } - return resultMap; - } + boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName); /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -450,9 +52,7 @@ private Map convertSection(ConfigurationSection section) { * @deprecated UUID must be supported now. */ @Deprecated - public GlobalProfile getGlobalProfile(String playerName) { - return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); - } + GlobalProfile getGlobalProfile(String playerName); /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -461,70 +61,7 @@ public GlobalProfile getGlobalProfile(String playerName) { * @param playerUUID The UUID of the player. * @return the global profile for the player with the given UUID. */ - public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { - GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); - if (cached != null) { - return cached; - } - File playerFile; - - // Migrate old data if necessary - try { - playerFile = getGlobalFile(playerName, false); - } catch (IOException e) { - // This won't ever happen - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName); - } - if (playerFile.exists()) { - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - if (!migrateGlobalProfileToUUID(profile, playerFile)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - globalProfileCache.put(playerUUID, profile); - return profile; - } - - // Load current format - try { - playerFile = getGlobalFile(playerUUID.toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName, playerUUID); - } - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - globalProfileCache.put(playerUUID, profile); - return profile; - } - - private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { - updateGlobalProfile(profile); - return playerFile.delete(); - } - - private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - ConfigurationSection section = playerData.getConfigurationSection("playerData"); - if (section == null) { - section = playerData.createSection("playerData"); - } - return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); - } - - private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, - Map playerData) { - GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); - for (String key : playerData.keySet()) { - if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { - globalProfile.setLastWorld(playerData.get(key).toString()); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { - globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { - globalProfile.setLastKnownName(playerData.get(key).toString()); - } - } - return globalProfile; - } + GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); /** * Update the file for a player's global profile. @@ -532,35 +69,7 @@ private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUI * @param globalProfile The GlobalProfile object to update the file for. * @return True if data successfully saved to file. */ - public boolean updateGlobalProfile(GlobalProfile globalProfile) { - File playerFile = null; - try { - playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save global data for player: " + globalProfile); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - - private Map serializeGlobalProfile(GlobalProfile profile) { - Map result = new HashMap(2); - if (profile.getLastWorld() != null) { - result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); - } - result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); - result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); - return result; - } + boolean updateGlobalProfile(GlobalProfile globalProfile); /** * A convenience method to update the GlobalProfile of a player with a specified world. @@ -568,13 +77,7 @@ private Map serializeGlobalProfile(GlobalProfile profile) { * @param playerName The player whose global profile this will update. * @param worldName The world to update the global profile with. */ - @Deprecated - // TODO replace for UUID - public void updateLastWorld(String playerName, String worldName) { - GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLastWorld(worldName); - updateGlobalProfile(globalProfile); - } + void updateLastWorld(String playerName, String worldName); /** * A convenience method for setting whether player data should be loaded on login for the specified player. @@ -582,73 +85,27 @@ public void updateLastWorld(String playerName, String worldName) { * @param playerName The player whose data should be loaded. * @param loadOnLogin Whether or not to load on login. */ - @Deprecated - // TODO replace for UUID - public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { - final GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLoadOnLogin(loadOnLogin); - updateGlobalProfile(globalProfile); - } + void setLoadOnLogin(String playerName, boolean loadOnLogin); /** * Copies all the data belonging to oldName to newName and removes the old data. * - * @param oldName the previous name of the player. - * @param newName the new name of the player. - * @param uuid the UUID of the player. + * @param oldName the previous name of the player. + * @param newName the new name of the player. + * @param playerUUID the UUID of the player. * @param removeOldData whether or not to remove the data belonging to oldName. * @throws IOException Thrown if something goes wrong while migrating the files. */ - public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { - File[] worldFolders = worldFolder.listFiles(File::isDirectory); - if (worldFolders == null) { - throw new IOException("Could not enumerate world folders"); - } - File[] groupFolders = groupFolder.listFiles(File::isDirectory); - if (groupFolders == null) { - throw new IOException("Could not enumerate group folders"); - } - - for (File worldFolder : worldFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - for (File groupFolder : groupFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - if (removeOldData) { - for (File worldFolder : worldFolders) { - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - for (File groupFolder : groupFolders) { - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - } - } + void migratePlayerData(String oldName, String newName, UUID playerUUID, boolean removeOldData) throws IOException; /** * Clears a single profile in cache. */ - public void clearProfileCache(ProfileKey key) { - profileCache.invalidate(key); - } + void clearProfileCache(ProfileKey key); - public void clearCache() { - globalProfileCache.invalidateAll(); - profileCache.invalidateAll(); - } + /** + * Clears all profiles in cache. + */ + void clearAllCache(); } From 17181ef720921b7d2d6cfcf20dbc571a25c232dd Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:33:49 +0800 Subject: [PATCH 041/180] Plugin class cannot be final for mockbukkit tests to work --- .../mvplugins/multiverse/inventories/MultiverseInventories.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 85812778..8e5f5645 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -40,7 +40,7 @@ * Multiverse-Inventories plugin main class. */ @Service -public final class MultiverseInventories extends MultiversePlugin implements Messaging { +public class MultiverseInventories extends MultiversePlugin implements Messaging { private static final int PROTOCOL = 50; From 403b5eae713be1e648247074ca3e3b29981503de Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:55:22 +0800 Subject: [PATCH 042/180] Fix player name change migration --- .../listeners/InventoriesListener.java | 44 ++++++----- .../profile/FlatFileProfileDataSource.java | 77 ++++++++++++------- .../profile/ProfileDataSource.java | 3 +- 3 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index 78a26eda..36a0eac0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -53,6 +53,7 @@ import java.io.File; import java.io.IOException; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -183,27 +184,9 @@ public void playerPreLogin(AsyncPlayerPreLoginEvent event) { if (event.getLoginResult() != Result.ALLOWED) { return; } - Logging.finer("Loading global profile for Player{name:'%s', uuid:'%s'}.", event.getName(), event.getUniqueId()); - - GlobalProfile globalProfile = profileDataSource.getGlobalProfile(event.getName(), event.getUniqueId()); - if (!globalProfile.getLastKnownName().equalsIgnoreCase(event.getName())) { - // Data must be migrated - Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", - event.getUniqueId(), globalProfile.getLastKnownName(), event.getName()); - try { - profileDataSource.migratePlayerData(globalProfile.getLastKnownName(), event.getName(), - event.getUniqueId(), true); - } catch (IOException e) { - Logging.severe("An error occurred while trying to migrate playerdata."); - e.printStackTrace(); - } - - globalProfile.setLastKnownName(event.getName()); - profileDataSource.updateGlobalProfile(globalProfile); - Logging.info("Migration complete!"); - } + verifyCorrectPlayerName(event.getUniqueId(), event.getName()); } /** @@ -214,6 +197,9 @@ public void playerPreLogin(AsyncPlayerPreLoginEvent event) { @EventHandler public void playerJoin(final PlayerJoinEvent event) { final Player player = event.getPlayer(); + // Just in case AsyncPlayerPreLoginEvent was still the old name + verifyCorrectPlayerName(player.getUniqueId(), player.getName()); + final GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId()); final String world = globalProfile.getLastWorld(); if (config.usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { @@ -228,6 +214,26 @@ public void playerJoin(final PlayerJoinEvent event) { verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); } + private void verifyCorrectPlayerName(UUID uuid, String name) { + GlobalProfile globalProfile = profileDataSource.getGlobalProfile(name, uuid); + if (globalProfile.getLastKnownName().equals(name)) { + return; + } + + // Data must be migrated + Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", + uuid, globalProfile.getLastKnownName(), name); + try { + profileDataSource.migratePlayerData(globalProfile.getLastKnownName(), name, uuid); + } catch (IOException e) { + Logging.severe("An error occurred while trying to migrate playerdata."); + e.printStackTrace(); + } + globalProfile.setLastKnownName(name); + profileDataSource.updateGlobalProfile(globalProfile); + Logging.info("Migration complete!"); + } + /** * Called when a player leaves the server. * diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 666ee66b..a5c29634 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -4,6 +4,7 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Sets; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; import org.bukkit.configuration.InvalidConfigurationException; @@ -143,11 +144,26 @@ private File getFolder(ContainerType type, String folderName) { * @return The data file for a player. * @throws IOException if there was a problem creating the file. */ - File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { + private File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { + return getPlayerFile(type, dataName, playerName, true); + } + + /** + * Retrieves the data file for a player based on a given world/group name, creating it if necessary. + * + * @param type Indicates whether data is for group or world. + * @param dataName The name of the group or world. + * @param playerName The name of the player. + * @return The data file for a player. + * @throws IOException if there was a problem creating the file. + */ + private File getPlayerFile(ContainerType type, String dataName, String playerName, boolean createNew) throws IOException { File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); if (!jsonPlayerFile.exists()) { try { - jsonPlayerFile.createNewFile(); + if (createNew) { + jsonPlayerFile.createNewFile(); + } } catch (IOException e) { throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName @@ -547,7 +563,9 @@ public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { } @Override - public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { + public void migratePlayerData(String oldName, String newName, UUID uuid) throws IOException { + clearPlayerCache(uuid); + File[] worldFolders = worldFolder.listFiles(File::isDirectory); if (worldFolders == null) { throw new IOException("Could not enumerate world folders"); @@ -557,36 +575,41 @@ public void migratePlayerData(String oldName, String newName, UUID uuid, boolean throw new IOException("Could not enumerate group folders"); } - for (File worldFolder : worldFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - for (File groupFolder : groupFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } + migrateForContainerType(worldFolders, ContainerType.WORLD, oldName, newName); + migrateForContainerType(groupFolders, ContainerType.GROUP, oldName, newName); + } - if (removeOldData) { - for (File worldFolder : worldFolders) { - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); + private void migrateForContainerType(File[] folders, ContainerType containerType, String oldName, String newName) throws IOException { + for (File folder : folders) { + File oldNameFile = getPlayerFile(containerType, folder.getName(), oldName, false); + File newNameFile = getPlayerFile(containerType, folder.getName(), newName, false); + if (!oldNameFile.exists()) { + Logging.fine("No old data for player %s in %s %s to migrate.", + oldName, containerType.name(), folder.getName()); + continue; + } + if (newNameFile.exists()) { + Logging.warning("Data already exists for player %s in %s %s. Not migrating.", + newName, containerType.name(), folder.getName()); + continue; } - for (File groupFolder : groupFolders) { - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); + if (!oldNameFile.renameTo(newNameFile)) { + Logging.warning("Could not rename old data file for player %s in %s %s to %s.", + oldName, containerType.name(), folder.getName(), newName); + continue; } + Logging.fine("Migrated data for player %s in %s %s to %s.", + oldName, containerType.name(), folder.getName(), newName); } } + void clearPlayerCache(UUID playerUUID) { + profileCache.invalidateAll(Sets.filter( + profileCache.asMap().keySet(), + key -> key.getPlayerUUID().equals(playerUUID) + )); + } + @Override public void clearProfileCache(ProfileKey key) { profileCache.invalidate(key); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index c9922395..b87292ff 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -93,10 +93,9 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param oldName the previous name of the player. * @param newName the new name of the player. * @param playerUUID the UUID of the player. - * @param removeOldData whether or not to remove the data belonging to oldName. * @throws IOException Thrown if something goes wrong while migrating the files. */ - void migratePlayerData(String oldName, String newName, UUID playerUUID, boolean removeOldData) throws IOException; + void migratePlayerData(String oldName, String newName, UUID playerUUID) throws IOException; /** * Clears a single profile in cache. From 91a116ced2075030ecde9bd08102d0cd7dba31a4 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:07:42 +0800 Subject: [PATCH 043/180] Move inventories config to use new config api --- .../inventories/MultiverseInventories.java | 7 +- .../inventories/commands/ToggleCommand.java | 8 +- .../inventories/config/InventoriesConfig.java | 265 ++++-------------- .../config/InventoriesConfigNodes.java | 119 ++++++++ 4 files changed, 186 insertions(+), 213 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 8e5f5645..f4ade941 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -86,6 +86,7 @@ public void onLoad() { public final void onEnable() { super.onEnable(); initializeDependencyInjection(); + inventoriesConfig.get().load().onFailure(e -> Logging.severe(e.getMessage())); Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); this.onMVPluginEnable(); @@ -206,6 +207,10 @@ public PluginServiceLocator getServiceLocator() { @Override public void reloadConfig() { try { + inventoriesConfig.get().load().onFailure(e -> { + Logging.severe("Failed to load config file!"); + Logging.severe(e.getMessage()); + }); worldGroupManager.get().load().onFailure(e -> { Logging.severe("Failed to load world groups!"); Logging.severe(e.getMessage()); @@ -216,7 +221,7 @@ public void reloadConfig() { profileDataSource.get().clearAllCache(); } - Logging.fine("Loaded config file!"); + Logging.fine("Reloaded all config and groups!"); } catch (Exception e) { // Catch errors loading the config file and exit out if found. Logging.severe(this.getMessager().getMessage(Message.ERROR_CONFIG_LOAD)); Logging.severe(e.getMessage()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 1348a895..414a41bc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -56,19 +56,21 @@ void onToggleCommand( return; } boolean foundOpt = false; + Shares optionalShares = inventoriesConfig.getOptionalShares(); for (Sharable sharable : shares) { if (sharable.isOptional()) { foundOpt = true; - if (inventoriesConfig.getOptionalShares().contains(sharable)) { - inventoriesConfig.getOptionalShares().remove(sharable); + if (optionalShares.contains(sharable)) { + optionalShares.remove(sharable); this.plugin.getMessager().normal(Message.NOW_NOT_USING_OPTIONAL, sender, sharable.getNames()[0]); } else { - inventoriesConfig.getOptionalShares().add(sharable); + optionalShares.add(sharable); this.plugin.getMessager().normal(Message.NOW_USING_OPTIONAL, sender, sharable.getNames()[0]); } } } if (foundOpt) { + inventoriesConfig.setOptionalShares(optionalShares); inventoriesConfig.save(); } else { this.plugin.getMessager().normal(Message.NO_OPTIONAL_SHARES, sender, shareName); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 0c0ef631..616c4298 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -2,20 +2,17 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.config.MVCoreConfig; -import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; +import org.mvplugins.multiverse.core.configuration.handle.CommentedConfigurationHandle; +import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle; import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; import org.bukkit.configuration.file.FileConfiguration; -import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.nio.file.Path; /** * Provides methods for interacting with the configuration of Multiverse-Inventories. @@ -23,180 +20,32 @@ @Service public final class InventoriesConfig { - /** - * Enum for easily keeping track of config paths, defaults and comments. - */ - public enum Path { - /** - * Locale name config path, default and comments. - */ - LANGUAGE_FILE_NAME("settings.locale", "en", "# This is the locale you wish to use."), - /** - * First Run flag config path, default and comments. - */ - FIRST_RUN("settings.first_run", true, "# If this is true it will generate world groups for you based on MV worlds."), - /** - * First Run flag config path, default and comments. - */ - USE_BYPASS("settings.use_bypass", false, "# If this is set to true, it will enable bypass permissions (Check the wiki for more info.)"), - - /** - * Whether or not to make ungrouped worlds use the default group. - */ - DEFAULT_UNGROUPED_WORLDS("settings.default_ungrouped_worlds", false, "# If set to true, any world not listed in a group will automatically use the settings for the default group!"), - - /** - * Whether or not to save/load player data on log out/in. - */ - LOGGING_SAVE_LOAD("settings.save_load_on_log_in_out", false, - "# The default and suggested setting for this is FALSE.", - "# False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out.", - "# That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY.", - "# Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in.", - "# The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back!"), - - USE_OPTIONALS_UNGROUPED("shares.optionals_for_ungrouped_worlds", true, - "# When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.", - "# An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.", - "# When set to false, optional shares WILL NOt be utilized in these cases, effectively disabling it for ungrouped worlds."), - /** - * First Run flag config path, default and comments. - */ - OPTIONAL_SHARES("shares.use_optionals", new ArrayList(), - "# You must specify optional shares you wish to use here or they will be ignored.", - "# The only built in optional shares are \"economy\" and \"last_location\"."), - /** - * Whether or not to split data based on game modes. - */ - USE_GAME_MODE_PROFILES("settings.use_game_mode_profiles", false, - "# If this is set to true, players will have different inventories/stats for each game mode.", - "# Please note that old data migrated to the version that has this feature will have their data copied for both game modes."); - - private String path; - private Object def; - private List comments; - - Path(String path, Object def, String... comments) { - this.path = path; - this.def = def; - this.comments = Arrays.asList(comments); - } - - /** - * Retrieves the path for a config option. - * - * @return The path for a config option. - */ - private String getPath() { - return this.path; - } - - /** - * Retrieves the default value for a config path. - * - * @return The default value for a config path. - */ - private Object getDefault() { - return this.def; - } - - /** - * Retrieves the comment for a config path. - * - * @return The comments for a config path. - */ - private List getComments() { - return this.comments; - } - } + public static final String CONFIG_FILENAME = "config.yml"; - private final CommentedConfiguration config; - private final MultiverseInventories plugin; - private final MVCoreConfig mvCoreConfig; + private final InventoriesConfigNodes configNodes; + private final CommentedConfigurationHandle configHandle; + private final StringPropertyHandle stringPropertyHandle; @Inject - InventoriesConfig(MultiverseInventories plugin, MVCoreConfig mvCoreConfig) throws IOException { - this.plugin = plugin; - this.mvCoreConfig = mvCoreConfig; - // Make the data folders - if (plugin.getDataFolder().mkdirs()) { - Logging.fine("Created data folder."); - } - - // Check if the config file exists. If not, create it. - File configFile = new File(plugin.getDataFolder(), "config.yml"); - boolean configFileExists = configFile.exists(); - if (!configFileExists) { - Logging.fine("Created config file."); - configFile.createNewFile(); - } - - // Load the configuration file into memory - config = new CommentedConfiguration(configFile.toPath()); - config.load(); - - // Sets defaults config values - this.setDefaults(); - - config.addComment("settings", "# Multiverse-Inventories Settings", ""); - - // Saves the configuration from memory to file - config.save(); - - Logging.setDebugLevel(this.getGlobalDebug()); + InventoriesConfig(MultiverseInventories inventories) throws IOException { + this.configNodes = new InventoriesConfigNodes(); + var configPath = Path.of(inventories.getDataFolder().getPath(), CONFIG_FILENAME); + this.configHandle = CommentedConfigurationHandle.builder(configPath, this.configNodes.getNodes()) + .logger(Logging.getLogger()) + .build(); + this.stringPropertyHandle = new StringPropertyHandle(this.configHandle); } - - /** - * Loads default settings for any missing config values. - */ - private void setDefaults() { - for (InventoriesConfig.Path path : InventoriesConfig.Path.values()) { - config.addComment(path.getPath(), path.getComments().toArray(new String[0])); - if (this.getConfig().get(path.getPath()) == null) { - if (path.getDefault() != null) { - Logging.fine("Config: Defaulting '" + path.getPath() + "' to " + path.getDefault()); - this.getConfig().set(path.getPath(), path.getDefault()); - } else { - this.getConfig().createSection(path.getPath()); - } - } - } - - } - - private Boolean getBoolean(Path path) { - return this.getConfig().getBoolean(path.getPath(), (Boolean) path.getDefault()); - } - - private Integer getInt(Path path) { - return this.getConfig().getInt(path.getPath(), (Integer) path.getDefault()); - } - - private String getString(Path path) { - return this.getConfig().getString(path.getPath(), (String) path.getDefault()); + public Try load() { + return this.configHandle.load(); } public FileConfiguration getConfig() { - return this.config; + return this.configHandle.getConfig(); } - /** - * Sets globalDebug level. - * - * @param globalDebug The new value. 0 = off. - */ - public void setGlobalDebug(int globalDebug) { - mvCoreConfig.setGlobalDebug(globalDebug); - } - - /** - * Gets globalDebug level. - * - * @return globalDebug. - */ - public int getGlobalDebug() { - return mvCoreConfig.getGlobalDebug(); + public StringPropertyHandle getStringPropertyHandle() { + return stringPropertyHandle; } /** @@ -205,7 +54,11 @@ public int getGlobalDebug() { * @return The locale string. */ public String getLocale() { - return this.getString(Path.LANGUAGE_FILE_NAME); + return this.configHandle.get(configNodes.locale); + } + + public Try setLocale(String locale) { + return this.configHandle.set(configNodes.locale, locale); } /** @@ -214,7 +67,7 @@ public String getLocale() { * @return True if first_run is set to true in config. */ public boolean isFirstRun() { - return this.getBoolean(Path.FIRST_RUN); + return this.configHandle.get(configNodes.firstRun); } /** @@ -222,22 +75,22 @@ public boolean isFirstRun() { * * @param firstRun What to set the flag to in the config. */ - public void setFirstRun(boolean firstRun) { - this.getConfig().set(Path.FIRST_RUN.getPath(), firstRun); + public Try setFirstRun(boolean firstRun) { + return this.configHandle.set(configNodes.firstRun, firstRun); } /** * @return True if we should check for bypass permissions. */ public boolean isUsingBypass() { - return this.getBoolean(Path.USE_BYPASS); + return this.configHandle.get(configNodes.useBypass); } /** * @param useBypass Whether or not to check for bypass permissions. */ - public void setUsingBypass(boolean useBypass) { - this.getConfig().set(Path.USE_BYPASS.getPath(), useBypass); + public Try setUsingBypass(boolean useBypass) { + return this.configHandle.set(configNodes.useBypass, useBypass); } /** @@ -246,7 +99,7 @@ public void setUsingBypass(boolean useBypass) { * @return True if should save and load on player log out and in. */ public boolean usingLoggingSaveLoad() { - return this.getBoolean(Path.LOGGING_SAVE_LOAD); + return this.configHandle.get(configNodes.loggingSaveLoad); } /** @@ -254,12 +107,10 @@ public boolean usingLoggingSaveLoad() { * * @param useLoggingSaveLoad true if should save and load on player log out and in. */ - public void setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { - this.getConfig().set(Path.LOGGING_SAVE_LOAD.getPath(), useLoggingSaveLoad); + public Try setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { + return this.configHandle.set(configNodes.loggingSaveLoad, useLoggingSaveLoad); } - private Shares optionalSharables = null; - /** * @return A list of optional {@link Sharable}s to be treated as * regular {@link Sharable}s throughout the code. @@ -267,44 +118,45 @@ public void setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { * contained in this list. */ public Shares getOptionalShares() { - if (this.optionalSharables == null) { - List list = this.getConfig().getList(Path.OPTIONAL_SHARES.getPath()); - if (list != null) { - this.optionalSharables = Sharables.fromList(list); - } else { - Logging.warning("'" + Path.OPTIONAL_SHARES.getPath() + "' is setup incorrectly!"); - this.optionalSharables = Sharables.noneOf(); - } - } - return this.optionalSharables; + return this.configHandle.get(configNodes.optionalShares); + } + + /** + * Sets the optional shares to be used. + * + * @param shares The optional shares to be used. + * @return True if successful. + */ + public Try setOptionalShares(Shares shares) { + return this.configHandle.set(configNodes.optionalShares, shares); } /** * @return true if worlds with no group should be considered part of the default group. */ public boolean isDefaultingUngroupedWorlds() { - return this.getBoolean(Path.DEFAULT_UNGROUPED_WORLDS); + return this.configHandle.get(configNodes.defaultUngroupedWorlds); } /** * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. */ - public void setDefaultingUngroupedWorlds(boolean useDefaultGroup) { - this.getConfig().set(Path.FIRST_RUN.getPath(), useDefaultGroup); + public Try setDefaultingUngroupedWorlds(boolean useDefaultGroup) { + return this.configHandle.set(configNodes.defaultUngroupedWorlds, useDefaultGroup); } /** * @return True if using separate data for game modes. */ public boolean isUsingGameModeProfiles() { - return this.getBoolean(Path.USE_GAME_MODE_PROFILES); + return this.configHandle.get(configNodes.useGameModeProfiles); } /** * @param useGameModeProfile whether to use separate data for game modes. */ - public void setUsingGameModeProfiles(boolean useGameModeProfile) { - this.getConfig().set(Path.USE_GAME_MODE_PROFILES.getPath(), useGameModeProfile); + public Try setUsingGameModeProfiles(boolean useGameModeProfile) { + return this.configHandle.set(configNodes.useGameModeProfiles, useGameModeProfile); } /** @@ -313,7 +165,7 @@ public void setUsingGameModeProfiles(boolean useGameModeProfile) { * @return true if should utilize optional shares in worlds that are not grouped. */ public boolean usingOptionalsForUngrouped() { - return this.getBoolean(Path.USE_OPTIONALS_UNGROUPED); + return this.configHandle.get(configNodes.useOptionalsForUngrouped); } /** @@ -321,20 +173,15 @@ public boolean usingOptionalsForUngrouped() { * * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. */ - public void setUsingOptionalsForUngrouped(final boolean usingOptionalsForUngrouped) { - this.getConfig().set(Path.USE_OPTIONALS_UNGROUPED.getPath(), usingOptionalsForUngrouped); + public Try setUsingOptionalsForUngrouped(final boolean usingOptionalsForUngrouped) { + return this.configHandle.set(configNodes.useOptionalsForUngrouped, usingOptionalsForUngrouped); } /** * Saves the configuration file to disk. */ - // TODO remove need for this method. - // TODO Figure out what I meant by the above todo message... - public void save() { - if (this.optionalSharables != null) { - this.getConfig().set(Path.OPTIONAL_SHARES.getPath(), this.optionalSharables.toStringList()); - } - this.config.save(); + public Try save() { + return this.configHandle.save(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java new file mode 100644 index 00000000..8f701531 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -0,0 +1,119 @@ +package org.mvplugins.multiverse.inventories.config; + +import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; +import org.mvplugins.multiverse.core.configuration.node.ConfigHeaderNode; +import org.mvplugins.multiverse.core.configuration.node.ConfigNode; +import org.mvplugins.multiverse.core.configuration.node.Node; +import org.mvplugins.multiverse.core.configuration.node.NodeGroup; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; +import java.util.Objects; + +final class InventoriesConfigNodes { + + private final NodeGroup nodes = new NodeGroup(); + + InventoriesConfigNodes() { + } + + NodeGroup getNodes() { + return nodes; + } + + private N node(N node) { + nodes.add(node); + return node; + } + + private final ConfigHeaderNode settingsHeader = node(ConfigHeaderNode.builder("settings") + .comment("#######################################") + .comment("# Settings for Multiverse-Inventories #") + .comment("#######################################") + .comment("") + .comment("") + .build()); + + final ConfigNode locale = node(ConfigNode.builder("settings.locale", String.class) + .comment("This is the locale you wish to use.") + .defaultValue("en") + .name("locale") + .build()); + + final ConfigNode firstRun = node(ConfigNode.builder("settings.first_run", Boolean.class) + .comment("") + .comment("If this is true it will generate world groups for you based on MV worlds.") + .defaultValue(true) + .name(null) + .build()); + + final ConfigNode useBypass = node(ConfigNode.builder("settings.use_bypass", Boolean.class) + .comment("") + .comment("If this is set to true, it will enable bypass permissions (Check the wiki for more info.)") + .defaultValue(false) + .name("use-bypass") + .build()); + + final ConfigNode defaultUngroupedWorlds = node(ConfigNode.builder("settings.default_ungrouped_worlds", Boolean.class) + .comment("") + .comment("If set to true, any world not listed in a group will automatically use the settings for the default group!") + .defaultValue(false) + .name("default-ungrouped-worlds") + .build()); + + final ConfigNode loggingSaveLoad = node(ConfigNode.builder("settings.save_load_on_log_in_out", Boolean.class) + .comment("") + .comment("The default and suggested setting for this is FALSE.") + .comment("False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out.") + .comment("That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY.") + .comment("Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in.") + .comment("The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back!") + .defaultValue(false) + .name("save-load-on-log-in-out") + .build()); + + private final ConfigHeaderNode sharesHeader = node(ConfigHeaderNode.builder("shares") + .comment("") + .comment("") + .build()); + + + final ConfigNode useOptionalsForUngrouped = node(ConfigNode.builder("shares.optionals_for_ungrouped_worlds", Boolean.class) + .comment("When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.") + .comment("An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.") + .comment("When set to false, optional shares WILL NOT be utilized in these cases, effectively disabling it for ungrouped worlds.") + .defaultValue(true) + .name("optionals-for-ungrouped-worlds") + .build()); + + final ConfigNode optionalShares = node(ConfigNode.builder("shares.use_optionals", Shares.class) + .comment("") + .comment("You must specify optional shares you wish to use here or they will be ignored.") + .comment("The only built-in optional shares are \"economy\" and \"last_location\".") + .defaultValue(Sharables.noneOf()) + .name(null) + .serializer(new NodeSerializer<>() { + @Override + public Shares deserialize(Object o, Class aClass) { + if (o instanceof List) { + return Sharables.fromList((List) o); + } + return Sharables.fromList(List.of(Objects.toString(o))); + } + + @Override + public Object serialize(Shares sharables, Class aClass) { + return sharables.toStringList(); + } + }) + .build()); + + final ConfigNode useGameModeProfiles = node(ConfigNode.builder("settings.use_game_mode_profiles", Boolean.class) + .comment("") + .comment("If this is set to true, players will have different inventories/stats for each game mode.") + .comment("Please note that old data migrated to the version that has this feature will have their data copied for both game modes.") + .defaultValue(false) + .name("use-game-mode-profiles") + .build()); +} From c4ad2aee54eb20b1ded8feb6c9fdd23c3f39fbd8 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:50:56 +0800 Subject: [PATCH 044/180] Remove deprecated WorldGroupManager methods --- .../group/AbstractWorldGroupManager.java | 17 ----------------- .../profile/group/WorldGroupManager.java | 19 ------------------- 2 files changed, 36 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index b8652ec6..1cf2d7ce 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -99,15 +99,6 @@ protected Map getGroupNames() { return groupNamesMap; } - /** - * {@inheritDoc} - */ - @Override - @Deprecated - public void addGroup(final WorldGroup worldGroup, final boolean persist) { - updateGroup(worldGroup); - } - @Override public void updateGroup(final WorldGroup worldGroup) { getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); @@ -135,14 +126,6 @@ public WorldGroup newEmptyGroup(String name) { return new WorldGroup(this, profileContainerStoreProvider, name); } - /** - * {@inheritDoc} - */ - @Override - @Deprecated - public void setGroups(List worldGroups) { - } - /** * {@inheritDoc} */ diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index 3cc7ede4..8527fe3b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -53,25 +53,6 @@ public sealed interface WorldGroupManager permits AbstractWorldGroupManager { */ boolean hasGroup(String worldName); - /** - * Sets up the World Groups in memory. - * - * @param worldGroups List of World Groups to store in memory. - * @deprecated This feature is now completely unused. - */ - @Deprecated - void setGroups(List worldGroups); - - /** - * Adds a World Group to the collection in memory, also writing it to the groups configuration. - * - * @param worldGroup World group to add. Casing is ignored. - * @param persist This parameter is unused due to deprecation of the method. - * @deprecated - */ - @Deprecated - void addGroup(WorldGroup worldGroup, boolean persist); - /** *

Adds or updates a world group in Multiverse-Inventories.

* From 422a678411cfcef0e26d9171b435d73802a822f5 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:51:50 +0800 Subject: [PATCH 045/180] Replace deprecated use of player name in favour of player uuid --- .../inventories/MultiverseInventories.java | 2 +- .../listeners/InventoriesListener.java | 12 ++++---- .../profile/FlatFileProfileDataSource.java | 25 ++++++++-------- .../inventories/profile/GlobalProfile.java | 10 +++---- .../inventories/profile/PlayerProfile.java | 10 ------- .../profile/ProfileDataSource.java | 29 ++++++++++++------- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 8e5f5645..657c8033 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -154,7 +154,7 @@ public void onDisable() { profileContainerStoreProvider.get().getStore(ContainerType.WORLD) .getContainer(world) .getPlayerData(player))); - profileDataSource.get().setLoadOnLogin(player.getName(), true); + profileDataSource.get().setLoadOnLogin(player.getUniqueId(), true); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index 78a26eda..cb1bac53 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -224,7 +224,7 @@ public void playerJoin(final PlayerJoinEvent event) { .getPlayerData(player) )); } - profileDataSource.setLoadOnLogin(player.getName(), false); + profileDataSource.setLoadOnLogin(player.getUniqueId(), false); verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); } @@ -237,7 +237,7 @@ public void playerJoin(final PlayerJoinEvent event) { public void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); - profileDataSource.updateLastWorld(player.getName(), world); + profileDataSource.updateLastWorld(player.getUniqueId(), world); if (config.usingLoggingSaveLoad()) { ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile( Sharables.allOf(), @@ -245,20 +245,20 @@ public void playerQuit(final PlayerQuitEvent event) { .getContainer(world) .getPlayerData(player) )); - profileDataSource.setLoadOnLogin(player.getName(), true); + profileDataSource.setLoadOnLogin(player.getUniqueId(), true); } SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); } private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { if (globalProfile.getLastWorld() == null) { - profileDataSource.updateLastWorld(player.getName(), world); + profileDataSource.updateLastWorld(player.getUniqueId(), world); } else { if (!world.equals(globalProfile.getLastWorld())) { Logging.fine("Player did not spawn in the world they were last reported to be in!"); new WorldChangeShareHandler(this.inventories, player, globalProfile.getLastWorld(), world).handleSharing(); - profileDataSource.updateLastWorld(player.getName(), world); + profileDataSource.updateLastWorld(player.getUniqueId(), world); } } } @@ -300,7 +300,7 @@ public void playerChangedWorld(PlayerChangedWorldEvent event) { } new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); - profileDataSource.updateLastWorld(player.getName(), toWorld.getName()); + profileDataSource.updateLastWorld(player.getUniqueId(), toWorld.getName()); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 666ee66b..e9b1b05f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -6,6 +6,7 @@ import com.google.common.cache.CacheBuilder; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; +import org.bukkit.OfflinePlayer; import org.bukkit.configuration.InvalidConfigurationException; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -423,9 +424,13 @@ private Map convertSection(ConfigurationSection section) { } @Override - @Deprecated - public GlobalProfile getGlobalProfile(String playerName) { - return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); + public GlobalProfile getGlobalProfile(UUID playerUUID) { + return getGlobalProfile(Bukkit.getOfflinePlayer(playerUUID)); + } + + @Override + public GlobalProfile getGlobalProfile(OfflinePlayer player) { + return getGlobalProfile(player.getName(), player.getUniqueId()); } @Override @@ -442,7 +447,7 @@ public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { } catch (IOException e) { // This won't ever happen e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName); + return GlobalProfile.createGlobalProfile(playerName, playerUUID); } if (playerFile.exists()) { GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); @@ -529,19 +534,15 @@ private Map serializeGlobalProfile(GlobalProfile profile) { } @Override - @Deprecated - // TODO replace for UUID - public void updateLastWorld(String playerName, String worldName) { - GlobalProfile globalProfile = getGlobalProfile(playerName); + public void updateLastWorld(UUID playerUUID, String worldName) { + GlobalProfile globalProfile = getGlobalProfile(playerUUID); globalProfile.setLastWorld(worldName); updateGlobalProfile(globalProfile); } @Override - @Deprecated - // TODO replace for UUID - public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { - final GlobalProfile globalProfile = getGlobalProfile(playerName); + public void setLoadOnLogin(final UUID playerUUID, final boolean loadOnLogin) { + final GlobalProfile globalProfile = getGlobalProfile(playerUUID); globalProfile.setLoadOnLogin(loadOnLogin); updateGlobalProfile(globalProfile); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index c95f7ddf..4dac573b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.profile; -import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import java.util.UUID; @@ -12,13 +12,11 @@ public final class GlobalProfile { /** * Creates a global profile object for the given player with default values. * - * @param playerName the player to create the profile object for. + * @param player the player to create the profile object for. * @return a new GlobalProfile for the given player. - * @deprecated Needs to use UUID. */ - @Deprecated - public static GlobalProfile createGlobalProfile(String playerName) { - return new GlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); + public static GlobalProfile createGlobalProfile(OfflinePlayer player) { + return new GlobalProfile(player.getName(), player.getUniqueId()); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 8ddc54d1..88e82d5c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -3,7 +3,6 @@ import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.SharableEntry; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import java.util.HashMap; @@ -20,15 +19,6 @@ public static PlayerProfile createPlayerProfile(ContainerType containerType, Str return new PlayerProfile(containerType, containerName, profileType, player); } - /** - * @deprecated Needs to use UUID for players - */ - @Deprecated - public static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, - ProfileType profileType, String playerName) { - return new PlayerProfile(containerType, containerName, profileType, Bukkit.getOfflinePlayer(playerName)); - } - private Map data = new HashMap(); private final OfflinePlayer player; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index c9922395..ad9e3092 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.profile; +import org.bukkit.OfflinePlayer; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -47,19 +48,25 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { /** * Retrieves the global profile for a player which contains meta-data for the player. * - * @param playerName The name of player to retrieve for. + * @param playerUUID The UUID of the player. * @return The global profile for the specified player. - * @deprecated UUID must be supported now. */ - @Deprecated - GlobalProfile getGlobalProfile(String playerName); + GlobalProfile getGlobalProfile(UUID playerUUID); /** * Retrieves the global profile for a player which contains meta-data for the player. * - * @param playerName The name of the player to retrieve for. This is required for updating name last known as. - * @param playerUUID The UUID of the player. - * @return the global profile for the player with the given UUID. + * @param player The player. + * @return The global profile for the specified player. + */ + GlobalProfile getGlobalProfile(OfflinePlayer player); + + /** + * Retrieves the global profile for a player which contains meta-data for the player. + * + * @param playerName The name of the player. + * @param playerUUID The UUID of the player. + * @return The global profile for the specified player. */ GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); @@ -74,18 +81,18 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { /** * A convenience method to update the GlobalProfile of a player with a specified world. * - * @param playerName The player whose global profile this will update. + * @param playerUUID The player whose global profile this will update. * @param worldName The world to update the global profile with. */ - void updateLastWorld(String playerName, String worldName); + void updateLastWorld(UUID playerUUID, String worldName); /** * A convenience method for setting whether player data should be loaded on login for the specified player. * - * @param playerName The player whose data should be loaded. + * @param playerUUID The player whose data should be loaded. * @param loadOnLogin Whether or not to load on login. */ - void setLoadOnLogin(String playerName, boolean loadOnLogin); + void setLoadOnLogin(UUID playerUUID, boolean loadOnLogin); /** * Copies all the data belonging to oldName to newName and removes the old data. From c0b2c2c82679bc89beb13e8d827f0ffe8bc6e6dc Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:35:29 +0800 Subject: [PATCH 046/180] Convert locale messages from yml to properties file --- .../multiverse-inventories_en.properties | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/resources/multiverse-inventories_en.properties diff --git a/src/main/resources/multiverse-inventories_en.properties b/src/main/resources/multiverse-inventories_en.properties new file mode 100644 index 00000000..c211aec7 --- /dev/null +++ b/src/main/resources/multiverse-inventories_en.properties @@ -0,0 +1,73 @@ +mv-inventories.test.string=a test-string from the resource + +# Generic Strings +mv-inventories.generic.sorry=Sorry... +mv-inventories.generic.page=Page +mv-inventories.generic.of=of +mv-inventories.generic.unloaded=UNLOADED +mv-inventories.generic.plugindisabled=This plugin is Disabled! +mv-inventories.generic.error=[Error] +mv-inventories.generic.success=[Success] +mv-inventories.generic.info=[Info] +mv-inventories.generic.help=[Help] +mv-inventories.generic.commandnopermission=You do not have permission to %1. (%2) +mv-inventories.generic.the-console=the console +mv-inventories.generic.notloggedin=%1 is not logged on right now! +mv-inventories.generic.off=OFF + +# Errors +mv-inventories.error.configload=Encountered an error while loading the configuration file. Disabling... +mv-inventories.error.dataload=Encountered an error while loading the data file. Disabling... +mv-inventories.error.nogroup=&6There is no group with the name: &f%1 +mv-inventories.error.noworld=&6There is no world with the name: &f%1 +mv-inventories.error.noworldprofile=&6There is no world profile for the world: &f%1 +mv-inventories.error.pluginnotenabled=&f%1 &6is not enabled so you may not import data from it! +mv-inventories.error.unsupportedimport=&6Sorry, ''&f%1&6'' is not supported for importing. +mv-inventories.error.nosharesspecified=&cYou did not specify any valid shares! + +# Conflicts +mv-inventories.conflict.results=Conflict found for groups: ''%1'' and ''%2'' because they both share: ''%3'' for the world(s): ''%4'' +mv-inventories.conflict.checking=Checking for conflicts in groups... +mv-inventories.conflict.found=Conflicts have been found... If these are not resolved, you may experience problems with your data. +mv-inventories.conflict.notfound=No group conflicts found! + +# Commands +## Info Command +mv-inventories.info.world=&b===[ Info for world: &6%1&b ]=== +mv-inventories.info.world.info=&6Groups:&f %1 +mv-inventories.info.group=&b===[ Info for group: &6%1&b ]=== +mv-inventories.info.group.info=&6Worlds:&f %1 +mv-inventories.info.group.infoshares=&bShares:&f %2 +mv-inventories.info.group.infonegativeshares=&bNegative Shares:&f %3 + +# List Command +mv-inventories.list.groups=&b===[ Group List ]=== +mv-inventories.list.groups-info=&6Groups:&f %1 + +# Reload Command +mv-inventories.reload.complete=&b===[ Reload Complete! ]=== + +# Addworld Command +mv-inventories.addworld.worldadded=&6World:&f %1 &6added to Group: &f%2 +mv-inventories.addworld.worldalreadyexists=&6World:&f %1 &6already part of Group: &f%2 + +# Removeworld Command +mv-inventories.removeworld.worldremoved=&6World:&f %1 &6removed from Group: &f%2 +mv-inventories.removeworld.worldnotingroup=&6World:&f %1 &6is not part of Group: &f%2 + +# Add/remove shares command +mv-inventories.shares.now-sharing=&6Group: &f%1 &6is now sharing: &f%2 &6and NOT sharing: &f%3 + +# Spawn command +mv-inventories.spawn.teleporting=Teleporting to this world group's spawn... +mv-inventories.spawn.teleportedby=You were teleported by: %1 +mv-inventories.spawn.teleportconsoleerror=From the console, you must provide a PLAYER + +# Debug command +mv-inventories.debug.invaliddebug=&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!) +mv-inventories.debug.set=Debug mode is %1 + +# Toggle command +mv-inventories.toggle.nowusingoptional=&f%1 &6will now be considered when player's change world. +mv-inventories.toggle.nownotusingoptional=&f%1 &6will no longer be considered when player's change world. +mv-inventories.toggle.nooptionalshares=&f%1 &6is not an optional share! From 8d817ca63473f6a26b28b9741032c137bf14a13c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:54:55 +0800 Subject: [PATCH 047/180] Use build logic from custom gradle plugins --- build.gradle | 166 ++++++------------------------------------------ settings.gradle | 9 +++ 2 files changed, 29 insertions(+), 146 deletions(-) diff --git a/build.gradle b/build.gradle index d6f991e5..b1fe23e1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,62 +1,12 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - id 'java-library' - id 'maven-publish' - id 'checkstyle' - id 'com.gradleup.shadow' version '8.3.5' - id "org.jetbrains.kotlin.jvm" version "2.0.21" + id 'org.mvplugins.multiverse-plugin' version '1.0.0' + id 'org.mvplugins.kotlin-test-only' version '1.0.0' } -version = System.getenv('GITHUB_VERSION') ?: 'local' group = 'org.mvplugins.multiverse.inventories' description = 'Multiverse-Inventories' -compileJava { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -compileKotlin { - // We're not using Kotlin in the plugin itself, just tests! - enabled = false -} - -compileTestJava { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -compileTestKotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_21) - javaParameters.set(true) - } -} - repositories { - mavenLocal() - mavenCentral() - - maven { - name = 'onarandombox' - url = uri('https://repo.onarandombox.com/content/groups/public') - } - - maven { - name = 'spigot' - url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' - content { - includeGroup 'org.bukkit' - includeGroup 'org.spigotmc' - } - } - - maven { - name ='papermc' - url = uri('https://papermc.io/repo/repository/maven-public/') - } - maven { name = 'jitpack.io' url = uri('https://jitpack.io/') @@ -68,41 +18,33 @@ repositories { } } -dependencies { - // Spigot - compileOnly('org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT') { - exclude group: 'junit', module: 'junit' - } +configure(apiDependencies) { + serverApiVersion = '1.21.4-R0.1-SNAPSHOT' + mockBukkitServerApiVersion = '1.21' +} +dependencies { // Core // TODO update to correct version once we have it published - implementation 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' - + externalPlugin 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' // Config - api 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' - api 'net.minidev:json-smart:2.5.1' + shadowed 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' + shadowed 'net.minidev:json-smart:2.5.1' // Utils - api('com.dumptruckman.minecraft:Logging:1.1.1') { + shadowed('com.dumptruckman.minecraft:Logging:1.1.1') { exclude group: 'junit', module: 'junit' } // Other plugins for import - implementation('uk.co:MultiInv:3.0.6') { + externalPlugin('uk.co:MultiInv:3.0.6') { exclude group: '*', module: '*' } - implementation('me.drayshak:WorldInventories:1.0.2') { + externalPlugin('me.drayshak:WorldInventories:1.0.2') { exclude group: '*', module: '*' } - // Tests - testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21' - testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.24.1' - testImplementation 'org.jetbrains.kotlin:kotlin-test' - testImplementation 'com.natpryce:hamkrest:1.8.0.1' - testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0' - // hk2 for annotation processing only compileOnly('org.glassfish.hk2:hk2-api:3.0.3') { exclude group: '*', module: '*' @@ -111,95 +53,20 @@ dependencies { testAnnotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' } - -java { - withSourcesJar() - withJavadocJar() -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} - -tasks.withType(JavaCompile) { - configure(options) { - options.compilerArgs << '-Aorg.glassfish.hk2.metadata.location=META-INF/hk2-locator/Multiverse-Inventories' - } -} - -tasks.withType(Javadoc).configureEach { - options.encoding = 'UTF-8' -} - -configurations { - [apiElements, runtimeElements].each { - it.outgoing.artifacts.removeIf { it.buildDependencies.getDependencies(null).contains(jar) } - it.outgoing.artifact(shadowJar) - } -} - -configurations.findAll { !it.name.startsWith('test') }.each { - it.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' -} - -processResources { - def props = [version: "${project.version}"] - inputs.properties props - filteringCharset 'UTF-8' - filesMatching('plugin.yml') { - expand props - } - - // This task should never be skipped. The tests depend on this having been run but we want the new version number - // that is created after tests are run and before we run again to publish. - outputs.upToDateWhen { false } -} - -checkstyle { - toolVersion = '6.1.1' - configFile file('config/mv_checks.xml') - ignoreFailures = true -} - -javadoc { - source = sourceSets.main.allJava - classpath = configurations.compileClasspath -} - -project.configurations.api.canBeResolved = true - shadowJar { relocate 'com.dumptruckman.minecraft.util.Logging', 'org.mvplugins.multiverse.inventories.utils.InvLogging' relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' - configurations = [project.configurations.api] - - archiveClassifier.set('') - dependencies { - exclude(dependency { - it.moduleGroup == 'org.jetbrains.kotlin' - }) - exclude(dependency { - it.moduleGroup == 'org.jetbrains' - }) exclude(dependency { it.moduleGroup == 'org.ow2.asm' }) } } -build.dependsOn shadowJar -jar.enabled = false - publishing { - publications { - maven(MavenPublication) { - from components.java - } - } repositories { maven { name = "GitHubPackages" @@ -209,5 +76,12 @@ publishing { password = System.getenv("GITHUB_TOKEN") } } + + maven { + // todo: remove before mv5 release + name = "multiverseBeta" + url = "https://repo.c0ding.party/multiverse-beta" + credentials(PasswordCredentials) + } } } diff --git a/settings.gradle b/settings.gradle index 62b418fb..4a328fcc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,13 @@ * This file was generated by the Gradle 'init' task. */ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url = uri('https://repo.onarandombox.com/multiverse-releases') + } + } +} + rootProject.name = 'multiverse-inventories' From eed936ce50e76a10f9c233b62f8715ee6fd78dca Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:11:48 +0800 Subject: [PATCH 048/180] Fix all command not registering --- .../multiverse/inventories/commands/InventoriesCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java index 19aee515..cd905e12 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -1,9 +1,9 @@ package org.mvplugins.multiverse.inventories.commands; +import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.jvnet.hk2.annotations.Contract; /** * Base class for all multiverse inventories commands. From 51a5df41d28b01e59ce414f137a4e0051b7c5efb Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:14:23 +0800 Subject: [PATCH 049/180] Fix some gradle configs --- build.gradle | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index b1fe23e1..be00e2ed 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,13 @@ plugins { - id 'org.mvplugins.multiverse-plugin' version '1.0.0' - id 'org.mvplugins.kotlin-test-only' version '1.0.0' + id 'checkstyle' + id 'org.mvplugins.multiverse-plugin' version '1.0.1' + id 'org.mvplugins.kotlin-test-only' version '1.0.1' } group = 'org.mvplugins.multiverse.inventories' description = 'Multiverse-Inventories' repositories { - maven { - name = 'jitpack.io' - url = uri('https://jitpack.io/') - } - maven { name = 'benwoo1110' url = uri('https://repo.c0ding.party/multiverse-beta') @@ -63,9 +59,18 @@ shadowJar { exclude(dependency { it.moduleGroup == 'org.ow2.asm' }) + exclude(dependency { + it.moduleGroup == 'org.jetbrains' + }) } } +checkstyle { + toolVersion = '6.1.1' + configFile file('config/mv_checks.xml') + ignoreFailures = true +} + publishing { repositories { maven { From 84f02635acbec6ba480f0192e5d09512d3985442 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:09:03 +0800 Subject: [PATCH 050/180] Make serializers package private --- .../multiverse/inventories/share/InventorySerializer.java | 2 +- .../multiverse/inventories/share/LocationSerializer.java | 2 +- .../multiverse/inventories/share/PotionEffectSerializer.java | 2 +- .../mvplugins/multiverse/inventories/share/SharableGroup.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index 4fdd7e57..67f8efad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -11,7 +11,7 @@ * A simple {@link SharableSerializer} usable with ItemStack[] which converts the ItemStack[] to the string format * that is used by default in Multiverse-Inventories. */ -public final class InventorySerializer implements SharableSerializer { +final class InventorySerializer implements SharableSerializer { private final int inventorySize; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java index a28e3f10..0a7db940 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java @@ -12,7 +12,7 @@ * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. This remains to convert legacy data. */ @Deprecated -public final class LocationSerializer implements SharableSerializer { +final class LocationSerializer implements SharableSerializer { @Override public Location deserialize(Object obj) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java index 191f21f8..2da4fcfb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java @@ -11,7 +11,7 @@ * A simple {@link SharableSerializer} usable with PotionEffect[] * which converts the PotionEffect[] to the string format that is used by default in Multiverse-Inventories. */ -public final class PotionEffectSerializer implements SharableSerializer { +final class PotionEffectSerializer implements SharableSerializer { @Override public PotionEffect[] deserialize(Object obj) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java index dc50a618..3f2e9b45 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java @@ -9,7 +9,7 @@ * This class represents a grouping of Sharable objects for the sole purpose of being able to use one keyword in * group setups to indicate multiple {@link Sharable}s. */ -public final class SharableGroup implements Shares { +final class SharableGroup implements Shares { private final String[] names; private final Shares shares; From e52c95b36ff7eca66eb68a4f5436695ed15f0c67 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:39:12 +0800 Subject: [PATCH 051/180] Implement various tests for gameplay behaviour and api classes --- build.gradle | 1 + .../profile/group/YamlWorldGroupManager.java | 1 + .../inventories/share/Sharables.java | 6 +- .../multiverse/inventories/InjectionTest.kt | 44 ++++++++++ .../inventories/TestWithMockBukkit.kt | 16 ++++ .../commands/AbstractCommandTest.kt | 18 +++++ .../inventories/commands/ToggleCommandTest.kt | 37 +++++++++ .../gameplay/GameModeChangeTest.kt | 7 ++ .../gameplay/PlayerNameChangeTest.kt | 81 +++++++++++++++++++ .../inventories/gameplay/WorldChangeTest.kt | 38 +++++++++ .../profile/ProfileContainerTest.kt | 7 ++ .../profile/ProfileDataSourceTest.kt | 7 ++ .../profile/WorldGroupManagerTest.kt | 50 ++++++++++++ .../resources/gameplay/name_change_groups.yml | 12 +++ .../gameplay/world_change_groups.yml | 21 +++++ src/test/resources/group/default_group.yml | 7 ++ src/test/resources/group/empty.yml | 0 src/test/resources/group/test_group.yml | 10 +++ 18 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt create mode 100644 src/test/resources/gameplay/name_change_groups.yml create mode 100644 src/test/resources/gameplay/world_change_groups.yml create mode 100644 src/test/resources/group/default_group.yml create mode 100644 src/test/resources/group/empty.yml create mode 100644 src/test/resources/group/test_group.yml diff --git a/build.gradle b/build.gradle index be00e2ed..93f54ed7 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ repositories { configure(apiDependencies) { serverApiVersion = '1.21.4-R0.1-SNAPSHOT' mockBukkitServerApiVersion = '1.21' + mockBukkitVersion = '4.31.0' } dependencies { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index b0854893..67b7285f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -88,6 +88,7 @@ public Try load() { for (final WorldGroup worldGroup : worldGroups) { getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); } + Logging.fine("Loaded " + worldGroups.size() + " world groups from config."); }); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 32362050..29098745 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -626,19 +626,19 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { /** * Grouping for inventory sharables. */ - public static final SharableGroup ALL_INVENTORY = new SharableGroup("inventory", + public static final Shares ALL_INVENTORY = new SharableGroup("inventory", fromSharables(INVENTORY, ARMOR, ENDER_CHEST, OFF_HAND), "inv", "inventories"); /** * Grouping for experience sharables. */ - public static final SharableGroup ALL_EXPERIENCE = new SharableGroup("experience", + public static final Shares ALL_EXPERIENCE = new SharableGroup("experience", fromSharables(EXPERIENCE, TOTAL_EXPERIENCE, LEVEL), "exp", "level"); /** * Grouping for air/breath related sharables. */ - public static final SharableGroup AIR = new SharableGroup("air", + public static final Shares AIR = new SharableGroup("air", fromSharables(REMAINING_AIR, MAXIMUM_AIR), "breath"); /** diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt new file mode 100644 index 00000000..1d843d57 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -0,0 +1,44 @@ +package org.mvplugins.multiverse.inventories + +import org.junit.jupiter.api.Test +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.listeners.InventoriesListener +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class InjectionTest : TestWithMockBukkit() { + + @Test + fun `InventoriesCommand are available as a service`() { + assertEquals(5, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + } + + @Test + fun `InventoriesConfig is available as a service`() { + assertNotNull(serviceLocator.getActiveService(InventoriesConfig::class.java)) + } + + @Test + fun `InventoriesListener is available as a service`() { + assertNotNull(serviceLocator.getActiveService(InventoriesListener::class.java)) + } + + @Test + fun `ProfileDataSource is available as a service`() { + assertNotNull(serviceLocator.getActiveService(ProfileDataSource::class.java)) + } + + @Test + fun `ProfileContainerStoreProvider is available as a service`() { + assertNotNull(serviceLocator.getActiveService(ProfileContainerStoreProvider::class.java)) + } + + @Test + fun `WorldGroupManager is available as a service`() { + assertNotNull(serviceLocator.getActiveService(WorldGroupManager::class.java)) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt index fe74d5f6..6a263b31 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -4,10 +4,15 @@ import com.dumptruckman.minecraft.util.Logging import org.bukkit.Location import org.bukkit.configuration.MemorySection import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.configuration.serialization.ConfigurationSerialization import org.mockbukkit.mockbukkit.MockBukkit +import org.mockbukkit.mockbukkit.inventory.ItemStackMock import org.mvplugins.multiverse.core.MultiverseCore import org.mvplugins.multiverse.core.inject.PluginServiceLocator import org.mvplugins.multiverse.inventories.mock.MVServerMock +import java.io.File +import java.nio.file.Path +import kotlin.io.path.absolutePathString import kotlin.test.* /** @@ -22,6 +27,8 @@ abstract class TestWithMockBukkit { @BeforeTest fun setUpMockBukkit() { + ConfigurationSerialization.registerClass(ItemStackMock::class.java) + server = MockBukkit.mock(MVServerMock()) multiverseCore = MockBukkit.load(MultiverseCore::class.java) multiverseInventories = MockBukkit.load(MultiverseInventories::class.java) @@ -32,11 +39,20 @@ abstract class TestWithMockBukkit { @AfterTest fun tearDownMockBukkit() { + server.pluginManager.disablePlugin(multiverseInventories) + server.pluginManager.disablePlugin(multiverseCore) MockBukkit.unmock() } fun getResourceAsText(path: String): String? = object {}.javaClass.getResource(path)?.readText() + fun writeResourceToConfigFile(resourcePath: String, configPath: String) { + val configResource = getResourceAsText(resourcePath) + assertNotNull(configResource) + File(Path.of(multiverseInventories.dataFolder.absolutePath, configPath).absolutePathString()) + .writeText(configResource) + } + fun assertConfigEquals(expectedPath: String, actualPath: String) { val actualString = multiverseInventories.dataFolder.toPath().resolve(actualPath).toFile().readText() val expectedString = getResourceAsText(expectedPath) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt new file mode 100644 index 00000000..325fd281 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt @@ -0,0 +1,18 @@ +package org.mvplugins.multiverse.inventories.commands + +import org.mockbukkit.mockbukkit.command.ConsoleCommandSenderMock +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import kotlin.test.BeforeTest + +abstract class AbstractCommandTest : TestWithMockBukkit() { + + protected lateinit var console: ConsoleCommandSenderMock + protected lateinit var player: PlayerMock + + @BeforeTest + fun setUpCommand() { + console = server.consoleSender + player = server.addPlayer("benwoo1110"); + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt new file mode 100644 index 00000000..5a3d7497 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt @@ -0,0 +1,37 @@ +package org.mvplugins.multiverse.inventories.commands + +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.share.Sharables +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ToggleCommandTest : AbstractCommandTest() { + + private lateinit var config : InventoriesConfig + + @BeforeTest + fun setUp() { + config = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") } + } + + @Test + fun `Toggle last_location on and off`() { + assertFalse(config.optionalShares.contains(Sharables.LAST_LOCATION)) + server.dispatchCommand(console, "mvinv toggle last_location") + assertTrue(config.optionalShares.contains(Sharables.LAST_LOCATION)) + server.dispatchCommand(console, "mvinv toggle last_location") + assertFalse(config.optionalShares.contains(Sharables.LAST_LOCATION)) + } + + @Test + fun `Toggle economy on and off`() { + assertFalse(config.optionalShares.contains(Sharables.ECONOMY)) + server.dispatchCommand(console, "mvinv toggle economy") + assertTrue(config.optionalShares.contains(Sharables.ECONOMY)) + server.dispatchCommand(console, "mvinv toggle economy") + assertFalse(config.optionalShares.contains(Sharables.ECONOMY)) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt new file mode 100644 index 00000000..372e82c8 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt @@ -0,0 +1,7 @@ +package org.mvplugins.multiverse.inventories.gameplay + +import org.mvplugins.multiverse.inventories.TestWithMockBukkit + +class GameModeChangeTest : TestWithMockBukkit() { + //TODO +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt new file mode 100644 index 00000000..b5610bcd --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt @@ -0,0 +1,81 @@ +package org.mvplugins.multiverse.inventories.gameplay + +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import java.nio.file.Path +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +class PlayerNameChangeTest : TestWithMockBukkit() { + + private lateinit var profileDataSource: ProfileDataSource + private lateinit var player: PlayerMock + + @BeforeTest + fun setUp() { + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") } + + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world_nether")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("test")).isSuccess) + + val worldGroupManager = serviceLocator.getActiveService(WorldGroupManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldGroupManager is not available as a service") } + writeResourceToConfigFile("/gameplay/name_change_groups.yml", "groups.yml") + assertTrue(worldGroupManager.load().isSuccess) + + player = server.addPlayer("Benji_0224") + } + + @Test + fun `Player name changes`() { + val stack = ItemStack.of(Material.STONE_BRICKS, 5) + player.health = 5.0 + player.inventory.setItem(0, stack) + + server.getWorld("world_nether")?.let { player.teleport(it.spawnLocation) } + assertEquals(5.0, player.health) + assertEquals(stack, player.inventory.getItem(0)) + + server.getWorld("test")?.let { player.teleport(it.spawnLocation) } + assertNotEquals(5.0, player.health) + assertNotEquals(stack, player.inventory.getItem(0)) + + assertTrue(player.disconnect()) + player.name = "benthecat10" + assertTrue(player.reconnect()) + + server.getWorld("world")?.let { player.teleport(it.spawnLocation) } + assertEquals(5.0, player.health) + assertEquals(Material.STONE_BRICKS, player.inventory.getItem(0)?.type) //TODO change to ItemStack equals after mockbukkit fix + + // check files + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world_nether", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "test", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "default", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "benthecat10.json").toFile().exists()) + + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world_nether", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "test", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "default", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "Benji_0224.json").toFile().exists()) + + // check player profile + assertEquals("benthecat10", profileDataSource.getGlobalProfile(player)?.lastKnownName) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt new file mode 100644 index 00000000..843ecd42 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt @@ -0,0 +1,38 @@ +package org.mvplugins.multiverse.inventories.gameplay + +import com.dumptruckman.minecraft.util.Logging +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import kotlin.test.* + +class WorldChangeTest : TestWithMockBukkit() { + + @BeforeTest + fun setUp() { + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world1")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world3")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world4")).isSuccess) + } + + @Test + fun `Test shares`() { + writeResourceToConfigFile("/gameplay/world_change_groups.yml", "groups.yml") + multiverseInventories.reloadConfig() + val player = server.addPlayer("Benji_0224") + Logging.fine("player world: " + server.getPlayer("Benji_0224")?.world?.name) + val stack = ItemStack.of(Material.STONE_BRICKS, 64) + player.inventory.setItem(0, stack) + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + server.getWorld("world4")?.let { player.teleport(it.spawnLocation) } + assertNotEquals(stack, player.inventory.getItem(0)) + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt new file mode 100644 index 00000000..7736f73c --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt @@ -0,0 +1,7 @@ +package org.mvplugins.multiverse.inventories.profile + +import org.mvplugins.multiverse.inventories.TestWithMockBukkit + +class ProfileContainerTest : TestWithMockBukkit() { + //TODO +} \ No newline at end of file diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt new file mode 100644 index 00000000..599011c0 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt @@ -0,0 +1,7 @@ +package org.mvplugins.multiverse.inventories.profile + +import org.mvplugins.multiverse.inventories.TestWithMockBukkit + +class ProfileDataSourceTest : TestWithMockBukkit() { + //TODO +} \ No newline at end of file diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt new file mode 100644 index 00000000..0348423e --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt @@ -0,0 +1,50 @@ +package org.mvplugins.multiverse.inventories.profile + +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import org.mvplugins.multiverse.inventories.share.Sharables +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class WorldGroupManagerTest : TestWithMockBukkit() { + + private lateinit var worldGroupManager: WorldGroupManager + + @BeforeTest + fun setUp() { + worldGroupManager = + serviceLocator.getActiveService(WorldGroupManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldGroupManager is not available as a service") + } + + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") + } + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world_nether")).isSuccess) + } + + @Test + fun `First run creates default group`() { + writeResourceToConfigFile("/group/empty.yml", "groups.yml") + assertTrue(worldGroupManager.load().isSuccess) + worldGroupManager.createDefaultGroup() + assertEquals("default", worldGroupManager.defaultGroup.name) + assertEquals(setOf("world", "world_nether"), worldGroupManager.defaultGroup.worlds) + assertConfigEquals("/group/default_group.yml", "groups.yml") + } + + @Test + fun `Create a new group`() { + val group = worldGroupManager.newEmptyGroup("test") + group.addWorld("test1") + group.addWorld("test2") + group.shares.setSharing(Sharables.ALL_INVENTORY, true) + worldGroupManager.updateGroup(group) + assertConfigEquals("/group/test_group.yml", "groups.yml") + } +} diff --git a/src/test/resources/gameplay/name_change_groups.yml b/src/test/resources/gameplay/name_change_groups.yml new file mode 100644 index 00000000..4d8e448c --- /dev/null +++ b/src/test/resources/gameplay/name_change_groups.yml @@ -0,0 +1,12 @@ +groups: + default: + worlds: + - world + - world_nether + shares: + - all + test: + worlds: + - test + shares: + - all \ No newline at end of file diff --git a/src/test/resources/gameplay/world_change_groups.yml b/src/test/resources/gameplay/world_change_groups.yml new file mode 100644 index 00000000..f44e2bbb --- /dev/null +++ b/src/test/resources/gameplay/world_change_groups.yml @@ -0,0 +1,21 @@ +groups: + group1: + worlds: + - world1 + - world2 + shares: + - inventory_contents + - hit_points + group2: + worlds: + - world3 + shares: + - inventory_contents + - hit_points + group3: + worlds: + - world1 + - world2 + - world3 + shares: + - off_hand diff --git a/src/test/resources/group/default_group.yml b/src/test/resources/group/default_group.yml new file mode 100644 index 00000000..5aaab393 --- /dev/null +++ b/src/test/resources/group/default_group.yml @@ -0,0 +1,7 @@ +groups: + default: + worlds: + - world + - world_nether + shares: + - all diff --git a/src/test/resources/group/empty.yml b/src/test/resources/group/empty.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/group/test_group.yml b/src/test/resources/group/test_group.yml new file mode 100644 index 00000000..cfc105c2 --- /dev/null +++ b/src/test/resources/group/test_group.yml @@ -0,0 +1,10 @@ +groups: + test: + worlds: + - test2 + - test1 + shares: + - inventory_contents + - armor_contents + - ender_chest + - off_hand From d6cb230a93a5cbb3820c51b84f5a4e12292b61a0 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:27:16 +0800 Subject: [PATCH 052/180] Bump mockbukkit that fixes ItemMeta serialization --- build.gradle | 2 +- .../multiverse/inventories/gameplay/PlayerNameChangeTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 93f54ed7..4706fbc6 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ repositories { configure(apiDependencies) { serverApiVersion = '1.21.4-R0.1-SNAPSHOT' mockBukkitServerApiVersion = '1.21' - mockBukkitVersion = '4.31.0' + mockBukkitVersion = '4.31.1' } dependencies { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt index b5610bcd..3962b697 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt @@ -60,7 +60,7 @@ class PlayerNameChangeTest : TestWithMockBukkit() { server.getWorld("world")?.let { player.teleport(it.spawnLocation) } assertEquals(5.0, player.health) - assertEquals(Material.STONE_BRICKS, player.inventory.getItem(0)?.type) //TODO change to ItemStack equals after mockbukkit fix + assertEquals(stack, player.inventory.getItem(0)) // check files assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world", "benthecat10.json").toFile().exists()) From 4378541fbc3c4fda80edb430d7b8a9867d58bc72 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:17:51 +0800 Subject: [PATCH 053/180] Enable debug log for tests --- .../multiverse/inventories/TestWithMockBukkit.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt index 6a263b31..0a9edad4 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -1,6 +1,5 @@ package org.mvplugins.multiverse.inventories -import com.dumptruckman.minecraft.util.Logging import org.bukkit.Location import org.bukkit.configuration.MemorySection import org.bukkit.configuration.file.YamlConfiguration @@ -8,12 +7,17 @@ import org.bukkit.configuration.serialization.ConfigurationSerialization import org.mockbukkit.mockbukkit.MockBukkit import org.mockbukkit.mockbukkit.inventory.ItemStackMock import org.mvplugins.multiverse.core.MultiverseCore +import org.mvplugins.multiverse.core.config.MVCoreConfig import org.mvplugins.multiverse.core.inject.PluginServiceLocator import org.mvplugins.multiverse.inventories.mock.MVServerMock import java.io.File import java.nio.file.Path import kotlin.io.path.absolutePathString -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull /** * Basic abstract test class that sets up MockBukkit and MultiverseCore. @@ -31,8 +35,8 @@ abstract class TestWithMockBukkit { server = MockBukkit.mock(MVServerMock()) multiverseCore = MockBukkit.load(MultiverseCore::class.java) + multiverseCore.serviceLocator.getService(MVCoreConfig::class.java).globalDebug = 3 multiverseInventories = MockBukkit.load(MultiverseInventories::class.java) - Logging.setDebugLevel(3) serviceLocator = multiverseInventories.serviceLocator assertNotNull(server.commandMap) } From 48baa8370eced3c4aa5b752dc4ae0d5cf2ed6e73 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 6 Feb 2025 22:41:26 +0800 Subject: [PATCH 054/180] Revamp 3rd party plugin import with perworldinventory support --- build.gradle | 15 +- .../inventories/MultiverseInventories.java | 25 +- .../inventories/commands/ImportCommand.java | 72 ++++ .../dataimport/AbstractDataImporter.java | 124 +++++++ .../dataimport/DataImportException.java | 56 +++ .../dataimport/DataImportManager.java | 107 ++++++ .../inventories/dataimport/DataImporter.java | 73 ++++ .../multiinv/MIInventoryConverter.java | 4 +- .../multiinv/MIInventoryInterface.java | 4 +- .../multiinv/MIInventoryOldWrapper.java | 4 +- .../multiinv/MIInventoryWrapper.java | 4 +- .../multiinv/MIPlayerFileLoader.java | 4 +- .../multiinv/MultiInvImportHelper.java} | 112 ++---- .../dataimport/multiinv/MultiInvImporter.java | 63 ++++ .../multiinv/package-info.java | 2 +- .../inventories/dataimport/package-info.java | 1 + .../PerWorldInventoryImporter.java | 66 ++++ .../perworldinventory/PwiImportHelper.java | 349 ++++++++++++++++++ .../perworldinventory/package-info.java | 1 + .../WorldInventoriesImportHelper.java} | 104 ++---- .../WorldInventoriesImporter.java | 64 ++++ .../worldinventories/package-info.java | 2 +- .../listeners/InventoriesListener.java | 40 +- .../inventories/migration/DataImporter.java | 22 -- .../inventories/migration/ImportManager.java | 76 ---- .../migration/MigrationException.java | 32 -- .../inventories/migration/package-info.java | 6 - src/main/resources/plugin.yml | 2 +- .../multiverse/inventories/InjectionTest.kt | 8 +- 29 files changed, 1099 insertions(+), 343 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/multiinv/MIInventoryConverter.java (89%) rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/multiinv/MIInventoryInterface.java (67%) rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/multiinv/MIInventoryOldWrapper.java (78%) rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/multiinv/MIInventoryWrapper.java (79%) rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/multiinv/MIPlayerFileLoader.java (97%) rename src/main/java/org/mvplugins/multiverse/inventories/{migration/multiinv/MultiInvImporter.java => dataimport/multiinv/MultiInvImportHelper.java} (63%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/multiinv/package-info.java (55%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java rename src/main/java/org/mvplugins/multiverse/inventories/{migration/worldinventories/WorldInventoriesImporter.java => dataimport/worldinventories/WorldInventoriesImportHelper.java} (78%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java rename src/main/java/org/mvplugins/multiverse/inventories/{migration => dataimport}/worldinventories/package-info.java (55%) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java diff --git a/build.gradle b/build.gradle index 4706fbc6..c6e91735 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,11 @@ group = 'org.mvplugins.multiverse.inventories' description = 'Multiverse-Inventories' repositories { + maven { + name = 'codemc' + url = uri('https://repo.codemc.org/repository/maven-releases') + } + maven { name = 'benwoo1110' url = uri('https://repo.c0ding.party/multiverse-beta') @@ -15,7 +20,7 @@ repositories { } configure(apiDependencies) { - serverApiVersion = '1.21.4-R0.1-SNAPSHOT' + serverApiVersion = '1.21.3-R0.1-SNAPSHOT' mockBukkitServerApiVersion = '1.21' mockBukkitVersion = '4.31.1' } @@ -35,10 +40,14 @@ dependencies { } // Other plugins for import - externalPlugin('uk.co:MultiInv:3.0.6') { + compileOnly('uk.co:MultiInv:3.0.6') { + exclude group: '*', module: '*' + } + compileOnly('me.drayshak:WorldInventories:1.0.2') { exclude group: '*', module: '*' } - externalPlugin('me.drayshak:WorldInventories:1.0.2') { + // perworldinventory is weird and has snakeyaml included in the jar, so we can only use compileOnly for build to work properly + compileOnly('me.ebonjaeger:perworldinventory-kt:2.3.2') { exclude group: '*', module: '*' } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 5113bbbe..c666d2bf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -11,12 +11,13 @@ import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.dataimport.DataImporter; import org.mvplugins.multiverse.inventories.listeners.InventoriesListener; import org.mvplugins.multiverse.inventories.listeners.SpawnChangeListener; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Messaging; -import org.mvplugins.multiverse.inventories.migration.ImportManager; import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -24,17 +25,13 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.util.Perm; -import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginManager; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jakarta.inject.Provider; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.vavr.control.Try; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; /** * Multiverse-Inventories plugin main class. @@ -59,7 +56,7 @@ public class MultiverseInventories extends MultiversePlugin implements Messaging @Inject private Provider profileContainerStoreProvider; @Inject - private Provider importManager; + private Provider dataImportManager; private PluginServiceLocator serviceLocator; private Messager messager = new DefaultMessager(this); @@ -86,9 +83,9 @@ public void onLoad() { public final void onEnable() { super.onEnable(); initializeDependencyInjection(); + Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); inventoriesConfig.get().load().onFailure(e -> Logging.severe(e.getMessage())); - Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); this.onMVPluginEnable(); Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), getTargetCoreProtocolVersion(), StringFormatter.joinAnd(this.getDescription().getAuthors())); @@ -174,15 +171,9 @@ private void registerCommands() { } private void hookImportables() { - final PluginManager pm = Bukkit.getPluginManager(); - Plugin plugin = pm.getPlugin("MultiInv"); - if (plugin != null) { - importManager.get().hookMultiInv((MultiInv) plugin); - } - plugin = pm.getPlugin("WorldInventories"); - if (plugin != null) { - importManager.get().hookWorldInventories((WorldInventories) plugin); - } + serviceLocator.getAllServices(DataImporter.class).forEach(dataImporter -> { + dataImportManager.get().register(dataImporter); + }); } /** @@ -207,6 +198,8 @@ public PluginServiceLocator getServiceLocator() { @Override public void reloadConfig() { try { + Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); + inventoriesConfig.get().load().onFailure(e -> { Logging.severe("Failed to load config file!"); Logging.severe(e.getMessage()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java new file mode 100644 index 00000000..31fbc3ae --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -0,0 +1,72 @@ +package org.mvplugins.multiverse.inventories.commands; + +import com.dumptruckman.minecraft.util.Logging; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.commandtools.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.commandtools.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class ImportCommand extends InventoriesCommand { + + private final DataImportManager dataImportManager; + private final CommandQueueManager commandQueueManager; + + @Inject + ImportCommand( + @NotNull MVCommandManager commandManager, + @NotNull DataImportManager dataImportManager, + @NotNull CommandQueueManager commandQueueManager) { + super(commandManager); + this.dataImportManager = dataImportManager; + this.commandQueueManager = commandQueueManager; + } + + @Subcommand("import") + @Syntax("") + @CommandPermission("multiverse.inventories.import") + @CommandCompletion("MultiInv|WorldInventories|PerWorldInventory") + @Description("Import inventories from MultiInv/WorldInventories/PerWorldInventory plugin.") + public void onImportCommand( + MVCommandIssuer issuer, + + @Single + @Syntax("") + String pluginName) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to import data from {plugin}? This will override existing Multiverse-Inventories playerdata!!!", + replace("{plugin}").with(pluginName))) + .action(() -> doDataImport(issuer, pluginName))); + } + + void doDataImport(MVCommandIssuer issuer, String pluginName) { + dataImportManager.getImporter(pluginName) + .onEmpty(() -> issuer.sendMessage("No importer found for " + pluginName)) + .peek(dataImporter -> { + if (!dataImporter.isEnabled()) { + issuer.sendMessage("Plugin " + pluginName + " is not running on your server!"); + return; + } + if (dataImporter.importData()) { + issuer.sendMessage("Successfully to imported data from " + pluginName + "!"); + } else { + issuer.sendMessage("Failed to import data from " + pluginName + "."); + } + }); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java new file mode 100644 index 00000000..a399506d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java @@ -0,0 +1,124 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jvnet.hk2.annotations.Contract; + +/** + * Abstract implementation of {@link DataImporter} without actual import logic. + */ +@Contract +public abstract class AbstractDataImporter implements DataImporter { + + protected Plugin importer = null; + + public AbstractDataImporter() { + } + + /** + * Logic that does the actual importing data. + * + * @throws DataImportException Errors occurred that caused import to fail. + */ + protected abstract void doDataImport() throws DataImportException; + + /** + * {@inheritDoc} + */ + @Override + public boolean importData(boolean disableOnSuccess) { + if (!isEnabled()) { + Logging.severe("Data importer %s not enabled. No data is imported.", this.getPluginName()); + return false; + } + + try { + doDataImport(); + } catch (DataImportException e) { + Logging.severe(e.getMessage()); + if(e.getCauseException() != null) { + Logging.severe("Cause: %s", e.getCauseException().getMessage()); + } + e.printStackTrace(); + return false; + } + + Logging.info("Successfully imported data from %s!", this.getPluginName()); + if (disableOnSuccess) { + Logging.info("Disabling %s...", this.getPluginName()); + Bukkit.getPluginManager().disablePlugin(this.importer); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean importData() { + return importData(true); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean enable(Plugin importerPlugin) { + if (isEnabled()) { + return false; + } + if (!importerPlugin.getClass().equals(this.getPluginClass())) { + Logging.warning("Plugin '%s' is not data importer for '%s'.", + importerPlugin.getClass().getName(), getPluginName()); + return false; + } + try { + this.importer = importerPlugin; + } catch (ClassCastException | NoClassDefFoundError e) { + Logging.warning("Error while enabling data importer for '%s'.", getPluginName()); + return false; + } + Logging.info("Successfully enabled data importer for '%s'.", getPluginName()); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean enable() { + Plugin importerPlugin = Bukkit.getPluginManager().getPlugin(this.getPluginName()); + if (importerPlugin == null) { + Logging.finer("Unable to get plugin '%s' for import hook.", this.getPluginName()); + return false; + } + return enable(importerPlugin); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean disable() { + this.importer = null; + Logging.info("Successfully disabled data importer for '%s'.", getPluginName()); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + return importer != null; + } + + /** + * {@inheritDoc} + */ + @Override + public Plugin getPlugin() { + return importer; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java new file mode 100644 index 00000000..13cc563b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import org.mvplugins.multiverse.core.exceptions.MultiverseException; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; + +/** + * Exception thrown when migration doesn't go well. + */ +public class DataImportException extends MultiverseException { + + private Exception causeException = null; + + public DataImportException(@Nullable String message) { + super(message); + } + + public DataImportException(@Nullable String message, Exception causeException) { + super(message); + this.causeException = causeException; + } + + public DataImportException(@Nullable Message message, Exception causeException) { + super(message); + this.causeException = causeException; + } + + public DataImportException(@Nullable String message, @Nullable Throwable cause, Exception causeException) { + super(message, cause); + this.causeException = causeException; + } + + public DataImportException(@Nullable Message message, @Nullable Throwable cause, Exception causeException) { + super(message, cause); + this.causeException = causeException; + } + + /** + * Sets what the causing exception was, if any. + * + * @param exception The cause exception. + * @return This exception for easy chainability. + */ + public DataImportException setCauseException(Exception exception) { + this.causeException = exception; + return this; + } + + /** + * @return The causing exception or null if none. + */ + public Exception getCauseException() { + return this.causeException; + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java new file mode 100644 index 00000000..4925b847 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java @@ -0,0 +1,107 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Manager class for importing data from other inventory plugins or similar, e.g. PerWorldInventory. + */ +@Service +public final class DataImportManager implements Listener { + + final private Map dataImporters; + + @Inject + DataImportManager(@NotNull MultiverseInventories inventories, @NotNull PluginManager pluginManager) { + this.dataImporters = new HashMap<>(); + pluginManager.registerEvents(this, inventories); + } + + /** + * Register a Data Importer and optionally try to enable to it as well. + * + * @param dataImporter The Data Importer to register. + * @param tryEnable Whether to try and {@link DataImporter#enable(Plugin)} the Data Importer. + */ + public void register(DataImporter dataImporter, boolean tryEnable) { + this.dataImporters.put(dataImporter.getPluginName().toLowerCase(), dataImporter); + if (tryEnable) { + dataImporter.enable(); + } + } + + /** + * Register a Data Importer and try to enable to it as well. + * + * @param dataImporter The Data Importer to register. + */ + public void register(DataImporter dataImporter) { + this.register(dataImporter, true); + } + + /** + * Gets a {@link DataImporter} based on an importable plugin name. + * + * @param pluginName The plugin name you want to import data from. + * @return The {@link DataImporter} if Data Importer present for that plugin, else null. + */ + public Option getImporter(String pluginName) { + return Option.of(this.dataImporters.get(pluginName.toLowerCase())); + } + + /** + * Gets a {@link DataImporter} based on an importable {@link Plugin}. + * + * @param plugin The plugin you want to import data from. + * @return The {@link DataImporter} if Data Importer present for that plugin, else null. + */ + public Option getImporter(Plugin plugin) { + return getImporter(plugin.getName()); + } + + /** + * Gets all the Data Importer names that are enabled. + * + * @return A collection of Data Importer names that are enabled. + */ + public Collection getEnabledImporterNames() { + return this.dataImporters.values().stream() + .filter(DataImporter::isEnabled) + .map(DataImporter::getPluginName) + .collect(Collectors.toList()); + } + + /** + * Called when a plugin is enabled. + * + * @param event The plugin enable event. + */ + @EventHandler + private void pluginEnable(PluginEnableEvent event) { + getImporter(event.getPlugin()).peek(dataImporter -> dataImporter.enable(event.getPlugin())); + } + + /** + * Called when a plugin is disabled. + * + * @param event The plugin disable event. + */ + @EventHandler + private void pluginDisable(PluginDisableEvent event) { + getImporter(event.getPlugin()).peek(DataImporter::disable); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java new file mode 100644 index 00000000..40d540f7 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java @@ -0,0 +1,73 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jvnet.hk2.annotations.Contract; + +/** + * Interface for data migration importers. + */ +@Contract +public interface DataImporter { + + /** + * Imports the data from another plugin and optionally disable it after successful import. + * + * @param disableOnSuccess Whether to disable the importer plugin after a successful import. + * @return True if data import is successful, else false. + */ + boolean importData(boolean disableOnSuccess); + + /** + * Imports the data from another plugin and disabled it upon success so Multiverse inventories + * can work without conflicts. + * + * @return True if data import is successful, else false. + */ + boolean importData(); + + /** + * Hooks plugin for importing its data. Needs plugin class of {@link #getPluginClass()}. + * + * @param plugin The target plugin instance to hook. + * @return True if successfully enabled, else false. + */ + boolean enable(Plugin plugin); + + /** + * Hooks plugin for importing its data. Needs plugin class of {@link #getPluginClass()}. + * + * @return True if successfully enabled, else false. + */ + boolean enable(); + + /** + * Unhook plugin from this Data Importer. + * + * @return True if successfully disabled, else false. + */ + boolean disable(); + + /** + * Checks if this Data Importer has been {@link #enable(Plugin)} successfully. + * + * @return True if is enabled, else false. + */ + boolean isEnabled(); + + /** + * @return The plugin associated with this Data Importer, null if not enabled. + */ + @Nullable Plugin getPlugin(); + + /** + * @return The plugin name associated with this Data Importer. + */ + @NotNull String getPluginName(); + + /** + * @return The plugin class associated with this Data Importer. + */ + @NotNull Class getPluginClass(); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryConverter.java similarity index 89% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryConverter.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryConverter.java index d4180de6..2cc77354 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryConverter.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.inventory.ItemStack; @@ -7,7 +7,7 @@ /** * Utility class for converting proprietary shit from MultiInv. */ -public class MIInventoryConverter { +final class MIInventoryConverter { /** * @param oldContents Proprietary shiet from MultiInv. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryInterface.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryInterface.java similarity index 67% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryInterface.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryInterface.java index 9259bd90..980e649b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryInterface.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryInterface.java @@ -1,11 +1,11 @@ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.bukkit.inventory.ItemStack; /** * A little interface for retrieving normal ItemStack from the MultiInv inventory classes. */ -public interface MIInventoryInterface { +sealed interface MIInventoryInterface permits MIInventoryWrapper, MIInventoryOldWrapper { /** * @return The inventory contents. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryOldWrapper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryOldWrapper.java similarity index 78% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryOldWrapper.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryOldWrapper.java index 89b3b614..8f5d90d1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryOldWrapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryOldWrapper.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIInventoryOld; @@ -6,7 +6,7 @@ /** * Wraps MIInventoryOld to provide a way of accessing the inventory/armor contents. */ -public class MIInventoryOldWrapper extends MIInventoryOld implements MIInventoryInterface { +final class MIInventoryOldWrapper extends MIInventoryOld implements MIInventoryInterface { public MIInventoryOldWrapper(String inventoryString) { super(inventoryString); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryWrapper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryWrapper.java similarity index 79% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryWrapper.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryWrapper.java index 7dadff75..b333eece 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIInventoryWrapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryWrapper.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIInventory; @@ -6,7 +6,7 @@ /** * Wraps MIInventory to provide a way of accessing the inventory/armor contents. */ -public class MIInventoryWrapper extends MIInventory implements MIInventoryInterface { +final class MIInventoryWrapper extends MIInventory implements MIInventoryInterface { public MIInventoryWrapper(String inventoryString) { super(inventoryString); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIPlayerFileLoader.java similarity index 97% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIPlayerFileLoader.java index 03988746..97dc42de 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MIPlayerFileLoader.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIPlayerFileLoader.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.mvplugins.multiverse.inventories.util.PlayerStats; import org.bukkit.OfflinePlayer; @@ -10,7 +10,7 @@ /** * A replacement for MultiInv's MIPlayerFile class so that it may accept an OfflinePlayer instead of Player. */ -public class MIPlayerFileLoader { +final class MIPlayerFileLoader { private final YamlConfiguration playerFile; private final File file; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java similarity index 63% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java index 8770201a..b0639d94 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/MultiInvImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java @@ -1,24 +1,21 @@ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; -import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.migration.DataImporter; -import org.mvplugins.multiverse.inventories.migration.MigrationException; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; import uk.co.tggl.pluckerpluck.multiinv.MIYamlFiles; import uk.co.tggl.pluckerpluck.multiinv.MultiInv; @@ -26,52 +23,33 @@ import java.util.HashMap; import java.util.Map; -/** - * A class to help with importing data from MultiInv. - */ -public class MultiInvImporter implements DataImporter { +final class MultiInvImportHelper { - private final MultiInv miPlugin; - private final InventoriesConfig config; + @NotNull + private final MultiInv multiInv; private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; private final ProfileDataSource profileDataSource; - private final ProfileContainerStore worldProfileContainerStore; - public MultiInvImporter(MultiverseInventories inventories, MultiInv miPlugin) { - this.miPlugin = miPlugin; - this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); - this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); - this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); - this.worldProfileContainerStore = inventories.getServiceLocator() - .getService(ProfileContainerStoreProvider.class) - .getStore(ContainerType.WORLD); + MultiInvImportHelper( + @NotNull MultiInv multiInv, + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.multiInv = multiInv; + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; } - /** - * @return The MultiInv plugin hooked to the importer. - */ - public MultiInv getMIPlugin() { - return this.miPlugin; - } - - /** - * {@inheritDoc} - */ - @Override - public Plugin getPlugin() { - return this.getMIPlugin(); - } - - /** - * Imports the data from MultiInv. - * - * @throws MigrationException If there was any MAJOR issue loading the data. - */ - @Override - public void importData() throws MigrationException { + void importData() throws DataImportException { HashMap miGroupMap = this.getGroupMap(); if (miGroupMap == null) { - throw new MigrationException("There is no data to import from MultiInv!"); + throw new DataImportException("There is no data to import from MultiInv!"); } if (!miGroupMap.isEmpty()) { WorldGroup defaultWorldGroup = worldGroupManager.getDefaultGroup(); @@ -90,15 +68,14 @@ public void importData() throws MigrationException { } worldGroup.addWorld(groupEntry.getValue()); } - config.save(); + inventoriesConfig.save(); for (OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) { Logging.info("Processing MultiInv data for player: " + player.getName()); for (Map.Entry entry : miGroupMap.entrySet()) { String worldName = entry.getKey(); String groupName = entry.getValue(); - MIPlayerFileLoader playerFileLoader = - new MIPlayerFileLoader(this.getMIPlugin(), player, groupName); + MIPlayerFileLoader playerFileLoader = new MIPlayerFileLoader(multiInv, player, groupName); if (!playerFileLoader.load()) { continue; } @@ -108,8 +85,7 @@ public void importData() throws MigrationException { } for (World world : Bukkit.getWorlds()) { String worldName = world.getName(); - MIPlayerFileLoader playerFileLoader = - new MIPlayerFileLoader(this.getMIPlugin(), player, worldName); + MIPlayerFileLoader playerFileLoader = new MIPlayerFileLoader(multiInv, player, worldName); if (!playerFileLoader.load()) { continue; } @@ -118,24 +94,22 @@ public void importData() throws MigrationException { mergeData(player, playerFileLoader, worldName, ContainerType.WORLD); } } - - Logging.info("Import from MultiInv finished. Disabling MultiInv."); - Bukkit.getPluginManager().disablePlugin(this.getMIPlugin()); } private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader, String dataName, ContainerType type) { PlayerProfile playerProfile; if (type.equals(ContainerType.GROUP)) { - WorldGroup group = worldGroupManager.getGroup(dataName); + WorldGroup group = worldGroupManager + .getGroup(dataName); if (group == null) { Logging.warning("Could not import player data for group: " + dataName); return; } playerProfile = group.getGroupProfileContainer().getPlayerData(ProfileTypes.SURVIVAL, player); } else { - playerProfile = worldProfileContainerStore.getContainer(dataName) - .getPlayerData(ProfileTypes.SURVIVAL, player); + playerProfile = profileContainerStoreProvider.getStore(type) + .getContainer(dataName).getPlayerData(ProfileTypes.SURVIVAL, player); } MIInventoryInterface inventoryInterface = playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); @@ -152,28 +126,24 @@ private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader /** * @return The group mapping from MultiInv, where worldName -> groupName. - * @throws MigrationException If there was any issues getting the data through reflection. + * @throws DataImportException If there was any issues getting the data through reflection. */ - private HashMap getGroupMap() throws MigrationException { + private HashMap getGroupMap() throws DataImportException { Field field; try { field = MIYamlFiles.class.getDeclaredField("groups"); } catch (NoSuchFieldException nsfe) { - throw new MigrationException("The running version of MultiInv is " + throw new DataImportException("The running version of MultiInv is " + "incompatible with the import feature.").setCauseException(nsfe); } field.setAccessible(true); HashMap miGroupMap = null; try { miGroupMap = (HashMap) field.get(null); - } catch (IllegalAccessException iae) { - throw new MigrationException("The running version of MultiInv is " + } catch (IllegalAccessException | ClassCastException iae) { + throw new DataImportException("The running version of MultiInv is " + "incompatible with the import feature.").setCauseException(iae); - } catch (ClassCastException cce) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(cce); } return miGroupMap; } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java new file mode 100644 index 00000000..3fd3b292 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java @@ -0,0 +1,63 @@ +package org.mvplugins.multiverse.inventories.dataimport.multiinv; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.AbstractDataImporter; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +@Service +final class MultiInvImporter extends AbstractDataImporter { + + private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ProfileDataSource profileDataSource; + + @Inject + MultiInvImporter( + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; + } + + /** + * {@inheritDoc} + */ + @Override + protected void doDataImport() throws DataImportException { + new MultiInvImportHelper( + (MultiInv) importer, + worldGroupManager, + inventoriesConfig, + profileContainerStoreProvider, + profileDataSource + ).importData(); + } + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPluginName() { + return "MultiInv"; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Class getPluginClass() { + return MultiInv.class; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/package-info.java similarity index 55% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/package-info.java index bfbc619f..46044732 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/multiinv/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/package-info.java @@ -1,5 +1,5 @@ /** * This package contains MultiInv classes to handle importing their data. */ -package org.mvplugins.multiverse.inventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java new file mode 100644 index 00000000..ce0d8910 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.dataimport; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java new file mode 100644 index 00000000..b85d1a88 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java @@ -0,0 +1,66 @@ +package org.mvplugins.multiverse.inventories.dataimport.perworldinventory; + +import me.ebonjaeger.perworldinventory.PerWorldInventory; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.AbstractDataImporter; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.Objects; + +@Service +public class PerWorldInventoryImporter extends AbstractDataImporter { + + private final InventoriesConfig inventoriesConfig; + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + + @Inject + PerWorldInventoryImporter( + @NotNull InventoriesConfig inventoriesConfig, + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.inventoriesConfig = inventoriesConfig; + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; + } + + /** + * {@inheritDoc} + */ + @Override + protected void doDataImport() throws DataImportException { + new PwiImportHelper( + Objects.requireNonNull(((PerWorldInventory) importer).getApi()), + inventoriesConfig, + worldManager, + worldGroupManager, + profileDataSource + ).importData(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPluginName() { + return "PerWorldInventory"; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Class getPluginClass() { + return PerWorldInventory.class; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java new file mode 100644 index 00000000..63563ef4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -0,0 +1,349 @@ +package org.mvplugins.multiverse.inventories.dataimport.perworldinventory; + +import com.dumptruckman.bukkit.configuration.util.SerializationHelper; +import com.dumptruckman.minecraft.util.Logging; +import me.ebonjaeger.perworldinventory.Group; +import me.ebonjaeger.perworldinventory.GroupManager; +import me.ebonjaeger.perworldinventory.api.PerWorldInventoryAPI; +import me.ebonjaeger.perworldinventory.configuration.PlayerSettings; +import me.ebonjaeger.perworldinventory.configuration.PluginSettings; +import me.ebonjaeger.perworldinventory.configuration.Settings; +import me.ebonjaeger.perworldinventory.data.FlatFile; +import me.ebonjaeger.perworldinventory.data.ProfileKey; +import me.ebonjaeger.perworldinventory.data.ProfileManager; +import me.ebonjaeger.perworldinventory.libs.json.JSONObject; +import me.ebonjaeger.perworldinventory.libs.json.parser.JSONParser; +import me.ebonjaeger.perworldinventory.serialization.PlayerSerializer; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; +import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.utils.ReflectHelper; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.PlayerStats; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +class PwiImportHelper { + + private final PerWorldInventoryAPI pwiAPI; + private final InventoriesConfig inventoriesConfig; + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + + private Settings pwiSettings; + private GroupManager pwiGroupManager; + private FlatFile pwiFlatFile; + private File dataDirectory; + private Method getFileMethod; + private Method deserializeMethod; + + private List playerList; + + PwiImportHelper( + @NotNull PerWorldInventoryAPI pwiAPI, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource) { + this.pwiAPI = pwiAPI; + this.inventoriesConfig = inventoriesConfig; + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; + } + + /** + * The 'Main' method for the import. + */ + void importData() throws DataImportException { + pwiSetUp(); + transferConfigOptions(); + findPlayersWithData(); + // Since there is no such thing as individual world container in PerWorldInventory, + // everything is just groups. No need for world playerData import. + for (Group group : getPWIGroups()) { + createMVGroup(group); + saveMVDataForGroup(group); + } + } + + /** + * Do the necessary reflection to get access to the classes needed for data import. + */ + private void pwiSetUp() { + this.pwiSettings = ReflectHelper.getFieldValue(pwiAPI, "settings", Settings.class); + this.pwiGroupManager = ReflectHelper.getFieldValue(this.pwiAPI, "groupManager", GroupManager.class); + ProfileManager pwiProfileManager = ReflectHelper.getFieldValue(this.pwiAPI, "profileManager", ProfileManager.class); + this.pwiFlatFile = ReflectHelper.getFieldValue(pwiProfileManager, "dataSource", FlatFile.class); + this.getFileMethod = ReflectHelper.getMethod(this.pwiFlatFile, "getFile", ProfileKey.class); + this.dataDirectory = ReflectHelper.getFieldValue(this.pwiFlatFile, "dataDirectory", File.class); + this.deserializeMethod = ReflectHelper.getMethod(SerializationHelper.class, "deserialize", Map.class, boolean.class); + } + + /** + * Set similar/supported config options in MultiverseInventories with the values used in PerWorldInventory. + */ + private void transferConfigOptions() { + inventoriesConfig.setUsingGameModeProfiles(this.pwiSettings.getProperty(PluginSettings.SEPARATE_GM_INVENTORIES)); + inventoriesConfig.setUsingLoggingSaveLoad(this.pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); + inventoriesConfig.setDefaultingUngroupedWorlds(this.pwiSettings.getProperty(PluginSettings.SHARE_IF_UNCONFIGURED)); + inventoriesConfig.getOptionalShares().setSharing(Sharables.ECONOMY, this.pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)); + inventoriesConfig.save(); + } + + private void findPlayersWithData() throws DataImportException { + if (dataDirectory == null) { + throw new DataImportException("PerWorldInventory data directory not found!"); + } + File[] playerFolders = dataDirectory.listFiles(); + if (playerFolders == null) { + throw new DataImportException("Unable to traverse PerWorldInventory data directory!"); + } + this.playerList = Arrays.stream(playerFolders) + .filter(File::isDirectory) + .map(file -> UUID.fromString(file.getName())) + .map(Bukkit::getOfflinePlayer) + .toList(); + } + + /** + * Gets all PerWorldInventory groups based on all the worlds known by Multiverse. + * + * @return A collection of PerWorldInventory groups. + */ + private Collection getPWIGroups() { + Set groups = new HashSet<>(this.pwiGroupManager.getGroups().values()); + if (!inventoriesConfig.isDefaultingUngroupedWorlds()) { + worldManager.getWorlds().forEach(world -> + groups.add(this.pwiGroupManager.getGroupFromWorld(world.getName()))); + } + return groups; + } + + /** + * Create a MultiverseInventories {@link WorldGroup} based on PerWorldInventory Group. + * + * @param group A PerWorldInventory Group. + */ + private void createMVGroup(Group group) { + Logging.finer("PerWorldInventory Group: %s", group); + WorldGroup worldGroup = worldGroupManager.getGroup(group.getName()); + if (worldGroup == null) { + worldGroup = worldGroupManager.newEmptyGroup(group.getName()); + } + + // In PerWorldInventory, shares can only be toggled to be enabled or disabled globally. + // So setting all shares here then transferring only those enabled later should work enough, + // since you can't actually disable shares in MultiverseInventories. + worldGroup.getShares().addAll(Sharables.allOf()); + worldGroup.addWorlds(group.getWorlds()); + if (group.getRespawnWorld() != null) { + worldGroup.setSpawnWorld(group.getRespawnWorld()); + } + worldGroupManager.updateGroup(worldGroup); + } + + /** + * Transfer all player data from PerWorldInventory to MultiverseInventories for a given group. + * + * @param group A PerWorldInventory Group. + */ + private void saveMVDataForGroup(Group group) throws DataImportException { + for (OfflinePlayer offlinePlayer : this.playerList) { + saveMVDataForPlayer(group, offlinePlayer); + } + } + + private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throws DataImportException { + GlobalProfile globalProfile = profileDataSource.getGlobalProfile(offlinePlayer); + globalProfile.setLoadOnLogin(pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); + profileDataSource.updateGlobalProfile(globalProfile); + for (GameMode gameMode : GameMode.values()) { + me.ebonjaeger.perworldinventory.data.PlayerProfile pwiPlayerData = getPWIPlayerData(offlinePlayer, group, gameMode); + if (pwiPlayerData == null) { + continue; + } + for (var mvProfile : getMVPlayerData(offlinePlayer, group, gameMode)) { + transferToMVPlayerData(mvProfile, pwiPlayerData); + } + } + } + + /** + * Gets MultiverseInventories PlayerProfile based on PerWorldInventory ProfileKey. + * + * @param offlinePlayer OfflinePlayer to get data for. + * @param group Group to get data for. + * @param gameMode GameMode to get data for. + * @return A MultiverseInventories PLayerProfile. + */ + private List getMVPlayerData( + @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { + List profiles = new ArrayList<>(); + profiles.add(profileDataSource.getPlayerData( + ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())); + for (var worldName : group.getWorlds()) { + profiles.add(profileDataSource.getPlayerData( + ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())); + } + return profiles; + } + + /** + * Gets PerWorldInventory PlayerProfile based on PerWorldInventory ProfileKey. + * + * @param offlinePlayer OfflinePlayer to get data for. + * @param group Group to get data for. + * @param gameMode GameMode to get data for. + * @return A PerWorldInventory PLayerProfile. + */ + private @Nullable me.ebonjaeger.perworldinventory.data.PlayerProfile getPWIPlayerData( + @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) throws DataImportException { + ProfileKey pwiKey = new ProfileKey(offlinePlayer.getUniqueId(), group, gameMode); + File pwiPlayerDataFile = getPWIFile(pwiKey); + if (!pwiPlayerDataFile.isFile()) { + Logging.finer("No data for %s.", pwiKey.toString()); + return null; + } + + me.ebonjaeger.perworldinventory.data.PlayerProfile pwiPlayerProfile; + try { + JSONParser parser = new JSONParser(JSONParser.USE_INTEGER_STORAGE); + JSONObject jsonObject = (JSONObject) parser.parse(new FileInputStream(pwiPlayerDataFile)); + if (jsonObject.containsKey("==")) { + pwiPlayerProfile = ReflectHelper.invokeMethod(null, deserializeMethod, jsonObject, true); + } else { + // Use legacy serialization that doesn't use ConfigurationSerializable + pwiPlayerProfile = PlayerSerializer.INSTANCE.deserialize( + jsonObject, + Objects.requireNonNull(offlinePlayer.getName()), + PlayerStats.INVENTORY_SIZE, + PlayerStats.ENDER_CHEST_SIZE); + } + } catch (Exception e) { + Logging.severe("Unable to parse file into profile: " + pwiPlayerDataFile.getAbsolutePath()); + e.printStackTrace(); + return null; + } + if (pwiPlayerProfile == null) { + Logging.warning("Empty serialization for %s.", pwiKey.toString()); + return null; + } + Logging.finer("Got pwiPlayerProfile for %s.", pwiKey.toString()); + return pwiPlayerProfile; + } + + /** + * Gets a PerWorldInventory data file based on it's ProfileKey. + * + * @param pwiKey PerWorldInventory profile key. + * @return A PerWorldInventory data file. + */ + private File getPWIFile(ProfileKey pwiKey) throws DataImportException { + return ReflectHelper.invokeMethod(this.pwiFlatFile, this.getFileMethod, pwiKey); + } + + /** + * Transfers supported player data from PerWorldInventory to MultiverseInventories. + * + * @param mvPlayerProfile MultiverseInventories PlayerProfile to transfer to. + * @param pwiPlayerProfile PerWorldInventory PlayerProfile to transfer from. + */ + private void transferToMVPlayerData( + PlayerProfile mvPlayerProfile, + me.ebonjaeger.perworldinventory.data.PlayerProfile pwiPlayerProfile + ) throws DataImportException { + if (pwiPlayerProfile == null || mvPlayerProfile == null) { + Logging.finer("Null profile(s). No data transferred for %s and %s.", mvPlayerProfile, pwiPlayerProfile); + return; + } + + // Move data from PerWorldInventory profile to MultiverseInventories profile + // Shares that are not available are commented out. + if (pwiSettings.getProperty(PlayerSettings.LOAD_INVENTORY)) { + mvPlayerProfile.set(Sharables.ARMOR, pwiPlayerProfile.getArmor()); + mvPlayerProfile.set(Sharables.INVENTORY, pwiPlayerProfile.getInventory()); + } + if (pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)) { + mvPlayerProfile.set(Sharables.ECONOMY, pwiPlayerProfile.getBalance()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_ENDER_CHEST)) { + mvPlayerProfile.set(Sharables.ENDER_CHEST, pwiPlayerProfile.getEnderChest()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_EXHAUSTION)) { + mvPlayerProfile.set(Sharables.EXHAUSTION, pwiPlayerProfile.getExhaustion()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_EXP)) { + mvPlayerProfile.set(Sharables.EXPERIENCE, pwiPlayerProfile.getExperience()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_FALL_DISTANCE)) { + mvPlayerProfile.set(Sharables.FALL_DISTANCE, pwiPlayerProfile.getFallDistance()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_FIRE_TICKS)) { + mvPlayerProfile.set(Sharables.FIRE_TICKS, pwiPlayerProfile.getFireTicks()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_HUNGER)) { + mvPlayerProfile.set(Sharables.FOOD_LEVEL, pwiPlayerProfile.getFoodLevel()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_HUNGER)) { + mvPlayerProfile.set(Sharables.FOOD_LEVEL, pwiPlayerProfile.getFoodLevel()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_HEALTH)) { + mvPlayerProfile.set(Sharables.HEALTH, pwiPlayerProfile.getHealth()); + // mvPlayerProfile.set(Sharables, pwiPlayerProfile.getMaxHealth()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_LEVEL)) { + mvPlayerProfile.set(Sharables.LEVEL, pwiPlayerProfile.getLevel()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_MAX_AIR)) { + mvPlayerProfile.set(Sharables.MAXIMUM_AIR, pwiPlayerProfile.getMaximumAir()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_POTION_EFFECTS)) { + mvPlayerProfile.set(Sharables.POTIONS, pwiPlayerProfile.getPotionEffects().toArray(new PotionEffect[0])); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_REMAINING_AIR)) { + mvPlayerProfile.set(Sharables.REMAINING_AIR, pwiPlayerProfile.getRemainingAir()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_SATURATION)) { + mvPlayerProfile.set(Sharables.REMAINING_AIR, pwiPlayerProfile.getRemainingAir()); + } + // if (pwiSettings.getProperty(PlayerSettings.LOAD_DISPLAY_NAME)) { + // mvPlayerProfile.set(Sharables, pwiPlayerProfile.getDisplayName()); + // } + // if (pwiSettings.getProperty(PlayerSettings.LOAD_FLYING)) { + // mvPlayerProfile.set(Sharables, pwiPlayerProfile.getAllowFlight()); + // } + // mvPlayerProfile.set(Sharables.BED_SPAWN, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.HUNGER, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.LAST_LOCATION, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.OFF_HAND, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.TOTAL_EXPERIENCE, pwiPlayerProfile); + + profileDataSource.updatePlayerData(mvPlayerProfile); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java new file mode 100644 index 00000000..c8e7f714 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.dataimport.perworldinventory; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java similarity index 78% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java index a97d9306..1fdc6ae6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/WorldInventoriesImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java @@ -1,20 +1,6 @@ -package org.mvplugins.multiverse.inventories.migration.worldinventories; +package org.mvplugins.multiverse.inventories.dataimport.worldinventories; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; -import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; -import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.migration.DataImporter; -import org.mvplugins.multiverse.inventories.migration.MigrationException; import me.drayshak.WorldInventories.Group; import me.drayshak.WorldInventories.WIPlayerInventory; import me.drayshak.WorldInventories.WIPlayerStats; @@ -22,7 +8,18 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.World; -import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; import java.io.File; import java.io.FileInputStream; @@ -32,60 +29,41 @@ import java.util.List; import java.util.Set; -/** - * Handles the importing of data from WorldInventories. - */ -public class WorldInventoriesImporter implements DataImporter { +final class WorldInventoriesImportHelper { - private final WorldInventories wiPlugin; - private final InventoriesConfig config; + @NotNull + private final WorldInventories worldInventories; private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; private final ProfileDataSource profileDataSource; - private final ProfileContainerStore worldProfileContainerStore; - - public WorldInventoriesImporter(MultiverseInventories inventories, WorldInventories wiPlugin) { - this.wiPlugin = wiPlugin; - this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); - this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); - this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); - this.worldProfileContainerStore = inventories.getServiceLocator() - .getService(ProfileContainerStoreProvider.class) - .getStore(ContainerType.WORLD); - } - - /** - * @return The WorldInventories plugin hooked to the importer. - */ - public WorldInventories getWIPlugin() { - return this.wiPlugin; - } - /** - * {@inheritDoc} - */ - @Override - public Plugin getPlugin() { - return this.getWIPlugin(); + WorldInventoriesImportHelper( + @NotNull WorldInventories worldInventories, + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.worldInventories = worldInventories; + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; } - /** - * Imports the data from WorldInventories into MultiverseInventories. - * - * @throws MigrationException If there was any MAJOR issues importing the data. - */ - @Override - public void importData() throws MigrationException { + void importData() throws DataImportException { List wiGroups; try { - wiGroups = this.getWIPlugin().getGroups(); + wiGroups = worldInventories.getGroups(); } catch (Exception e) { - throw new MigrationException("Unable to import from this version of WorldInventories!") + throw new DataImportException("Unable to import from this version of WorldInventories!") .setCauseException(e); } catch (Error e) { - throw new MigrationException("Unable to import from this version of WorldInventories!"); + throw new DataImportException("Unable to import from this version of WorldInventories!"); } if (wiGroups == null) { - throw new MigrationException("No data to import from WorldInventories!"); + throw new DataImportException("No data to import from WorldInventories!"); } if (!wiGroups.isEmpty()) { @@ -98,7 +76,7 @@ public void importData() throws MigrationException { this.createGroups(wiGroups); Set noGroupWorlds = this.getWorldsWithoutGroups(); - config.save(); + inventoriesConfig.save(); OfflinePlayer[] offlinePlayers = Bukkit.getServer().getOfflinePlayers(); Logging.info("Processing data for " + offlinePlayers.length + " players. The larger than number, the longer" @@ -121,9 +99,6 @@ public void importData() throws MigrationException { this.transferData(player, null, container); } } - - Logging.info("Import from WorldInventories finished. Disabling WorldInventories."); - Bukkit.getPluginManager().disablePlugin(this.getWIPlugin()); } private void createGroups(List wiGroups) { @@ -160,7 +135,8 @@ private Set getWorldsWithoutGroups() { for (World world : Bukkit.getWorlds()) { if (worldGroupManager.getGroupsForWorld(world.getName()).isEmpty()) { Logging.fine("Added ungrouped world for importing."); - ProfileContainer container = worldProfileContainerStore.getContainer(world.getName()); + ProfileContainer container = profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(world.getName()); noGroupWorlds.add(container); } } @@ -197,7 +173,7 @@ private File getFile(OfflinePlayer player, Group group, DataType dataType) { } else { path.append(group.getName()); } - path.insert(0, this.getWIPlugin().getDataFolder().getAbsolutePath()); + path.insert(0, worldInventories.getDataFolder().getAbsolutePath()); path.append(File.separator).append(player.getName()).append(dataType.fileExtension); File file = new File(path.toString()); @@ -271,6 +247,7 @@ private WIPlayerStats loadPlayerStats(OfflinePlayer player, Group group) { return playerstats; } + /** * Indicates the type of data we're importing for. */ @@ -285,4 +262,3 @@ private enum DataType { } } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java new file mode 100644 index 00000000..42ce3963 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java @@ -0,0 +1,64 @@ +package org.mvplugins.multiverse.inventories.dataimport.worldinventories; + +import me.drayshak.WorldInventories.WorldInventories; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.AbstractDataImporter; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +@Service +final class WorldInventoriesImporter extends AbstractDataImporter { + + private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ProfileDataSource profileDataSource; + + @Inject + WorldInventoriesImporter( + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; + } + + /** + * {@inheritDoc} + */ + @Override + protected void doDataImport() throws DataImportException { + new WorldInventoriesImportHelper( + (WorldInventories) importer, + worldGroupManager, + inventoriesConfig, + profileContainerStoreProvider, + profileDataSource + ).importData(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPluginName() { + return "WorldInventories"; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Class getPluginClass() { + return WorldInventories.class; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/package-info.java similarity index 55% rename from src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/package-info.java index e7666201..adae4de0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/worldinventories/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/package-info.java @@ -1,5 +1,5 @@ /** * This package contains WorldInventories classes to handle importing their data. */ -package org.mvplugins.multiverse.inventories.migration.worldinventories; +package org.mvplugins.multiverse.inventories.dataimport.worldinventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index e571e54d..c55c1af3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -11,7 +11,6 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.migration.ImportManager; import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -68,7 +67,6 @@ public class InventoriesListener implements Listener { private final WorldGroupManager worldGroupManager; private final ProfileDataSource profileDataSource; private final ProfileContainerStoreProvider profileContainerStoreProvider; - private final Provider importManager; private List currentGroups; private Location spawnLoc = null; @@ -79,15 +77,13 @@ public class InventoriesListener implements Listener { @NotNull WorldManager worldManager, @NotNull WorldGroupManager worldGroupManager, @NotNull ProfileDataSource profileDataSource, - @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, - @NotNull Provider importManager) { + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider) { this.inventories = inventories; this.config = config; this.worldManager = worldManager; this.worldGroupManager = worldGroupManager; this.profileDataSource = profileDataSource; this.profileContainerStoreProvider = profileContainerStoreProvider; - this.importManager = importManager; } /** @@ -145,40 +141,6 @@ public void configReload(MVConfigReloadEvent event) { event.addConfig("Multiverse-Inventories - config.yml"); } - /** - * Called when a plugin is enabled. - * - * @param event The plugin enable event. - */ - @EventHandler - public void pluginEnable(PluginEnableEvent event) { - try { - if (event.getPlugin() instanceof MultiInv) { - importManager.get().hookMultiInv((MultiInv) event.getPlugin()); - } else if (event.getPlugin() instanceof WorldInventories) { - importManager.get().hookWorldInventories((WorldInventories) event.getPlugin()); - } - } catch (NoClassDefFoundError ignore) { - } - } - - /** - * Called when a plugin is disabled. - * - * @param event The plugin disable event. - */ - @EventHandler - public void pluginDisable(PluginDisableEvent event) { - try { - if (event.getPlugin() instanceof MultiInv) { - importManager.get().unHookMultiInv(); - } else if (event.getPlugin() instanceof WorldInventories) { - importManager.get().unHookWorldInventories(); - } - } catch (NoClassDefFoundError ignore) { - } - } - @EventHandler(priority = EventPriority.MONITOR) public void playerPreLogin(AsyncPlayerPreLoginEvent event) { if (event.getLoginResult() != Result.ALLOWED) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java deleted file mode 100644 index 52af376f..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/DataImporter.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.mvplugins.multiverse.inventories.migration; - -import org.bukkit.plugin.Plugin; - -/** - * Interface for data migration importers. - */ -public interface DataImporter { - - /** - * Imports the data from another plugin. - * - * @throws MigrationException If there was any MAJOR issue loading the data. - */ - void importData() throws MigrationException; - - /** - * @return The plugin associated with this Importer. - */ - Plugin getPlugin(); -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java deleted file mode 100644 index 8b01736b..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/ImportManager.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.mvplugins.multiverse.inventories.migration; - -import com.dumptruckman.minecraft.util.Logging; -import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.external.jakarta.inject.Inject; -import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.migration.multiinv.MultiInvImporter; -import org.mvplugins.multiverse.inventories.migration.worldinventories.WorldInventoriesImporter; -import me.drayshak.WorldInventories.WorldInventories; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -/** - * Manages the import heplers for other similar plugins. - */ -@Service -public class ImportManager { - - private MultiInvImporter multiInvImporter = null; - private WorldInventoriesImporter worldInventoriesImporter = null; - private final MultiverseInventories inventories; - - @Inject - public ImportManager(@NotNull MultiverseInventories inventories) { - this.inventories = inventories; - } - - /** - * Hooks MultiInv for importing it's data. - * - * @param plugin Instance of MultiInv. - */ - public void hookMultiInv(MultiInv plugin) { - this.multiInvImporter = new MultiInvImporter(this.inventories, plugin); - Logging.info("Hooked MultiInv for importing!"); - } - - /** - * Hooks WorldInventories for importing it's data. - * - * @param plugin Instance of WorldInventories. - */ - public void hookWorldInventories(WorldInventories plugin) { - this.worldInventoriesImporter = new WorldInventoriesImporter(this.inventories, plugin); - Logging.info("Hooked WorldInventories for importing!"); - } - - /** - * @return The MultiInv importer class or null if not hooked. - */ - public MultiInvImporter getMultiInvImporter() { - return this.multiInvImporter; - } - - /** - * @return The WorldInventories importer class or null if not hooked. - */ - public WorldInventoriesImporter getWorldInventoriesImporter() { - return this.worldInventoriesImporter; - } - - /** - * Un-hooks MultiInv so we're not able to import from it anymore. - */ - public void unHookMultiInv() { - this.multiInvImporter = null; - } - - /** - * Un-hooks WorldInventories so we're not able to import from it anymore. - */ - public void unHookWorldInventories() { - this.worldInventoriesImporter = null; - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java deleted file mode 100644 index f7319478..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/MigrationException.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.mvplugins.multiverse.inventories.migration; - -/** - * Exception thrown when migration doesn't go well. - */ -public class MigrationException extends Exception { - - private Exception causeException = null; - - public MigrationException(String message) { - super(message); - } - - /** - * Sets what the causing exception was, if any. - * - * @param exception The cause exception. - * @return This exception for easy chainability. - */ - public MigrationException setCauseException(Exception exception) { - this.causeException = exception; - return this; - } - - /** - * @return The causing exception or null if none. - */ - public Exception getCauseException() { - return this.causeException; - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java deleted file mode 100644 index d66b1e65..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/migration/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * This package contains thigns to help with importing player stats/inventory data from - * other similar plugins. - */ -package org.mvplugins.multiverse.inventories.migration; - diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7dc9b458..368aa413 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,4 +5,4 @@ api-version: 1.13 authors: ['dumptruckman', 'benwoo1110'] website: 'https://dev.bukkit.org/projects/multiverse-inventories' depend: ['Multiverse-Core'] -softdepend: [MultiInv, WorldInventories] +softdepend: [MultiInv, WorldInventories, PerWorldInventory] diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 1d843d57..6abfcd2f 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -3,6 +3,7 @@ package org.mvplugins.multiverse.inventories import org.junit.jupiter.api.Test import org.mvplugins.multiverse.inventories.commands.InventoriesCommand import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager import org.mvplugins.multiverse.inventories.listeners.InventoriesListener import org.mvplugins.multiverse.inventories.profile.ProfileDataSource import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider @@ -14,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(5, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(6, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test @@ -41,4 +42,9 @@ class InjectionTest : TestWithMockBukkit() { fun `WorldGroupManager is available as a service`() { assertNotNull(serviceLocator.getActiveService(WorldGroupManager::class.java)) } + + @Test + fun `DataImportManager is available as a service`() { + assertNotNull(serviceLocator.getActiveService(DataImportManager::class.java)) + } } From d8a7dd10301de2b2b223e8d4fd1a72176e1e0a6c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 09:33:21 +0800 Subject: [PATCH 055/180] Check for valid data importer before adding to queue --- .../inventories/commands/ImportCommand.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index 31fbc3ae..f7c2e6eb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -1,6 +1,5 @@ package org.mvplugins.multiverse.inventories.commands; -import com.dumptruckman.minecraft.util.Logging; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; @@ -17,6 +16,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.dataimport.DataImporter; import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @@ -48,13 +48,7 @@ public void onImportCommand( @Single @Syntax("") String pluginName) { - commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) - .prompt(Message.of("Are you sure you want to import data from {plugin}? This will override existing Multiverse-Inventories playerdata!!!", - replace("{plugin}").with(pluginName))) - .action(() -> doDataImport(issuer, pluginName))); - } - void doDataImport(MVCommandIssuer issuer, String pluginName) { dataImportManager.getImporter(pluginName) .onEmpty(() -> issuer.sendMessage("No importer found for " + pluginName)) .peek(dataImporter -> { @@ -62,11 +56,19 @@ void doDataImport(MVCommandIssuer issuer, String pluginName) { issuer.sendMessage("Plugin " + pluginName + " is not running on your server!"); return; } - if (dataImporter.importData()) { - issuer.sendMessage("Successfully to imported data from " + pluginName + "!"); - } else { - issuer.sendMessage("Failed to import data from " + pluginName + "."); - } + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to import data from {plugin}? " + + "This will override existing Multiverse-Inventories playerdata!!!", + replace("{plugin}").with(pluginName))) + .action(() -> doDataImport(issuer, dataImporter))); }); } + + void doDataImport(MVCommandIssuer issuer, DataImporter dataImporter) { + if (dataImporter.importData()) { + issuer.sendMessage("Successfully to imported data from " + dataImporter.getPluginName() + "!"); + } else { + issuer.sendMessage("Failed to import data from " + dataImporter.getPluginName() + "."); + } + } } From e024b4ecbcc43869ad63d99345c1947360e2d1de Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:25:16 +0800 Subject: [PATCH 056/180] Add performance test for global profile --- .../profile/FilePerformanceTest.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt new file mode 100644 index 00000000..4346d78b --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.profile + +import com.dumptruckman.minecraft.util.Logging +import org.junit.jupiter.api.Test +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import java.util.* +import kotlin.test.BeforeTest + +class FilePerformanceTest : TestWithMockBukkit() { + + private lateinit var profileDataSource: ProfileDataSource + + @BeforeTest + fun setUp() { + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") } + } + + @Test + fun `Test 20K global profiles`() { + val startTime = System.nanoTime() + for (i in 1..20000) { + val globalProfile = profileDataSource.getGlobalProfile("player-$i", UUID.randomUUID()) + globalProfile.setLoadOnLogin(true) + profileDataSource.updateGlobalProfile(globalProfile) + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + + profileDataSource.clearAllCache(); + + val startTime2 = System.nanoTime() + for (i in 1..20000) { + val globalProfile = profileDataSource.getGlobalProfile("player-$i", UUID.randomUUID()) + globalProfile.setLoadOnLogin(false) + profileDataSource.updateGlobalProfile(globalProfile) + } + Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") + } +} \ No newline at end of file From 4e0c4e4cabbdd05bf35984a042aed4815ec8e70e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:43:29 +0800 Subject: [PATCH 057/180] Refactor global profile to improve efficiency --- .../profile/FlatFileProfileDataSource.java | 108 +++++------------- .../inventories/profile/GlobalProfile.java | 48 +++++++- .../profile/ProfileDataSource.java | 14 ++- 3 files changed, 89 insertions(+), 81 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 1b026183..5ebc40b6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -9,8 +9,10 @@ import net.minidev.json.parser.ParseException; import org.bukkit.OfflinePlayer; import org.bukkit.configuration.InvalidConfigurationException; +import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.ProfileEntry; import org.mvplugins.multiverse.inventories.share.Sharable; @@ -177,24 +179,14 @@ private File getPlayerFile(ContainerType type, String dataName, String playerNam } /** - * Retrieves the data file for a player for their global data, creating it if necessary. + * Retrieves the data file for a player for their global data. * * @param fileName The name of the file (player name or UUID) without extension. - * @param createIfMissing If true, the file will be created it it does not exist. * @return The data file for a player. * @throws IOException if there was a problem creating the file. */ - File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { - File jsonPlayerFile = new File(playerFolder, fileName + JSON); - if (createIfMissing && !jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " - + "There may be issues with " + fileName + "'s metadata", e); - } - } - return jsonPlayerFile; + File getGlobalFile(String fileName) { + return new File(playerFolder, fileName + JSON); } private void queueWrite(PlayerProfile profile) { @@ -450,45 +442,36 @@ public GlobalProfile getGlobalProfile(OfflinePlayer player) { } @Override - public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { + public @NotNull GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { + return getExistingGlobalProfile(playerName, playerUUID) + .getOrElse(() -> GlobalProfile.createGlobalProfile(playerName, playerUUID)); + } + + @Override + public @NotNull Option getExistingGlobalProfile(String playerName, UUID playerUUID) { GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); if (cached != null) { - return cached; + return Option.of(cached); } - File playerFile; - // Migrate old data if necessary - try { - playerFile = getGlobalFile(playerName, false); - } catch (IOException e) { - // This won't ever happen - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName, playerUUID); - } - if (playerFile.exists()) { - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - if (!migrateGlobalProfileToUUID(profile, playerFile)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - globalProfileCache.put(playerUUID, profile); - return profile; + // Migrate from player name to uuid profile file + File legacyFile = getGlobalFile(playerName); + if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { + Logging.warning("Could not properly migrate player global data file for " + playerName); } - // Load current format - try { - playerFile = getGlobalFile(playerUUID.toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName, playerUUID); + // Load from existing profile file + File uuidFile = getGlobalFile(playerUUID.toString()); + if (uuidFile.exists()) { + GlobalProfile globalProfile = loadGlobalProfile(uuidFile, playerName, playerUUID); + globalProfileCache.put(playerUUID, globalProfile); + return Option.of(globalProfile); } - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - globalProfileCache.put(playerUUID, profile); - return profile; + return Option.none(); } - private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { - updateGlobalProfile(profile); - return playerFile.delete(); + private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { + return legacyFile.renameTo(getGlobalFile(playerUUID.toString())); } private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { @@ -497,22 +480,7 @@ private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID if (section == null) { section = playerData.createSection("playerData"); } - return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); - } - - private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, - Map playerData) { - GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); - for (String key : playerData.keySet()) { - if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { - globalProfile.setLastWorld(playerData.get(key).toString()); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { - globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { - globalProfile.setLastKnownName(playerData.get(key).toString()); - } - } - return globalProfile; + return GlobalProfile.deserialize(playerName, playerUUID, section); } /** @@ -520,15 +488,9 @@ private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUI */ @Override public boolean updateGlobalProfile(GlobalProfile globalProfile) { - File playerFile = null; - try { - playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); + File playerFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); + FileConfiguration playerData = new JsonConfiguration(); + playerData.createSection("playerData", globalProfile.serialize(globalProfile)); try { playerData.save(playerFile); } catch (IOException e) { @@ -539,16 +501,6 @@ public boolean updateGlobalProfile(GlobalProfile globalProfile) { return true; } - private Map serializeGlobalProfile(GlobalProfile profile) { - Map result = new HashMap(2); - if (profile.getLastWorld() != null) { - result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); - } - result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); - result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); - return result; - } - @Override public void updateLastWorld(UUID playerUUID, String worldName) { GlobalProfile globalProfile = getGlobalProfile(playerUUID); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index 4dac573b..fb80d856 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -1,7 +1,11 @@ package org.mvplugins.multiverse.inventories.profile; import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.ConfigurationSection; +import org.mvplugins.multiverse.inventories.util.DataStrings; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; /** @@ -15,7 +19,7 @@ public final class GlobalProfile { * @param player the player to create the profile object for. * @return a new GlobalProfile for the given player. */ - public static GlobalProfile createGlobalProfile(OfflinePlayer player) { + static GlobalProfile createGlobalProfile(OfflinePlayer player) { return new GlobalProfile(player.getName(), player.getUniqueId()); } @@ -26,7 +30,7 @@ public static GlobalProfile createGlobalProfile(OfflinePlayer player) { * @param playerUUID the UUID of the player to create the profile for. * @return a new GlobalProfile for the given player. */ - public static GlobalProfile createGlobalProfile(String playerName, UUID playerUUID) { + static GlobalProfile createGlobalProfile(String playerName, UUID playerUUID) { return new GlobalProfile(playerName, playerUUID); } @@ -40,6 +44,13 @@ private GlobalProfile(String name, UUID uuid) { this.lastKnownName = name; } + public GlobalProfile(UUID uuid, String lastWorld, String lastKnownName, boolean loadOnLogin) { + this.uuid = uuid; + this.lastWorld = lastWorld; + this.lastKnownName = lastKnownName; + this.loadOnLogin = loadOnLogin; + } + /** * Returns the name of the player. * @@ -127,4 +138,37 @@ public String toString() { ", loadOnLogin=" + loadOnLogin + '}'; } + + /** + * Converts a global profile to a map that can be serialized into the profile data file. + * + * @param profile The global profile data. + * @return The serialized profile map. + */ + Map serialize(GlobalProfile profile) { + Map result = new HashMap<>(3); + if (profile.getLastWorld() != null) { + result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); + } + result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); + result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); + return result; + } + + /** + * Converts a configuration section to a global profile. + * + * @param playerName The player name. + * @param playerUUID The player UUID. + * @param data The configuration section to convert. + * @return The global profile. + */ + static GlobalProfile deserialize(String playerName, UUID playerUUID, ConfigurationSection data) { + return new GlobalProfile( + playerUUID, + data.getString(DataStrings.PLAYER_LAST_WORLD, null), + data.getString(DataStrings.PLAYER_LAST_KNOWN_NAME, playerName), + data.getBoolean(DataStrings.PLAYER_SHOULD_LOAD, false) + ); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index c3354c75..9630daef 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,7 +1,9 @@ package org.mvplugins.multiverse.inventories.profile; import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import java.io.IOException; @@ -63,12 +65,22 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { /** * Retrieves the global profile for a player which contains meta-data for the player. + * Creates the profile if it doesn't exist. * * @param playerName The name of the player. * @param playerUUID The UUID of the player. * @return The global profile for the specified player. */ - GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); + @NotNull GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); + + /** + * Retrieves the global profile for a player which contains meta-data for the player if it exists. + * + * @param playerName The name of the player. + * @param playerUUID The UUID of the player. + * @return The global profile for the specified player or empty if it doesn't exist. + */ + @NotNull Option getExistingGlobalProfile(String playerName, UUID playerUUID); /** * Update the file for a player's global profile. From 499e90328339c408d4b26b6c9e551080b1e757df Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 12:27:33 +0800 Subject: [PATCH 058/180] Abstract out file operations to ProfileFileIO --- .../profile/FlatFileProfileDataSource.java | 220 +++++++----------- .../inventories/profile/PlayerProfile.java | 43 +--- .../inventories/profile/ProfileFileIO.java | 74 ++++++ .../inventories/util/DataStrings.java | 4 + .../profile/FilePerformanceTest.kt | 63 ++++- 5 files changed, 225 insertions(+), 179 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 5ebc40b6..e162a4a0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -8,7 +8,6 @@ import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.InvalidConfigurationException; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -16,7 +15,6 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.ProfileEntry; import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.SharableEntry; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import net.minidev.json.JSONObject; import org.bukkit.Bukkit; @@ -30,11 +28,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -45,8 +39,6 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); - private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); - // TODO these probably need configurable max sizes private final Cache profileCache = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) @@ -61,8 +53,12 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final File groupFolder; private final File playerFolder; + private final ProfileFileIO profileFileIO; + @Inject - FlatFileProfileDataSource(MultiverseInventories plugin) throws IOException { + FlatFileProfileDataSource(@NotNull MultiverseInventories plugin, @NotNull ProfileFileIO profileFileIO) throws IOException { + this.profileFileIO = profileFileIO; + // Make the data folders plugin.getDataFolder().mkdirs(); @@ -87,57 +83,6 @@ final class FlatFileProfileDataSource implements ProfileDataSource { } } - private FileConfiguration waitForConfigHandle(File file) { - Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - while (true) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - - private static FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { - JsonConfiguration jsonConfiguration = new JsonConfiguration(); - jsonConfiguration.options().continueOnSerializationError(true); - jsonConfiguration.load(file); - return jsonConfiguration; - } - - private static class ConfigLoader implements Callable { - private final File file; - - private ConfigLoader(File file) { - this.file = file; - } - - @Override - public FileConfiguration call() throws Exception { - return getConfigHandleNow(file); - } - } - - private File getFolder(ContainerType type, String folderName) { - File folder; - switch (type) { - case GROUP: - folder = new File(this.groupFolder, folderName); - break; - case WORLD: - folder = new File(this.worldFolder, folderName); - break; - default: - folder = new File(this.worldFolder, folderName); - break; - } - - if (!folder.exists()) { - folder.mkdirs(); - } - return folder; - } - /** * Retrieves the data file for a player based on a given world/group name, creating it if necessary. * @@ -161,7 +106,7 @@ private File getPlayerFile(ContainerType type, String dataName, String playerNam * @throws IOException if there was a problem creating the file. */ private File getPlayerFile(ContainerType type, String dataName, String playerName, boolean createNew) throws IOException { - File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); + File jsonPlayerFile = new File(getProfileContainerFolder(type, dataName), playerName + JSON); if (!jsonPlayerFile.exists()) { try { if (createNew) { @@ -178,40 +123,32 @@ private File getPlayerFile(ContainerType type, String dataName, String playerNam return jsonPlayerFile; } - /** - * Retrieves the data file for a player for their global data. - * - * @param fileName The name of the file (player name or UUID) without extension. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getGlobalFile(String fileName) { - return new File(playerFolder, fileName + JSON); - } - - private void queueWrite(PlayerProfile profile) { - fileIOExecutorService.submit(new FileWriter(profile.clone())); - } - - private class FileWriter implements Callable { - private final PlayerProfile profile; + private File getProfileContainerFolder(ContainerType type, String folderName) { + File folder = switch (type) { + case GROUP -> new File(this.groupFolder, folderName); + case WORLD -> new File(this.worldFolder, folderName); + default -> new File(this.worldFolder, folderName); + }; - private FileWriter(PlayerProfile profile) { - this.profile = profile; + if (!folder.exists()) { + folder.mkdirs(); } + return folder; + } - @Override - public Void call() throws Exception { - processProfileWrite(profile); - return null; - } + /** + * {@inheritDoc} + */ + @Override + public void updatePlayerData(PlayerProfile playerProfile) { + profileFileIO.queueWrite(() -> processProfileWrite(playerProfile.clone())); } private void processProfileWrite(PlayerProfile playerProfile) { try { File playerFile = this.getPlayerFile(playerProfile.getContainerType(), playerProfile.getContainerName(), playerProfile.getPlayer().getName()); - FileConfiguration playerData = getConfigHandleNow(playerFile); + FileConfiguration playerData = profileFileIO.getConfigHandleNow(playerFile); playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); try { playerData.save(playerFile); @@ -226,21 +163,20 @@ private void processProfileWrite(PlayerProfile playerProfile) { } private Map serializePlayerProfile(PlayerProfile playerProfile) { - Map playerData = new LinkedHashMap(); + Map playerData = new LinkedHashMap<>(); JSONObject jsonStats = new JSONObject(); - for (SharableEntry entry : playerProfile) { - if (entry.getValue() != null) { - if (entry.getSharable().getSerializer() == null) { - continue; - } - Sharable sharable = entry.getSharable(); - if (sharable.getProfileEntry().isStat()) { - jsonStats.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } else { - playerData.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } + for (var entry : playerProfile.getData().entrySet()) { + Sharable sharable = entry.getKey(); + Object sharableValue = entry.getValue(); + if (sharableValue == null || sharable.getSerializer() == null) { + continue; + } + if (sharable.getProfileEntry().isStat()) { + jsonStats.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(sharableValue)); + } else { + playerData.put(sharable.getProfileEntry().getFileTag(), + sharable.getSerializer().serialize(sharableValue)); } } if (!jsonStats.isEmpty()) { @@ -253,8 +189,8 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) * {@inheritDoc} */ @Override - public void updatePlayerData(PlayerProfile playerProfile) { - queueWrite(playerProfile); + public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { + return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); } private PlayerProfile getPlayerData(ProfileKey key) { @@ -262,7 +198,7 @@ private PlayerProfile getPlayerData(ProfileKey key) { if (cached != null) { return cached; } - File playerFile = null; + File playerFile; try { playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); } catch (IOException e) { @@ -271,7 +207,7 @@ private PlayerProfile getPlayerData(ProfileKey key) { return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID())); } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); + FileConfiguration playerData = profileFileIO.waitForConfigHandle(playerFile); if (convertConfig(playerData)) { try { playerData.save(playerFile); @@ -290,44 +226,41 @@ private PlayerProfile getPlayerData(ProfileKey key) { return result; } - @Override - public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); - } - private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); for (Object keyObj : playerData.keySet()) { String key = keyObj.toString(); + final Object value = playerData.get(key); + if (value == null) { + Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { - final Object statsObject = playerData.get(key); - if (statsObject instanceof String) { - parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); + if (value instanceof String) { + parseJsonPlayerStatsIntoProfile((String) value, profile); + continue; + } + if (value instanceof Map) { + parsePlayerStatsIntoProfile((Map) value, profile); } else { - if (statsObject instanceof Map) { - parsePlayerStatsIntoProfile((Map) statsObject, profile); - } else { - Logging.warning("Could not parse stats for " + pKey.getPlayerName()); - } + Logging.warning("Could not parse stats for " + pKey.getPlayerName()); } - } else { - if (playerData.get(key) == null) { - Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + + try { + Sharable sharable = ProfileEntry.lookup(false, key); + if (sharable == null) { + Logging.fine("Player fileTag '" + key + "' is unrecognized!"); continue; } - try { - Sharable sharable = ProfileEntry.lookup(false, key); - if (sharable == null) { - Logging.fine("Player fileTag '" + key + "' is unrecognized!"); - continue; - } - profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); - } catch (Exception e) { - Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); - Logging.getLogger().log(Level.FINE, "Exception: ", e); - e.printStackTrace(); - } + profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); + } catch (Exception e) { + Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); + Logging.getLogger().log(Level.FINE, "Exception: ", e); + e.printStackTrace(); } } Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); @@ -404,7 +337,7 @@ public boolean removePlayerData(ContainerType containerType, String dataName, Pr + " " + dataName + " but the file did not exist."); return false; } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); + FileConfiguration playerData = profileFileIO.waitForConfigHandle(playerFile); playerData.set(profileType.getName(), null); try { playerData.save(playerFile); @@ -474,11 +407,11 @@ private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { return legacyFile.renameTo(getGlobalFile(playerUUID.toString())); } - private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - ConfigurationSection section = playerData.getConfigurationSection("playerData"); + private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID playerUUID) { + FileConfiguration playerData = profileFileIO.waitForConfigHandle(globalFile); + ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { - section = playerData.createSection("playerData"); + section = playerData.createSection(DataStrings.PLAYER_DATA); } return GlobalProfile.deserialize(playerName, playerUUID, section); } @@ -490,7 +423,7 @@ private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID public boolean updateGlobalProfile(GlobalProfile globalProfile) { File playerFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); FileConfiguration playerData = new JsonConfiguration(); - playerData.createSection("playerData", globalProfile.serialize(globalProfile)); + playerData.createSection(DataStrings.PLAYER_DATA, globalProfile.serialize(globalProfile)); try { playerData.save(playerFile); } catch (IOException e) { @@ -501,6 +434,16 @@ public boolean updateGlobalProfile(GlobalProfile globalProfile) { return true; } + /** + * Retrieves the data file for a player for their global data. + * + * @param fileName The name of the file (player name or UUID) without extension. + * @return The data file for a player. + */ + private File getGlobalFile(String fileName) { + return new File(playerFolder, fileName + JSON); + } + @Override public void updateLastWorld(UUID playerUUID, String worldName) { GlobalProfile globalProfile = getGlobalProfile(playerUUID); @@ -574,4 +517,3 @@ public void clearAllCache() { profileCache.invalidateAll(); } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 88e82d5c..57d253ec 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -1,25 +1,23 @@ package org.mvplugins.multiverse.inventories.profile; import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.SharableEntry; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.bukkit.OfflinePlayer; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; /** * Contains all the world/group specific data for a player. */ -public final class PlayerProfile implements Cloneable, Iterable { +public final class PlayerProfile implements Cloneable { - public static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, + static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, OfflinePlayer player) { return new PlayerProfile(containerType, containerName, profileType, player); } - private Map data = new HashMap(); + private final Map data = new HashMap<>(); private final OfflinePlayer player; private final ContainerType containerType; @@ -69,8 +67,7 @@ public ProfileType getProfileType() { * @return The value of the sharable for this profile. Null if no value is set. */ public T get(Sharable sharable) { - SharableEntry entry = this.data.get(sharable); - return sharable.getType().cast(entry != null ? entry.getValue() : null); + return sharable.getType().cast(this.data.get(sharable)); } /** @@ -81,7 +78,7 @@ public T get(Sharable sharable) { * @param The type of value to be expected. */ public void set(Sharable sharable, T value) { - this.data.put(sharable, new SharableEntry(sharable, value)); + this.data.put(sharable, value); } public PlayerProfile clone() { @@ -92,33 +89,7 @@ public PlayerProfile clone() { } } - @Override - public Iterator iterator() { - return new SharablesIterator(data.values().iterator()); - } - - private static class SharablesIterator implements Iterator { - - private final Iterator backingIterator; - - private SharablesIterator(Iterator backingIterator) { - this.backingIterator = backingIterator; - } - - @Override - public boolean hasNext() { - return backingIterator.hasNext(); - } - - @Override - public SharableEntry next() { - return backingIterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + public Map getData() { + return data; } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java new file mode 100644 index 00000000..d01731a2 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -0,0 +1,74 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.FileConfiguration; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +@Service +final class ProfileFileIO { + + private final ExecutorService fileIOExecutorService = Executors.newWorkStealingPool(); + + @Inject + public ProfileFileIO() { + } + + FileConfiguration waitForConfigHandle(File file) { + Future future = fileIOExecutorService.submit(new ConfigLoader(file)); + while (true) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + } + + FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(true); + jsonConfiguration.load(file); + return jsonConfiguration; + } + + private class ConfigLoader implements Callable { + private final File file; + + private ConfigLoader(File file) { + this.file = file; + } + + @Override + public FileConfiguration call() throws Exception { + return getConfigHandleNow(file); + } + } + + Future queueWrite(Runnable action) { + return fileIOExecutorService.submit(new FileWriter(action)); + } + + private static class FileWriter implements Callable { + private final Runnable action; + + private FileWriter(Runnable action) { + this.action = action; + } + + @Override + public Void call() { + action.run(); + return null; + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java index cdf2eff3..2d72b1ac 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java @@ -52,6 +52,10 @@ public class DataStrings { * Player last location identifier. */ public static final String PLAYER_LAST_LOCATION = "lastLocation"; + /** + * Player global profile data + */ + public static final String PLAYER_DATA = "playerData"; /** * Player last world identifier. */ diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 4346d78b..65aae39c 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -1,10 +1,19 @@ package org.mvplugins.multiverse.inventories.profile import com.dumptruckman.minecraft.util.Logging +import org.bukkit.Material +import org.bukkit.enchantments.Enchantment +import org.bukkit.inventory.ItemStack +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType import org.junit.jupiter.api.Test import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.container.ContainerType +import org.mvplugins.multiverse.inventories.share.Sharables import java.util.* +import java.util.function.Consumer import kotlin.test.BeforeTest +import kotlin.test.assertEquals class FilePerformanceTest : TestWithMockBukkit() { @@ -14,12 +23,13 @@ class FilePerformanceTest : TestWithMockBukkit() { fun setUp() { profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { throw IllegalStateException("ProfileDataSource is not available as a service") } + Logging.setDebugLevel(0) } @Test - fun `Test 20K global profiles`() { + fun `Test 10K global profiles`() { val startTime = System.nanoTime() - for (i in 1..20000) { + for (i in 0..9999) { val globalProfile = profileDataSource.getGlobalProfile("player-$i", UUID.randomUUID()) globalProfile.setLoadOnLogin(true) profileDataSource.updateGlobalProfile(globalProfile) @@ -29,11 +39,56 @@ class FilePerformanceTest : TestWithMockBukkit() { profileDataSource.clearAllCache(); val startTime2 = System.nanoTime() - for (i in 1..20000) { + for (i in 0..9999) { val globalProfile = profileDataSource.getGlobalProfile("player-$i", UUID.randomUUID()) globalProfile.setLoadOnLogin(false) profileDataSource.updateGlobalProfile(globalProfile) } Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") } -} \ No newline at end of file + + @Test + fun `Test 5K player profiles`() { + server.setPlayers(5000) + val startTime = System.nanoTime() + for (i in 0..4999) { + val player = server.getPlayer(i) + val playerProfile = profileDataSource.getPlayerData( + ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + playerProfile.set(Sharables.HEALTH, 5.0) + playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) + playerProfile.set(Sharables.INVENTORY, arrayOf( + ItemStack(Material.STONE_BRICKS, 10), + ItemStack(Material.ACACIA_LOG, 10), + createItemStack(Material.BOW, 1, { itemStack -> + itemStack.addEnchantment(Enchantment.UNBREAKING, 2) + }), + ItemStack(Material.WATER_BUCKET, 64) + )) + playerProfile.set(Sharables.POTIONS, arrayOf( + PotionEffect(PotionEffectType.POISON, 100, 1), + PotionEffect(PotionEffectType.SPEED, 50, 1), + )) + profileDataSource.updatePlayerData(playerProfile) + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + + profileDataSource.clearAllCache(); + + val startTime2 = System.nanoTime() + for (i in 0..4999) { + val player = server.getPlayer(i) + val playerProfile = profileDataSource.getPlayerData( + ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) + assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) + } + Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") + } + + fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { + val itemStack = ItemStack(material, amount) + modify.accept(itemStack) + return itemStack + } +} From b2cc30018becdfaa18f27c5ae28f43662ff005f4 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 15:00:12 +0800 Subject: [PATCH 059/180] Improve player profile performance with config cache --- .../profile/FlatFileProfileDataSource.java | 219 ++++++++++-------- .../profile/ProfileDataSource.java | 4 +- .../inventories/profile/ProfileFileIO.java | 32 +-- .../profile/container/ProfileContainer.java | 4 +- .../profile/FilePerformanceTest.kt | 77 +++--- 5 files changed, 190 insertions(+), 146 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index e162a4a0..85c9686b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -8,6 +8,7 @@ import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.InvalidConfigurationException; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -28,7 +29,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -40,10 +40,16 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); // TODO these probably need configurable max sizes + private final Cache configCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(1000) + .build(); + private final Cache profileCache = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) .maximumSize(1000) .build(); + private final Cache globalProfileCache = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) .maximumSize(500) @@ -90,34 +96,9 @@ final class FlatFileProfileDataSource implements ProfileDataSource { * @param dataName The name of the group or world. * @param playerName The name of the player. * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - private File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - return getPlayerFile(type, dataName, playerName, true); - } - - /** - * Retrieves the data file for a player based on a given world/group name, creating it if necessary. - * - * @param type Indicates whether data is for group or world. - * @param dataName The name of the group or world. - * @param playerName The name of the player. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. */ - private File getPlayerFile(ContainerType type, String dataName, String playerName, boolean createNew) throws IOException { + private File getPlayerFile(ContainerType type, String dataName, String playerName) { File jsonPlayerFile = new File(getProfileContainerFolder(type, dataName), playerName + JSON); - if (!jsonPlayerFile.exists()) { - try { - if (createNew) { - jsonPlayerFile.createNewFile(); - } - } catch (IOException e) { - throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() - + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName - + " may not be saved.", e); - } - } Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", jsonPlayerFile.getPath(), type, dataName, playerName); return jsonPlayerFile; @@ -141,47 +122,74 @@ private File getProfileContainerFolder(ContainerType type, String folderName) { */ @Override public void updatePlayerData(PlayerProfile playerProfile) { - profileFileIO.queueWrite(() -> processProfileWrite(playerProfile.clone())); + profileFileIO.queueAction(() -> processProfileWrite(playerProfile.clone())); } private void processProfileWrite(PlayerProfile playerProfile) { + File playerFile = getPlayerFile( + playerProfile.getContainerType(), + playerProfile.getContainerName(), + playerProfile.getPlayer().getName() + ); try { - File playerFile = this.getPlayerFile(playerProfile.getContainerType(), - playerProfile.getContainerName(), playerProfile.getPlayer().getName()); - FileConfiguration playerData = profileFileIO.getConfigHandleNow(playerFile); - playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() - + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); - Logging.severe(e.getMessage()); + ProfileKey fileProfileKey = ProfileKey.createProfileKey( + playerProfile.getContainerType(), + playerProfile.getContainerName(), + null, + playerProfile.getPlayer().getUniqueId(), + playerProfile.getPlayer().getName()); + FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); + if (playerData == null) { + playerData = playerFile.exists() + ? profileFileIO.getConfigHandleNow(playerFile) + : new JsonConfiguration(); + configCache.put(fileProfileKey, playerData); } - } catch (final Exception e) { - Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); + Map serializedData = serializePlayerProfile(playerProfile); + if (serializedData.isEmpty()) { + return; + } + playerData.createSection(playerProfile.getProfileType().getName(), serializedData); + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); + Logging.severe(e.getMessage()); + } catch (Exception e) { + Logging.severe("Unknown error while attempting to write profile data.", e); } } private Map serializePlayerProfile(PlayerProfile playerProfile) { Map playerData = new LinkedHashMap<>(); JSONObject jsonStats = new JSONObject(); + for (var entry : playerProfile.getData().entrySet()) { Sharable sharable = entry.getKey(); Object sharableValue = entry.getValue(); - if (sharableValue == null || sharable.getSerializer() == null) { + if (sharableValue == null) { continue; } - if (sharable.getProfileEntry().isStat()) { - jsonStats.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(sharableValue)); + + var serializer = sharable.getSerializer(); + var profileEntry = sharable.getProfileEntry(); + if (serializer == null || profileEntry == null) { + continue; + } + + String fileTag = profileEntry.getFileTag(); + Object serializedValue = serializer.serialize(sharableValue); + if (profileEntry.isStat()) { + jsonStats.put(fileTag, serializedValue); } else { - playerData.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(sharableValue)); + playerData.put(fileTag, serializedValue); } } + if (!jsonStats.isEmpty()) { playerData.put(DataStrings.PLAYER_STATS, jsonStats); } + return playerData; } @@ -198,16 +206,21 @@ private PlayerProfile getPlayerData(ProfileKey key) { if (cached != null) { return cached; } - File playerFile; - try { - playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); - } catch (IOException e) { - e.printStackTrace(); - // Return an empty profile - return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), - Bukkit.getOfflinePlayer(key.getPlayerUUID())); + File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); + if (!playerFile.exists()) { + PlayerProfile playerProfile = PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), + key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID())); + profileCache.put(key, playerProfile); + return playerProfile; + } + + // Migrate from none profile-type data + ProfileKey fileProfileKey = ProfileKey.createProfileKey(key, (ProfileType) null); + FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); + if (playerData == null) { + playerData = profileFileIO.waitForConfigHandle(playerFile); + configCache.put(fileProfileKey, playerData); } - FileConfiguration playerData = profileFileIO.waitForConfigHandle(playerFile); if (convertConfig(playerData)) { try { playerData.save(playerFile); @@ -217,13 +230,14 @@ private PlayerProfile getPlayerData(ProfileKey key) { Logging.severe(e.getMessage()); } } + ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); if (section == null) { section = playerData.createSection(key.getProfileType().getName()); } - PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); - profileCache.put(key, result); - return result; + PlayerProfile playerProfile = deserializePlayerProfile(key, convertSection(section)); + profileCache.put(key, playerProfile); + return playerProfile; } private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { @@ -301,54 +315,68 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile // TODO Remove this conversion private boolean convertConfig(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection("playerData"); - if (section != null) { - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set("playerData", null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; + ConfigurationSection section = config.getConfigurationSection(DataStrings.PLAYER_DATA); + if (section == null) { + return false; } - return false; + config.set(ProfileTypes.SURVIVAL.getName(), section); + config.set(ProfileTypes.CREATIVE.getName(), section); + config.set(ProfileTypes.ADVENTURE.getName(), section); + config.set(DataStrings.PLAYER_DATA, null); + Logging.finer("Migrated old player data to new multi-profile format"); + return true; } /** * {@inheritDoc} */ @Override - public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { - if (profileType == null) { + public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { + ProfileKey profileKey = ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID); + if (profileKey.getProfileType() == null) { try { - File playerFile = getPlayerFile(containerType, dataName, playerName); + File playerFile = getPlayerFile(containerType, dataName, profileKey.getPlayerName()); + configCache.invalidate(profileKey); + profileCache.invalidateAll(Sets.filter( + profileCache.asMap().keySet(), + key -> key.getPlayerUUID().equals(playerUUID) + && key.getContainerType().equals(containerType) + && key.getDataName().equals(dataName) + )); return playerFile.delete(); - } catch (IOException ignore) { - Logging.warning("Attempted to delete file that did not exist for player " + playerName + } catch (Exception ignore) { + Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + " in " + containerType.name().toLowerCase() + " " + dataName); return false; } - } else { - File playerFile; - try { - playerFile = getPlayerFile(containerType, dataName, playerName); - } catch (IOException e) { - Logging.warning("Attempted to delete " + playerName + "'s data for " - + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() - + " " + dataName + " but the file did not exist."); - return false; + } + try { + File playerFile = getPlayerFile(containerType, dataName, profileKey.getPlayerName()); + ProfileKey fileProfileKey = ProfileKey.createProfileKey(profileKey, (ProfileType) null); + FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); + if (playerData == null) { + if (!playerFile.exists()) { + return false; + } + playerData = profileFileIO.getConfigHandleNow(playerFile); } - FileConfiguration playerData = profileFileIO.waitForConfigHandle(playerFile); playerData.set(profileType.getName(), null); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not delete data for player: " + playerName - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; - } - return true; + profileCache.invalidate(profileKey); + FileConfiguration finalPlayerData = playerData; + profileFileIO.queueAction(() -> { + try { + finalPlayerData.save(playerFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (InvalidConfigurationException | IOException e) { + Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() + + " for " + containerType.toString() + ": " + dataName); + Logging.severe(e.getMessage()); + return false; } + return true; } private Map convertSection(ConfigurationSection section) { @@ -477,8 +505,8 @@ public void migratePlayerData(String oldName, String newName, UUID uuid) throws private void migrateForContainerType(File[] folders, ContainerType containerType, String oldName, String newName) throws IOException { for (File folder : folders) { - File oldNameFile = getPlayerFile(containerType, folder.getName(), oldName, false); - File newNameFile = getPlayerFile(containerType, folder.getName(), newName, false); + File oldNameFile = getPlayerFile(containerType, folder.getName(), oldName); + File newNameFile = getPlayerFile(containerType, folder.getName(), newName); if (!oldNameFile.exists()) { Logging.fine("No old data for player %s in %s %s to migrate.", oldName, containerType.name(), folder.getName()); @@ -504,6 +532,10 @@ void clearPlayerCache(UUID playerUUID) { profileCache.asMap().keySet(), key -> key.getPlayerUUID().equals(playerUUID) )); + configCache.invalidateAll(Sets.filter( + configCache.asMap().keySet(), + key -> key.getPlayerUUID().equals(playerUUID) + )); } @Override @@ -513,6 +545,7 @@ public void clearProfileCache(ProfileKey key) { @Override public void clearAllCache() { + configCache.invalidateAll(); globalProfileCache.invalidateAll(); profileCache.invalidateAll(); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 9630daef..5765c995 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -42,10 +42,10 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param dataName The name of the world/group the player's data is associated with. * @param profileType The type of profile we're removing, as per {@link ProfileType}. If null, this will remove * remove all profile types. - * @param playerName The name of the player whose data is being removed. + * @param playerUUID The UUID of the player whose data is being removed. * @return True if successfully removed. */ - boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName); + boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); /** * Retrieves the global profile for a player which contains meta-data for the player. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index d01731a2..f0fbca29 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -13,11 +13,13 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; @Service final class ProfileFileIO { - private final ExecutorService fileIOExecutorService = Executors.newWorkStealingPool(); + private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); @Inject public ProfileFileIO() { @@ -25,12 +27,10 @@ public ProfileFileIO() { FileConfiguration waitForConfigHandle(File file) { Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - while (true) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } + try { + return future.get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); } } @@ -54,21 +54,7 @@ public FileConfiguration call() throws Exception { } } - Future queueWrite(Runnable action) { - return fileIOExecutorService.submit(new FileWriter(action)); - } - - private static class FileWriter implements Callable { - private final Runnable action; - - private FileWriter(Runnable action) { - this.action = action; - } - - @Override - public Void call() { - action.run(); - return null; - } + void queueAction(Runnable action) { + fileIOExecutorService.submit(action); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 97bcd957..011844e8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -101,7 +101,7 @@ public void addPlayerData(PlayerProfile playerProfile) { */ public void removeAllPlayerData(OfflinePlayer player) { this.getPlayerData(player.getName()).clear(); - profileDataSource.removePlayerData(getContainerType(), getContainerName(), null, player.getName()); + profileDataSource.removePlayerData(getContainerType(), getContainerName(), null, player.getUniqueId()); } /** @@ -112,7 +112,7 @@ public void removeAllPlayerData(OfflinePlayer player) { */ public void removePlayerData(ProfileType profileType, OfflinePlayer player) { this.getPlayerData(player.getName()).remove(profileType); - profileDataSource.removePlayerData(getContainerType(), getContainerName(), profileType, player.getName()); + profileDataSource.removePlayerData(getContainerType(), getContainerName(), profileType, player.getUniqueId()); } /** diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 65aae39c..28193a49 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.profile import com.dumptruckman.minecraft.util.Logging +import org.bukkit.GameMode import org.bukkit.Material import org.bukkit.enchantments.Enchantment import org.bukkit.inventory.ItemStack @@ -14,6 +15,7 @@ import java.util.* import java.util.function.Consumer import kotlin.test.BeforeTest import kotlin.test.assertEquals +import kotlin.test.assertNull class FilePerformanceTest : TestWithMockBukkit() { @@ -37,6 +39,7 @@ class FilePerformanceTest : TestWithMockBukkit() { Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") profileDataSource.clearAllCache(); + Thread.sleep(800) // Wait for files to write finish val startTime2 = System.nanoTime() for (i in 0..9999) { @@ -48,42 +51,64 @@ class FilePerformanceTest : TestWithMockBukkit() { } @Test - fun `Test 5K player profiles`() { - server.setPlayers(5000) + fun `Test 1K player profiles`() { + server.setPlayers(1000) val startTime = System.nanoTime() - for (i in 0..4999) { + for (i in 0..999) { val player = server.getPlayer(i) - val playerProfile = profileDataSource.getPlayerData( - ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) - playerProfile.set(Sharables.HEALTH, 5.0) - playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) - playerProfile.set(Sharables.INVENTORY, arrayOf( - ItemStack(Material.STONE_BRICKS, 10), - ItemStack(Material.ACACIA_LOG, 10), - createItemStack(Material.BOW, 1, { itemStack -> - itemStack.addEnchantment(Enchantment.UNBREAKING, 2) - }), - ItemStack(Material.WATER_BUCKET, 64) - )) - playerProfile.set(Sharables.POTIONS, arrayOf( - PotionEffect(PotionEffectType.POISON, 100, 1), - PotionEffect(PotionEffectType.SPEED, 50, 1), - )) - profileDataSource.updatePlayerData(playerProfile) + for (gameMode in GameMode.entries) { + val playerProfile = profileDataSource.getPlayerData( + ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId) + playerProfile.set(Sharables.HEALTH, 5.0) + playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) + playerProfile.set(Sharables.INVENTORY, arrayOf( + ItemStack(Material.STONE_BRICKS, 10), + ItemStack(Material.ACACIA_LOG, 10), + createItemStack(Material.BOW, 1, { itemStack -> + itemStack.addEnchantment(Enchantment.UNBREAKING, 2) + }), + ItemStack(Material.WATER_BUCKET, 64) + )) + playerProfile.set(Sharables.POTIONS, arrayOf( + PotionEffect(PotionEffectType.POISON, 100, 1), + PotionEffect(PotionEffectType.SPEED, 50, 1), + )) + profileDataSource.updatePlayerData(playerProfile) + } } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - profileDataSource.clearAllCache(); + profileDataSource.clearAllCache() + Thread.sleep(800) // Wait for files to write finish val startTime2 = System.nanoTime() - for (i in 0..4999) { + for (i in 0..999) { val player = server.getPlayer(i) - val playerProfile = profileDataSource.getPlayerData( - ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) - assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) - assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) + for (gameMode in GameMode.entries) { + val playerProfile = profileDataSource.getPlayerData( + ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId + ) + assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) + assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) + } } Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") + + val startTime3 = System.nanoTime() + for (i in 0..999) { + val player = server.getPlayer(i) + for (gameMode in GameMode.entries) { + profileDataSource.removePlayerData( + ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId + ) + val playerProfile = profileDataSource.getPlayerData( + ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId + ) + assertNull(playerProfile.get(Sharables.HEALTH)) + assertNull(playerProfile.get(Sharables.OFF_HAND)) + } + } + Logging.info("Time taken: " + (System.nanoTime() - startTime3) / 1000000 + "ms") } fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { From 6580584d1e684ef59f8d846d35d9349114ede02a Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 15:09:39 +0800 Subject: [PATCH 060/180] Clear profile cache with profile key predicate --- .../inventories/profile/FlatFileProfileDataSource.java | 6 ++++++ .../multiverse/inventories/profile/ProfileDataSource.java | 6 ++++++ .../inventories/profile/container/ProfileContainer.java | 7 ++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 85c9686b..eb26741c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.logging.Level; @Service @@ -543,6 +544,11 @@ public void clearProfileCache(ProfileKey key) { profileCache.invalidate(key); } + @Override + public void clearProfileCache(Predicate predicate) { + configCache.invalidateAll(Sets.filter(configCache.asMap().keySet(), predicate::test)); + } + @Override public void clearAllCache() { configCache.invalidateAll(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 5765c995..91875707 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.util.UUID; +import java.util.function.Predicate; /** * A source for updating and retrieving player profiles via persistence. @@ -121,6 +122,11 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { */ void clearProfileCache(ProfileKey key); + /** + * Clears a single profile in cache. + */ + void clearProfileCache(Predicate predicate); + /** * Clears all profiles in cache. */ diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 011844e8..9fc234c1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -139,11 +139,8 @@ public ContainerType getContainerType() { * Clears all cached data in the container. */ public void clearContainer() { - for (Map profiles : playerData.values()) { - for (PlayerProfile profile : profiles.values()) { - profileDataSource.clearProfileCache(ProfileKey.createProfileKey(profile)); - } - } + profileDataSource.clearProfileCache(key -> + key.getContainerType().equals(type) && key.getDataName().equals(name)); this.playerData.clear(); } } From 43db3ffbb69c90f5e514972e6f4e659c406b2b87 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 15:42:31 +0800 Subject: [PATCH 061/180] Invalidate configCache as well --- .../profile/FlatFileProfileDataSource.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index eb26741c..c8d7460f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -529,24 +529,19 @@ private void migrateForContainerType(File[] folders, ContainerType containerType } void clearPlayerCache(UUID playerUUID) { - profileCache.invalidateAll(Sets.filter( - profileCache.asMap().keySet(), - key -> key.getPlayerUUID().equals(playerUUID) - )); - configCache.invalidateAll(Sets.filter( - configCache.asMap().keySet(), - key -> key.getPlayerUUID().equals(playerUUID) - )); + clearProfileCache(key -> key.getPlayerUUID().equals(playerUUID)); } @Override public void clearProfileCache(ProfileKey key) { + configCache.invalidate(key); profileCache.invalidate(key); } @Override public void clearProfileCache(Predicate predicate) { configCache.invalidateAll(Sets.filter(configCache.asMap().keySet(), predicate::test)); + profileCache.invalidateAll(Sets.filter(profileCache.asMap().keySet(), predicate::test)); } @Override From 8fb6d0c535a39b56b6cc4c6c6b8f65590b869f11 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:00:44 +0800 Subject: [PATCH 062/180] Fix profile being updated for ungrouped world even when default_ungrouped_worlds is enabled --- .../inventories/listeners/ShareHandler.java | 4 ++++ .../listeners/WorldChangeShareHandler.java | 5 +++++ .../group/AbstractWorldGroupManager.java | 20 +++++++++---------- .../profile/group/WorldGroupManager.java | 3 +-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java index 995fba97..822df3c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java @@ -1,8 +1,10 @@ package org.mvplugins.multiverse.inventories.listeners; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; @@ -26,6 +28,7 @@ public abstract class ShareHandler { protected final MultiverseInventories inventories; protected final Player player; + protected final @Nullable InventoriesConfig inventoriesConfig; protected final WorldGroupManager worldGroupManager; protected final ProfileContainerStore worldProfileContainerStore; final AffectedProfiles affectedProfiles; @@ -34,6 +37,7 @@ public abstract class ShareHandler { this.inventories = inventories; this.player = player; this.affectedProfiles = new AffectedProfiles(); + this.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); this.worldProfileContainerStore = inventories.getServiceLocator() .getService(ProfileContainerStoreProvider.class) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java index 7f3a4ae3..d7902276 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java @@ -144,6 +144,11 @@ private boolean isPlayerBypassingChange(WorldGroup worldGroup) { } private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { + if (inventoriesConfig.isDefaultingUngroupedWorlds() + && !worldGroupManager.hasConfiguredGroup(fromWorld) + && worldGroup.equals(worldGroupManager.getDefaultGroup())) { + return false; + } return !worldGroup.containsWorld(fromWorld); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index 1cf2d7ce..f5cbf060 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -2,6 +2,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.MultiverseInventories; @@ -20,6 +21,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Abstract implementation of GroupManager with no persistence of groups. @@ -86,8 +88,9 @@ public List getGroupsForWorld(String worldName) { * {@inheritDoc} */ @Override - public boolean hasGroup(String worldName) { - return !getGroupsForWorld(worldName).isEmpty(); + public boolean hasConfiguredGroup(String worldName) { + return groupNamesMap.values().stream() + .anyMatch(worldGroup -> worldGroup.getWorlds().contains(worldName)); } /** @@ -104,9 +107,6 @@ public void updateGroup(final WorldGroup worldGroup) { getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); } - protected void persistGroup(final WorldGroup worldGroup) { - } - /** * {@inheritDoc} */ @@ -134,24 +134,24 @@ public void createDefaultGroup() { if (getGroup(DEFAULT_GROUP_NAME) != null) { return; } - World defaultWorld = Bukkit.getWorlds().get(0); + World defaultWorld = worldManager.getDefaultWorld() + .flatMap(LoadedMultiverseWorld::getBukkitWorld) + .fold(() -> Bukkit.getWorlds().get(0), world -> world); World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, DEFAULT_GROUP_NAME); worldGroup.getShares().mergeShares(Sharables.allOf()); worldGroup.addWorld(defaultWorld); - StringBuilder worlds = new StringBuilder().append(defaultWorld.getName()); if (defaultNether != null) { worldGroup.addWorld(defaultNether); - worlds.append(", ").append(defaultNether.getName()); } if (defaultEnd != null) { worldGroup.addWorld(defaultEnd); - worlds.append(", ").append(defaultEnd.getName()); } updateGroup(worldGroup); inventoriesConfig.save(); - Logging.info("Created a default group for you containing all of your default worlds: " + worlds.toString()); + Logging.info("Created a default group for you containing all of your default worlds: " + + String.join(", ", worldGroup.getWorlds())); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index 8527fe3b..a716e3db 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -4,7 +4,6 @@ import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Try; -import java.io.IOException; import java.util.List; /** @@ -51,7 +50,7 @@ public sealed interface WorldGroupManager permits AbstractWorldGroupManager { * @param worldName Name of the world to check. * @return true if this world has one or more groups. */ - boolean hasGroup(String worldName); + boolean hasConfiguredGroup(String worldName); /** *

Adds or updates a world group in Multiverse-Inventories.

From 60cc22d216fea814de8a45c92abcec17e57d138d Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:22:27 +0800 Subject: [PATCH 063/180] Write last_location for gamemode change --- .../multiverse/inventories/listeners/InventoriesListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index c55c1af3..d6df0c54 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -242,6 +242,7 @@ public void playerGameModeChange(PlayerGameModeChangeEvent event) { return; } Player player = event.getPlayer(); + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); new GameModeShareHandler(this.inventories, player, player.getGameMode(), event.getNewGameMode()).handleSharing(); } From 7965303da0776154c7239dd22a6d7b01f10285c9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:41:28 +0800 Subject: [PATCH 064/180] Only verify player name if global profile exists for player --- .../listeners/InventoriesListener.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java index d6df0c54..f4b939b6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java @@ -177,23 +177,24 @@ public void playerJoin(final PlayerJoinEvent event) { } private void verifyCorrectPlayerName(UUID uuid, String name) { - GlobalProfile globalProfile = profileDataSource.getGlobalProfile(name, uuid); - if (globalProfile.getLastKnownName().equals(name)) { - return; - } + profileDataSource.getExistingGlobalProfile(name, uuid).peek(globalProfile -> { + if (globalProfile.getLastKnownName().equals(name)) { + return; + } - // Data must be migrated - Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", - uuid, globalProfile.getLastKnownName(), name); - try { - profileDataSource.migratePlayerData(globalProfile.getLastKnownName(), name, uuid); - } catch (IOException e) { - Logging.severe("An error occurred while trying to migrate playerdata."); - e.printStackTrace(); - } - globalProfile.setLastKnownName(name); - profileDataSource.updateGlobalProfile(globalProfile); - Logging.info("Migration complete!"); + // Data must be migrated + Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", + uuid, globalProfile.getLastKnownName(), name); + try { + profileDataSource.migratePlayerData(globalProfile.getLastKnownName(), name, uuid); + } catch (IOException e) { + Logging.severe("An error occurred while trying to migrate playerdata."); + e.printStackTrace(); + } + globalProfile.setLastKnownName(name); + profileDataSource.updateGlobalProfile(globalProfile); + Logging.info("Migration complete!"); + }); } /** From 2922dc4db153e5def3c64872b980a61653e741b9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:33:05 +0800 Subject: [PATCH 065/180] Cleanup share handling classes --- .../inventories/MultiverseInventories.java | 18 +- .../inventories/RespawnListener.java | 134 +++++++++++++++ .../GameModeChangeShareHandlingEvent.java | 4 +- .../inventories/event/ShareHandlingEvent.java | 6 +- .../event/WorldChangeShareHandlingEvent.java | 10 +- .../handleshare/AffectedProfiles.java | 51 ++++++ .../GameModeShareHandler.java | 2 +- .../PersistingProfile.java | 21 +-- .../ShareHandleListener.java} | 158 +++--------------- .../ShareHandler.java | 58 +------ .../ShareHandlingUpdater.java | 53 +++--- .../SingleShareWriter.java | 4 +- .../handleshare/SpawnChangeListener.java | 52 ++++++ .../WorldChangeShareHandler.java | 2 +- .../inventories/handleshare/package-info.java | 1 + .../listeners/SpawnChangeListener.java | 88 ---------- .../profile/FlatFileProfileDataSource.java | 2 +- .../inventories/share/ProfileEntry.java | 20 +-- .../inventories/share/Sharables.java | 6 +- .../inventories/util/MinecraftTools.java | 51 +++++- .../multiverse/inventories/InjectionTest.kt | 4 +- .../inventories/gameplay/WorldChangeTest.kt | 2 + 22 files changed, 395 insertions(+), 352 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java rename src/main/java/org/mvplugins/multiverse/inventories/{listeners => handleshare}/GameModeShareHandler.java (98%) rename src/main/java/org/mvplugins/multiverse/inventories/{profile => handleshare}/PersistingProfile.java (59%) rename src/main/java/org/mvplugins/multiverse/inventories/{listeners/InventoriesListener.java => handleshare/ShareHandleListener.java} (76%) rename src/main/java/org/mvplugins/multiverse/inventories/{listeners => handleshare}/ShareHandler.java (72%) rename src/main/java/org/mvplugins/multiverse/inventories/{ => handleshare}/ShareHandlingUpdater.java (58%) rename src/main/java/org/mvplugins/multiverse/inventories/{listeners => handleshare}/SingleShareWriter.java (94%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java rename src/main/java/org/mvplugins/multiverse/inventories/{listeners => handleshare}/WorldChangeShareHandler.java (99%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index c666d2bf..d4426e04 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -4,6 +4,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; import org.mvplugins.multiverse.core.MultiverseCoreApi; import org.mvplugins.multiverse.core.MultiversePlugin; import org.mvplugins.multiverse.core.config.MVCoreConfig; @@ -13,12 +14,13 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; import org.mvplugins.multiverse.inventories.dataimport.DataImporter; -import org.mvplugins.multiverse.inventories.listeners.InventoriesListener; -import org.mvplugins.multiverse.inventories.listeners.SpawnChangeListener; +import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; +import org.mvplugins.multiverse.inventories.handleshare.ShareHandlingUpdater; +import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.locale.Messager; import org.mvplugins.multiverse.inventories.locale.Messaging; -import org.mvplugins.multiverse.inventories.profile.PersistingProfile; +import org.mvplugins.multiverse.inventories.handleshare.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; @@ -48,7 +50,9 @@ public class MultiverseInventories extends MultiversePlugin implements Messaging @Inject private Provider mvCoreConfig; @Inject - private Provider inventoriesListener; + private Provider shareHandleListener; + @Inject + private Provider respawnListener; @Inject private Provider worldGroupManager; @Inject @@ -116,10 +120,12 @@ private void onMVPluginEnable() { } // Register Events - Bukkit.getPluginManager().registerEvents(inventoriesListener.get(), this); + PluginManager pluginManager = this.getServer().getPluginManager(); + pluginManager.registerEvents(shareHandleListener.get(), this); + pluginManager.registerEvents(respawnListener.get(), this); try { Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); - Bukkit.getPluginManager().registerEvents(new SpawnChangeListener(this), this); + pluginManager.registerEvents(new SpawnChangeListener(this), this); usingSpawnChangeEvent = true; Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); } catch (ClassNotFoundException e) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java b/src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java new file mode 100644 index 00000000..176b6917 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java @@ -0,0 +1,134 @@ +package org.mvplugins.multiverse.inventories; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.List; + +/** + * Specific events for handling player respawns location + */ +@Service +final class RespawnListener implements Listener { + + private final WorldGroupManager worldGroupManager; + private final WorldManager worldManager; + + private List currentGroups; + private Location spawnLoc = null; + + @Inject + RespawnListener(WorldGroupManager worldGroupManager, WorldManager worldManager) { + this.worldGroupManager = worldGroupManager; + this.worldManager = worldManager; + } + + /** + * Handles player respawns at the LOWEST priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.LOWEST) + void lowestPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + World world = event.getPlayer().getWorld(); + this.currentGroups = worldGroupManager.getGroupsForWorld(world.getName()); + this.handleRespawn(event, EventPriority.LOWEST); + } + } + + /** + * Handles player respawns at the LOW priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.LOW) + void lowPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.LOW); + } + } + + /** + * Handles player respawns at the NORMAL priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.NORMAL) + void normalPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.NORMAL); + } + } + + /** + * Handles player respawns at the HIGH priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.HIGH) + void highPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.HIGH); + } + } + + /** + * Handles player respawns at the HIGHEST priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.HIGHEST) + void highestPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.HIGHEST); + } + } + + /** + * Handles player respawns at the MONITOR priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.MONITOR) + void monitorPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.MONITOR); + this.updateCompass(event); + } + } + + private void handleRespawn(PlayerRespawnEvent event, EventPriority priority) { + for (WorldGroup group : this.currentGroups) { + if (!group.getSpawnPriority().equals(priority)) { + continue; + } + String spawnWorldName = group.getSpawnWorld(); + if (spawnWorldName == null) { + continue; + } + LoadedMultiverseWorld mvWorld = worldManager.getLoadedWorld(spawnWorldName).getOrNull(); + if (mvWorld != null) { + this.spawnLoc = mvWorld.getSpawnLocation(); + event.setRespawnLocation(this.spawnLoc); + break; + } + } + } + + private void updateCompass(PlayerRespawnEvent event) { + if (event.getRespawnLocation().equals(this.spawnLoc)) { + event.getPlayer().setCompassTarget(this.spawnLoc); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java index 26c43d77..75d3c697 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.event; -import org.mvplugins.multiverse.inventories.listeners.ShareHandler; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; import org.bukkit.GameMode; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -21,7 +21,7 @@ public static HandlerList getHandlerList() { private final GameMode fromGameMode; private final GameMode toGameMode; - public GameModeChangeShareHandlingEvent(Player player, ShareHandler.AffectedProfiles affectedProfiles, + public GameModeChangeShareHandlingEvent(Player player, AffectedProfiles affectedProfiles, GameMode fromGameMode, GameMode toGameMode) { super(player, affectedProfiles); this.fromGameMode = fromGameMode; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index ef07ad7b..079e866e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.event; -import org.mvplugins.multiverse.inventories.profile.PersistingProfile; -import org.mvplugins.multiverse.inventories.listeners.ShareHandler; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; +import org.mvplugins.multiverse.inventories.handleshare.PersistingProfile; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -20,7 +20,7 @@ public abstract class ShareHandlingEvent extends Event implements Cancellable { private final List writeProfiles; private final List readProfiles; - ShareHandlingEvent(Player player, ShareHandler.AffectedProfiles affectedProfiles) { + ShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { this.player = player; this.alwaysWriteProfile = affectedProfiles.getAlwaysWriteProfile(); this.writeProfiles = affectedProfiles.getWriteProfiles(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java index f2a715a0..4602d052 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.event; -import org.mvplugins.multiverse.inventories.listeners.ShareHandler; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; @@ -20,8 +20,11 @@ public static HandlerList getHandlerList() { private final String fromWorld; private final String toWorld; - public WorldChangeShareHandlingEvent(Player player, ShareHandler.AffectedProfiles affectedProfiles, - String fromWorld, String toWorld) { + public WorldChangeShareHandlingEvent( + Player player, + AffectedProfiles affectedProfiles, + String fromWorld, + String toWorld) { super(player, affectedProfiles); this.fromWorld = fromWorld; this.toWorld = toWorld; @@ -48,5 +51,4 @@ public String getFromWorld() { public String getToWorld() { return this.toWorld; } - } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java new file mode 100644 index 00000000..7e98b7e9 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -0,0 +1,51 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.LinkedList; +import java.util.List; + +import static org.mvplugins.multiverse.inventories.share.Sharables.allOf; + +public final class AffectedProfiles { + + private PersistingProfile alwaysWriteProfile; + private final List writeProfiles = new LinkedList<>(); + private final List readProfiles = new LinkedList<>(); + + AffectedProfiles() { + } + + void setAlwaysWriteProfile(PlayerProfile profile) { + alwaysWriteProfile = new PersistingProfile(allOf(), profile); + } + + /** + * @param profile The player profile that will need data saved to. + * @param shares What from this group needs to be saved. + */ + void addWriteProfile(PlayerProfile profile, Shares shares) { + writeProfiles.add(new PersistingProfile(shares, profile)); + } + + /** + * @param profile The player profile that will need data loaded from. + * @param shares What from this group needs to be loaded. + */ + void addReadProfile(PlayerProfile profile, Shares shares) { + readProfiles.add(new PersistingProfile(shares, profile)); + } + + public PersistingProfile getAlwaysWriteProfile() { + return alwaysWriteProfile; + } + + public List getWriteProfiles() { + return writeProfiles; + } + + public List getReadProfiles() { + return readProfiles; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java similarity index 98% rename from src/main/java/org/mvplugins/multiverse/inventories/listeners/GameModeShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 39cfd96d..f056a1e0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.listeners; +package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java similarity index 59% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/PersistingProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java index ee543114..ded31a71 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java @@ -1,28 +1,22 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.handleshare; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.share.Shares; /** * Simple class for groups that are going to be saved/loaded. This is used specifically for when a user's world * change is being handled. */ -public final class PersistingProfile { - - private final Shares shares; - private final PlayerProfile profile; - - public PersistingProfile(Shares shares, PlayerProfile profile) { - this.shares = shares; - this.profile = profile; - } +public record PersistingProfile(Shares shares, PlayerProfile profile) { /** * Gets the shares that will be saved/loaded for the profile. * * @return The shares that will be saved/loaded for the profile. This is the set of all Sharables that will be acted - * upon when passed through the ShareHandler class, or any of its subclasses. + * upon when passed through the ShareHandler class, or any of its subclasses. */ - public Shares getShares() { + @Override + public Shares shares() { return this.shares; } @@ -31,7 +25,8 @@ public Shares getShares() { * * @return The player profile for the world/group that will be saved/loaded for. */ - public PlayerProfile getProfile() { + @Override + public PlayerProfile profile() { return this.profile; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java similarity index 76% rename from src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index f4b939b6..048ed5db 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/InventoriesListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -1,17 +1,13 @@ -package org.mvplugins.multiverse.inventories.listeners; +package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; import org.mvplugins.multiverse.core.event.MVDebugModeEvent; import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; -import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; -import org.mvplugins.multiverse.external.jakarta.inject.Provider; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; @@ -21,7 +17,6 @@ import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; -import me.drayshak.WorldInventories.WorldInventories; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -41,13 +36,10 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.inventory.InventoryHolder; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; import java.io.File; import java.io.IOException; @@ -56,10 +48,10 @@ import java.util.stream.Collectors; /** - * PlayerListener for MultiverseInventories. + * Events related to handling of player profile changes. */ @Service -public class InventoriesListener implements Listener { +public final class ShareHandleListener implements Listener { private final MultiverseInventories inventories; private final InventoriesConfig config; @@ -68,11 +60,8 @@ public class InventoriesListener implements Listener { private final ProfileDataSource profileDataSource; private final ProfileContainerStoreProvider profileContainerStoreProvider; - private List currentGroups; - private Location spawnLoc = null; - @Inject - InventoriesListener( + ShareHandleListener( @NotNull MultiverseInventories inventories, InventoriesConfig config, @NotNull WorldManager worldManager, @NotNull WorldGroupManager worldGroupManager, @@ -92,7 +81,7 @@ public class InventoriesListener implements Listener { * @param event The MVVersionEvent that this plugin will listen for. */ @EventHandler - public void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { + void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { event.appendDebugInfo(getDebugInfo()); File configFile = new File(this.inventories.getDataFolder(), "config.yml"); File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); @@ -105,7 +94,7 @@ public void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { * * @return The version info. */ - public String getDebugInfo() { + private String getDebugInfo() { StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + inventories.getDescription().getVersion() + '\n' + "[Multiverse-Inventories] === Settings ===" + '\n' + "[Multiverse-Inventories] First Run: " + config.isFirstRun() + '\n' @@ -126,7 +115,7 @@ public String getDebugInfo() { } @EventHandler - public void onDebugModeChange(MVDebugModeEvent event) { + void onDebugModeChange(MVDebugModeEvent event) { Logging.setDebugLevel(event.getLevel()); } @@ -136,13 +125,13 @@ public void onDebugModeChange(MVDebugModeEvent event) { * @param event The MVConfigReloadEvent that this plugin will listen for. */ @EventHandler - public void configReload(MVConfigReloadEvent event) { + void configReload(MVConfigReloadEvent event) { this.inventories.reloadConfig(); event.addConfig("Multiverse-Inventories - config.yml"); } @EventHandler(priority = EventPriority.MONITOR) - public void playerPreLogin(AsyncPlayerPreLoginEvent event) { + void playerPreLogin(AsyncPlayerPreLoginEvent event) { if (event.getLoginResult() != Result.ALLOWED) { return; } @@ -157,7 +146,7 @@ public void playerPreLogin(AsyncPlayerPreLoginEvent event) { * @param event The player join event. */ @EventHandler - public void playerJoin(final PlayerJoinEvent event) { + void playerJoin(final PlayerJoinEvent event) { final Player player = event.getPlayer(); // Just in case AsyncPlayerPreLoginEvent was still the old name verifyCorrectPlayerName(player.getUniqueId(), player.getName()); @@ -203,7 +192,7 @@ private void verifyCorrectPlayerName(UUID uuid, String name) { * @param event The player quit event. */ @EventHandler - public void playerQuit(final PlayerQuitEvent event) { + void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); profileDataSource.updateLastWorld(player.getUniqueId(), world); @@ -238,7 +227,7 @@ private void verifyCorrectWorld(Player player, String world, GlobalProfile globa * @param event The game mode change event. */ @EventHandler(priority = EventPriority.MONITOR) - public void playerGameModeChange(PlayerGameModeChangeEvent event) { + void playerGameModeChange(PlayerGameModeChangeEvent event) { if (event.isCancelled() || !config.isUsingGameModeProfiles()) { return; } @@ -254,7 +243,7 @@ public void playerGameModeChange(PlayerGameModeChangeEvent event) { * @param event The world change event. */ @EventHandler(priority = EventPriority.LOW) - public void playerChangedWorld(PlayerChangedWorldEvent event) { + void playerChangedWorld(PlayerChangedWorldEvent event) { Player player = event.getPlayer(); World fromWorld = event.getFrom(); World toWorld = player.getWorld(); @@ -279,7 +268,7 @@ public void playerChangedWorld(PlayerChangedWorldEvent event) { * @param event The player teleport event. */ @EventHandler(priority = EventPriority.MONITOR) - public void playerTeleport(PlayerTeleportEvent event) { + void playerTeleport(PlayerTeleportEvent event) { if (event.isCancelled() || event.getFrom().getWorld().equals(event.getTo().getWorld()) || !config.getOptionalShares().contains(Sharables.LAST_LOCATION)) { @@ -299,7 +288,7 @@ public void playerTeleport(PlayerTeleportEvent event) { * @param event The player death event. */ @EventHandler(priority = EventPriority.MONITOR) - public void playerDeath(PlayerDeathEvent event) { + void playerDeath(PlayerDeathEvent event) { Logging.finer("=== Handling PlayerDeathEvent for: " + event.getEntity().getName() + " ==="); String deathWorld = event.getEntity().getWorld().getName(); ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(deathWorld); @@ -319,120 +308,24 @@ public void playerDeath(PlayerDeathEvent event) { } @EventHandler(priority = EventPriority.MONITOR) - public void playerRespawn(PlayerRespawnEvent event) { + void playerRespawn(PlayerRespawnEvent event) { Location respawnLoc = event.getRespawnLocation(); if (respawnLoc == null) { // This probably only happens if a naughty plugin sets the location to null... return; } final Player player = event.getPlayer(); - Bukkit.getScheduler().scheduleSyncDelayedTask(inventories, new Runnable() { - public void run() { - verifyCorrectWorld(player, player.getWorld().getName(), - profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId())); - } - }, 2L); - } - - /** - * Handles player respawns at the LOWEST priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.LOWEST) - public void lowestPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - World world = event.getPlayer().getWorld(); - this.currentGroups = worldGroupManager.getGroupsForWorld(world.getName()); - this.handleRespawn(event, EventPriority.LOWEST); - } - } - - /** - * Handles player respawns at the LOW priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.LOW) - public void lowPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.LOW); - } - } - - /** - * Handles player respawns at the NORMAL priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.NORMAL) - public void normalPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.NORMAL); - } - } - - /** - * Handles player respawns at the HIGH priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.HIGH) - public void highPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.HIGH); - } - } - - /** - * Handles player respawns at the HIGHEST priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void highestPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.HIGHEST); - } - } - - /** - * Handles player respawns at the MONITOR priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.MONITOR) - public void monitorPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.MONITOR); - this.updateCompass(event); - } - } - - private void handleRespawn(PlayerRespawnEvent event, EventPriority priority) { - for (WorldGroup group : this.currentGroups) { - if (group.getSpawnPriority().equals(priority)) { - String spawnWorldName = group.getSpawnWorld(); - if (spawnWorldName != null) { - LoadedMultiverseWorld mvWorld = this.worldManager.getLoadedWorld(spawnWorldName).getOrNull(); - if (mvWorld != null) { - this.spawnLoc = mvWorld.getSpawnLocation(); - event.setRespawnLocation(this.spawnLoc); - break; - } - } - } - } - } - - private void updateCompass(PlayerRespawnEvent event) { - if (event.getRespawnLocation().equals(this.spawnLoc)) { - event.getPlayer().setCompassTarget(this.spawnLoc); - } + Bukkit.getScheduler().scheduleSyncDelayedTask( + inventories, + () -> verifyCorrectWorld( + player, + player.getWorld().getName(), + profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId())), + 2L); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void entityPortal(EntityPortalEvent event) { + void entityPortal(EntityPortalEvent event) { Entity entity = event.getEntity(); if (!(entity instanceof Item) && !(entity instanceof InventoryHolder)) { return; @@ -476,7 +369,7 @@ public void entityPortal(EntityPortalEvent event) { } @EventHandler - public void worldUnload(WorldUnloadEvent event) { + void worldUnload(WorldUnloadEvent event) { String unloadWorldName = event.getWorld().getName(); Logging.finer("Clearing data for world/groups container with '%s' world.", unloadWorldName); @@ -491,4 +384,3 @@ public void worldUnload(WorldUnloadEvent event) { } } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java similarity index 72% rename from src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 822df3c0..8604e88a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -1,11 +1,9 @@ -package org.mvplugins.multiverse.inventories.listeners; +package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -16,19 +14,16 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import java.util.LinkedList; import java.util.List; -import static org.mvplugins.multiverse.inventories.share.Sharables.allOf; - /** * Abstract class for handling sharing of data between worlds and game modes. */ -public abstract class ShareHandler { +sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShareHandler { protected final MultiverseInventories inventories; protected final Player player; - protected final @Nullable InventoriesConfig inventoriesConfig; + protected final InventoriesConfig inventoriesConfig; protected final WorldGroupManager worldGroupManager; protected final ProfileContainerStore worldProfileContainerStore; final AffectedProfiles affectedProfiles; @@ -52,9 +47,11 @@ final void handleSharing() { ShareHandlingEvent event = this.createEvent(); Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) { - this.completeSharing(event); + if (event.isCancelled()) { + Logging.fine("Share handling has been cancelled by another plugin!"); + return; } + this.completeSharing(event); } protected final void setAlwaysWriteProfile(PlayerProfile profile) { @@ -134,45 +131,4 @@ private void logHandlingComplete(ShareHandlingEvent event) { Logging.finer("=== %s complete for %s ===", event.getPlayer().getName(), event.getEventName()); } - public static class AffectedProfiles { - - private PersistingProfile alwaysWriteProfile; - private final List writeProfiles = new LinkedList<>(); - private final List readProfiles = new LinkedList<>(); - - AffectedProfiles() { } - - protected final void setAlwaysWriteProfile(PlayerProfile profile) { - alwaysWriteProfile = new PersistingProfile(allOf(), profile); - } - - /** - * @param profile The player profile that will need data saved to. - * @param shares What from this group needs to be saved. - */ - protected final void addWriteProfile(PlayerProfile profile, Shares shares) { - writeProfiles.add(new PersistingProfile(shares, profile)); - } - - /** - * @param profile The player profile that will need data loaded from. - * @param shares What from this group needs to be loaded. - */ - protected final void addReadProfile(PlayerProfile profile, Shares shares) { - readProfiles.add(new PersistingProfile(shares, profile)); - } - - public PersistingProfile getAlwaysWriteProfile() { - return alwaysWriteProfile; - } - - public List getWriteProfiles() { - return writeProfiles; - } - - public List getReadProfiles() { - return readProfiles; - } - } - } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java similarity index 58% rename from src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index bf450ead..4677bc40 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -1,18 +1,15 @@ -package org.mvplugins.multiverse.inventories; +package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.Sharables; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; public final class ShareHandlingUpdater { @@ -32,10 +29,6 @@ public static void updatePlayer(final MultiverseInventories inventories, private final Player player; private final PersistingProfile profile; - private final List> saved = new ArrayList<>(Sharables.all().size()); - private final List> loaded = new ArrayList<>(Sharables.all().size()); - private final List> defaulted = new ArrayList<>(Sharables.all().size()); - private ShareHandlingUpdater(MultiverseInventories inventories, Player player, PersistingProfile profile) { this.inventories = inventories; this.player = player; @@ -43,27 +36,31 @@ private ShareHandlingUpdater(MultiverseInventories inventories, Player player, P } private void updateProfile() { - for (Sharable sharable : profile.getShares()) { + final List> saved = new ArrayList<>(profile.shares().size()); + for (Sharable sharable : profile.shares()) { if (isSharableUsed(sharable)) { saved.add(sharable); - sharable.getHandler().updateProfile(profile.getProfile(), player); + sharable.getHandler().updateProfile(profile.profile(), player); } } if (!saved.isEmpty()) { - Logging.finer("Persisted: " - + saved.stream().map(Objects::toString).collect(Collectors.joining(", ")) + " to " - + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() - + " (" + profile.getProfile().getProfileType() + ")" - + " for player " + profile.getProfile().getPlayer().getName()); + Logging.finer("Persisted: " + saved + " to " + + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() + + " (" + profile.profile().getProfileType() + ")" + + " for player " + profile.profile().getPlayer().getName()); } - inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(profile.getProfile()); + inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(profile.profile()); } private void updatePlayer() { player.closeInventory(); - for (Sharable sharable : profile.getShares()) { + + final List> loaded = new ArrayList<>(profile.shares().size()); + final List> defaulted = new ArrayList<>(profile.shares().size()); + + for (Sharable sharable : profile.shares()) { if (isSharableUsed(sharable)) { - if (sharable.getHandler().updatePlayer(player, profile.getProfile())) { + if (sharable.getHandler().updatePlayer(player, profile.profile())) { loaded.add(sharable); } else { defaulted.add(sharable); @@ -71,16 +68,16 @@ private void updatePlayer() { } } if (!loaded.isEmpty()) { - Logging.finer("Updated: " + loaded.toString() + " for " - + profile.getProfile().getPlayer().getName() + " for " - + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() - + " (" + profile.getProfile().getProfileType() + ")"); + Logging.finer("Updated: " + loaded + " for " + + profile.profile().getPlayer().getName() + " for " + + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() + + " (" + profile.profile().getProfileType() + ")"); } if (!defaulted.isEmpty()) { - Logging.finer("Defaulted: " + defaulted.toString() + " for " - + profile.getProfile().getPlayer().getName() + " for " - + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() - + " (" + profile.getProfile().getProfileType() + ")"); + Logging.finer("Defaulted: " + defaulted + " for " + + profile.profile().getPlayer().getName() + " for " + + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() + + " (" + profile.profile().getProfileType() + ")"); } } @@ -91,7 +88,7 @@ private boolean isSharableUsed(Sharable sharable) { Logging.finest("Ignoring optional share: " + sharable.getNames()[0]); return false; } - if (profile.getProfile().getContainerType() == ContainerType.WORLD + if (profile.profile().getContainerType() == ContainerType.WORLD && !config.usingOptionalsForUngrouped()) { Logging.finest("Ignoring optional share '" + sharable.getNames()[0] + "' for ungrouped world!"); return false; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java similarity index 94% rename from src/main/java/org/mvplugins/multiverse/inventories/listeners/SingleShareWriter.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 0a98f111..b7ead4e7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.listeners; +package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; import org.bukkit.entity.Player; @@ -17,7 +17,7 @@ final class SingleShareWriter { public static SingleShareWriter of(MultiverseInventories inventories, Player player, Sharable sharable) { - return new SingleShareWriter(inventories, player, sharable); + return new SingleShareWriter<>(inventories, player, sharable); } private final MultiverseInventories inventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java new file mode 100644 index 00000000..8451dd56 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java @@ -0,0 +1,52 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Location; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerSpawnChangeEvent; +import org.bukkit.event.player.PlayerSpawnChangeEvent.Cause; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import javax.annotation.Nullable; + +import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findAnchorFromRespawnLocation; +import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; + +/** + * Handles player spawn location changes for BED_SPAWN sharable. + */ +public final class SpawnChangeListener implements Listener { + + private final MultiverseInventories inventories; + + public SpawnChangeListener(MultiverseInventories inventories) { + this.inventories = inventories; + } + + @EventHandler(priority = EventPriority.MONITOR) + void onSpawnChange(PlayerSpawnChangeEvent event) { + Player player = event.getPlayer(); + + Logging.fine("Respawn cause: %s", event.getCause()); + + if (event.getCause() == Cause.BED) { + updatePlayerSpawn(player, findBedFromRespawnLocation(event.getNewSpawn())); + return; + } + if (event.getCause() == Cause.RESPAWN_ANCHOR) { + updatePlayerSpawn(player, findAnchorFromRespawnLocation(event.getNewSpawn())); + return; + } + updatePlayerSpawn(player, event.getNewSpawn()); + } + + private void updatePlayerSpawn(Player player, Location location) { + SingleShareWriter.of(this.inventories, player, Sharables.BED_SPAWN).write(location); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java similarity index 99% rename from src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index d7902276..f1140fa9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.listeners; +package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java new file mode 100644 index 00000000..9d808b6e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.handleshare; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java deleted file mode 100644 index 0e72763c..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/listeners/SpawnChangeListener.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.mvplugins.multiverse.inventories.listeners; - -import com.dumptruckman.minecraft.util.Logging; -import org.bukkit.Location; -import org.bukkit.block.data.type.Bed; -import org.bukkit.block.data.type.RespawnAnchor; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerSpawnChangeEvent; -import org.bukkit.event.player.PlayerSpawnChangeEvent.Cause; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.share.Sharables; - -import javax.annotation.Nullable; - -public final class SpawnChangeListener implements Listener { - - private final MultiverseInventories inventories; - - public SpawnChangeListener(MultiverseInventories inventories) { - this.inventories = inventories; - } - - @EventHandler(priority = EventPriority.MONITOR) - void onSpawnChange(PlayerSpawnChangeEvent event) { - Player player = event.getPlayer(); - - Logging.fine("Respawn cause: %s", event.getCause()); - - if (event.getCause() == Cause.BED) { - updatePlayerSpawn(player, findBedFromRespawnLocation(event.getNewSpawn())); - return; - } - if (event.getCause() == Cause.RESPAWN_ANCHOR) { - updatePlayerSpawn(player, findAnchorFromRespawnLocation(event.getNewSpawn())); - return; - } - updatePlayerSpawn(player, event.getNewSpawn()); - } - - private void updatePlayerSpawn(Player player, Location location) { - SingleShareWriter.of(this.inventories, player, Sharables.BED_SPAWN).write(location); - } - - public static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { - if (respawnLocation == null) { - return null; - } - var bedSpawnBlock = respawnLocation.getBlock(); - for(int x = -2; x <= 2; x++) { - for (int y = -1; y <= 1; y++) { - for (int z = -2; z <= 2; z++) { - var newBedBlock = bedSpawnBlock.getRelative(x, y, z); - Logging.finest("Finding bed at: " + newBedBlock); - if (newBedBlock.getBlockData() instanceof Bed) { - Logging.finer("Found bed!"); - return newBedBlock.getLocation(); - } - } - } - } - Logging.warning("Unable to anchor, respawn may not work as expected!"); - return respawnLocation; - } - - public static @Nullable Location findAnchorFromRespawnLocation(@Nullable Location respawnLocation) { - if (respawnLocation == null) { - return null; - } - var bedSpawnBlock = respawnLocation.getBlock(); - for(int x = -2; x <= 2; x++) { - for (int y = -2; y <= 2; y++) { - for (int z = -2; z <= 2; z++) { - var newBedBlock = bedSpawnBlock.getRelative(x, y, z); - Logging.finest("Finding anchor at: " + newBedBlock); - if (newBedBlock.getBlockData() instanceof RespawnAnchor) { - Logging.finer("Found anchor!"); - return newBedBlock.getLocation(); - } - } - } - } - Logging.warning("Unable to anchor, respawn may not work as expected!"); - return respawnLocation; - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index c8d7460f..91058fc5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -178,7 +178,7 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) continue; } - String fileTag = profileEntry.getFileTag(); + String fileTag = profileEntry.fileTag(); Object serializedValue = serializer.serialize(sharableValue); if (profileEntry.isStat()) { jsonStats.put(fileTag, serializedValue); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java index 2d19d5d7..95b4ad6f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java @@ -7,23 +7,16 @@ * Indicates how a Sharable should be stored in the profile file. Serves as a lookup for finding a sharable based on * it's file tag. */ -public final class ProfileEntry { +public record ProfileEntry(boolean isStat, String fileTag) { private static final Map STATS_MAP = new HashMap<>(); private static final Map OTHERS_MAP = new HashMap<>(); - private final boolean isStat; - private final String fileTag; - - public ProfileEntry(boolean isStat, String fileTag) { - this.isStat = isStat; - this.fileTag = fileTag; - } - /** * @return True if this indicates a {@link Sharable} whose data will be stored in the stats string of the player - * file. + * file. */ + @Override public boolean isStat() { return this.isStat; } @@ -31,7 +24,8 @@ public boolean isStat() { /** * @return The String that represents where this file is stored in the player profile. */ - public String getFileTag() { + @Override + public String fileTag() { return this.fileTag; } @@ -47,9 +41,9 @@ static void register(Sharable sharable) { return; } if (entry.isStat()) { - STATS_MAP.put(entry.getFileTag(), sharable); + STATS_MAP.put(entry.fileTag(), sharable); } else { - OTHERS_MAP.put(entry.getFileTag(), sharable); + OTHERS_MAP.put(entry.fileTag(), sharable); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 29098745..8cf5fbd3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -5,7 +5,7 @@ import org.bukkit.Registry; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.listeners.SpawnChangeListener; +import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.util.DataStrings; @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Set; +import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; + /** * The Sharables class is where all the default Sharable instances are located as constants as well as a factory class * for generating Shares. @@ -503,7 +505,7 @@ public void updateProfile(PlayerProfile profile, Player player) { Location bedSpawnLocation = null; try { Logging.finer("profile bed: " + player.getBedSpawnLocation()); - bedSpawnLocation = SpawnChangeListener.findBedFromRespawnLocation(player.getBedSpawnLocation()); + bedSpawnLocation = findBedFromRespawnLocation(player.getBedSpawnLocation()); } catch (NullPointerException e) { // TODO this is a temporary fix for the bug occurring in 1.16.X CB/Spigot/Paper StackTraceElement[] stackTrace = e.getStackTrace(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java index 79181301..a4bf794a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java @@ -1,12 +1,18 @@ package org.mvplugins.multiverse.inventories.util; +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.RespawnAnchor; import org.bukkit.inventory.ItemStack; +import javax.annotation.Nullable; + /** * General tools to help with minecraftian things. */ -public class MinecraftTools { +public final class MinecraftTools { private static final int TICKS_PER_SECOND = 20; @@ -34,5 +40,46 @@ public static ItemStack[] fillWithAir(ItemStack[] items) { } return items; } -} + public static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var bedSpawnBlock = respawnLocation.getBlock(); + for(int x = -2; x <= 2; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -2; z <= 2; z++) { + var newBedBlock = bedSpawnBlock.getRelative(x, y, z); + Logging.finest("Finding bed at: " + newBedBlock); + if (newBedBlock.getBlockData() instanceof Bed) { + Logging.finer("Found bed!"); + return newBedBlock.getLocation(); + } + } + } + } + Logging.warning("Unable to anchor, respawn may not work as expected!"); + return respawnLocation; + } + + public static @Nullable Location findAnchorFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var bedSpawnBlock = respawnLocation.getBlock(); + for(int x = -2; x <= 2; x++) { + for (int y = -2; y <= 2; y++) { + for (int z = -2; z <= 2; z++) { + var newBedBlock = bedSpawnBlock.getRelative(x, y, z); + Logging.finest("Finding anchor at: " + newBedBlock); + if (newBedBlock.getBlockData() instanceof RespawnAnchor) { + Logging.finer("Found anchor!"); + return newBedBlock.getLocation(); + } + } + } + } + Logging.warning("Unable to anchor, respawn may not work as expected!"); + return respawnLocation; + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 6abfcd2f..0abea1f6 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test import org.mvplugins.multiverse.inventories.commands.InventoriesCommand import org.mvplugins.multiverse.inventories.config.InventoriesConfig import org.mvplugins.multiverse.inventories.dataimport.DataImportManager -import org.mvplugins.multiverse.inventories.listeners.InventoriesListener +import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener import org.mvplugins.multiverse.inventories.profile.ProfileDataSource import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager @@ -25,7 +25,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesListener is available as a service`() { - assertNotNull(serviceLocator.getActiveService(InventoriesListener::class.java)) + assertNotNull(serviceLocator.getActiveService(ShareHandleListener::class.java)) } @Test diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt index 843ecd42..c53f48d9 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt @@ -28,11 +28,13 @@ class WorldChangeTest : TestWithMockBukkit() { Logging.fine("player world: " + server.getPlayer("Benji_0224")?.world?.name) val stack = ItemStack.of(Material.STONE_BRICKS, 64) player.inventory.setItem(0, stack) + val startTime = System.nanoTime() server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) server.getWorld("world4")?.let { player.teleport(it.spawnLocation) } assertNotEquals(stack, player.inventory.getItem(0)) server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") } } From d2e9509d9eba7536f03f743abaa2adba549f7f06 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 9 Feb 2025 19:57:20 +0800 Subject: [PATCH 066/180] Convert all messages to use core's locale system --- .../inventories/DefaultMessageProvider.java | 250 ------------------ .../inventories/DefaultMessager.java | 94 ------- .../inventories/MultiverseInventories.java | 85 ++---- .../inventories/commands/GroupCommand.java | 13 +- .../inventories/commands/ImportCommand.java | 13 +- .../inventories/commands/InfoCommand.java | 39 ++- .../inventories/commands/ListCommand.java | 11 +- .../inventories/commands/ReloadCommand.java | 8 +- .../inventories/commands/ToggleCommand.java | 15 +- .../commands/prompts/GroupControlPrompt.java | 32 ++- .../commands/prompts/GroupCreatePrompt.java | 27 +- .../commands/prompts/GroupDeletePrompt.java | 27 +- .../commands/prompts/GroupEditPrompt.java | 27 +- .../commands/prompts/GroupModifyPrompt.java | 29 +- .../commands/prompts/GroupSharesPrompt.java | 44 +-- .../commands/prompts/GroupWorldsPrompt.java | 44 +-- .../commands/prompts/InventoriesPrompt.java | 28 +- .../locale/LazyLocaleMessageProvider.java | 44 --- .../locale/LocalizationLoadingException.java | 44 --- .../inventories/locale/Message.java | 113 -------- .../inventories/locale/MessageProvider.java | 72 ----- .../inventories/locale/Messager.java | 75 ------ .../inventories/locale/Messaging.java | 20 -- .../locale/NoSuchLocalizationException.java | 27 -- .../inventories/locale/package-info.java | 5 - .../group/AbstractWorldGroupManager.java | 46 ++-- .../profile/group/WorldGroupManager.java | 7 +- .../profile/group/YamlWorldGroupManager.java | 4 +- .../inventories/util/MVInvi18n.java | 115 ++++++++ src/main/resources/de.yml | 110 -------- src/main/resources/en.yml | 89 ------- .../multiverse-inventories_en.properties | 77 ++++-- .../multiverse/inventories/LocaleTest.kt | 30 +++ 33 files changed, 454 insertions(+), 1210 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java delete mode 100644 src/main/resources/de.yml delete mode 100644 src/main/resources/en.yml create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java deleted file mode 100644 index 77ca906a..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessageProvider.java +++ /dev/null @@ -1,250 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.jvnet.hk2.annotations.Contract; -import org.mvplugins.multiverse.inventories.locale.LazyLocaleMessageProvider; -import org.mvplugins.multiverse.inventories.locale.LocalizationLoadingException; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.mvplugins.multiverse.inventories.locale.NoSuchLocalizationException; -import org.mvplugins.multiverse.inventories.util.Font; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -/** - * Implementation of MessageProvider. - */ -@Contract -class DefaultMessageProvider implements LazyLocaleMessageProvider { - - /** - * Name of localization folder. - */ - public static final String LOCALIZATION_FOLDER_NAME = "localization"; - - private final HashMap>> messages; - private final JavaPlugin plugin; - - private Locale locale = DEFAULT_LOCALE; - - public DefaultMessageProvider(JavaPlugin plugin) { - this.plugin = plugin; - messages = new HashMap>>(); - - try { - loadLocale(locale); - } catch (NoSuchLocalizationException e) { - // let's take the defaults from the enum! - } - } - - /** - * Tries to load the locale. - * - * @param locale Locale to try to load. - * @throws LocalizationLoadingException if the Locale could not be loaded. - */ - public void maybeLoadLocale(Locale locale) throws LocalizationLoadingException { - if (!isLocaleLoaded(locale)) { - try { - loadLocale(locale); - } catch (NoSuchLocalizationException e) { - throw e; - } - } - if (!isLocaleLoaded(locale)) - throw new LocalizationLoadingException("Couldn't load the localization: " - + locale.toString(), locale); - } - - /** - * Formats a list of strings by passing each through {@link #format(String, Object...)}. - * - * @param strings List of strings to format. - * @param args Arguments to pass in via %n. - * @return List of formatted strings. - */ - public List format(List strings, Object... args) { - List formattedStrings = new ArrayList(); - for (String string : strings) { - formattedStrings.add(format(string, args)); - } - return formattedStrings; - } - - /** - * Formats a string by replacing ampersand with the Section symbol and %n with the corresponding args - * object where n = argument index + 1. - * - * @param string String to format. - * @param args Arguments to pass in via %n. - * @return The formatted string. - */ - public String format(String string, Object... args) { - // Replaces & with the Section character - string = string.replaceAll("(&([a-fA-FkK0-9]))", Font.SECTION_SYMBOL + "$2"); - // If there are arguments, %n notations in the message will be - // replaced - if (args != null) { - for (int j = 0; j < args.length; j++) { - string = string.replace("%" + (j + 1), args[j].toString()); - } - } - return string; - } - - /** - * {@inheritDoc} - */ - @Override - public void loadLocale(Locale l) throws NoSuchLocalizationException { - messages.remove(l); - - InputStream resstream = null; - InputStream filestream = null; - - try { - filestream = new FileInputStream(new File(plugin.getDataFolder(), l.getLanguage() + ".yml")); - } catch (FileNotFoundException e) { - } - - try { - resstream = plugin.getResource(new StringBuilder(LOCALIZATION_FOLDER_NAME).append("/") - .append(l.getLanguage()).append(".yml").toString()); - } catch (Exception e) { - } - - if ((resstream == null) && (filestream == null)) - throw new NoSuchLocalizationException(l); - - messages.put(l, new HashMap>(Message.values().length)); - - FileConfiguration resconfig = (resstream == null) ? null : YamlConfiguration.loadConfiguration(new InputStreamReader(resstream)); - FileConfiguration fileconfig = (filestream == null) ? null : YamlConfiguration.loadConfiguration(new InputStreamReader(filestream)); - for (Message m : Message.values()) { - List values = m.getDefault(); - - if (resconfig != null) { - if (resconfig.isList(m.toString())) { - values = resconfig.getStringList(m.toString()); - } else { - values.add(resconfig.getString(m.toString(), values.get(0))); - } - } - if (fileconfig != null) { - if (fileconfig.isList(m.toString())) { - values = fileconfig.getStringList(m.toString()); - } else { - values.add(fileconfig.getString(m.toString(), values.get(0))); - } - } - - messages.get(l).put(m, values); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Set getLoadedLocales() { - return messages.keySet(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLocaleLoaded(Locale l) { - return messages.containsKey(l); - } - - /** - * {@inheritDoc} - */ - @Override - public String getMessage(Message key, Object... args) { - if (!isLocaleLoaded(locale)) { - return format(key.getDefault().get(0), args); - } else - return format(messages.get(locale).get(key).get(0), args); - } - - /** - * {@inheritDoc} - */ - @Override - public String getMessage(Message key, Locale locale, Object... args) { - try { - maybeLoadLocale(locale); - } catch (LocalizationLoadingException e) { - e.printStackTrace(); - return getMessage(key, args); - } - return format(messages.get(locale).get(key).get(0), args); - } - - /** - * {@inheritDoc} - */ - @Override - public List getMessages(Message key, Object... args) { - List result; - if (!isLocaleLoaded(locale)) { - result = format(key.getDefault(), args); - } else { - result = format(this.messages.get(locale).get(key), args); - } - return result; - } - - /** - * {@inheritDoc} - */ - @Override - public List getMessages(Message key, Locale locale, Object... args) { - try { - maybeLoadLocale(locale); - } catch (LocalizationLoadingException e) { - e.printStackTrace(); - return format(getMessages(key), args); - } - return format(messages.get(locale).get(key), args); - } - - /** - * {@inheritDoc} - */ - @Override - public Locale getLocale() { - return locale; - } - - /** - * {@inheritDoc} - */ - @Override - public void setLocale(Locale locale) { - if (locale == null) - throw new IllegalArgumentException("Can't set locale to null!"); - try { - maybeLoadLocale(locale); - } catch (LocalizationLoadingException e) { - if (!locale.equals(DEFAULT_LOCALE)) - throw new IllegalArgumentException("Error while trying to load localization for the given Locale!", e); - } - - this.locale = locale; - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java b/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java deleted file mode 100644 index 7cee3a00..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/DefaultMessager.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.external.jakarta.inject.Inject; -import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.inventories.locale.MessageProvider; -import org.mvplugins.multiverse.inventories.locale.Messager; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.java.JavaPlugin; -import org.mvplugins.multiverse.inventories.util.Font; - -import java.util.List; - -/** - * Implementation of a Messager and MessageProvider using DefaultMessageProvider to implement the latter. - */ -@Service -final class DefaultMessager extends DefaultMessageProvider implements Messager, MessageProvider { - - @Inject - public DefaultMessager(@NotNull MultiverseInventories plugin) { - super(plugin); - } - - private void send(Message message, String prefix, CommandSender sender, Object... args) { - List messages = this.getMessages(message, args); - if (!messages.isEmpty()) { - messages.set(0, prefix + " " + messages.get(0)); - sendMessages(sender, messages); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void bad(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.RED + this.getMessage(Message.GENERIC_ERROR), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void normal(Message message, CommandSender sender, Object... args) { - send(message, "", sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void good(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.GREEN + this.getMessage(Message.GENERIC_SUCCESS), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void info(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.YELLOW + this.getMessage(Message.GENERIC_INFO), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void help(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.GRAY + this.getMessage(Message.GENERIC_HELP), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void sendMessage(CommandSender player, String message) { - List messages = Font.splitString(message); - sendMessages(player, messages); - } - - /** - * {@inheritDoc} - */ - @Override - public void sendMessages(CommandSender player, List messages) { - for (String s : messages) { - player.sendMessage(s); - } - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index d4426e04..385bf167 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -1,7 +1,5 @@ package org.mvplugins.multiverse.inventories; -import java.util.Locale; - import com.dumptruckman.minecraft.util.Logging; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; @@ -17,9 +15,6 @@ import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; import org.mvplugins.multiverse.inventories.handleshare.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.mvplugins.multiverse.inventories.locale.Messager; -import org.mvplugins.multiverse.inventories.locale.Messaging; import org.mvplugins.multiverse.inventories.handleshare.PersistingProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -39,7 +34,7 @@ * Multiverse-Inventories plugin main class. */ @Service -public class MultiverseInventories extends MultiversePlugin implements Messaging { +public class MultiverseInventories extends MultiversePlugin { private static final int PROTOCOL = 50; @@ -63,7 +58,6 @@ public class MultiverseInventories extends MultiversePlugin implements Messaging private Provider dataImportManager; private PluginServiceLocator serviceLocator; - private Messager messager = new DefaultMessager(this); private InventoriesDupingPatch dupingPatch; private boolean usingSpawnChangeEvent = false; @@ -86,39 +80,11 @@ public void onLoad() { @Override public final void onEnable() { super.onEnable(); - initializeDependencyInjection(); - Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); - inventoriesConfig.get().load().onFailure(e -> Logging.severe(e.getMessage())); - - this.onMVPluginEnable(); - Logging.config("Version %s (API v%s) Enabled - By %s", - this.getDescription().getVersion(), getTargetCoreProtocolVersion(), StringFormatter.joinAnd(this.getDescription().getAuthors())); - } - - private void initializeDependencyInjection() { - serviceLocator = PluginServiceLocatorFactory.get() - .registerPlugin(new MultiverseInventoriesPluginBinder(this), MultiverseCoreApi.get().getServiceLocator()) - .flatMap(PluginServiceLocator::enable) - .getOrElseThrow(exception -> { - Logging.severe("Failed to initialize dependency injection!"); - getServer().getPluginManager().disablePlugin(this); - return new RuntimeException(exception); - }); - } - private void onMVPluginEnable() { + initializeDependencyInjection(); Perm.register(this); - this.reloadConfig(); - try { - this.getMessager().setLocale(new Locale(inventoriesConfig.get().getLocale())); - } catch (IllegalArgumentException e) { - Logging.severe(e.getMessage()); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - // Register Events PluginManager pluginManager = this.getServer().getPluginManager(); pluginManager.registerEvents(shareHandleListener.get(), this); @@ -135,13 +101,24 @@ private void onMVPluginEnable() { // Register Commands this.registerCommands(); - // Hook plugins that can be imported from this.hookImportables(); - Sharables.init(this); - this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); + + Logging.config("Version %s (API v%s) Enabled - By %s", + this.getDescription().getVersion(), getTargetCoreProtocolVersion(), StringFormatter.joinAnd(this.getDescription().getAuthors())); + } + + private void initializeDependencyInjection() { + serviceLocator = PluginServiceLocatorFactory.get() + .registerPlugin(new MultiverseInventoriesPluginBinder(this), MultiverseCoreApi.get().getServiceLocator()) + .flatMap(PluginServiceLocator::enable) + .getOrElseThrow(exception -> { + Logging.severe("Failed to initialize dependency injection!"); + getServer().getPluginManager().disablePlugin(this); + return new RuntimeException(exception); + }); } /** @@ -150,6 +127,7 @@ private void onMVPluginEnable() { @Override public void onDisable() { super.onDisable(); + for (final Player player : getServer().getOnlinePlayers()) { final String world = player.getWorld().getName(); if (inventoriesConfig.get().usingLoggingSaveLoad()) { @@ -168,8 +146,12 @@ public void onDisable() { private void registerCommands() { Try.of(() -> commandManager.get()) - .andThenTry(commandManager -> serviceLocator.getAllServices(InventoriesCommand.class) - .forEach(commandManager::registerCommand)) + .andThenTry(commandManager -> { + commandManager.getLocales().addFileResClassLoader(this); + commandManager.getLocales().addBundleClassLoader(this.getClassLoader()); + commandManager.getLocales().addMessageBundles("multiverse-inventories"); + serviceLocator.getAllServices(InventoriesCommand.class).forEach(commandManager::registerCommand); + }) .onFailure(e -> { Logging.severe("Failed to register commands"); e.printStackTrace(); @@ -222,7 +204,7 @@ public void reloadConfig() { Logging.fine("Reloaded all config and groups!"); } catch (Exception e) { // Catch errors loading the config file and exit out if found. - Logging.severe(this.getMessager().getMessage(Message.ERROR_CONFIG_LOAD)); + Logging.severe("Encountered an error while loading the configuration file. Disabling..."); Logging.severe(e.getMessage()); Bukkit.getPluginManager().disablePlugin(this); return; @@ -245,25 +227,6 @@ public void run() { }, 1L); } - /** - * {@inheritDoc} - */ - @Override - public Messager getMessager() { - return messager; - } - - /** - * {@inheritDoc} - */ - @Override - public void setMessager(Messager messager) { - if (messager == null) { - throw new IllegalArgumentException("The new messager can't be null!"); - } - this.messager = messager; - } - public boolean isUsingSpawnChangeEvent() { return usingSpawnChangeEvent; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index e3eb632a..6bde5da1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.commands.prompts.GroupControlPrompt; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.command.CommandSender; import org.bukkit.conversations.Conversable; import org.bukkit.conversations.Conversation; @@ -15,6 +15,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; @Service @CommandAlias("mvinv") @@ -31,14 +32,14 @@ class GroupCommand extends InventoriesCommand { @CommandAlias("mvinvgroup|mvinvg") @Subcommand("group") @CommandPermission("multiverse.inventories.group") - @Description("Manage a world group wiht prompts!") - void onGroupCommand(@NotNull CommandSender sender) { - if (!(sender instanceof Conversable conversable)) { - this.plugin.getMessager().normal(Message.NON_CONVERSABLE, sender); + @Description("Manage a world group with prompts!") + void onGroupCommand(@NotNull MVCommandIssuer issuer) { + if (!(issuer.getIssuer() instanceof Conversable conversable)) { + issuer.sendError(MVInvi18n.GROUP_NONCONVERSABLE); return; } Conversation conversation = new ConversationFactory(plugin) - .withFirstPrompt(new GroupControlPrompt(plugin, sender)) + .withFirstPrompt(new GroupControlPrompt(plugin, issuer)) .withEscapeSequence("##") .withModality(false).buildConversation(conversable); conversation.begin(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index f7c2e6eb..e09ee400 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -17,6 +17,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; import org.mvplugins.multiverse.inventories.dataimport.DataImporter; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @@ -50,25 +51,23 @@ public void onImportCommand( String pluginName) { dataImportManager.getImporter(pluginName) - .onEmpty(() -> issuer.sendMessage("No importer found for " + pluginName)) + .onEmpty(() -> issuer.sendError(MVInvi18n.IMPORT_UNSUPPORTEDPLUGIN, replace("{plugin}").with(pluginName))) .peek(dataImporter -> { if (!dataImporter.isEnabled()) { - issuer.sendMessage("Plugin " + pluginName + " is not running on your server!"); + issuer.sendError(MVInvi18n.IMPORT_PLUGINNOTENABLED, replace("{plugin}").with(pluginName)); return; } commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) - .prompt(Message.of("Are you sure you want to import data from {plugin}? " + - "This will override existing Multiverse-Inventories playerdata!!!", - replace("{plugin}").with(pluginName))) + .prompt(Message.of(MVInvi18n.IMPORT_CONFIRMPROMPT, replace("{plugin}").with(pluginName))) .action(() -> doDataImport(issuer, dataImporter))); }); } void doDataImport(MVCommandIssuer issuer, DataImporter dataImporter) { if (dataImporter.importData()) { - issuer.sendMessage("Successfully to imported data from " + dataImporter.getPluginName() + "!"); + issuer.sendInfo(MVInvi18n.IMPORT_SUCCESS, replace("{plugin}").with(dataImporter.getPluginName())); } else { - issuer.sendMessage("Failed to import data from " + dataImporter.getPluginName() + "."); + issuer.sendError(MVInvi18n.IMPORT_FAILED, replace("{plugin}").with(dataImporter.getPluginName())); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index c0f55b1c..3ef90e8b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -1,13 +1,11 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; @@ -22,10 +20,13 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.List; import java.util.Set; +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + @Service @CommandAlias("mvinv") class InfoCommand extends InventoriesCommand { @@ -53,7 +54,7 @@ class InfoCommand extends InventoriesCommand { @Syntax("") @Description("World and Group Information") void onInfoCommand( - @NotNull CommandSender sender, + @NotNull MVCommandIssuer issuer, @Optional @Single @@ -62,30 +63,30 @@ void onInfoCommand( @NotNull String name ) { if (name == null) { - if (!(sender instanceof Player)) { - this.plugin.getMessager().normal(Message.INFO_ZERO_ARG, sender); + if (!issuer.isPlayer()) { + issuer.sendError(MVInvi18n.INFO_ZEROARG); return; } - name = ((Player) sender).getWorld().getName(); + name = issuer.getPlayer().getWorld().getName(); } ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(name); - plugin.getMessager().normal(Message.INFO_WORLD, sender, name); + issuer.sendInfo(MVInvi18n.INFO_WORLD, replace("{world}").with(name)); if (worldProfileContainer != null && Bukkit.getWorld(worldProfileContainer.getContainerName()) != null) { - worldInfo(sender, worldProfileContainer); + worldInfo(issuer, worldProfileContainer); } else { - plugin.getMessager().normal(Message.ERROR_NO_WORLD_PROFILE, sender, name); + issuer.sendError(MVInvi18n.ERROR_NOWORLDPROFILE, replace("{world}").with(name)); } WorldGroup worldGroup = worldGroupManager.getGroup(name); - this.plugin.getMessager().normal(Message.INFO_GROUP, sender, name); + issuer.sendInfo(MVInvi18n.INFO_GROUP, replace("{group}").with(name)); if (worldGroup != null) { - this.groupInfo(sender, worldGroup); + this.groupInfo(issuer, worldGroup); } else { - this.plugin.getMessager().normal(Message.ERROR_NO_GROUP, sender, name); + issuer.sendError(MVInvi18n.ERROR_NOGROUP, replace("{group}").with(name)); } } - private void groupInfo(CommandSender sender, WorldGroup worldGroup) { + private void groupInfo(MVCommandIssuer issuer, WorldGroup worldGroup) { StringBuilder worldsString = new StringBuilder(); Set worlds = worldGroup.getWorlds(); if (worlds.isEmpty()) { @@ -98,11 +99,11 @@ private void groupInfo(CommandSender sender, WorldGroup worldGroup) { worldsString.append(world); } } - this.plugin.getMessager().normal(Message.INFO_GROUPS_INFO, - sender, worldsString, worldGroup.getShares().toString()); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(worldsString)); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(worldGroup.getShares())); } - private void worldInfo(CommandSender sender, ProfileContainer worldProfileContainer) { + private void worldInfo(MVCommandIssuer issuer, ProfileContainer worldProfileContainer) { StringBuilder groupsString = new StringBuilder(); List worldGroups = worldGroupManager.getGroupsForWorld(worldProfileContainer.getContainerName()); @@ -116,8 +117,6 @@ private void worldInfo(CommandSender sender, ProfileContainer worldProfileContai groupsString.append(worldGroup.getName()); } } - - this.plugin.getMessager().normal(Message.INFO_WORLD_INFO, - sender, groupsString.toString()); + issuer.sendInfo(MVInvi18n.INFO_WORLD_INFO, replace("{groups}").with(groupsString)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index a86c013d..41685f7f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -1,9 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; @@ -13,9 +12,12 @@ import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.Collection; +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + @Service @CommandAlias("mvinv") class ListCommand extends InventoriesCommand { @@ -37,7 +39,7 @@ class ListCommand extends InventoriesCommand { @Subcommand("list") @CommandPermission("multiverse.inventories.list") @Description("World and Group Information") - void onListCommand(@NotNull CommandSender sender) { + void onListCommand(@NotNull MVCommandIssuer issuer) { Collection groups = worldGroupManager.getGroups(); String groupsString = "N/A"; if (!groups.isEmpty()) { @@ -50,6 +52,7 @@ void onListCommand(@NotNull CommandSender sender) { } groupsString = builder.toString(); } - this.plugin.getMessager().normal(Message.LIST_GROUPS, sender, groupsString); + issuer.sendInfo(MVInvi18n.LIST_GROUPS); + issuer.sendInfo(MVInvi18n.LIST_GROUPS_INFO, replace("{groups}").with(groupsString)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index 0d6b6483..ad139d8b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -1,8 +1,7 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; @@ -11,6 +10,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; @Service @CommandAlias("mvinv") @@ -28,8 +28,8 @@ class ReloadCommand extends InventoriesCommand { @Subcommand("reload") @CommandPermission("multiverse.inventories.reload") @Description("Reloads config file") - void onReloadCommand(@NotNull CommandSender sender) { + void onReloadCommand(@NotNull MVCommandIssuer issuer) { this.plugin.reloadConfig(); - this.plugin.getMessager().normal(Message.RELOAD_COMPLETE, sender); + issuer.sendInfo(MVInvi18n.RELOAD_COMPLETE); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 414a41bc..27b0b7bb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.locale.Message; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; @@ -18,6 +18,9 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service @CommandAlias("mvinv") @@ -43,7 +46,7 @@ class ToggleCommand extends InventoriesCommand { @Syntax("") @Description("Toggles the usage of optional sharables") void onToggleCommand( - @NotNull CommandSender sender, + @NotNull MVCommandIssuer issuer, @Single @Syntax("") @@ -52,7 +55,7 @@ void onToggleCommand( ) { Shares shares = Sharables.lookup(shareName.toLowerCase()); if (shares == null) { - this.plugin.getMessager().normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); + issuer.sendError(MVInvi18n.ERROR_NOSHARESSPECIFIED); return; } boolean foundOpt = false; @@ -62,10 +65,10 @@ void onToggleCommand( foundOpt = true; if (optionalShares.contains(sharable)) { optionalShares.remove(sharable); - this.plugin.getMessager().normal(Message.NOW_NOT_USING_OPTIONAL, sender, sharable.getNames()[0]); + issuer.sendInfo(MVInvi18n.TOGGLE_NOWNOTUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); } else { optionalShares.add(sharable); - this.plugin.getMessager().normal(Message.NOW_USING_OPTIONAL, sender, sharable.getNames()[0]); + issuer.sendInfo(MVInvi18n.TOGGLE_NOWUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); } } } @@ -73,7 +76,7 @@ void onToggleCommand( inventoriesConfig.setOptionalShares(optionalShares); inventoriesConfig.save(); } else { - this.plugin.getMessager().normal(Message.NO_OPTIONAL_SHARES, sender, shareName); + issuer.sendError(MVInvi18n.TOGGLE_NOOPTIONALSHARES, replace("{share}").with(shareName)); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java index 48c084fe..55379059 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java @@ -1,32 +1,36 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; public class GroupControlPrompt extends InventoriesPrompt { - public GroupControlPrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); + public GroupControlPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_COMMAND_PROMPT); + Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + return Message.of(MVInvi18n.GROUP_COMMANDPROMPT); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equalsIgnoreCase("delete")) { - return new GroupDeletePrompt(plugin, sender); - } else if (s.equalsIgnoreCase("create")) { - return new GroupCreatePrompt(plugin, sender); - } else if (s.equalsIgnoreCase("edit")) { - return new GroupEditPrompt(plugin, sender); + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if (input.equalsIgnoreCase("delete")) { + return new GroupDeletePrompt(plugin, issuer); + } else if (input.equalsIgnoreCase("create")) { + return new GroupCreatePrompt(plugin, issuer); + } else if (input.equalsIgnoreCase("edit")) { + return new GroupEditPrompt(plugin, issuer); } else { - messager.normal(Message.INVALID_PROMPT_OPTION, sender); + issuer.sendError(MVInvi18n.GROUP_INVALIDOPTION); return this; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java index 017e9a5c..355cd3c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java @@ -1,36 +1,41 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; class GroupCreatePrompt extends InventoriesPrompt { - public GroupCreatePrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); + public GroupCreatePrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_CREATE_PROMPT); + Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + return Message.of(MVInvi18n.GROUP_CREATEPROMPT); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String s) { final WorldGroup group = worldGroupManager.getGroup(s); if (group == null) { if (s.isEmpty() || !s.matches("^[a-zA-Z0-9][a-zA-Z0-9_]*$")) { - messager.normal(Message.GROUP_INVALID_NAME, sender); + issuer.sendError(MVInvi18n.GROUP_INVALIDNAME); return this; } final WorldGroup newGroup = worldGroupManager.newEmptyGroup(s); - return new GroupWorldsPrompt(plugin, sender, newGroup, - new GroupSharesPrompt(plugin, sender, newGroup, Prompt.END_OF_CONVERSATION, true), true); + return new GroupWorldsPrompt(plugin, issuer, newGroup, + new GroupSharesPrompt(plugin, issuer, newGroup, Prompt.END_OF_CONVERSATION, true), true); } else { - messager.normal(Message.GROUP_EXISTS, sender, s); + issuer.sendError(MVInvi18n.GROUP_EXISTS, replace("{group}").with(s)); } return Prompt.END_OF_CONVERSATION; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java index ff7ba782..885e9792 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java @@ -1,41 +1,46 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; class GroupDeletePrompt extends InventoriesPrompt { - public GroupDeletePrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); + public GroupDeletePrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { + Message getPromptMessage(@NotNull final ConversationContext conversationContext) { final StringBuilder builder = new StringBuilder(); for (WorldGroup group : worldGroupManager.getGroups()) { - if (builder.length() == 0) { + if (builder.isEmpty()) { builder.append(ChatColor.WHITE); } else { builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); } builder.append(group.getName()); } - return messager.getMessage(Message.GROUP_DELETE_PROMPT, builder.toString()); + return Message.of(MVInvi18n.GROUP_DELETEPROMPT, replace("{groups}").with(builder.toString())); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = worldGroupManager.getGroup(s); + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + final WorldGroup group = worldGroupManager.getGroup(input); if (group == null) { - messager.normal(Message.ERROR_NO_GROUP, sender, s); + issuer.sendError(MVInvi18n.ERROR_NOGROUP, replace("{group}").with(input)); } else { worldGroupManager.removeGroup(group); - messager.normal(Message.GROUP_REMOVED, sender, s); + issuer.sendInfo(MVInvi18n.GROUP_REMOVED, replace("{group}").with(input)); } return Prompt.END_OF_CONVERSATION; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java index 0bae766e..63ab32ec 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java @@ -1,40 +1,45 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; class GroupEditPrompt extends InventoriesPrompt { - public GroupEditPrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); + public GroupEditPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { final StringBuilder builder = new StringBuilder(); for (WorldGroup group : worldGroupManager.getGroups()) { - if (builder.length() == 0) { + if (builder.isEmpty()) { builder.append(ChatColor.WHITE); } else { builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); } builder.append(group.getName()); } - return messager.getMessage(Message.GROUP_EDIT_PROMPT, builder.toString()); + return Message.of(MVInvi18n.GROUP_EDITPROMPT, replace("{groups}").with(builder.toString())); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = worldGroupManager.getGroup(s); + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + final WorldGroup group = worldGroupManager.getGroup(input); if (group == null) { - messager.normal(Message.ERROR_NO_GROUP, sender, s); + issuer.sendError(MVInvi18n.ERROR_NOGROUP, replace("{group}").with(input)); } else { - return new GroupModifyPrompt(plugin, sender, group); + return new GroupModifyPrompt(plugin, issuer, group); } return Prompt.END_OF_CONVERSATION; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java index 1308957c..347404b2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java @@ -1,35 +1,40 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; class GroupModifyPrompt extends InventoriesPrompt { protected final WorldGroup group; - public GroupModifyPrompt(final MultiverseInventories plugin, final CommandSender sender, + public GroupModifyPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer, final WorldGroup group) { - super(plugin, sender); + super(plugin, issuer); this.group = group; } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_MODIFY_PROMPT, group.getName()); + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + return Message.of(MVInvi18n.GROUP_MODIFYPROMPT, replace("{group}").with(group.getName())); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equalsIgnoreCase("worlds")) { - return new GroupWorldsPrompt(plugin, sender, group, this, false); - } else if (s.equalsIgnoreCase("shares")) { - return new GroupSharesPrompt(plugin, sender, group, this, false); + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if ("worlds".equalsIgnoreCase(input)) { + return new GroupWorldsPrompt(plugin, issuer, group, this, false); + } else if (input.equalsIgnoreCase("shares")) { + return new GroupSharesPrompt(plugin, issuer, group, this, false); } else { - messager.normal(Message.INVALID_PROMPT_OPTION, sender); + issuer.sendError(MVInvi18n.GROUP_INVALIDOPTION); return this; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java index b6ecb217..ff949c10 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -1,15 +1,19 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; class GroupSharesPrompt extends InventoriesPrompt { @@ -18,56 +22,60 @@ class GroupSharesPrompt extends InventoriesPrompt { protected final boolean isCreating; protected final Shares shares; - public GroupSharesPrompt(final MultiverseInventories plugin, final CommandSender sender, + public GroupSharesPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer, final WorldGroup group, final Prompt nextPrompt, final boolean creatingGroup) { - super(plugin, sender); + super(plugin, issuer); this.group = group; this.nextPrompt = nextPrompt; this.isCreating = creatingGroup; this.shares = Sharables.fromShares(group.getShares()); } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { final StringBuilder builder = new StringBuilder(); for (final Sharable sharable : shares) { - if (builder.length() == 0) { + if (builder.isEmpty()) { builder.append(ChatColor.WHITE); } else { builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); } builder.append(sharable.toString()); } - return messager.getMessage(Message.GROUP_SHARES_PROMPT, group.getName(), builder.toString()); + return Message.of(MVInvi18n.GROUP_SHARESPROMPT, + replace("{group}").with(group.getName()), + replace("{shares}").with(builder.toString())); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equals("@")) { + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if ("@".equals(input)) { group.getShares().clear(); group.getShares().addAll(this.shares); worldGroupManager.updateGroup(group); if (isCreating) { - messager.normal(Message.GROUP_CREATION_COMPLETE, sender); + issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE); } else { - messager.normal(Message.GROUP_UPDATED, sender); + issuer.sendInfo(MVInvi18n.GROUP_UPDATED); } - messager.normal(Message.INFO_GROUP, sender, group.getName()); - messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); - worldGroupManager.checkForConflicts(sender); + issuer.sendInfo(MVInvi18n.INFO_GROUP, replace("{group}").with(group.getName())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(group.getWorlds())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(group.getShares())); + worldGroupManager.checkForConflicts(issuer); return nextPrompt; } boolean negative = false; - Shares shares = Sharables.lookup(s.toLowerCase()); - if (shares == null && s.startsWith("-") && s.length() > 1) { + Shares shares = Sharables.lookup(input.toLowerCase()); + if (shares == null && input.startsWith("-") && input.length() > 1) { negative = true; - shares = Sharables.lookup(s.toLowerCase().substring(1)); + shares = Sharables.lookup(input.toLowerCase().substring(1)); } if (shares == null) { - messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); + issuer.sendError(MVInvi18n.ERROR_NOSHARESSPECIFIED); return this; } if (negative) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java index bcb1bcaf..ffff3d19 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java @@ -1,18 +1,22 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.HashSet; import java.util.Set; +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + class GroupWorldsPrompt extends InventoriesPrompt { protected final WorldGroup group; @@ -20,18 +24,19 @@ class GroupWorldsPrompt extends InventoriesPrompt { protected final boolean isCreating; protected final Set worlds; - public GroupWorldsPrompt(final MultiverseInventories plugin, final CommandSender sender, + public GroupWorldsPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer, final WorldGroup group, final Prompt nextPrompt, final boolean creatingGroup) { - super(plugin, sender); + super(plugin, issuer); this.group = group; this.nextPrompt = nextPrompt; this.isCreating = creatingGroup; this.worlds = new HashSet(group.getWorlds()); } + @NotNull @Override - public String getPromptText(final ConversationContext conversationContext) { + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { final StringBuilder builder = new StringBuilder(); for (final String world : worlds) { if (builder.length() == 0) { @@ -41,41 +46,46 @@ public String getPromptText(final ConversationContext conversationContext) { } builder.append(world); } - return messager.getMessage(Message.GROUP_WORLDS_PROMPT, group.getName(), builder.toString()); + return Message.of(MVInvi18n.GROUP_WORLDSPROMPT, + replace("{group}").with(group.getName()), + replace("{worlds}").with(builder.toString())); } @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equals("@")) { + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if (input.equals("@")) { if (worlds.isEmpty()) { - messager.normal(Message.GROUP_WORLDS_EMPTY, sender); + issuer.sendInfo(MVInvi18n.GROUP_WORLDSEMPTY); return this; } group.removeAllWorlds(false); group.addWorlds(worlds, false); if (!isCreating) { worldGroupManager.updateGroup(group); - messager.normal(Message.GROUP_UPDATED, sender); - messager.normal(Message.INFO_GROUP, sender, group.getName()); - messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); + issuer.sendInfo(MVInvi18n.GROUP_UPDATED); + issuer.sendInfo(MVInvi18n.INFO_GROUP, replace("{group}").with(group.getName())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(group.getWorlds())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(group.getShares())); } return nextPrompt; } boolean negative = false; - World world = Bukkit.getWorld(s); - if (world == null && s.startsWith("-") && s.length() > 1) { + World world = Bukkit.getWorld(input); + if (world == null && input.startsWith("-") && input.length() > 1) { negative = true; - world = Bukkit.getWorld(s.substring(1)); + world = Bukkit.getWorld(input.substring(1)); } if (world == null) { - messager.normal(Message.ERROR_NO_WORLD, sender, s); + issuer.sendError(MVInvi18n.ERROR_NOWORLD, replace("{world}").with(input)); return this; } if (negative) { if (!worlds.contains(world.getName())) { - messager.normal(Message.WORLD_NOT_IN_GROUP, sender, world.getName(), group.getName()); + issuer.sendError(MVInvi18n.REMOVEWORLD_WORLDNOTINGROUP, + replace("{world}").with(input), + replace("{group}").with(group.getName())); return this; } worlds.remove(world.getName()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java index b1a00c7e..fd4c0115 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java @@ -1,8 +1,12 @@ package org.mvplugins.multiverse.inventories.commands.prompts; +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.locale.PluginLocales; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.locale.Messager; -import org.bukkit.command.CommandSender; import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; @@ -10,19 +14,27 @@ abstract class InventoriesPrompt implements Prompt { protected final MultiverseInventories plugin; + protected final PluginLocales locales; protected final WorldGroupManager worldGroupManager; - protected final Messager messager; - protected final CommandSender sender; + protected final MVCommandIssuer issuer; - InventoriesPrompt(final MultiverseInventories plugin, final CommandSender sender) { + InventoriesPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { this.plugin = plugin; - this.messager = plugin.getMessager(); - this.sender = sender; + this.locales = plugin.getServiceLocator().getService(MVCommandManager.class).getLocales(); + this.issuer = issuer; this.worldGroupManager = this.plugin.getServiceLocator().getService(WorldGroupManager.class); } + abstract Message getPromptMessage(@NotNull ConversationContext conversationContext); + + @NotNull + @Override + public String getPromptText(@NotNull ConversationContext context) { + return ChatColor.translateAlternateColorCodes('&', getPromptMessage(context).formatted(locales, issuer)); + } + @Override - public boolean blocksForInput(final ConversationContext conversationContext) { + public boolean blocksForInput(@NotNull final ConversationContext conversationContext) { return true; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java deleted file mode 100644 index 40d78e89..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/LazyLocaleMessageProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -import org.jvnet.hk2.annotations.Contract; - -import java.util.Locale; -import java.util.Set; - -/** - *

Multiverse 2 LazyMessageProvider

- * - * This interface describes a Multiverse-MessageProvider that only loads locales when they're needed. - */ -@Contract -public interface LazyLocaleMessageProvider extends MessageProvider { - - /** - *

Loads a localization for a specified {@link Locale}.

- * - * If that localization is already loaded, this method will reload it. - * - * @param locale The desired {@link Locale}. - * @throws LocalizationLoadingException When an error occurs while trying to load the specified localization. - * @throws NoSuchLocalizationException When no localization was found for the desired locale. - */ - void loadLocale(Locale locale) throws NoSuchLocalizationException, // SUPPRESS CHECKSTYLE: Redundant - LocalizationLoadingException; // SUPPRESS CHECKSTYLE: Redundant - - /** - * Retrieves all loaded localizations. - * - * @return A {@link Set} of {@link Locale}s whose localizations are currently loaded. - */ - Set getLoadedLocales(); - - /** - * Detects if a localization is loaded for the specified {@link Locale}. - * - * @param locale The {@link Locale}. - * @return Whether a localization is loaded for the specified {@link Locale}. - */ - boolean isLocaleLoaded(Locale locale); - -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java deleted file mode 100644 index acd78fb4..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/LocalizationLoadingException.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -import java.io.IOException; -import java.util.Locale; - -/** - * Thrown when an error occurs while a localization is loaded. - */ -public class LocalizationLoadingException extends IOException { - private final Locale locale; - - public LocalizationLoadingException(Locale locale) { - this.locale = locale; - } - - public LocalizationLoadingException(String message, Locale locale) { - super(message); - this.locale = locale; - } - - public LocalizationLoadingException(Throwable cause, Locale locale) { - super(cause); - this.locale = locale; - } - - public LocalizationLoadingException(String message, Throwable cause, Locale locale) { - super(message, cause); - this.locale = locale; - } - - /** - * Retrieves the locale that was attempting to be loaded. - * - * @return The locale that was attempting to be loaded. - */ - public Locale getLocale() { - return locale; - } - - @Override - public String getMessage() { - return super.getMessage() + " (While trying to load localization for locale " + getLocale() + ")"; - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java deleted file mode 100644 index 42f6f27d..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/Message.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * An enum containing all messages/strings used by Multiverse. - */ -public enum Message { - // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc - TEST_STRING("a test-string from the enum"), - - // Generic Strings - GENERIC_SORRY("Sorry..."), - GENERIC_PAGE("Page"), - GENERIC_OF("of"), - GENERIC_UNLOADED("UNLOADED"), - GENERIC_PLUGIN_DISABLED("This plugin is Disabled!"), - GENERIC_ERROR("[Error]"), - GENERIC_SUCCESS("[Success]"), - GENERIC_INFO("[Info]"), - GENERIC_HELP("[Help]"), - GENERIC_COMMAND_NO_PERMISSION("You do not have permission to %1. (%2)"), - GENERIC_THE_CONSOLE("the console"), - GENERIC_NOT_LOGGED_IN("%1 is not logged on right now!"), - GENERIC_OFF("OFF"), - - // Errors - ERROR_CONFIG_LOAD("Encountered an error while loading the configuration file. Disabling..."), - ERROR_DATA_LOAD("Encountered an error while loading the data file. Disabling..."), - ERROR_NO_GROUP("&6There is no group with the name: &f%1"), - ERROR_NO_WORLD("&6There is no world with the name: &f%1"), - ERROR_NO_WORLD_PROFILE("&6There is no profile container for the world: &f%1"), - ERROR_PLUGIN_NOT_ENABLED("&f%1 &6is not enabled so you may not import data from it!"), - ERROR_UNSUPPORTED_IMPORT("&6Sorry, ''&f%1&6'' is not supported for importing."), - ERROR_NO_SHARES_SPECIFIED("&cYou did not specify any valid shares!"), - - // Group Conflicts - CONFLICT_RESULTS("Conflict found for groups: '%1' and '%2' because they both share: '%3' for the world(s): '%4'"), - CONFLICT_CHECKING("Checking for conflicts in groups..."), - CONFLICT_FOUND("Conflicts have been found... If these are not resolved, you may experience problems with your data."), - CONFLICT_NOT_FOUND("No group conflicts found!"), - - //// Commands - NON_CONVERSABLE("You are not allowed to access conversations (remote console?)"), - INVALID_PROMPT_OPTION("&cThat is not a valid option! Type &f##&c to stop working on groups."), - // Info Command - INFO_ZERO_ARG("You may only use the no argument version of this command in game!"), - INFO_WORLD("&b===[ Info for world: &6%1&b ]==="), - INFO_WORLD_INFO("&6Groups:&f %1"), - INFO_GROUP("&b===[ Info for group: &6%1&b ]==="), - INFO_GROUPS_INFO("&6Worlds:&f %1", "&bShares:&f %2"), - // Group Command - GROUP_COMMAND_PROMPT("&6What would you like to do? &fCreate&6, &fEdit &6or &fDelete&6. Enter &f##&6 at any time to cancel."), - GROUP_CREATE_PROMPT("&6Please name your new group: "), - GROUP_EDIT_PROMPT("&6Edit which group? %1"), - GROUP_DELETE_PROMPT("&6Delete which group? %1"), - GROUP_MODIFY_PROMPT("&6Which would you like to change for &e%1&6? &fWorlds &6or &fShares&6. Enter &f##&6 to finish."), - GROUP_WORLDS_PROMPT("&6Enter the name of a world to add to group &f%1&6 or enter &f@&6 to continue. To remove a world, precede the name with the minus symbol. (ex: &f-worldname&6). Current worlds: %2"), - GROUP_SHARES_PROMPT("&6Enter &fall&6 or a specific share to add to group &f%1&6 or enter &f@&6 to continue. To remove shares, precede the name with the minus symbol (ex: &f-inventory&6). Current shares: %2"), - GROUP_INVALID_NAME("&cThat name is not valid! May only contain letters, numbers, and underscores."), - GROUP_EXISTS("&cThat group already exists! (&f%1&c)"), - GROUP_REMOVED("&2Removed group: &f%1"), - GROUP_WORLDS_EMPTY("&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel."), - GROUP_CREATION_COMPLETE("&2You created a new group!"), - GROUP_UPDATED("&2Group has been updated!"), - // List Command - LIST_GROUPS("&b===[ Group List ]===", "&6Groups:&f %1"), - // Reload Command - RELOAD_COMPLETE("&b===[ Reload Complete! ]==="), - // AddWorld Command - WORLD_ADDED("&6World:&f %1 &6added to Group: &f%2"), - WORLD_ALREADY_EXISTS("&6World:&f %1 &6already part of Group: &f%2"), - // RemoveWorld Command - WORLD_REMOVED("&6World:&f %1 &6removed from Group: &f%2"), - WORLD_NOT_IN_GROUP("&6World:&f %1 &6is not part of Group: &f%2"), - // AddShares Command - NOW_SHARING("&6Group: &f%1 &6is now sharing: &f%2"), - // Spawn Command - TELEPORTING("Teleporting to this world group's spawn..."), - TELEPORTED_BY("You were teleported by: %1"), - TELEPORT_CONSOLE_ERROR("From the console, you must provide a PLAYER"), - // DebugCommand - INVALID_DEBUG("&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!)"), - DEBUG_SET("Debug mode is %1"), - // Toggle Command - NOW_USING_OPTIONAL("&f%1 &6will now be considered when player's change world."), - NOW_NOT_USING_OPTIONAL("&f%1 &6will no longer be considered when player's change world."), - NO_OPTIONAL_SHARES("&f%1 &6is not an optional share!"), - // Migrate Command - MIGRATE_FAILED("Failed to migrate data from %1 to %2! Check logs for error details."), - MIGRATE_SUCCESSFUL("Migrated data from %1 to %2!"); - - // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc - - private final List def; - - Message(String def, String... extra) { - this.def = new ArrayList(); - this.def.add(def); - this.def.addAll(Arrays.asList(extra)); - } - - /** - * @return This {@link Message}'s default-message - */ - public List getDefault() { - return def; - } - -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java deleted file mode 100644 index dc32aa43..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/MessageProvider.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -import org.jvnet.hk2.annotations.Contract; - -import java.util.List; -import java.util.Locale; - -/** - *

Multiverse 2 MessageProvider.

- * - * This interface describes a Multiverse-MessageProvider. - */ -@Contract -public interface MessageProvider { - /** - * The default locale. - */ - Locale DEFAULT_LOCALE = Locale.ENGLISH; - - /** - * Returns a message (as {@link String}) for the specified key (as {@link Message}). - * - * @param key The key - * @param args Args for String.format() - * @return The message - */ - String getMessage(Message key, Object... args); - - /** - * Returns a message (as {@link String}) in a specified {@link Locale} for the specified key (as {@link Message}). - * - * @param key The Key - * @param locale The {@link Locale} - * @param args Args for String.format() - * @return The message - */ - String getMessage(Message key, Locale locale, Object... args); - - /** - * Returns a message (as {@link List}) of Strings for the specified key (as {@link Message}). - * - * @param key The key - * @param args Args for String.format() - * @return The messages - */ - List getMessages(Message key, Object... args); - - /** - * Returns a message (as {@link List}) of Strings in a specified {@link Locale} for the specified key (as {@link Message}). - * - * @param key The key - * @param locale The {@link Locale} - * @param args Args for String.format() - * @return The messages - */ - List getMessages(Message key, Locale locale, Object... args); - - /** - * Returns the Locale this MessageProvider is currently using. - * - * @return The locale this MessageProvider is currently using. - */ - Locale getLocale(); - - /** - * Sets the locale for this MessageProvider. - * - * @param locale The new {@link Locale}. - */ - void setLocale(Locale locale); -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java deleted file mode 100644 index 5f6c2e0a..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/Messager.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -import org.bukkit.command.CommandSender; -import org.jvnet.hk2.annotations.Contract; - -import java.util.List; - -/** - * This interface describes a Messager which sends messages to CommandSenders. - */ -@Contract -public interface Messager extends MessageProvider { - - /** - * Sends a message to the specified player with the generic ERROR prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void bad(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with NO special prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void normal(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with the generic SUCCESS prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void good(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with the generic INFO prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void info(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with the generic HELP prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void help(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to a player that automatically takes words that are too long and puts them on a new line. - * - * @param player Player to send message to. - * @param message Message to send. - */ - void sendMessage(CommandSender player, String message); - - /** - * Sends a message to a player that automatically takes words that are too long and puts them on a new line. - * - * @param player Player to send message to. - * @param messages Messages to send. - */ - void sendMessages(CommandSender player, List messages); -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java deleted file mode 100644 index 01cfb473..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/Messaging.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -/** - * This interface is implemented by classes that use a {@link Messager}. - */ -public interface Messaging { - - /** - * @return The {@link Messager} used by the Plugin. - */ - Messager getMessager(); - - /** - * Sets the {@link Messager} used by the Plugin. - * - * @param messager The new {@link Messager}. Must not be null! - */ - void setMessager(Messager messager); -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java deleted file mode 100644 index f586b5ad..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/NoSuchLocalizationException.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.mvplugins.multiverse.inventories.locale; - -import java.util.Locale; - -/** - * Thrown when the requested localization is not found. - */ -public class NoSuchLocalizationException extends LocalizationLoadingException { - - public NoSuchLocalizationException(Locale locale) { - super(locale); - } - - public NoSuchLocalizationException(String message, Locale locale) { - super(message, locale); - } - - public NoSuchLocalizationException(String message, Throwable cause, Locale locale) { - super(message, cause, locale); - } - - public NoSuchLocalizationException(Throwable cause, Locale locale) { - super(cause, locale); - } - -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java deleted file mode 100644 index e13d4271..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/locale/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains Multiverse-Inventories localization features. - */ -package org.mvplugins.multiverse.inventories.locale; - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index f5cbf060..86e19f4e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -2,6 +2,8 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; @@ -10,10 +12,9 @@ import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.locale.Message; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.ArrayList; import java.util.Collections; @@ -21,7 +22,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; /** * Abstract implementation of GroupManager with no persistence of groups. @@ -32,16 +34,19 @@ abstract sealed class AbstractWorldGroupManager implements WorldGroupManager per static final String DEFAULT_GROUP_NAME = "default"; protected final Map groupNamesMap = new LinkedHashMap<>(); protected final MultiverseInventories plugin; + protected final MVCommandManager commandManager; protected final InventoriesConfig inventoriesConfig; protected final ProfileContainerStoreProvider profileContainerStoreProvider; protected final WorldManager worldManager; public AbstractWorldGroupManager( @NotNull MultiverseInventories plugin, + @NotNull MVCommandManager commandManager, @NotNull InventoriesConfig config, @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, @NotNull WorldManager worldManager) { this.plugin = plugin; + this.commandManager = commandManager; this.inventoriesConfig = config; this.profileContainerStoreProvider = profileContainerStoreProvider; this.worldManager = worldManager; @@ -213,35 +218,24 @@ public List checkGroups() { * {@inheritDoc} */ @Override - public void checkForConflicts(CommandSender sender) { - String message = plugin.getMessager().getMessage(Message.CONFLICT_CHECKING); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); + public void checkForConflicts(MVCommandIssuer issuer) { + if (issuer == null) { + issuer = commandManager.getCommandIssuer(Bukkit.getConsoleSender()); } - Logging.fine(message); + + issuer.sendInfo(MVInvi18n.CONFLICT_CHECKING); List conflicts = checkGroups(); for (GroupingConflict conflict : conflicts) { - message = plugin.getMessager().getMessage(Message.CONFLICT_RESULTS, - conflict.getFirstGroup().getName(), conflict.getSecondGroup().getName(), - conflict.getConflictingShares().toString(), conflict.getWorldsString()); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.info(message); + issuer.sendInfo(MVInvi18n.CONFLICT_RESULTS, + replace("{group1}").with(conflict.getFirstGroup().getName()), + replace("{group2}").with(conflict.getSecondGroup().getName()), + replace("{shares}").with(conflict.getConflictingShares().toString()), + replace("{worlds}").with(conflict.getWorldsString())); } if (!conflicts.isEmpty()) { - message = plugin.getMessager().getMessage(Message.CONFLICT_FOUND); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.info(message); + issuer.sendInfo(MVInvi18n.CONFLICT_FOUND); } else { - message = plugin.getMessager().getMessage(Message.CONFLICT_NOT_FOUND); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.fine(message); + issuer.sendInfo(MVInvi18n.CONFLICT_NOTFOUND); } } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index a716e3db..4188bebe 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -2,6 +2,7 @@ import org.bukkit.command.CommandSender; import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.external.vavr.control.Try; import java.util.List; @@ -104,10 +105,10 @@ public sealed interface WorldGroupManager permits AbstractWorldGroupManager { List checkGroups(); /** - * Runs a check for conflicts between groups and displays them to console and sender if not null. + * Runs a check for conflicts between groups and displays them to issuer or console. * - * @param sender The sender to relay information to. If null, info only displayed in console. + * @param issuer The issuer to relay information to. If null, info only displayed in console. */ - void checkForConflicts(CommandSender sender); + void checkForConflicts(MVCommandIssuer issuer); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index 67b7285f..17a355a7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -3,6 +3,7 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.collect.Lists; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -43,10 +44,11 @@ final class YamlWorldGroupManager extends AbstractWorldGroupManager { @Inject YamlWorldGroupManager( @NotNull MultiverseInventories plugin, + @NotNull MVCommandManager commandManager, @NotNull InventoriesConfig inventoriesConfig, @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, @NotNull WorldManager worldManager) { - super(plugin, inventoriesConfig, profileContainerStoreProvider, worldManager); + super(plugin, commandManager, inventoriesConfig, profileContainerStoreProvider, worldManager); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java new file mode 100644 index 00000000..bbe1a6a7 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java @@ -0,0 +1,115 @@ +package org.mvplugins.multiverse.inventories.util; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.locale.message.MessageReplacement; +import org.mvplugins.multiverse.external.acf.locales.MessageKey; +import org.mvplugins.multiverse.external.acf.locales.MessageKeyProvider; + +public enum MVInvi18n implements MessageKeyProvider { + TEST_STRING, + + GENERIC_SORRY, + GENERIC_PAGE, + GENERIC_OF, + GENERIC_UNLOADED, + GENERIC_PLUGINDISABLED, + GENERIC_ERROR, + GENERIC_SUCCESS, + GENERIC_INFO, + GENERIC_HELP, + GENERIC_COMMANDNOPERMISION, + GENERIC_THECONSOLE, + GENERIC_NOTLOGGEDIN, + GENERIC_OFF, + + ERROR_CONFIGLOAD, + ERROR_DATALOAD, + ERROR_NOGROUP, + ERROR_NOWORLD, + ERROR_NOWORLDPROFILE, + ERROR_NOSHARESSPECIFIED, + + CONFLICT_RESULTS, + CONFLICT_CHECKING, + CONFLICT_FOUND, + CONFLICT_NOTFOUND, + + INFO_WORLD, + INFO_WORLD_INFO, + INFO_GROUP, + INFO_GROUP_INFO, + INFO_GROUP_INFOSHARES, + INFO_GROUP_INFONEGATIVESHARES, + INFO_ZEROARG, + + LIST_GROUPS, + LIST_GROUPS_INFO, + + RELOAD_COMPLETE, + + ADDWORLD_WORLDADDED, + ADDWORLD_WORLDALREADYEXISTS, + + REMOVEWORLD_WORLDREMOVED, + REMOVEWORLD_WORLDNOTINGROUP, + + SHARES_NOWSHARING, + + SPAWN_TELEPORTING, + SPAWN_TELEPORTEDBY, + SPAWN_TELEPORTCONSOLEERROR, + + DEBUG_INVALIDDEBUG, + DEBUG_SET, + + TOGGLE_NOWUSINGOPTIONAL, + TOGGLE_NOWNOTUSINGOPTIONAL, + TOGGLE_NOOPTIONALSHARES, + + GROUP_COMMANDPROMPT, + GROUP_CREATEPROMPT, + GROUP_EDITPROMPT, + GROUP_DELETEPROMPT, + GROUP_MODIFYPROMPT, + GROUP_WORLDSPROMPT, + GROUP_SHARESPROMPT, + GROUP_INVALIDNAME, + GROUP_EXISTS, + GROUP_REMOVED, + GROUP_WORLDSEMPTY, + GROUP_CREATIONCOMPLETE, + GROUP_UPDATED, + GROUP_NONCONVERSABLE, + GROUP_INVALIDOPTION, + + IMPORT_PLUGINNOTENABLED, + IMPORT_UNSUPPORTEDPLUGIN, + IMPORT_CONFIRMPROMPT, + IMPORT_SUCCESS, + IMPORT_FAILED, + ; + + private final MessageKey key = MessageKey.of("mv-inventories." + this.name().replace('_', '.') + .toLowerCase()); + + /** + * {@inheritDoc} + */ + @Override + public MessageKey getMessageKey() { + return this.key; + } + + /** + * Creates a message with non-localized message fallback and replacements + * @param nonLocalizedMessage The non-localized message + * @param replacements The replacements + * + * @return A new localizable Message + */ + @NotNull + public Message bundle(@NotNull String nonLocalizedMessage, @NotNull MessageReplacement... replacements) { + return Message.of(this, nonLocalizedMessage, replacements); + } +} diff --git a/src/main/resources/de.yml b/src/main/resources/de.yml deleted file mode 100644 index bb2343c0..00000000 --- a/src/main/resources/de.yml +++ /dev/null @@ -1,110 +0,0 @@ -TEST_STRING: 'ein Test String von der Ressource' - -# Generic Strings -GENERIC_SORRY: 'Tut uns leid...' -GENERIC_PAGE: 'Seite' -GENERIC_OF: 'von' -GENERIC_UNLOADED: 'NICHT GELADEN' -GENERIC_PLUGIN_DISABLED: 'Dieses Plugin ist deaktiviert!' -GENERIC_ERROR: '[Fehler]' -GENERIC_SUCCESS: '[Erfolg]' -GENERIC_INFO: '[Info]' -GENERIC_HELP: '[Hilfe]' -GENERIC_COMMAND_NO_PERMISSION: 'Du hast nicht die Berechtigung für %1. (%2)' -GENERIC_THE_CONSOLE: 'die Konsole' -GENERIC_NOT_LOGGED_IN: '%1 wird gerade nicht geloggt!' -GENERIC_OFF: 'AUS' - -# Errors -ERROR_CONFIG_LOAD: 'Es ist ein Fehler beim Laden der Konfigurations-Datei aufgetreten. Deaktivieren...' -ERROR_DATA_LOAD: 'Es ist ein Fehler beim Laden der Daten-Datei aufgetreten. Deaktivieren...' -ERROR_NO_GROUP: '&6Es gibt keine Gruppe mit dem Namen: &f%1' -ERROR_NO_WORLD: '&6Es gibt keine Welt mit dem Namen: &f%1' -ERROR_NO_WORLD_PROFILE: '&6Es gibt kein Welt-Profil für die Welt: &f%1' -ERROR_PLUGIN_NOT_ENABLED: '&f%1 &6ist nicht aktiviert, deswegen kannst du keine Daten davon importieren!' -ERROR_UNSUPPORTED_IMPORT: '&6Tut uns leid, ''&f%1&6'' kann nicht importiert werden.' -ERROR_NO_SHARES_SPECIFIED: '&cDu hast keine gültigen Teilungen angegeben!' - -CONFLICT_RESULTS: 'Konflikt bei den Gruppen gefunden: ''%1'' und ''%2'' da beide Teilen: ''%3'' für die Welt(en): ''%4''' -CONFLICT_CHECKING: 'Überprüfe die Konfigurationen für die Gruppen...' -CONFLICT_FOUND: 'Konflikt gefunden... Wenn er nicht gelöst wird, werden die Daten eventuell korrupt.' -CONFLICT_NOT_FOUND: 'Keine Konflikte bei den Gruppen gefunden!' - -NON_CONVERSABLE: 'Du bist nicht berechtigt auf Konversationen zu zugreifen (remote Konsole?)' -INVALID_PROMPT_OPTION: '&cDas ist keine gültige Option! Schreibe &f##&c um das Arbeiten an Gruppen abzubrechen.' - -## Commands -# Info Command -INFO_WORLD: -- '&b===[ Info für Welt: &6%1&b ]===' -INFO_WORLD_INFO: -- '&6Gruppe:&f %1' -INFO_GROUP: -- '&b===[ Info für Gruppe: &6%1&b ]===' -INFO_GROUPS_INFO: -- '&6Welten:&f %1' -- '&bTeilungen:&f %2' -- '&bTrennungen:&f %3' - -# Group Command -GROUP_COMMAND_PROMPT: '&6Was möchtest du tun? &fErstellen&6, &fBearbeiten &6oder &fLöschen&6. Schreibe &f##&6 zum Abbrechen.' -GROUP_CREATE_PROMPT: '&6Bitte gib der Gruppe einen Namen: ' -GROUP_EDIT_PROMPT: '&6Welche Gruppe möchtest du bearbeiten? %1' -GROUP_DELETE_PROMPT: '&6Welche Gruppe möchtest du löschen? %1' -GROUP_MODIFY_PROMPT: '&6Was möchtest du ändern für &e%1&6? &fWelten &6oder &fTeilungen&6. Schreibe &f##&6 zum Abbrechen.' -GROUP_WORLDS_PROMPT: '6Gib den Namen der Welt ein, um ihn zur Gruppe hinzuzufügen &f%1&6 oder schreibe &f@&6 zum Fortfahren. Um eine Welt zu entfernen, schreibe vor den Namen ein Minus-Symbol (ex: &f-worldname&6). Aktuelle Welten: %2' -GROUP_SHARES_PROMPT: '&6Schreibe &fall&6 oder eine spezielle Teilung, um sie der Gruppe hinzuzufügen &f%1&6 oder schreibe &f@&6 zum Fortfahren. Um Teilungen zu entfernen, schreibe vor den Namen ein Minus-Symbol (ex: &f-inventory&6). Aktuelle Teilungen: %2' -GROUP_INVALID_NAME: '&cDieser Name ist nicht gültig! Er darf nur Buchstaben, Nummern, und unterstriche enthalten.' -GROUP_EXISTS: '&cDiese Gruppe existiert bereits! (&f%1&c)' -GROUP_REMOVED: '&2Gelöschte Gruppe: &f%1' -GROUP_WORLDS_EMPTY: '&cDu kannst keine Gruppe ohne Welten erstellen, bitte füge welche hinzu oder schreibe &f##&c zum Abbrechen.' -GROUP_CREATION_COMPLETE: '&2Du hast eine neue Gruppe erstellt!' -GROUP_UPDATED: '&2Die Gruppe wurde geupdated!' - -# List Command -LIST_GROUPS: -- '&b===[ Gruppen Liste ]===' -- '&6Gruppe:&f %1' - -# Reload Command -RELOAD_COMPLETE: -- '&b===[ Neu laden fertig! ]===' - -# Addworld Command -WORLD_ADDED: -- '&6Welt:&f %1 &6wurde hinzugefügt zur Gruppe: &f%2' -WORLD_ALREADY_EXISTS: -- '&6Welt:&f %1 &6ist bereits Teil der Gruppe: &f%2' - -# Removeworld Command -WORLD_REMOVED: -- '&6Welt:&f %1 &6wurde entfernt aus der Gruppe: &f%2' -WORLD_NOT_IN_GROUP: -- '&6Welt:&f %1 &6ist kein Teil der Gruppe: &f%2' - -# Add/remove shares command -NOW_SHARING: -- '&6Gruppe: &f%1 &6teilt nun: &f%2 &6und teilt NICHT: &f%3' - -# Spawn command -TELEPORTING: -- 'Teleportiere zum Spawn dieser Welt-Gruppe...' -TELEPORTED_BY: -- 'Du wurdest teleportiert von: %1' -TELEPORT_CONSOLE_ERROR: -- 'Von der Konsole, MUSS ein Spieler angegeben werden!' - -# Debug command -INVALID_DEBUG: -- '&fUngültige Debug Einstellung. Bitte verwende eine Nummer von 0-3. &b(3 sind viele Nachrichten!)' -DEBUG_SET: -- 'Der Debug Mode ist %1' - -# Toggle command -NOW_USING_OPTIONAL: '&f%1 &6wird nun in Betracht gezogen, wenn Spieler die Welt wechseln.' -NOW_NOT_USING_OPTIONAL: '&f%1 &6wird nun nicht mehr in Betracht gezogen, wenn Spieler die Welt wechseln.' -NO_OPTIONAL_SHARES: '&f%1 &6ist keine optionale Teilung!' - -# Migrate Command -MIGRATE_FAILED: 'Fehler beim übertragen der Daten von %1 zu %2! Siehe die logs für Details.' -MIGRATE_SUCCESSFUL: 'Die Daten von %1 wurden zu %2 übertragen!' diff --git a/src/main/resources/en.yml b/src/main/resources/en.yml deleted file mode 100644 index a32fcf0d..00000000 --- a/src/main/resources/en.yml +++ /dev/null @@ -1,89 +0,0 @@ -TEST_STRING: 'a test-string from the resource' - -# Generic Strings -GENERIC_SORRY: 'Sorry...' -GENERIC_PAGE: 'Page' -GENERIC_OF: 'of' -GENERIC_UNLOADED: 'UNLOADED' -GENERIC_PLUGIN_DISABLED: 'This plugin is Disabled!' -GENERIC_ERROR: '[Error]' -GENERIC_SUCCESS: '[Success]' -GENERIC_INFO: '[Info]' -GENERIC_HELP: '[Help]' -GENERIC_COMMAND_NO_PERMISSION: 'You do not have permission to %1. (%2)' -GENERIC_THE_CONSOLE: 'the console' -GENERIC_NOT_LOGGED_IN: '%1 is not logged on right now!' -GENERIC_OFF: 'OFF' - -# Errors -ERROR_CONFIG_LOAD: 'Encountered an error while loading the configuration file. Disabling...' -ERROR_DATA_LOAD: 'Encountered an error while loading the data file. Disabling...' -ERROR_NO_GROUP: '&6There is no group with the name: &f%1' -ERROR_NO_WORLD: '&6There is no world with the name: &f%1' -ERROR_NO_WORLD_PROFILE: '&6There is no world profile for the world: &f%1' -ERROR_PLUGIN_NOT_ENABLED: '&f%1 &6is not enabled so you may not import data from it!' -ERROR_UNSUPPORTED_IMPORT: '&6Sorry, ''&f%1&6'' is not supported for importing.' -ERROR_NO_SHARES_SPECIFIED: '&cYou did not specify any valid shares!' - -CONFLICT_RESULTS: 'Conflict found for groups: ''%1'' and ''%2'' because they both share: ''%3'' for the world(s): ''%4''' -CONFLICT_CHECKING: 'Checking for conflicts in groups...' -CONFLICT_FOUND: 'Conflicts have been found... If these are not resolved, you may experience problems with your data.' -CONFLICT_NOT_FOUND: 'No group conflicts found!' - - -## Commands -# Info Command -INFO_WORLD: -- '&b===[ Info for world: &6%1&b ]===' -INFO_WORLD_INFO: -- '&6Groups:&f %1' -INFO_GROUP: -- '&b===[ Info for group: &6%1&b ]===' -INFO_GROUP_INFO: -- '&6Worlds:&f %1' -- '&bShares:&f %2' -- '&bNegative Shares:&f %3' - -# List Command -LIST_GROUPS: -- '&b===[ Group List ]===' -- '&6Groups:&f %1' - -# Reload Command -RELOAD_COMPLETE: -- '&b===[ Reload Complete! ]===' - -# Addworld Command -WORLD_ADDED: -- '&6World:&f %1 &6added to Group: &f%2' -WORLD_ALREADY_EXISTS: -- '&6World:&f %1 &6already part of Group: &f%2' - -# Removeworld Command -WORLD_REMOVED: -- '&6World:&f %1 &6removed from Group: &f%2' -WORLD_NOT_IN_GROUP: -- '&6World:&f %1 &6is not part of Group: &f%2' - -# Add/remove shares command -NOW_SHARING: -- '&6Group: &f%1 &6is now sharing: &f%2 &6and NOT sharing: &f%3' - -# Spawn command -TELEPORTING: -- 'Teleporting to this world group''s spawn...' -TELEPORTED_BY: -- 'You were teleported by: %1' -TELEPORT_CONSOLE_ERROR: -- 'From the console, you must provide a PLAYER' - -# Debug command -INVALID_DEBUG: -- '&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!)' -DEBUG_SET: -- 'Debug mode is %1' - -# Toggle command -NOW_USING_OPTIONAL: '&f%1 &6will now be considered when player''s change world.' -NOW_NOT_USING_OPTIONAL: '&f%1 &6will no longer be considered when player''s change world.' -NO_OPTIONAL_SHARES: '&f%1 &6is not an optional share!' \ No newline at end of file diff --git a/src/main/resources/multiverse-inventories_en.properties b/src/main/resources/multiverse-inventories_en.properties index c211aec7..d1ae0b38 100644 --- a/src/main/resources/multiverse-inventories_en.properties +++ b/src/main/resources/multiverse-inventories_en.properties @@ -10,64 +10,87 @@ mv-inventories.generic.error=[Error] mv-inventories.generic.success=[Success] mv-inventories.generic.info=[Info] mv-inventories.generic.help=[Help] -mv-inventories.generic.commandnopermission=You do not have permission to %1. (%2) -mv-inventories.generic.the-console=the console -mv-inventories.generic.notloggedin=%1 is not logged on right now! +mv-inventories.generic.commandnopermission=You do not have permission to {command}. ({permission}) +mv-inventories.generic.theconsole=the console +mv-inventories.generic.notloggedin={player} is not logged on right now! mv-inventories.generic.off=OFF # Errors mv-inventories.error.configload=Encountered an error while loading the configuration file. Disabling... mv-inventories.error.dataload=Encountered an error while loading the data file. Disabling... -mv-inventories.error.nogroup=&6There is no group with the name: &f%1 -mv-inventories.error.noworld=&6There is no world with the name: &f%1 -mv-inventories.error.noworldprofile=&6There is no world profile for the world: &f%1 -mv-inventories.error.pluginnotenabled=&f%1 &6is not enabled so you may not import data from it! -mv-inventories.error.unsupportedimport=&6Sorry, ''&f%1&6'' is not supported for importing. +mv-inventories.error.nogroup=&6There is no group with the name: &f{group} +mv-inventories.error.noworld=&6There is no world with the name: &f{world} +mv-inventories.error.noworldprofile=&6There is no world profile for the world: &f{world} mv-inventories.error.nosharesspecified=&cYou did not specify any valid shares! # Conflicts -mv-inventories.conflict.results=Conflict found for groups: ''%1'' and ''%2'' because they both share: ''%3'' for the world(s): ''%4'' +mv-inventories.conflict.results=Conflict found for groups: ''{group1}'' and ''{group2}'' because they both share: ''{shares}'' for the world(s): ''{worlds}'' mv-inventories.conflict.checking=Checking for conflicts in groups... mv-inventories.conflict.found=Conflicts have been found... If these are not resolved, you may experience problems with your data. mv-inventories.conflict.notfound=No group conflicts found! # Commands ## Info Command -mv-inventories.info.world=&b===[ Info for world: &6%1&b ]=== -mv-inventories.info.world.info=&6Groups:&f %1 -mv-inventories.info.group=&b===[ Info for group: &6%1&b ]=== -mv-inventories.info.group.info=&6Worlds:&f %1 -mv-inventories.info.group.infoshares=&bShares:&f %2 -mv-inventories.info.group.infonegativeshares=&bNegative Shares:&f %3 +mv-inventories.info.world=&b===[ Info for world: &6{world}&b ]=== +mv-inventories.info.world.info=&6Groups:&f {groups} +mv-inventories.info.group=&b===[ Info for group: &6{group}&b ]=== +mv-inventories.info.group.info=&6Worlds:&f {worlds} +mv-inventories.info.group.infoshares=&bShares:&f {shares} +mv-inventories.info.group.infonegativeshares=&bNegative Shares:&f {shares} +mv-inventories.info.zeroarg=You may only use the no argument version of this command in game! # List Command mv-inventories.list.groups=&b===[ Group List ]=== -mv-inventories.list.groups-info=&6Groups:&f %1 +mv-inventories.list.groups.info=&6Groups:&f {groups} # Reload Command mv-inventories.reload.complete=&b===[ Reload Complete! ]=== # Addworld Command -mv-inventories.addworld.worldadded=&6World:&f %1 &6added to Group: &f%2 -mv-inventories.addworld.worldalreadyexists=&6World:&f %1 &6already part of Group: &f%2 +mv-inventories.addworld.worldadded=&6World:&f {world} &6added to Group: &f{group} +mv-inventories.addworld.worldalreadyexists=&6World:&f {world} &6already part of Group: &f{group} # Removeworld Command -mv-inventories.removeworld.worldremoved=&6World:&f %1 &6removed from Group: &f%2 -mv-inventories.removeworld.worldnotingroup=&6World:&f %1 &6is not part of Group: &f%2 +mv-inventories.removeworld.worldremoved=&6World:&f {world} &6removed from Group: &f{group} +mv-inventories.removeworld.worldnotingroup=&6World:&f {world} &6is not part of Group: &f{group} # Add/remove shares command -mv-inventories.shares.now-sharing=&6Group: &f%1 &6is now sharing: &f%2 &6and NOT sharing: &f%3 +mv-inventories.shares.nowsharing=&6Group: &f{group} &6is now sharing: &f{shares} &6and NOT sharing: &f{negativeshares} # Spawn command mv-inventories.spawn.teleporting=Teleporting to this world group's spawn... -mv-inventories.spawn.teleportedby=You were teleported by: %1 +mv-inventories.spawn.teleportedby=You were teleported by: {player} mv-inventories.spawn.teleportconsoleerror=From the console, you must provide a PLAYER # Debug command -mv-inventories.debug.invaliddebug=&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!) -mv-inventories.debug.set=Debug mode is %1 +mv-inventories.debug.invaliddebug=&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!) +mv-inventories.debug.set=Debug mode is set to {mode}. # Toggle command -mv-inventories.toggle.nowusingoptional=&f%1 &6will now be considered when player's change world. -mv-inventories.toggle.nownotusingoptional=&f%1 &6will no longer be considered when player's change world. -mv-inventories.toggle.nooptionalshares=&f%1 &6is not an optional share! +mv-inventories.toggle.nowusingoptional=&f{share} &6will now be considered when player's change world. +mv-inventories.toggle.nownotusingoptional=&f{share} &6will no longer be considered when player's change world. +mv-inventories.toggle.nooptionalshares=&f{share} &6is not an optional share! + +# Group command +mv-inventories.group.commandprompt=&6What would you like to do? &fCreate&6, &fEdit &6or &fDelete&6. Enter &f##&6 at any time to cancel. +mv-inventories.group.createprompt=&6Please name your new group: +mv-inventories.group.editprompt=&6Edit which group? {groups} +mv-inventories.group.deleteprompt=&6Delete which group? {groups} +mv-inventories.group.modifyprompt=&6Which would you like to change for &e{group}&6? &fWorlds &6or &fShares&6. Enter &f##&6 to finish. +mv-inventories.group.worldsprompt=&6Enter the name of a world to add to group &f{group}&6 or enter &f@&6 to continue. To remove a world, precede the name with the minus symbol. (ex: &f-worldname&6). Current worlds: {worlds} +mv-inventories.group.sharesprompt=&6Enter &fall&6 or a specific share to add to group &f{group}&6 or enter &f@&6 to continue. To remove shares, precede the name with the minus symbol (ex: &f-inventory&6). Current shares: {shares} +mv-inventories.group.invalidname=&cThat name is not valid! May only contain letters, numbers, and underscores. +mv-inventories.group.exists=&cThat group already exists! (&f{group}&c) +mv-inventories.group.removed=&2Removed group: &f{group} +mv-inventories.group.worldsempty=&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel. +mv-inventories.group.creationcomplete=&2You created a new group! +mv-inventories.group.updated=&2Group has been updated! +mv-inventories.group.nonconversable=You are not allowed to access conversations (remote console?) +mv-inventories.group.invalidoption=&cThat is not a valid option! Type &f##&c to stop working on groups. + +# Import command +mv-inventories.import.pluginnotenabled=&f{plugin} &6is not enabled so you may not import data from it! +mv-inventories.import.unsupportedplugin=&6Sorry, ''&f{plugin}&6'' is not supported for importing. +mv-inventories.import.confirmprompt=&6Are you sure you want to import data from &f{plugin}&6? This will override existing Multiverse-Inventories playerdata!!! +mv-inventories.import.success=&2Successfully imported data from &f{plugin}! +mv-inventories.import.failed=Failed to import data from &f{plugin}! diff --git a/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt new file mode 100644 index 00000000..2405130a --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt @@ -0,0 +1,30 @@ +package org.mvplugins.multiverse.inventories + +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.core.commandtools.MVCommandManager +import org.mvplugins.multiverse.core.locale.message.Message +import org.mvplugins.multiverse.inventories.util.MVInvi18n +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class LocaleTest : TestWithMockBukkit() { + + private lateinit var player: PlayerMock + private lateinit var commandManager: MVCommandManager + + @BeforeTest + fun setUp() { + commandManager = serviceLocator.getService(MVCommandManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("Locales is not available as a service") } + player = server.addPlayer("Benji_0224") + } + + @Test + fun `Test locale message`() { + assertEquals( + "a test-string from the resource", + Message.of(MVInvi18n.TEST_STRING).formatted(commandManager.locales, commandManager.getCommandIssuer(player)) + ) + } +} From 55b9325bbc68d0419fb1b17fcb9b1422caa598cd Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 9 Feb 2025 20:16:27 +0800 Subject: [PATCH 067/180] Some minor code cleanup --- .../inventories/InventoriesDupingPatch.java | 8 +- .../profile/group/YamlWorldGroupManager.java | 6 +- .../share/InventorySerializer.java | 21 +- .../inventories/share/LocationSerializer.java | 17 +- .../share/PotionEffectSerializer.java | 6 +- .../inventories/share/SharableEntry.java | 20 -- .../inventories/util/DataStrings.java | 163 +-------------- .../multiverse/inventories/util/Font.java | 196 ------------------ .../inventories/util/LegacyParsers.java | 171 +++++++++++++++ .../inventories/util/PlayerStats.java | 2 +- 10 files changed, 200 insertions(+), 410 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/Font.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java index 080ee1b6..86034fa5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java @@ -78,7 +78,7 @@ public void onCreativeSlotChange(InventoryCreativeEvent event) { return; // Saves performance for most common case } InventoryHolder holder = event.getInventory().getHolder(); - if (holder instanceof Player && timeouts.containsKey(((Player) holder).getUniqueId())) { + if (holder instanceof Player player && timeouts.containsKey(player.getUniqueId())) { event.setResult(Result.DENY); } } @@ -115,13 +115,13 @@ public void run() { Iterator> iter = timeouts.entrySet().iterator(); while (iter.hasNext()) { Map.Entry e = iter.next(); - int value = e.getValue().intValue() - 1; + int value = e.getValue() - 1; if (value > 0) { - e.setValue(Integer.valueOf(value)); + e.setValue(value); } else { iter.remove(); } } } } -} \ No newline at end of file +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index 17a355a7..b91e099c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -162,13 +162,13 @@ private WorldGroup deserializeGroup(final String name, final Map profile.addWorld(worldNameObj.toString(), false); World world = Bukkit.getWorld(worldNameObj.toString()); if (world == null) { - if (builder.length() != 0) { + if (!builder.isEmpty()) { builder.append(", "); } - builder.append(worldNameObj.toString()); + builder.append(worldNameObj); } } - if (builder.length() > 0) { + if (!builder.isEmpty()) { Logging.config("The following worlds for group '%s' are not loaded: %s", name, builder.toString()); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index 67f8efad..513274b2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -40,19 +40,14 @@ private Map mapSlots(ItemStack[] itemStacks) { } private ItemStack[] unmapSlots(Object obj) { - ItemStack[] result = new ItemStack[inventorySize]; - if (obj instanceof Map) { - Map invMap = (Map) obj; - for (int i = 0; i < result.length; i++) { - Object value = invMap.get(Integer.toString(i)); - if (value != null && value instanceof ItemStack) { - result[i] = (ItemStack) value; - } else { - result[i] = new ItemStack(Material.AIR); - } - } - return result; + ItemStack[] inventory = new ItemStack[inventorySize]; + if (!(obj instanceof Map invMap)) { + return MinecraftTools.fillWithAir(inventory); + } + for (int i = 0; i < inventory.length; i++) { + Object value = invMap.get(Integer.toString(i)); + inventory[i] = value instanceof ItemStack item ? item : new ItemStack(Material.AIR); } - return MinecraftTools.fillWithAir(result); + return inventory; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java index 0a7db940..430e13ef 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.share; -import org.mvplugins.multiverse.inventories.util.DataStrings; import org.bukkit.Location; +import org.mvplugins.multiverse.inventories.util.LegacyParsers; import java.util.Map; @@ -18,15 +18,14 @@ final class LocationSerializer implements SharableSerializer { public Location deserialize(Object obj) { if (obj instanceof Location) { return (Location) obj; - } else if (obj instanceof String) { - return DataStrings.parseLocation(obj.toString()); - } else { - if (obj instanceof Map) { - return DataStrings.parseLocation((Map) obj); - } else { - return DataStrings.parseLocation(obj.toString()); - } } + if (obj instanceof String) { + return LegacyParsers.parseLocation(obj.toString()); + } + if (obj instanceof Map) { + return LegacyParsers.parseLocation((Map) obj); + } + return LegacyParsers.parseLocation(obj.toString()); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java index 2da4fcfb..8be1eae4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.share; -import org.mvplugins.multiverse.inventories.util.DataStrings; import org.bukkit.potion.PotionEffect; +import org.mvplugins.multiverse.inventories.util.LegacyParsers; import java.util.ArrayList; import java.util.Arrays; @@ -23,9 +23,9 @@ public PotionEffect[] deserialize(Object obj) { resultList.add((PotionEffect) o); } } - return resultList.toArray(new PotionEffect[resultList.size()]); + return resultList.toArray(new PotionEffect[0]); } else { - return DataStrings.parsePotionEffects(obj.toString()); + return LegacyParsers.parsePotionEffects(obj.toString()); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java deleted file mode 100644 index 9f13d1a6..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableEntry.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.mvplugins.multiverse.inventories.share; - -public final class SharableEntry { - - private final Sharable sharable; - private final T value; - - public SharableEntry(Sharable sharable, T initialValue) { - this.sharable = sharable; - this.value = initialValue; - } - - public Sharable getSharable() { - return sharable; - } - - public T getValue() { - return value; - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java index 2d72b1ac..8adeeb99 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java @@ -1,24 +1,9 @@ package org.mvplugins.multiverse.inventories.util; -import com.dumptruckman.minecraft.util.Logging; -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - /** * This class handles the formatting of strings for data i/o. */ -public class DataStrings { +public final class DataStrings { /** * Delimiter to separate a key and it's value. @@ -154,150 +139,6 @@ public class DataStrings { public static final String POTION_AMPLIFIER = "pa"; private DataStrings() { - throw new AssertionError(); - } - - /** - * @param locString Parses this string and creates Location. - * @return New location object or null if no location could be created. - * @deprecated Locations do not use special handling because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. - */ - @Deprecated - public static Location parseLocation(String locString) { - if (locString.isEmpty()) { - return null; - } - JSONObject jsonLoc; - try { - jsonLoc = (JSONObject) JSON_PARSER.parse(locString); - } catch (ParseException | ClassCastException e) { - Logging.warning("Could not parse location! " + e.getMessage()); - return null; - } - return parseLocMap(jsonLoc); - } - - /** - * @deprecated Locations do not use special handling because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. - */ - @Deprecated - public static Location parseLocation(Map locMap) { - return parseLocMap(locMap); - } - - @Deprecated - private static Location parseLocMap(Map locMap) { - World world = null; - double x = 0; - double y = 0; - double z = 0; - float pitch = 0; - float yaw = 0; - if (locMap.containsKey(LOCATION_WORLD)) { - world = Bukkit.getWorld(locMap.get(LOCATION_WORLD).toString()); - } - if (locMap.containsKey(LOCATION_X)) { - Object value = locMap.get(LOCATION_X); - if (value instanceof Number) { - x = ((Number) value).doubleValue(); - } - } - if (locMap.containsKey(LOCATION_Y)) { - Object value = locMap.get(LOCATION_Y); - if (value instanceof Number) { - y = ((Number) value).doubleValue(); - } - } - if (locMap.containsKey(LOCATION_Z)) { - Object value = locMap.get(LOCATION_Z); - if (value instanceof Number) { - z = ((Number) value).doubleValue(); - } - } - if (locMap.containsKey(LOCATION_PITCH)) { - Object value = locMap.get(LOCATION_PITCH); - if (value instanceof Number) { - pitch = ((Number) value).floatValue(); - } - } - if (locMap.containsKey(LOCATION_YAW)) { - Object value = locMap.get(LOCATION_YAW); - if (value instanceof Number) { - yaw = ((Number) value).floatValue(); - } - } - if (world == null) { - return null; - } - return new Location(world, x, y, z, yaw, pitch); - } - - /** - * @param potionsString A player's potion effects in string form to be parsed into - * {@link java.util.Collection}<{@link org.bukkit.potion.PotionEffect}>. - * @return a collection of potion effects parsed from potionsString. - * @deprecated PotionEffect do not use special handling because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. - */ - @Deprecated - public static PotionEffect[] parsePotionEffects(String potionsString) { - List potionEffectList = new LinkedList(); - if (potionsString.isEmpty()) { - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } - JSONArray jsonPotions; - try { - jsonPotions = (JSONArray) JSON_PARSER.parse(potionsString); - } catch (ParseException e) { - Logging.warning("Could not parse potions! " + e.getMessage()); - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } catch (ClassCastException e) { - Logging.warning("Could not parse potions! " + e.getMessage()); - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } - for (Object obj : jsonPotions) { - if (obj instanceof JSONObject) { - JSONObject jsonPotion = (JSONObject) obj; - int type = -1; - int duration = -1; - int amplifier = -1; - if (jsonPotion.containsKey(POTION_TYPE)) { - Object value = jsonPotion.get(POTION_TYPE); - if (value instanceof Number) { - type = ((Number) value).intValue(); - } - } - if (jsonPotion.containsKey(POTION_AMPLIFIER)) { - Object value = jsonPotion.get(POTION_AMPLIFIER); - if (value instanceof Number) { - amplifier = ((Number) value).intValue(); - } - } - if (jsonPotion.containsKey(POTION_DURATION)) { - Object value = jsonPotion.get(POTION_DURATION); - if (value instanceof Number) { - duration = ((Number) value).intValue(); - } - } - if (type == -1 || duration == -1 || amplifier == -1) { - Logging.fine("Could not parse potion effect string: " + obj); - } else { - PotionEffectType pType = PotionEffectType.getById(type); - if (pType == null) { - Logging.warning("Could not parse potion effect type: " + type); - continue; - } - potionEffectList.add(new PotionEffect(pType, duration, amplifier)); - } - } else { - Logging.warning("Could not parse potion effect: " + obj); - } - } - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + throw new IllegalStateException(); } - - private static final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/Font.java b/src/main/java/org/mvplugins/multiverse/inventories/util/Font.java deleted file mode 100644 index 494b211d..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/Font.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.mvplugins.multiverse.inventories.util; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * Class for font functions including font width specifications. - */ -public class Font { - - /** - * Provides the char for a Section symbol. - */ - public static final char SECTION_SYMBOL = (char) 167; - - private static final int LINE_LENGTH = 318; - private static final HashMap FONT_WIDTH; - - static { - FONT_WIDTH = new HashMap(); - /* - * Widths is in pixels - * Got them from fontWidths.txt uploaded to the Bukkit forum by Edward Hand - * http://forums.bukkit.org/threads/formatting-module-output-text-into-columns.8481/ - */ - // BEGIN CHECKSTYLE-SUPPRESSION: MagicNumberCheck - FONT_WIDTH.put(" ", 4); - FONT_WIDTH.put("!", 2); - FONT_WIDTH.put("\"", 5); - FONT_WIDTH.put("#", 6); - FONT_WIDTH.put("$", 6); - FONT_WIDTH.put("%", 6); - FONT_WIDTH.put("&", 6); - FONT_WIDTH.put("'", 3); - FONT_WIDTH.put("(", 5); - FONT_WIDTH.put(")", 5); - FONT_WIDTH.put("*", 5); - FONT_WIDTH.put("+", 6); - FONT_WIDTH.put(",", 2); - FONT_WIDTH.put("-", 6); - FONT_WIDTH.put(".", 2); - FONT_WIDTH.put("/", 6); - FONT_WIDTH.put("0", 6); - FONT_WIDTH.put("1", 6); - FONT_WIDTH.put("2", 6); - FONT_WIDTH.put("3", 6); - FONT_WIDTH.put("4", 6); - FONT_WIDTH.put("5", 6); - FONT_WIDTH.put("6", 6); - FONT_WIDTH.put("7", 6); - FONT_WIDTH.put("8", 6); - FONT_WIDTH.put("9", 6); - FONT_WIDTH.put(":", 2); - FONT_WIDTH.put(";", 2); - FONT_WIDTH.put("<", 5); - FONT_WIDTH.put("=", 6); - FONT_WIDTH.put(">", 5); - FONT_WIDTH.put("?", 6); - FONT_WIDTH.put("@", 7); - FONT_WIDTH.put("A", 6); - FONT_WIDTH.put("B", 6); - FONT_WIDTH.put("C", 6); - FONT_WIDTH.put("D", 6); - FONT_WIDTH.put("E", 6); - FONT_WIDTH.put("F", 6); - FONT_WIDTH.put("G", 6); - FONT_WIDTH.put("H", 6); - FONT_WIDTH.put("I", 4); - FONT_WIDTH.put("J", 6); - FONT_WIDTH.put("K", 6); - FONT_WIDTH.put("L", 6); - FONT_WIDTH.put("M", 6); - FONT_WIDTH.put("N", 6); - FONT_WIDTH.put("O", 6); - FONT_WIDTH.put("P", 6); - FONT_WIDTH.put("Q", 6); - FONT_WIDTH.put("R", 6); - FONT_WIDTH.put("S", 6); - FONT_WIDTH.put("T", 6); - FONT_WIDTH.put("U", 6); - FONT_WIDTH.put("V", 6); - FONT_WIDTH.put("W", 6); - FONT_WIDTH.put("X", 6); - FONT_WIDTH.put("Y", 6); - FONT_WIDTH.put("Z", 6); - FONT_WIDTH.put("_", 6); - FONT_WIDTH.put("'", 3); - FONT_WIDTH.put("a", 6); - FONT_WIDTH.put("b", 6); - FONT_WIDTH.put("c", 6); - FONT_WIDTH.put("d", 6); - FONT_WIDTH.put("e", 6); - FONT_WIDTH.put("f", 5); - FONT_WIDTH.put("g", 6); - FONT_WIDTH.put("h", 6); - FONT_WIDTH.put("i", 2); - FONT_WIDTH.put("j", 6); - FONT_WIDTH.put("k", 5); - FONT_WIDTH.put("l", 3); - FONT_WIDTH.put("m", 6); - FONT_WIDTH.put("n", 6); - FONT_WIDTH.put("o", 6); - FONT_WIDTH.put("p", 6); - FONT_WIDTH.put("q", 6); - FONT_WIDTH.put("r", 6); - FONT_WIDTH.put("s", 6); - FONT_WIDTH.put("t", 4); - FONT_WIDTH.put("u", 6); - FONT_WIDTH.put("v", 6); - FONT_WIDTH.put("w", 6); - FONT_WIDTH.put("x", 6); - FONT_WIDTH.put("y", 6); - FONT_WIDTH.put("z", 6); - // END CHECKSTYLE-SUPPRESSION: MagicNumberCheck - } - - private Font() { } - - /** - * Get width of string in pixels. - * - * @param text String. - * @return Length of string in pixels. - */ - public static int stringWidth(String text) { - if (FONT_WIDTH.isEmpty()) { - return 0; - } - char[] chars = text.toCharArray(); - int width = 0; - int spacepos = 0; - for (int i = 0; i < chars.length; i++) { - if (FONT_WIDTH.containsKey(String.valueOf(chars[i]))) { - width += FONT_WIDTH.get(String.valueOf(chars[i])); - } else if (chars[i] == SECTION_SYMBOL) { - i++; - } - } - return width; - } - - /** - * Get a List of Strings where none exceed the maximum line length. - * - * @param text String - * @return List of Strings - */ - public static List splitString(String text) { - List split = new ArrayList(); - if (FONT_WIDTH.isEmpty()) { - split.add(text); - return split; - } - char[] chars = text.toCharArray(); - int width = 0; - int lastspaceindex = 0; - int lastlineindex = 0; - String lastcolor = null; - boolean colorfoundthisline = false; - for (int i = 0; i < chars.length; i++) { - if (FONT_WIDTH.containsKey(String.valueOf(chars[i]))) { - width += FONT_WIDTH.get(String.valueOf(chars[i])); - } else if (chars[i] == SECTION_SYMBOL) { - i++; - lastcolor = Character.toString(chars[i]); - colorfoundthisline = true; - } - if ((width > LINE_LENGTH) && (lastspaceindex != 0)) { - if (lastcolor != null && !colorfoundthisline) { - split.add(Character.toString(SECTION_SYMBOL) + lastcolor - + text.substring(lastlineindex, lastspaceindex)); - } else { - split.add(text.substring(lastlineindex, lastspaceindex)); - } - colorfoundthisline = false; - lastlineindex = lastspaceindex; - i = lastspaceindex; - width = 0; - } - if (String.valueOf(chars[i]).equals(" ")) { - lastspaceindex = i; - } - } - if (!text.substring(lastlineindex).isEmpty()) - if (lastcolor != null && !colorfoundthisline) { - split.add(Character.toString(SECTION_SYMBOL) + lastcolor - + text.substring(lastlineindex)); - } else { - split.add(text.substring(lastlineindex)); - } - - return split; - } -} - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java b/src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java new file mode 100644 index 00000000..466b1aa1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java @@ -0,0 +1,171 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Exists for backward compatibility with older versions of Multiverse-Inventories. + */ +@Deprecated +public final class LegacyParsers { + + private static final JSONParser JSON_PARSER = new JSONParser( + JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); + + /** + * @param locString Parses this string and creates Location. + * @return New location object or null if no location could be created. + * @deprecated Locations do not use special handling because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. + */ + @Deprecated + public static Location parseLocation(String locString) { + if (locString.isEmpty()) { + return null; + } + JSONObject jsonLoc; + try { + jsonLoc = (JSONObject) JSON_PARSER.parse(locString); + } catch (ParseException | ClassCastException e) { + Logging.warning("Could not parse location! " + e.getMessage()); + return null; + } + return parseLocMap(jsonLoc); + } + + /** + * @deprecated Locations do not use special handling because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. + */ + @Deprecated + public static Location parseLocation(Map locMap) { + return parseLocMap(locMap); + } + + @Deprecated + private static Location parseLocMap(Map locMap) { + World world = null; + double x = 0; + double y = 0; + double z = 0; + float pitch = 0; + float yaw = 0; + if (locMap.containsKey(DataStrings.LOCATION_WORLD)) { + world = Bukkit.getWorld(locMap.get(DataStrings.LOCATION_WORLD).toString()); + } + if (locMap.containsKey(DataStrings.LOCATION_X)) { + Object value = locMap.get(DataStrings.LOCATION_X); + if (value instanceof Number) { + x = ((Number) value).doubleValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_Y)) { + Object value = locMap.get(DataStrings.LOCATION_Y); + if (value instanceof Number) { + y = ((Number) value).doubleValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_Z)) { + Object value = locMap.get(DataStrings.LOCATION_Z); + if (value instanceof Number) { + z = ((Number) value).doubleValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_PITCH)) { + Object value = locMap.get(DataStrings.LOCATION_PITCH); + if (value instanceof Number) { + pitch = ((Number) value).floatValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_YAW)) { + Object value = locMap.get(DataStrings.LOCATION_YAW); + if (value instanceof Number) { + yaw = ((Number) value).floatValue(); + } + } + if (world == null) { + return null; + } + return new Location(world, x, y, z, yaw, pitch); + } + + /** + * @param potionsString A player's potion effects in string form to be parsed into + * {@link java.util.Collection}<{@link org.bukkit.potion.PotionEffect}>. + * @return a collection of potion effects parsed from potionsString. + * @deprecated PotionEffect do not use special handling because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. + */ + @Deprecated + public static PotionEffect[] parsePotionEffects(String potionsString) { + List potionEffectList = new LinkedList(); + if (potionsString.isEmpty()) { + return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + } + JSONArray jsonPotions; + try { + jsonPotions = (JSONArray) JSON_PARSER.parse(potionsString); + } catch (ParseException e) { + Logging.warning("Could not parse potions! " + e.getMessage()); + return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + } catch (ClassCastException e) { + Logging.warning("Could not parse potions! " + e.getMessage()); + return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + } + for (Object obj : jsonPotions) { + if (obj instanceof JSONObject) { + JSONObject jsonPotion = (JSONObject) obj; + int type = -1; + int duration = -1; + int amplifier = -1; + if (jsonPotion.containsKey(DataStrings.POTION_TYPE)) { + Object value = jsonPotion.get(DataStrings.POTION_TYPE); + if (value instanceof Number) { + type = ((Number) value).intValue(); + } + } + if (jsonPotion.containsKey(DataStrings.POTION_AMPLIFIER)) { + Object value = jsonPotion.get(DataStrings.POTION_AMPLIFIER); + if (value instanceof Number) { + amplifier = ((Number) value).intValue(); + } + } + if (jsonPotion.containsKey(DataStrings.POTION_DURATION)) { + Object value = jsonPotion.get(DataStrings.POTION_DURATION); + if (value instanceof Number) { + duration = ((Number) value).intValue(); + } + } + if (type == -1 || duration == -1 || amplifier == -1) { + Logging.fine("Could not parse potion effect string: " + obj); + } else { + PotionEffectType pType = PotionEffectType.getById(type); + if (pType == null) { + Logging.warning("Could not parse potion effect type: " + type); + continue; + } + potionEffectList.add(new PotionEffect(pType, duration, amplifier)); + } + } else { + Logging.warning("Could not parse potion effect: " + obj); + } + } + return potionEffectList.toArray(new PotionEffect[0]); + } + + private LegacyParsers() { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java index c7e46921..4783e115 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java @@ -63,7 +63,7 @@ public class PlayerStats { public static final int MAXIMUM_AIR = 300; private PlayerStats() { - throw new AssertionError(); + throw new IllegalStateException(); } } From 2143369aab2106e8a588695cc635972b2a8f4e53 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:33:34 +0800 Subject: [PATCH 068/180] Log world change sharehandle time taken --- .../multiverse/inventories/handleshare/ShareHandleListener.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 048ed5db..2754eaa8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -258,8 +258,10 @@ void playerChangedWorld(PlayerChangedWorldEvent event) { Logging.fine("The from or to world is not managed by Multiverse-Core!"); } + long startTime = System.nanoTime(); new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); profileDataSource.updateLastWorld(player.getUniqueId(), toWorld.getName()); + Logging.finest("WorldChangeShareHandler took " + (System.nanoTime() - startTime) / 1000000 + " ms."); } /** From acc420ab59ee228785326b6ea33e06322068a9df Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:56:17 +0800 Subject: [PATCH 069/180] Improve null checking for maxhealth attribute --- .../inventories/share/Sharables.java | 20 +++++++++++++------ .../inventories/util/PlayerStats.java | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 8cf5fbd3..8302273f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -3,7 +3,9 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.NamespacedKey; import org.bukkit.Registry; +import org.bukkit.attribute.AttributeInstance; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; @@ -65,7 +67,10 @@ public static void init(MultiverseInventories inventories) { if (Sharables.safetyTeleporter == null) { Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); } - Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max-health")); + Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); + if (Sharables.maxHealthAttr == null) { + Logging.warning("Could not find max_health attribute. Health related sharables may not work as expected."); + } } /** @@ -178,8 +183,10 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public void updateProfile(PlayerProfile profile, Player player) { double health = player.getHealth(); // Player is dead, so health should be regained to full. - if (health <= 0 && maxHealthAttr != null) { - health = player.getAttribute(maxHealthAttr).getValue(); + if (health <= 0) { + health = Option.of(maxHealthAttr).map(player::getAttribute) + .map(AttributeInstance::getValue) + .getOrElse(PlayerStats.HEALTH); } profile.set(HEALTH, health); } @@ -195,9 +202,10 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { player.setHealth(value); } catch (IllegalArgumentException e) { Logging.fine("Invalid value '" + value + "': " + e.getMessage()); - if (maxHealthAttr != null) { - player.setHealth(player.getAttribute(maxHealthAttr).getValue()); - } + Option.of(maxHealthAttr).map(player::getAttribute) + .map(AttributeInstance::getValue) + .peek(player::setHealth) + .onEmpty(() -> player.setHealth(PlayerStats.HEALTH)); return false; } return true; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java index 4783e115..d32a7a56 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java @@ -20,7 +20,7 @@ public class PlayerStats { /** * Default health value. */ - public static final int HEALTH = 20; + public static final double HEALTH = 20; /** * Default experience value. */ From eae0b35119f6b292535771dc676c1f4b49338195 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:16:01 +0800 Subject: [PATCH 070/180] Use separate thread and implement async write for global profile --- .../profile/FlatFileProfileDataSource.java | 28 +++++++++++-------- .../profile/ProfileDataSource.java | 2 +- .../inventories/profile/ProfileFileIO.java | 9 ++---- .../profile/FilePerformanceTest.kt | 25 +++++++++++++++++ 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 91058fc5..15dad4c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -60,11 +60,13 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final File groupFolder; private final File playerFolder; - private final ProfileFileIO profileFileIO; + private final ProfileFileIO playerProfileIO; + private final ProfileFileIO globalProfileIO; @Inject - FlatFileProfileDataSource(@NotNull MultiverseInventories plugin, @NotNull ProfileFileIO profileFileIO) throws IOException { - this.profileFileIO = profileFileIO; + FlatFileProfileDataSource(@NotNull MultiverseInventories plugin) throws IOException { + this.playerProfileIO = new ProfileFileIO(); + this.globalProfileIO = new ProfileFileIO(); // Make the data folders plugin.getDataFolder().mkdirs(); @@ -123,7 +125,7 @@ private File getProfileContainerFolder(ContainerType type, String folderName) { */ @Override public void updatePlayerData(PlayerProfile playerProfile) { - profileFileIO.queueAction(() -> processProfileWrite(playerProfile.clone())); + playerProfileIO.queueAction(() -> processProfileWrite(playerProfile.clone())); } private void processProfileWrite(PlayerProfile playerProfile) { @@ -142,7 +144,7 @@ private void processProfileWrite(PlayerProfile playerProfile) { FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); if (playerData == null) { playerData = playerFile.exists() - ? profileFileIO.getConfigHandleNow(playerFile) + ? playerProfileIO.getConfigHandleNow(playerFile) : new JsonConfiguration(); configCache.put(fileProfileKey, playerData); } @@ -219,7 +221,7 @@ private PlayerProfile getPlayerData(ProfileKey key) { ProfileKey fileProfileKey = ProfileKey.createProfileKey(key, (ProfileType) null); FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); if (playerData == null) { - playerData = profileFileIO.waitForConfigHandle(playerFile); + playerData = playerProfileIO.waitForConfigHandle(playerFile); configCache.put(fileProfileKey, playerData); } if (convertConfig(playerData)) { @@ -359,12 +361,12 @@ public boolean removePlayerData(ContainerType containerType, String dataName, Pr if (!playerFile.exists()) { return false; } - playerData = profileFileIO.getConfigHandleNow(playerFile); + playerData = playerProfileIO.getConfigHandleNow(playerFile); } playerData.set(profileType.getName(), null); profileCache.invalidate(profileKey); FileConfiguration finalPlayerData = playerData; - profileFileIO.queueAction(() -> { + playerProfileIO.queueAction(() -> { try { finalPlayerData.save(playerFile); } catch (IOException e) { @@ -437,7 +439,7 @@ private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { } private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = profileFileIO.waitForConfigHandle(globalFile); + FileConfiguration playerData = globalProfileIO.waitForConfigHandle(globalFile); ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { section = playerData.createSection(DataStrings.PLAYER_DATA); @@ -449,7 +451,11 @@ private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID * {@inheritDoc} */ @Override - public boolean updateGlobalProfile(GlobalProfile globalProfile) { + public void updateGlobalProfile(GlobalProfile globalProfile) { + globalProfileIO.queueAction(() -> processGlobalProfileWrite(globalProfile)); + } + + private void processGlobalProfileWrite(GlobalProfile globalProfile) { File playerFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); FileConfiguration playerData = new JsonConfiguration(); playerData.createSection(DataStrings.PLAYER_DATA, globalProfile.serialize(globalProfile)); @@ -458,9 +464,7 @@ public boolean updateGlobalProfile(GlobalProfile globalProfile) { } catch (IOException e) { Logging.severe("Could not save global data for player: " + globalProfile); Logging.severe(e.getMessage()); - return false; } - return true; } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 91875707..c5ef4e59 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -89,7 +89,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param globalProfile The GlobalProfile object to update the file for. * @return True if data successfully saved to file. */ - boolean updateGlobalProfile(GlobalProfile globalProfile); + void updateGlobalProfile(GlobalProfile globalProfile); /** * A convenience method to update the GlobalProfile of a player with a specified world. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index f0fbca29..938d19b1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -3,8 +3,6 @@ import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; -import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.external.jakarta.inject.Inject; import java.io.File; import java.io.IOException; @@ -16,13 +14,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -@Service final class ProfileFileIO { - private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); + private final ExecutorService fileIOExecutorService; - @Inject - public ProfileFileIO() { + ProfileFileIO() { + fileIOExecutorService = Executors.newSingleThreadExecutor(); } FileConfiguration waitForConfigHandle(File file) { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 28193a49..94154891 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -8,6 +8,8 @@ import org.bukkit.inventory.ItemStack import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.junit.jupiter.api.Test +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.container.ContainerType import org.mvplugins.multiverse.inventories.share.Sharables @@ -16,16 +18,21 @@ import java.util.function.Consumer import kotlin.test.BeforeTest import kotlin.test.assertEquals import kotlin.test.assertNull +import kotlin.test.assertTrue class FilePerformanceTest : TestWithMockBukkit() { + private lateinit var worldManager: WorldManager private lateinit var profileDataSource: ProfileDataSource @BeforeTest fun setUp() { + worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { throw IllegalStateException("ProfileDataSource is not available as a service") } Logging.setDebugLevel(0) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) } @Test @@ -116,4 +123,22 @@ class FilePerformanceTest : TestWithMockBukkit() { modify.accept(itemStack) return itemStack } + + @Test + fun `Teleport 50 players consecutively`() { + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) + server.setPlayers(50) + val startTime = System.nanoTime() + for (player in server.playerList.onlinePlayers) { + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + } + + @Test + fun `50 players join the server consecutively`() { + val startTime = System.nanoTime() + server.setPlayers(50) + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + } } From 3d24c1ad74b564e66008d2b1b33c17208e938dc9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:17:24 +0800 Subject: [PATCH 071/180] Reduce number of global profile writes when player join --- .../inventories/handleshare/ShareHandleListener.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 2754eaa8..085184ee 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -161,8 +161,9 @@ void playerJoin(final PlayerJoinEvent event) { .getPlayerData(player) )); } - profileDataSource.setLoadOnLogin(player.getUniqueId(), false); + globalProfile.setLoadOnLogin(false); verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); + profileDataSource.updateGlobalProfile(globalProfile); } private void verifyCorrectPlayerName(UUID uuid, String name) { @@ -210,13 +211,13 @@ void playerQuit(final PlayerQuitEvent event) { private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { if (globalProfile.getLastWorld() == null) { - profileDataSource.updateLastWorld(player.getUniqueId(), world); + globalProfile.setLastWorld(world); } else { if (!world.equals(globalProfile.getLastWorld())) { Logging.fine("Player did not spawn in the world they were last reported to be in!"); new WorldChangeShareHandler(this.inventories, player, globalProfile.getLastWorld(), world).handleSharing(); - profileDataSource.updateLastWorld(player.getUniqueId(), world); + globalProfile.setLastWorld(world); } } } From 56c9892c3f5dc15661e577eecefcf691aefe63a2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 15 Feb 2025 13:44:53 +0800 Subject: [PATCH 072/180] Minor cleanup --- .../profile/group/AbstractWorldGroupManager.java | 4 ++-- .../inventories/profile/group/WorldGroup.java | 4 ++-- .../inventories/gameplay/WorldChangeTest.kt | 12 +++++++++++- .../inventories/profile/FilePerformanceTest.kt | 11 ----------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index 86e19f4e..bf76efad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -65,7 +65,7 @@ public WorldGroup getGroup(String groupName) { */ @Override public List getGroups() { - return Collections.unmodifiableList(new ArrayList(getGroupNames().values())); + return List.copyOf(getGroupNames().values()); } /** @@ -178,7 +178,7 @@ public WorldGroup getDefaultGroup() { */ @Override public List checkGroups() { - List conflicts = new ArrayList(); + List conflicts = new ArrayList<>(); Map previousConflicts = new HashMap<>(); for (WorldGroup checkingGroup : getGroupNames().values()) { for (String worldName : checkingGroup.getWorlds()) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index 2880e98d..a711d762 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -237,14 +237,14 @@ public ProfileContainer getGroupProfileContainer() { public String toString() { StringBuilder builder = new StringBuilder(); builder.append(this.getName()).append(": {Worlds: ["); - String[] worldsString = this.getWorlds().toArray(new String[this.getWorlds().size()]); + String[] worldsString = this.getWorlds().toArray(new String[0]); for (int i = 0; i < worldsString.length; i++) { if (i != 0) { builder.append(", "); } builder.append(worldsString[i]); } - builder.append("], Shares: [").append(this.getShares().toString()).append("]"); + builder.append("], Shares: [").append(this.getShares()).append("]"); if (this.getSpawnWorld() != null) { builder.append(", Spawn World: ").append(this.getSpawnWorld()); builder.append(", Spawn Priority: ").append(this.getSpawnPriority().toString()); diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt index c53f48d9..ad4ff9f0 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt @@ -21,7 +21,7 @@ class WorldChangeTest : TestWithMockBukkit() { } @Test - fun `Test shares`() { + fun `Shares updates correctly on world change`() { writeResourceToConfigFile("/gameplay/world_change_groups.yml", "groups.yml") multiverseInventories.reloadConfig() val player = server.addPlayer("Benji_0224") @@ -37,4 +37,14 @@ class WorldChangeTest : TestWithMockBukkit() { assertEquals(stack, player.inventory.getItem(0)) Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") } + + @Test + fun `World change performance with 50 players`() { + server.setPlayers(50) + val startTime = System.nanoTime() + for (player in server.playerList.onlinePlayers) { + server.getWorld("world3")?.let { player.teleport(it.spawnLocation) } + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 94154891..f7e5c70b 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -124,17 +124,6 @@ class FilePerformanceTest : TestWithMockBukkit() { return itemStack } - @Test - fun `Teleport 50 players consecutively`() { - assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) - server.setPlayers(50) - val startTime = System.nanoTime() - for (player in server.playerList.onlinePlayers) { - server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } - } - Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - } - @Test fun `50 players join the server consecutively`() { val startTime = System.nanoTime() From 91ed12d8985947cd009acb21f16c6024f19746a3 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 10 Feb 2025 21:28:12 +0800 Subject: [PATCH 073/180] Add max_health sharable --- .../inventories/share/Sharables.java | 56 +++++++++++++++---- .../inventories/util/DataStrings.java | 4 ++ .../inventories/util/PlayerStats.java | 4 ++ .../GameModeChangeTest.kt | 2 +- .../handleshare/ShareHandlingUpdaterTest.kt | 49 ++++++++++++++++ .../WorldChangeTest.kt | 2 +- .../PlayerNameChangeTest.kt | 3 +- 7 files changed, 105 insertions(+), 15 deletions(-) rename src/test/java/org/mvplugins/multiverse/inventories/{gameplay => handleshare}/GameModeChangeTest.kt (69%) create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt rename src/test/java/org/mvplugins/multiverse/inventories/{gameplay => handleshare}/WorldChangeTest.kt (97%) rename src/test/java/org/mvplugins/multiverse/inventories/{gameplay => profile}/PlayerNameChangeTest.kt (97%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 8302273f..6a191bf4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -174,6 +174,31 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { }).serializer(new ProfileEntry(false, DataStrings.PLAYER_OFF_HAND_ITEM), new DefaultSerializer<>(ItemStack.class)).altName("shield").build(); + /** + * Sharing Max Health. + */ + public static final Sharable MAX_HEALTH = new Sharable.Builder<>("max_hit_points", Double.class, + new SharableHandler() { + @Override + public void updateProfile(PlayerProfile profile, Player player) { + profile.set(MAX_HEALTH, getMaxHealth(player)); + } + + @Override + public boolean updatePlayer(Player player, PlayerProfile profile) { + Double value = profile.get(MAX_HEALTH); + if (value == null) { + Option.of(maxHealthAttr).map(player::getAttribute) + .peek(attr -> attr.setBaseValue(attr.getDefaultValue())); + return false; + } + Option.of(maxHealthAttr).map(player::getAttribute) + .peek(attr -> attr.setBaseValue(value)); + return true; + } + }).stringSerializer(new ProfileEntry(true, DataStrings.PLAYER_MAX_HEALTH)) + .altName("maxhealth").altName("maxhp").altName("maxhitpoints").build(); + /** * Sharing Health. */ @@ -184,9 +209,7 @@ public void updateProfile(PlayerProfile profile, Player player) { double health = player.getHealth(); // Player is dead, so health should be regained to full. if (health <= 0) { - health = Option.of(maxHealthAttr).map(player::getAttribute) - .map(AttributeInstance::getValue) - .getOrElse(PlayerStats.HEALTH); + health = getMaxHealth(player); } profile.set(HEALTH, health); } @@ -199,20 +222,31 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { return false; } try { + double maxHealth = getMaxHealth(player); + // This share may handled before MAX_HEALTH. + // Thus this is needed to ensure there is no loss in health stored + if (value > maxHealth) { + Option.of(maxHealthAttr).map(player::getAttribute) + .peek(attr -> attr.setBaseValue(maxHealth)); + } player.setHealth(value); } catch (IllegalArgumentException e) { Logging.fine("Invalid value '" + value + "': " + e.getMessage()); - Option.of(maxHealthAttr).map(player::getAttribute) - .map(AttributeInstance::getValue) - .peek(player::setHealth) - .onEmpty(() -> player.setHealth(PlayerStats.HEALTH)); + player.setHealth(PlayerStats.HEALTH); return false; } return true; } + }).stringSerializer(new ProfileEntry(true, DataStrings.PLAYER_HEALTH)) .altName("health").altName("hp").altName("hitpoints").build(); + private static double getMaxHealth(Player player) { + return Option.of(maxHealthAttr).map(player::getAttribute) + .map(AttributeInstance::getValue) + .getOrElse(PlayerStats.MAX_HEALTH); + } + /** * Sharing Remaining Air. */ @@ -661,21 +695,21 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { * Grouping for player health related sharables. */ public static final SharableGroup ALL_HEALTH = new SharableGroup("health", - fromSharables(HEALTH, REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS)); + fromSharables(HEALTH, MAX_HEALTH, REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS)); /** * Grouping for player stat related sharables not including inventory. */ public static final SharableGroup STATS = new SharableGroup("stats", - fromSharables(HEALTH, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, + fromSharables(HEALTH, MAX_HEALTH, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS)); /** * Grouping for ALL default sharables. * TODO: make this really mean all, including 3rd party. */ - public static final SharableGroup ALL_DEFAULT = new SharableGroup("all", fromSharables(HEALTH, ECONOMY, - FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, INVENTORY, ARMOR, BED_SPAWN, + public static final SharableGroup ALL_DEFAULT = new SharableGroup("all", fromSharables(HEALTH, MAX_HEALTH, + ECONOMY, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, INVENTORY, ARMOR, BED_SPAWN, MAXIMUM_AIR, REMAINING_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, LAST_LOCATION, ENDER_CHEST, OFF_HAND), "*", "everything"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java index 8adeeb99..3e7be136 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java @@ -57,6 +57,10 @@ public final class DataStrings { * Player profile type identifier. */ public static final String PLAYER_PROFILE_TYPE = "profileType"; + /** + * Player max health identifier. + */ + public static final String PLAYER_MAX_HEALTH = "mhp"; /** * Player health identifier. */ diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java index d32a7a56..679d7db7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java @@ -17,6 +17,10 @@ public class PlayerStats { * Number of slots in an ender chest. */ public static final int ENDER_CHEST_SIZE = 27; + /** + * Default max health value. + */ + public static final double MAX_HEALTH = 20; /** * Default health value. */ diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt similarity index 69% rename from src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt rename to src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt index 372e82c8..53a84e5a 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/GameModeChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.gameplay +package org.mvplugins.multiverse.inventories.handleshare import org.mvplugins.multiverse.inventories.TestWithMockBukkit diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt new file mode 100644 index 00000000..7e7718bf --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -0,0 +1,49 @@ +package org.mvplugins.multiverse.inventories.handleshare + +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource +import org.mvplugins.multiverse.inventories.profile.ProfileTypes +import org.mvplugins.multiverse.inventories.profile.container.ContainerType +import org.mvplugins.multiverse.inventories.share.Sharables +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ShareHandlingUpdaterTest : TestWithMockBukkit() { + + private lateinit var profileDataSource: ProfileDataSource + private lateinit var player: PlayerMock + + @BeforeTest + fun setUp() { + player = server.addPlayer("benthecat10") + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") + } + } + + @Test + fun `Test updating profile`() { + player.health = 4.4 + player.maxHealth = 15.1 + + val playerProfile = profileDataSource.getPlayerData(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfile)) + + assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) + assertEquals(15.1, playerProfile.get(Sharables.MAX_HEALTH)) + } + + @Test + fun `Test updating player`() { + val playerProfile = profileDataSource.getPlayerData(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + playerProfile.set(Sharables.HEALTH, 4.4) + playerProfile.set(Sharables.MAX_HEALTH, 15.1) + + ShareHandlingUpdater.updatePlayer(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfile)) + + assertEquals(4.4, player.health) + assertEquals(15.1, player.maxHealth) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt similarity index 97% rename from src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt rename to src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt index ad4ff9f0..6cb4b5bd 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.gameplay +package org.mvplugins.multiverse.inventories.handleshare import com.dumptruckman.minecraft.util.Logging import org.bukkit.Material diff --git a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt similarity index 97% rename from src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt rename to src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt index 3962b697..a34ec36a 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/gameplay/PlayerNameChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.gameplay +package org.mvplugins.multiverse.inventories.profile import org.bukkit.Material import org.bukkit.inventory.ItemStack @@ -6,7 +6,6 @@ import org.mockbukkit.mockbukkit.entity.PlayerMock import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager import java.nio.file.Path import kotlin.test.BeforeTest From 5db12e7c634cadb5e3bbad762672b965f2cf7b64 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 15 Feb 2025 13:48:37 +0800 Subject: [PATCH 074/180] Fix javadocs return --- .../multiverse/inventories/profile/ProfileDataSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index c5ef4e59..04cf8ef5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -87,7 +87,6 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * Update the file for a player's global profile. * * @param globalProfile The GlobalProfile object to update the file for. - * @return True if data successfully saved to file. */ void updateGlobalProfile(GlobalProfile globalProfile); From d80ad60ea673c441b9db079af0dacfbf5b746e23 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 15 Feb 2025 15:51:18 +0800 Subject: [PATCH 075/180] Add group modification commands --- .../inventories/MultiverseInventories.java | 10 ++ .../commands/AddSharesCommand.java | 60 ++++++++++ .../commands/AddWorldsCommand.java | 66 +++++++++++ .../commands/CreateGroupCommand.java | 47 ++++++++ .../commands/DeleteGroupCommand.java | 59 ++++++++++ .../inventories/commands/GroupCommand.java | 2 +- .../inventories/commands/ImportCommand.java | 2 +- .../inventories/commands/InfoCommand.java | 2 +- .../inventories/commands/ListCommand.java | 2 +- .../inventories/commands/ReloadCommand.java | 2 +- .../commands/RemoveSharesCommand.java | 58 ++++++++++ .../commands/RemoveWorldsCommand.java | 68 +++++++++++ .../inventories/commands/ToggleCommand.java | 2 +- .../commands/prompts/GroupSharesPrompt.java | 2 +- .../commandtools/MVInvCommandCompletion.java | 106 ++++++++++++++++++ .../commandtools/MVInvCommandConditions.java | 47 ++++++++ .../commandtools/MVInvCommandContexts.java | 90 +++++++++++++++ .../inventories/profile/group/WorldGroup.java | 10 ++ .../inventories/share/Sharables.java | 9 +- .../inventories/util/MVInvi18n.java | 3 + .../multiverse-inventories_en.properties | 6 +- .../multiverse/inventories/InjectionTest.kt | 2 +- 22 files changed, 645 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 385bf167..e0e3c620 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -9,6 +9,8 @@ import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.commandtools.MVInvCommandCompletion; +import org.mvplugins.multiverse.inventories.commandtools.MVInvCommandContexts; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; import org.mvplugins.multiverse.inventories.dataimport.DataImporter; @@ -56,6 +58,10 @@ public class MultiverseInventories extends MultiversePlugin { private Provider profileContainerStoreProvider; @Inject private Provider dataImportManager; + @Inject + private Provider mvInvCommandCompletion; + @Inject + private Provider mvInvCommandContexts; private PluginServiceLocator serviceLocator; private InventoriesDupingPatch dupingPatch; @@ -150,6 +156,10 @@ private void registerCommands() { commandManager.getLocales().addFileResClassLoader(this); commandManager.getLocales().addBundleClassLoader(this.getClassLoader()); commandManager.getLocales().addMessageBundles("multiverse-inventories"); + + mvInvCommandCompletion.get(); + mvInvCommandContexts.get(); + serviceLocator.getAllServices(InventoriesCommand.class).forEach(commandManager::registerCommand); }) .onFailure(e -> { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java new file mode 100644 index 00000000..724b358b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java @@ -0,0 +1,60 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandIssuer; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class AddSharesCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + AddSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("addshares") + @CommandPermission("multiverse.inventories.addshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Add one or more shares to a group.") + void onAddSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to add the shares to.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to add.") + Shares shares + ) { + group.getShares().mergeShares(shares); + worldGroupManager.updateGroup(group); + var negativeshares = Sharables.allOf(); + negativeshares.setSharing(group.getShares(), false); + issuer.sendInfo(MVInvi18n.SHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getShares().toStringList()), + replace("{negativeshares}").with(negativeshares.toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java new file mode 100644 index 00000000..161b0c42 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java @@ -0,0 +1,66 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.Arrays; +import java.util.List; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class AddWorldsCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + AddWorldsCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("addworlds") + @CommandPermission("multiverse.inventories.addworlds") + @CommandCompletion("@worldGroups @mvworlds:multiple,scope=both") + @Syntax(" ") + @Description("Adds a World to a World Group.") + void onAddWorldCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to add the world to.") + WorldGroup group, + + @Syntax("") + @Description("World name to add.") + MultiverseWorld[] worlds + ) { + List worldNames = Arrays.stream(worlds).map(MultiverseWorld::getName).toList(); + String worldNamesString = String.join(", ", worldNames); + if (!group.getWorlds().addAll(worldNames)) { + issuer.sendError(MVInvi18n.ADDWORLD_WORLDALREADYEXISTS, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNamesString)); + return; + } + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.ADDWORLD_WORLDADDED, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNamesString)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java new file mode 100644 index 00000000..21314e19 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Conditions; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class CreateGroupCommand extends InventoriesCommand { + private final WorldGroupManager worldGroupManager; + + @Inject + CreateGroupCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("creategroup") + @CommandPermission("multiverse.inventories.creategroup") + @Syntax("") + @Description("Creates a new empty World Group with no worlds and no shares.") + void onCreateGroupCommand( + MVCommandIssuer issuer, + + @Conditions("newWorldGroupName") + @Syntax("") + @Description("New group name to create.") + @NotNull String groupName + ) { + WorldGroup worldGroup = worldGroupManager.newEmptyGroup(groupName); + worldGroupManager.updateGroup(worldGroup); + issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE, replace("{group}").with(groupName)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java new file mode 100644 index 00000000..17703501 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java @@ -0,0 +1,59 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.commandtools.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.commandtools.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class DeleteGroupCommand extends InventoriesCommand { + private final CommandQueueManager commandQueueManager; + private final WorldGroupManager worldGroupManager; + + @Inject + DeleteGroupCommand(@NotNull MVCommandManager commandManager, + @NotNull CommandQueueManager commandQueueManager, + @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.commandQueueManager = commandQueueManager; + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("deletegroup") + @CommandPermission("multiverse.inventories.deletegroup") + @CommandCompletion("@worldGroups") + @Syntax("") + @Description("Deletes a World Group.") + void onDeleteGroupCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Inventories group to delete.") + WorldGroup group + ) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of(MVInvi18n.DELETEGROUP_CONFIRMPROMPT, replace("{group}").with(group.getName()))) + .action(() -> doDeleteGroup(issuer, group))); + } + + private void doDeleteGroup(MVCommandIssuer issuer, WorldGroup group) { + worldGroupManager.removeGroup(group); + issuer.sendInfo(MVInvi18n.DELETEGROUP_SUCCESS, replace("{group}").with(group.getName())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index 6bde5da1..a6793789 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -19,7 +19,7 @@ @Service @CommandAlias("mvinv") -class GroupCommand extends InventoriesCommand { +final class GroupCommand extends InventoriesCommand { private final MultiverseInventories plugin; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index e09ee400..21178ca2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -43,7 +43,7 @@ final class ImportCommand extends InventoriesCommand { @CommandPermission("multiverse.inventories.import") @CommandCompletion("MultiInv|WorldInventories|PerWorldInventory") @Description("Import inventories from MultiInv/WorldInventories/PerWorldInventory plugin.") - public void onImportCommand( + void onImportCommand( MVCommandIssuer issuer, @Single diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index 3ef90e8b..99b89b75 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -29,7 +29,7 @@ @Service @CommandAlias("mvinv") -class InfoCommand extends InventoriesCommand { +final class InfoCommand extends InventoriesCommand { private final MultiverseInventories plugin; private final ProfileContainerStoreProvider profileContainerStoreProvider; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 41685f7f..d76c22e5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -20,7 +20,7 @@ @Service @CommandAlias("mvinv") -class ListCommand extends InventoriesCommand { +final class ListCommand extends InventoriesCommand { private final MultiverseInventories plugin; private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index ad139d8b..2324a206 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -14,7 +14,7 @@ @Service @CommandAlias("mvinv") -class ReloadCommand extends InventoriesCommand { +final class ReloadCommand extends InventoriesCommand { private final MultiverseInventories plugin; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java new file mode 100644 index 00000000..b6f037ca --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java @@ -0,0 +1,58 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class RemoveSharesCommand extends InventoriesCommand { + private final WorldGroupManager worldGroupManager; + + @Inject + RemoveSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("removeshares") + @CommandPermission("multiverse.inventories.removeshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Remove one or more shares from a group.") + void onRemoveSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to remove the shares from.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to remove.") + Shares shares + ) { + group.getShares().setSharing(shares, false); + worldGroupManager.updateGroup(group); + var negativeshares = Sharables.allOf(); + negativeshares.setSharing(group.getShares(), false); + issuer.sendInfo(MVInvi18n.SHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getShares().toStringList()), + replace("{negativeshares}").with(negativeshares.toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java new file mode 100644 index 00000000..7f728a08 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java @@ -0,0 +1,68 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.Arrays; +import java.util.List; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + + +@Service +@CommandAlias("mvinv") +final class RemoveWorldsCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + RemoveWorldsCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("removeworlds") + @CommandPermission("multiverse.inventories.removeworlds") + @CommandCompletion("@worldGroups @worldGroupWorlds") + @Syntax(", ") + @Description("Adds a World to a World Group.") + void onRemoveWorldCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to remove the world from.") + @NotNull WorldGroup group, + + @Single + @Syntax("") + @Description("World name to remove.") + String worldNames + ) { + List worldNamesArr = Arrays.stream(worldNames.split(",")).toList(); + if (!group.removeWorlds(worldNamesArr)) { + issuer.sendError(MVInvi18n.REMOVEWORLD_WORLDNOTINGROUP, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNames)); + return; + } + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.REMOVEWORLD_WORLDREMOVED, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNames)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 27b0b7bb..3699b238 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -24,7 +24,7 @@ @Service @CommandAlias("mvinv") -class ToggleCommand extends InventoriesCommand { +final class ToggleCommand extends InventoriesCommand { private final MultiverseInventories plugin; private final InventoriesConfig inventoriesConfig; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java index ff949c10..4387a4c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -56,7 +56,7 @@ public Prompt acceptInput(@NotNull final ConversationContext conversationContext group.getShares().addAll(this.shares); worldGroupManager.updateGroup(group); if (isCreating) { - issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE); + issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE, replace("{group}").with(group.getName())); } else { issuer.sendInfo(MVInvi18n.GROUP_UPDATED); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java new file mode 100644 index 00000000..bfe3f892 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java @@ -0,0 +1,106 @@ +package org.mvplugins.multiverse.inventories.commandtools; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandCompletions; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.mvplugins.multiverse.core.utils.StringFormatter.addonToCommaSeperated; + +@Service +public final class MVInvCommandCompletion { + + private final InventoriesConfig inventoriesConfig; + private final WorldGroupManager worldGroupManager; + + @Inject + private MVInvCommandCompletion( + @NotNull InventoriesConfig inventoriesConfig, + @NotNull WorldGroupManager worldGroupManager, + @NotNull MVCommandManager mvCommandManager) { + this.inventoriesConfig = inventoriesConfig; + this.worldGroupManager = worldGroupManager; + + MVCommandCompletions commandCompletions = mvCommandManager.getCommandCompletions(); + commandCompletions.registerAsyncCompletion("sharables", this::suggestSharables); + commandCompletions.registerAsyncCompletion("shares", this::suggestShares); + commandCompletions.registerAsyncCompletion("worldGroups", this::suggestWorldGroups); + commandCompletions.registerAsyncCompletion("worldGroupWorlds", this::suggestWorldGroupWorlds); + } + + private Collection suggestSharables(BukkitCommandCompletionContext context) { + String scope = context.getConfig("scope", "enabled"); + + return Sharables.all().stream() + .filter(sharable -> switch (scope) { + case "enabled" -> + !sharable.isOptional() || inventoriesConfig.getOptionalShares().contains(sharable); + case "optional" -> sharable.isOptional(); + default -> true; + }) + .filter(sharable -> sharable.getNames().length > 0) + .map(sharable -> sharable.getNames()[0]) + .toList(); + } + + private Collection suggestShares(BukkitCommandCompletionContext context) { + String input = context.getInput(); + + if (input.isEmpty()) { + // No input, so we're suggesting the first share + return Sharables.getShareNames(); + } + + int lastComma = input.lastIndexOf(","); + if (lastComma == -1) { + // No comma, so we're suggesting the first share + if (input.startsWith("-")) { + return Sharables.getShareNames().stream() + .map(name -> "-" + name) + .collect(Collectors.toList()); + } + return Sharables.getShareNames(); + } + + // We're suggesting a share after a comma + String lastShare = input.substring(lastComma + 1); + String currentSharesString = input.substring(0, lastComma + (lastShare.startsWith("-") ? 2 : 1)); + Set currentShares = Arrays.stream(input.split(",")) + .map(share -> share.startsWith("-") ? share.substring(1) : share) + .collect(Collectors.toSet()); + + return Sharables.getShareNames().stream() + .filter(name -> !currentShares.contains(name)) + .map(name -> currentSharesString + name) + .toList(); + } + + private Collection suggestWorldGroups(BukkitCommandCompletionContext context) { + return worldGroupManager.getGroups().stream() + .map(WorldGroup::getName) + .toList(); + } + + private Collection suggestWorldGroupWorlds(BukkitCommandCompletionContext context) { + + var worlds = Try.of(() -> context.getContextValue(WorldGroup.class)) + .map(WorldGroup::getWorlds) + .getOrElse(Collections.emptySet()); + + return addonToCommaSeperated(context.getInput(), worlds); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java new file mode 100644 index 00000000..e47db16b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.inventories.commandtools; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandIssuer; +import org.mvplugins.multiverse.external.acf.commands.BukkitConditionContext; +import org.mvplugins.multiverse.external.acf.commands.CommandConditions; +import org.mvplugins.multiverse.external.acf.commands.ConditionContext; +import org.mvplugins.multiverse.external.acf.commands.ConditionFailedException; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +@Service +public final class MVInvCommandConditions { + + private final WorldGroupManager worldGroupManager; + + @Inject + private MVInvCommandConditions(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + + CommandConditions commandConditions + = commandManager.getCommandConditions(); + commandConditions.addCondition(Sharable.class, "optionalSharable", this::checkOptionalSharable); + commandConditions.addCondition(String.class, "newWorldGroupName", this::checkNewWorldGroupName); + } + + private void checkOptionalSharable(ConditionContext context, + BukkitCommandExecutionContext executionContext, + Sharable sharable) { + if (sharable == null || !sharable.isOptional()) { + throw new ConditionFailedException(MVInvi18n.TOGGLE_NOOPTIONALSHARES); + } + } + + private void checkNewWorldGroupName(ConditionContext context, + BukkitCommandExecutionContext executionContext, + String worldGroupName) { + if (worldGroupManager.getGroup(worldGroupName) != null) { + throw new ConditionFailedException(MVInvi18n.GROUP_EXISTS, "{group}", worldGroupName); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java new file mode 100644 index 00000000..dd98879f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java @@ -0,0 +1,90 @@ +package org.mvplugins.multiverse.inventories.commandtools; + +import com.google.common.base.Strings; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; +import org.mvplugins.multiverse.external.acf.commands.CommandContexts; +import org.mvplugins.multiverse.external.acf.commands.InvalidCommandArgument; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +@Service +public final class MVInvCommandContexts { + + private final WorldGroupManager worldGroupManager; + + @Inject + private MVInvCommandContexts(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + + CommandContexts commandContexts = commandManager.getCommandContexts(); + commandContexts.registerContext(Sharable.class, this::parseSharable); + commandContexts.registerContext(Shares.class, this::parseShares); + commandContexts.registerContext(WorldGroup.class, this::parseWorldGroup); + } + + private Sharable parseSharable(BukkitCommandExecutionContext context) { + String sharableName = context.popFirstArg(); + Sharable targetSharable = Sharables.all().stream() + .filter(sharable -> sharable.getNames().length > 0) + .filter(sharable -> sharable.getNames()[0].equals(sharableName)) + .findFirst() + .orElse(null); + + if (targetSharable != null) { + return targetSharable; + } + if (context.isOptional()) { + return null; + } + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOSHARESSPECIFIED); + } + + private Shares parseShares(BukkitCommandExecutionContext context) { + String shareStrings = context.popFirstArg(); + if (Strings.isNullOrEmpty(shareStrings)) { + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOSHARESSPECIFIED); + } + + String[] shareNames = shareStrings.split(","); + Shares newShares = Sharables.noneOf(); + Shares negativeShares = Sharables.noneOf(); + for (String shareName : shareNames) { + if (shareName.startsWith("-")) { + shareName = shareName.substring(1); + Option.of(Sharables.lookup(shareName)) + .peek(shares -> negativeShares.setSharing(shares, true)); + continue; + } + Option.of(Sharables.lookup(shareName)) + .peek(shares -> newShares.setSharing(shares, true)); + } + + newShares.setSharing(negativeShares, false); + if (newShares.isEmpty()) { + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOSHARESSPECIFIED); + } + + return newShares; + } + + private WorldGroup parseWorldGroup(BukkitCommandExecutionContext context) { + String groupName = context.popFirstArg(); + WorldGroup group = worldGroupManager.getGroup(groupName); + if (group != null) { + return group; + } + if (context.isOptional()) { + return null; + } + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOGROUP); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index a711d762..c23ec62f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -127,6 +127,16 @@ public void removeWorld(World world) { this.removeWorld(world.getName()); } + /** + * Removes multiple worlds from this World Group. + * + * @param removeWorlds A collection of world names to remove. + * @return True if any of the worlds were removed. + */ + public boolean removeWorlds(Collection removeWorlds) { + return this.getWorlds().removeAll(removeWorlds); + } + /** * Remove all the worlds in this World Group. */ diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 6a191bf4..6368ac44 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -40,7 +40,7 @@ */ public final class Sharables implements Shares { - private static final Shares ALL_SHARABLES = new Sharables(new LinkedHashSet()); + private static final Shares ALL_SHARABLES = new Sharables(new LinkedHashSet<>()); /** * The map used to lookup a Sharable or set of Sharables by their name. @@ -758,6 +758,13 @@ public static Shares lookup(String name) { return LOOKUP_MAP.get(name.toLowerCase()); } + /** + * @return A collection of all registered {@link Shares}. This is NOT to be modified and serves only as a reference. + */ + public static Collection getShareNames() { + return LOOKUP_MAP.keySet(); + } + /** * @return A {@link Shares} collection containing ALL registered {@link Sharable}s. This is NOT to be modified and * serves only as a reference. For a version you can do what you want with, see {@link #allOf()}. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java index bbe1a6a7..44e9085f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java @@ -88,6 +88,9 @@ public enum MVInvi18n implements MessageKeyProvider { IMPORT_CONFIRMPROMPT, IMPORT_SUCCESS, IMPORT_FAILED, + + DELETEGROUP_CONFIRMPROMPT, + DELETEGROUP_SUCCESS, ; private final MessageKey key = MessageKey.of("mv-inventories." + this.name().replace('_', '.') diff --git a/src/main/resources/multiverse-inventories_en.properties b/src/main/resources/multiverse-inventories_en.properties index d1ae0b38..fc7b6294 100644 --- a/src/main/resources/multiverse-inventories_en.properties +++ b/src/main/resources/multiverse-inventories_en.properties @@ -83,7 +83,7 @@ mv-inventories.group.invalidname=&cThat name is not valid! May only contain let mv-inventories.group.exists=&cThat group already exists! (&f{group}&c) mv-inventories.group.removed=&2Removed group: &f{group} mv-inventories.group.worldsempty=&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel. -mv-inventories.group.creationcomplete=&2You created a new group! +mv-inventories.group.creationcomplete=&2You created a new group '{group}'! mv-inventories.group.updated=&2Group has been updated! mv-inventories.group.nonconversable=You are not allowed to access conversations (remote console?) mv-inventories.group.invalidoption=&cThat is not a valid option! Type &f##&c to stop working on groups. @@ -94,3 +94,7 @@ mv-inventories.import.unsupportedplugin=&6Sorry, ''&f{plugin}&6'' is not support mv-inventories.import.confirmprompt=&6Are you sure you want to import data from &f{plugin}&6? This will override existing Multiverse-Inventories playerdata!!! mv-inventories.import.success=&2Successfully imported data from &f{plugin}! mv-inventories.import.failed=Failed to import data from &f{plugin}! + +# Deletegroup command +mv-inventories.deletegroup.confirmprompt=Are you sure you want to delete group &f{group}&6? +mv-inventories.deletegroup.success=&2Successfully deleted group: &f{group}! \ No newline at end of file diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 0abea1f6..085396e2 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(6, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(12, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From af16d5973869ba193f096ad3ca9bb4c4cf80225e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 15 Feb 2025 15:58:39 +0800 Subject: [PATCH 076/180] Add worlds and shares params to create command --- .../commands/CreateGroupCommand.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java index 21314e19..177babeb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -3,18 +3,24 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Conditions; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Optional; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Shares; import org.mvplugins.multiverse.inventories.util.MVInvi18n; +import java.util.Arrays; + import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service @@ -30,7 +36,8 @@ final class CreateGroupCommand extends InventoriesCommand { @Subcommand("creategroup") @CommandPermission("multiverse.inventories.creategroup") - @Syntax("") + @CommandCompletion("@empty @mvworlds:multiple,scope=both, @shares") + @Syntax(" [share[,extra]] [world[,extra]]") @Description("Creates a new empty World Group with no worlds and no shares.") void onCreateGroupCommand( MVCommandIssuer issuer, @@ -38,10 +45,26 @@ void onCreateGroupCommand( @Conditions("newWorldGroupName") @Syntax("") @Description("New group name to create.") - @NotNull String groupName + @NotNull String groupName, + + @Optional + @Syntax("[world[,extra]]") + MultiverseWorld[] worlds, + + @Optional + @Syntax("[share,[extra]]") + Shares shares ) { WorldGroup worldGroup = worldGroupManager.newEmptyGroup(groupName); + if (worlds != null) { + worldGroup.getWorlds().addAll(Arrays.stream(worlds).map(MultiverseWorld::getName).toList()); + } + if (shares != null) { + worldGroup.getShares().mergeShares(shares); + } worldGroupManager.updateGroup(worldGroup); issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE, replace("{group}").with(groupName)); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(worldGroup.getWorlds())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(worldGroup.getShares())); } } From 1da061db977234e5fcd9e191a2da794dc0ddd801 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 16 Feb 2025 11:26:32 +0800 Subject: [PATCH 077/180] Further optimize player profile --- .../inventories/commands/UsageCommand.java | 38 +++++++++ .../profile/FlatFileProfileDataSource.java | 83 ++++++++++++------- .../inventories/profile/PlayerProfile.java | 3 +- .../profile/ProfileDataSource.java | 11 ++- .../inventories/profile/ProfileFileIO.java | 13 +++ .../inventories/profile/ProfileKey.java | 60 ++++++-------- .../profile/container/ProfileContainer.java | 1 - .../profile/FilePerformanceTest.kt | 11 ++- 8 files changed, 151 insertions(+), 69 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java new file mode 100644 index 00000000..7eb96bff --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java @@ -0,0 +1,38 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.CommandHelp; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.HelpCommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; + +@Service +@CommandAlias("mvinv") +final class UsageCommand extends InventoriesCommand { + + @Inject + UsageCommand(@NotNull MVCommandManager commandManager) { + super(commandManager); + } + + @HelpCommand + @Subcommand("help") + @CommandPermission("multiverse.inventories.help") + @CommandCompletion("@commands:mvinv") + @Syntax("[filter] [page]") + @Description("Displays a list of available commands.") + void onUsageCommand(CommandHelp help) { + if (help.getIssuer().isPlayer()) { + // Prevent flooding the chat + help.setPerPage(4); + } + this.commandManager.showUsage(help); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 15dad4c0..f617ba05 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -4,6 +4,7 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheStats; import com.google.common.collect.Sets; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; @@ -42,18 +43,21 @@ final class FlatFileProfileDataSource implements ProfileDataSource { // TODO these probably need configurable max sizes private final Cache configCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(1000) + .expireAfterAccess(60, TimeUnit.MINUTES) + .maximumSize(2000) + .recordStats() .build(); private final Cache profileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(1000) + .expireAfterAccess(60, TimeUnit.MINUTES) + .maximumSize(6000) + .recordStats() .build(); private final Cache globalProfileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) + .expireAfterAccess(60, TimeUnit.MINUTES) .maximumSize(500) + .recordStats() .build(); private final File worldFolder; @@ -135,7 +139,7 @@ private void processProfileWrite(PlayerProfile playerProfile) { playerProfile.getPlayer().getName() ); try { - ProfileKey fileProfileKey = ProfileKey.createProfileKey( + ProfileKey fileProfileKey = ProfileKey.create( playerProfile.getContainerType(), playerProfile.getContainerName(), null, @@ -201,7 +205,7 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) */ @Override public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); + return getPlayerData(ProfileKey.create(containerType, dataName, profileType, playerUUID)); } private PlayerProfile getPlayerData(ProfileKey key) { @@ -216,12 +220,19 @@ private PlayerProfile getPlayerData(ProfileKey key) { profileCache.put(key, playerProfile); return playerProfile; } + return playerProfileIO.waitForData(() -> loadPlayerData(key, playerFile)); + } + private PlayerProfile loadPlayerData(ProfileKey key, File playerFile) { // Migrate from none profile-type data - ProfileKey fileProfileKey = ProfileKey.createProfileKey(key, (ProfileType) null); + ProfileKey fileProfileKey = key.forProfileType(null); FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); if (playerData == null) { - playerData = playerProfileIO.waitForConfigHandle(playerFile); + try { + playerData = playerProfileIO.getConfigHandleNow(playerFile); + } catch (IOException | InvalidConfigurationException e) { + throw new RuntimeException(e); + } configCache.put(fileProfileKey, playerData); } if (convertConfig(playerData)) { @@ -316,7 +327,7 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile parsePlayerStatsIntoProfile(jsonStats, profile); } - // TODO Remove this conversion + @Deprecated private boolean convertConfig(FileConfiguration config) { ConfigurationSection section = config.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { @@ -335,7 +346,7 @@ private boolean convertConfig(FileConfiguration config) { */ @Override public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - ProfileKey profileKey = ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID); + ProfileKey profileKey = ProfileKey.create(containerType, dataName, profileType, playerUUID); if (profileKey.getProfileType() == null) { try { File playerFile = getPlayerFile(containerType, dataName, profileKey.getPlayerName()); @@ -346,7 +357,7 @@ public boolean removePlayerData(ContainerType containerType, String dataName, Pr && key.getContainerType().equals(containerType) && key.getDataName().equals(dataName) )); - return playerFile.delete(); + playerProfileIO.queueAction(playerFile::delete); } catch (Exception ignore) { Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + " in " + containerType.name().toLowerCase() + " " + dataName); @@ -354,32 +365,35 @@ public boolean removePlayerData(ContainerType containerType, String dataName, Pr } } try { - File playerFile = getPlayerFile(containerType, dataName, profileKey.getPlayerName()); - ProfileKey fileProfileKey = ProfileKey.createProfileKey(profileKey, (ProfileType) null); + profileCache.invalidate(profileKey); + playerProfileIO.queueAction(() -> processProfileRemove(profileKey)); + } catch (Exception e) { + Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() + + " for " + containerType.toString() + ": " + dataName); + Logging.severe(e.getMessage()); + return false; + } + return true; + } + + private void processProfileRemove(ProfileKey profileKey) { + try { + File playerFile = getPlayerFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); + ProfileKey fileProfileKey = profileKey.forProfileType(null); FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); if (playerData == null) { if (!playerFile.exists()) { - return false; + return; } playerData = playerProfileIO.getConfigHandleNow(playerFile); + configCache.put(fileProfileKey, playerData); } - playerData.set(profileType.getName(), null); - profileCache.invalidate(profileKey); + playerData.set(profileKey.getProfileType().getName(), null); FileConfiguration finalPlayerData = playerData; - playerProfileIO.queueAction(() -> { - try { - finalPlayerData.save(playerFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } catch (InvalidConfigurationException | IOException e) { - Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; + finalPlayerData.save(playerFile); + } catch (IOException | InvalidConfigurationException e) { + throw new RuntimeException(e); } - return true; } private Map convertSection(ConfigurationSection section) { @@ -554,4 +568,13 @@ public void clearAllCache() { globalProfileCache.invalidateAll(); profileCache.invalidateAll(); } + + @Override + public Map getCacheStats() { + Map stats = new HashMap<>(); + stats.put("configCache", configCache.stats()); + stats.put("globalProfileCache", globalProfileCache.stats()); + stats.put("profileCache", profileCache.stats()); + return stats; + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 57d253ec..e0faaabf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -3,6 +3,7 @@ import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.bukkit.OfflinePlayer; +import org.mvplugins.multiverse.inventories.share.Sharables; import java.util.HashMap; import java.util.Map; @@ -17,7 +18,7 @@ static PlayerProfile createPlayerProfile(ContainerType containerType, String con return new PlayerProfile(containerType, containerName, profileType, player); } - private final Map data = new HashMap<>(); + private final Map data = new HashMap<>(Sharables.all().size()); private final OfflinePlayer player; private final ContainerType containerType; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 04cf8ef5..3f05614d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.profile; +import com.google.common.cache.CacheStats; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; @@ -7,6 +8,7 @@ import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import java.io.IOException; +import java.util.Map; import java.util.UUID; import java.util.function.Predicate; @@ -122,7 +124,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { void clearProfileCache(ProfileKey key); /** - * Clears a single profile in cache. + * Clears all profiles in cache that match the predicate. */ void clearProfileCache(Predicate predicate); @@ -130,5 +132,12 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * Clears all profiles in cache. */ void clearAllCache(); + + /** + * Gets the cache stats for the profile data source. + * + * @return The cache stats. + */ + Map getCacheStats(); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 938d19b1..8af2ab26 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -13,6 +13,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; final class ProfileFileIO { @@ -54,4 +55,16 @@ public FileConfiguration call() throws Exception { void queueAction(Runnable action) { fileIOExecutorService.submit(action); } + + Future queueSupplier(Supplier supplier) { + return fileIOExecutorService.submit(supplier::get); + } + + T waitForData(Supplier supplier) { + try { + return queueSupplier(supplier).get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java index f0335e1c..ee01e459 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java @@ -1,6 +1,8 @@ package org.mvplugins.multiverse.inventories.profile; import com.google.common.base.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.bukkit.Bukkit; @@ -8,33 +10,24 @@ public final class ProfileKey { - public static ProfileKey createProfileKey(ContainerType containerType, String dataName, - ProfileType profileType, UUID playerUUID, String playerName) { + public static ProfileKey create( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID, + String playerName) { return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); } - public static ProfileKey createProfileKey(ContainerType containerType, String dataName, - ProfileType profileType, UUID playerUUID) { - return new ProfileKey(containerType, dataName, profileType, playerUUID); + public static ProfileKey create( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, Bukkit.getOfflinePlayer(playerUUID).getName()); } - public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType) { - return new ProfileKey(containerType, copyKey.getDataName(), copyKey.getProfileType(), copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ProfileType profileType) { - return new ProfileKey(copyKey.getContainerType(), copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType, - ProfileType profileType) { - return new ProfileKey(containerType, copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(PlayerProfile profile) { + public static ProfileKey fromPlayerProfile(PlayerProfile profile) { return new ProfileKey(profile.getContainerType(), profile.getContainerName(), profile.getProfileType(), profile.getPlayer().getUniqueId(), profile.getPlayer().getName()); } @@ -44,14 +37,7 @@ public static ProfileKey createProfileKey(PlayerProfile profile) { private final ProfileType profileType; private final String playerName; private final UUID playerUUID; - - private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - this.containerType = containerType; - this.dataName = dataName; - this.profileType = profileType; - this.playerUUID = playerUUID; - this.playerName = Bukkit.getOfflinePlayer(playerUUID).getName(); - } + private final int hashCode; private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID, String playerName) { @@ -60,6 +46,15 @@ private ProfileKey(ContainerType containerType, String dataName, ProfileType pro this.profileType = profileType; this.playerUUID = playerUUID; this.playerName = playerName; + this.hashCode = Objects.hashCode(getContainerType(), getDataName(), getProfileType(), getPlayerName(), getPlayerUUID()); + } + + public ProfileKey forProfileType(@Nullable ProfileType profileType) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); + } + + public ProfileKey forContainerType(@NotNull ContainerType containerType) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); } public ContainerType getContainerType() { @@ -85,8 +80,7 @@ public UUID getPlayerUUID() { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ProfileKey)) return false; - final ProfileKey that = (ProfileKey) o; + if (!(o instanceof ProfileKey that)) return false; return getContainerType() == that.getContainerType() && Objects.equal(getDataName(), that.getDataName()) && Objects.equal(getProfileType(), that.getProfileType()) && @@ -96,7 +90,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hashCode(getContainerType(), getDataName(), getProfileType(), getPlayerName(), getPlayerUUID()); + return hashCode; } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 9fc234c1..78fd90f8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -4,7 +4,6 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileKey; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileType; diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index f7e5c70b..6162324a 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.profile import com.dumptruckman.minecraft.util.Logging +import com.google.common.cache.CacheStats import org.bukkit.GameMode import org.bukkit.Material import org.bukkit.enchantments.Enchantment @@ -8,6 +9,7 @@ import org.bukkit.inventory.ItemStack import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.junit.jupiter.api.Test +import org.mockbukkit.mockbukkit.inventory.ItemStackMock import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit @@ -85,9 +87,6 @@ class FilePerformanceTest : TestWithMockBukkit() { } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - profileDataSource.clearAllCache() - Thread.sleep(800) // Wait for files to write finish - val startTime2 = System.nanoTime() for (i in 0..999) { val player = server.getPlayer(i) @@ -116,6 +115,12 @@ class FilePerformanceTest : TestWithMockBukkit() { } } Logging.info("Time taken: " + (System.nanoTime() - startTime3) / 1000000 + "ms") + + Thread.sleep(1000) // Wait for files to write finish + + val cacheStats = profileDataSource.getCacheStats() + Logging.info("Cache stats: $cacheStats") + } fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { From 9cf3295ed4c26eadff384ca473777c9a3dc32e21 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 16 Feb 2025 11:35:47 +0800 Subject: [PATCH 078/180] Add cache command --- .../inventories/commands/CacheCommand.java | 64 +++++++++++++++++++ .../multiverse/inventories/InjectionTest.kt | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java new file mode 100644 index 00000000..e8138afc --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -0,0 +1,64 @@ +package org.mvplugins.multiverse.inventories.commands; + +import com.google.common.cache.CacheStats; +import org.bukkit.entity.Player; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Flags; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; + +import java.util.Map; + +@Service +@CommandAlias("mvinv") +final class CacheCommand extends InventoriesCommand { + + private final ProfileDataSource profileDataSource; + + @Inject + CacheCommand(@NotNull MVCommandManager commandManager, @NotNull ProfileDataSource profileDataSource) { + super(commandManager); + this.profileDataSource = profileDataSource; + } + + @Subcommand("cache stats") + @CommandPermission("multiverse.inventories.cache.stats") + void onCacheStatsCommand(MVCommandIssuer issuer) { + Map stats = this.profileDataSource.getCacheStats(); + for (Map.Entry entry : stats.entrySet()) { + issuer.sendInfo("Cache: " + entry.getKey()); + issuer.sendInfo(" hits count: " + entry.getValue().hitCount()); + issuer.sendInfo(" misses count: " + entry.getValue().missCount()); + issuer.sendInfo(" loads count: " + entry.getValue().loadCount()); + issuer.sendInfo(" avg load time: " + entry.getValue().averageLoadPenalty()); + issuer.sendInfo(" exceptions: " + entry.getValue().loadExceptionCount()); + issuer.sendInfo(" evictions: " + entry.getValue().evictionCount()); + } + } + + @Subcommand("cache invalidate all") + @CommandPermission("multiverse.inventories.cache.invalidate") + void onCacheClearAllCommand(MVCommandIssuer issuer) { + this.profileDataSource.clearAllCache(); + } + + @Subcommand("cache invalidate player") + @CommandPermission("multiverse.inventories.cache.invalidate") + @CommandCompletion("@players") + @Syntax("") + void onCacheClearProfileCommand( + MVCommandIssuer issuer, + + @Flags("resolve=issuerAware") + Player player) { + this.profileDataSource.clearProfileCache(key -> key.getPlayerUUID().equals(player.getUniqueId())); + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 085396e2..7e91eb2c 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(12, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(14, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From 40e9b337034b7d23dc488a12f108b909d270bbef Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 16 Feb 2025 15:21:09 +0800 Subject: [PATCH 079/180] Refactor profile source with cache info --- .../inventories/MultiverseInventories.java | 2 +- .../inventories/commands/CacheCommand.java | 17 +- .../perworldinventory/PwiImportHelper.java | 8 +- .../handleshare/ShareHandleListener.java | 14 +- .../profile/FlatFileProfileDataSource.java | 377 +++++++++--------- .../inventories/profile/GlobalProfile.java | 4 +- .../profile/ProfileDataSource.java | 76 ++-- .../inventories/profile/ProfileFileIO.java | 56 +-- .../inventories/profile/ProfileKey.java | 9 + .../profile/container/ProfileContainer.java | 20 +- .../handleshare/ShareHandlingUpdaterTest.kt | 7 +- .../profile/FilePerformanceTest.kt | 21 +- 12 files changed, 297 insertions(+), 314 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index e0e3c620..513dee1f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -142,7 +142,7 @@ public void onDisable() { profileContainerStoreProvider.get().getStore(ContainerType.WORLD) .getContainer(world) .getPlayerData(player))); - profileDataSource.get().setLoadOnLogin(player.getUniqueId(), true); + profileDataSource.get().modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index e8138afc..83f5a657 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -34,13 +34,16 @@ final class CacheCommand extends InventoriesCommand { void onCacheStatsCommand(MVCommandIssuer issuer) { Map stats = this.profileDataSource.getCacheStats(); for (Map.Entry entry : stats.entrySet()) { - issuer.sendInfo("Cache: " + entry.getKey()); - issuer.sendInfo(" hits count: " + entry.getValue().hitCount()); - issuer.sendInfo(" misses count: " + entry.getValue().missCount()); - issuer.sendInfo(" loads count: " + entry.getValue().loadCount()); - issuer.sendInfo(" avg load time: " + entry.getValue().averageLoadPenalty()); - issuer.sendInfo(" exceptions: " + entry.getValue().loadExceptionCount()); - issuer.sendInfo(" evictions: " + entry.getValue().evictionCount()); + issuer.sendMessage("Cache: " + entry.getKey()); + issuer.sendMessage(" hits count: " + entry.getValue().hitCount()); + issuer.sendMessage(" misses count: " + entry.getValue().missCount()); + issuer.sendMessage(" loads count: " + entry.getValue().loadCount()); + issuer.sendMessage(" exceptions: " + entry.getValue().loadExceptionCount()); + issuer.sendMessage(" evictions: " + entry.getValue().evictionCount()); + issuer.sendMessage(" hit rate: " + entry.getValue().hitRate() * 100 + "%"); + issuer.sendMessage(" miss rate: " + entry.getValue().missRate() * 100 + "%"); + issuer.sendMessage(" avg load penalty: " + entry.getValue().averageLoadPenalty() / 1000000 + "ms"); + issuer.sendMessage("--------"); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 63563ef4..2666ea99 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -205,11 +205,11 @@ private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throw private List getMVPlayerData( @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { List profiles = new ArrayList<>(); - profiles.add(profileDataSource.getPlayerData( - ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())); + profiles.add(profileDataSource.getPlayerData(org.mvplugins.multiverse.inventories.profile.ProfileKey + .create(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); for (var worldName : group.getWorlds()) { - profiles.add(profileDataSource.getPlayerData( - ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())); + profiles.add(profileDataSource.getPlayerData(org.mvplugins.multiverse.inventories.profile.ProfileKey + .create(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); } return profiles; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 085184ee..9299e0db 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -151,7 +151,7 @@ void playerJoin(final PlayerJoinEvent event) { // Just in case AsyncPlayerPreLoginEvent was still the old name verifyCorrectPlayerName(player.getUniqueId(), player.getName()); - final GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId()); + final GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player); final String world = globalProfile.getLastWorld(); if (config.usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile( @@ -167,7 +167,7 @@ void playerJoin(final PlayerJoinEvent event) { } private void verifyCorrectPlayerName(UUID uuid, String name) { - profileDataSource.getExistingGlobalProfile(name, uuid).peek(globalProfile -> { + profileDataSource.getExistingGlobalProfile(uuid, name).peek(globalProfile -> { if (globalProfile.getLastKnownName().equals(name)) { return; } @@ -196,7 +196,8 @@ private void verifyCorrectPlayerName(UUID uuid, String name) { void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); - profileDataSource.updateLastWorld(player.getUniqueId(), world); + GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player); + globalProfile.setLastWorld(world); if (config.usingLoggingSaveLoad()) { ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile( Sharables.allOf(), @@ -204,8 +205,9 @@ void playerQuit(final PlayerQuitEvent event) { .getContainer(world) .getPlayerData(player) )); - profileDataSource.setLoadOnLogin(player.getUniqueId(), true); + globalProfile.setLoadOnLogin(true); } + profileDataSource.updateGlobalProfile(globalProfile); SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); } @@ -261,7 +263,7 @@ void playerChangedWorld(PlayerChangedWorldEvent event) { long startTime = System.nanoTime(); new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); - profileDataSource.updateLastWorld(player.getUniqueId(), toWorld.getName()); + profileDataSource.modifyGlobalProfile(player, profile -> profile.setLastWorld(toWorld.getName())); Logging.finest("WorldChangeShareHandler took " + (System.nanoTime() - startTime) / 1000000 + " ms."); } @@ -323,7 +325,7 @@ void playerRespawn(PlayerRespawnEvent event) { () -> verifyCorrectWorld( player, player.getWorld().getName(), - profileDataSource.getGlobalProfile(player.getName(), player.getUniqueId())), + profileDataSource.getGlobalProfile(player)), 2L); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index f617ba05..b7d32222 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -9,11 +9,11 @@ import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.InvalidConfigurationException; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.ProfileEntry; import org.mvplugins.multiverse.inventories.share.Sharable; @@ -29,8 +29,12 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.logging.Level; @@ -97,7 +101,17 @@ final class FlatFileProfileDataSource implements ProfileDataSource { } /** - * Retrieves the data file for a player based on a given world/group name, creating it if necessary. + * Retrieves the data file for a player based on a given world/group name. + * + * @param profileKey The profile target to get the file + * @return The data file for a player. + */ + private File getPlayerFile(ProfileKey profileKey) { + return getPlayerFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); + } + + /** + * Retrieves the data file for a player based on a given world/group name. * * @param type Indicates whether data is for group or world. * @param dataName The name of the group or world. @@ -118,53 +132,46 @@ private File getProfileContainerFolder(ContainerType type, String folderName) { default -> new File(this.worldFolder, folderName); }; - if (!folder.exists()) { - folder.mkdirs(); + if (!folder.exists() && !folder.mkdirs()) { + Logging.severe("Could not create profile container folder!"); } return folder; } + private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playerFile) { + ProfileKey fileProfileKey = profileKey.forProfileType(null); + return Try.of(() -> + configCache.get(fileProfileKey, () -> playerFile.exists() + ? playerProfileIO.getConfigHandleNow(playerFile) + : new JsonConfiguration()) + ).getOrElseThrow(e -> { + Logging.severe("Could not load profile data for player: " + fileProfileKey); + return new RuntimeException(e); + }); + } + /** * {@inheritDoc} */ @Override - public void updatePlayerData(PlayerProfile playerProfile) { - playerProfileIO.queueAction(() -> processProfileWrite(playerProfile.clone())); + public Future updatePlayerData(PlayerProfile playerProfile) { + return playerProfileIO.queueAction(() -> processUpdatePlayerData(playerProfile.clone())); } - private void processProfileWrite(PlayerProfile playerProfile) { - File playerFile = getPlayerFile( - playerProfile.getContainerType(), - playerProfile.getContainerName(), - playerProfile.getPlayer().getName() - ); - try { - ProfileKey fileProfileKey = ProfileKey.create( - playerProfile.getContainerType(), - playerProfile.getContainerName(), - null, - playerProfile.getPlayer().getUniqueId(), - playerProfile.getPlayer().getName()); - FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); - if (playerData == null) { - playerData = playerFile.exists() - ? playerProfileIO.getConfigHandleNow(playerFile) - : new JsonConfiguration(); - configCache.put(fileProfileKey, playerData); - } - Map serializedData = serializePlayerProfile(playerProfile); - if (serializedData.isEmpty()) { - return; - } - playerData.createSection(playerProfile.getProfileType().getName(), serializedData); - playerData.save(playerFile); - } catch (IOException e) { + private void processUpdatePlayerData(PlayerProfile playerProfile) { + ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); + File playerFile = getPlayerFile(profileKey); + FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); + Map serializedData = serializePlayerProfile(playerProfile); + if (serializedData.isEmpty()) { + return; + } + playerData.createSection(playerProfile.getProfileType().getName(), serializedData); + Try.run(() -> playerData.save(playerFile)).onFailure(e -> { Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); Logging.severe(e.getMessage()); - } catch (Exception e) { - Logging.severe("Unknown error while attempting to write profile data.", e); - } + }); } private Map serializePlayerProfile(PlayerProfile playerProfile) { @@ -204,38 +211,28 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) * {@inheritDoc} */ @Override - public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.create(containerType, dataName, profileType, playerUUID)); - } - - private PlayerProfile getPlayerData(ProfileKey key) { - PlayerProfile cached = profileCache.getIfPresent(key); - if (cached != null) { - return cached; - } - File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); - if (!playerFile.exists()) { - PlayerProfile playerProfile = PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), - key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID())); - profileCache.put(key, playerProfile); - return playerProfile; + public PlayerProfile getPlayerData(ProfileKey key) { + try { + return profileCache.get(key, () -> { + File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); + if (!playerFile.exists()) { + return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), + key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID())); + } + return playerProfileIO.waitForData(() -> getPlayerDataFromDisk(key, playerFile)); + }); + } catch (ExecutionException e) { + Logging.severe("Could not get data for player: " + key.getPlayerName() + + " for " + key.getContainerType().toString() + ": " + key.getDataName()); + throw new RuntimeException(e); } - return playerProfileIO.waitForData(() -> loadPlayerData(key, playerFile)); } - private PlayerProfile loadPlayerData(ProfileKey key, File playerFile) { + private PlayerProfile getPlayerDataFromDisk(ProfileKey key, File playerFile) { + FileConfiguration playerData = getOrLoadProfileFile(key, playerFile); + // Migrate from none profile-type data - ProfileKey fileProfileKey = key.forProfileType(null); - FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); - if (playerData == null) { - try { - playerData = playerProfileIO.getConfigHandleNow(playerFile); - } catch (IOException | InvalidConfigurationException e) { - throw new RuntimeException(e); - } - configCache.put(fileProfileKey, playerData); - } - if (convertConfig(playerData)) { + if (migrateToProfileType(playerData)) { try { playerData.save(playerFile); } catch (IOException e) { @@ -254,6 +251,34 @@ private PlayerProfile loadPlayerData(ProfileKey key, File playerFile) { return playerProfile; } + @Deprecated + private boolean migrateToProfileType(FileConfiguration config) { + ConfigurationSection section = config.getConfigurationSection(DataStrings.PLAYER_DATA); + if (section == null) { + return false; + } + config.set(ProfileTypes.SURVIVAL.getName(), section); + config.set(ProfileTypes.CREATIVE.getName(), section); + config.set(ProfileTypes.ADVENTURE.getName(), section); + config.set(DataStrings.PLAYER_DATA, null); + Logging.finer("Migrated old player data to new multi-profile format"); + return true; + } + + private Map convertSection(ConfigurationSection section) { + Set keys = section.getKeys(false); + Map resultMap = new HashMap<>(keys.size()); + for (String key : keys) { + Object obj = section.get(key); + if (obj instanceof ConfigurationSection) { + resultMap.put(key, convertSection((ConfigurationSection) obj)); + } else { + resultMap.put(key, obj); + } + } + return resultMap; + } + private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); @@ -327,111 +352,119 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile parsePlayerStatsIntoProfile(jsonStats, profile); } - @Deprecated - private boolean convertConfig(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection(DataStrings.PLAYER_DATA); - if (section == null) { - return false; - } - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set(DataStrings.PLAYER_DATA, null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; - } - /** * {@inheritDoc} */ @Override - public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - ProfileKey profileKey = ProfileKey.create(containerType, dataName, profileType, playerUUID); + public boolean removePlayerData(ProfileKey profileKey) { if (profileKey.getProfileType() == null) { - try { - File playerFile = getPlayerFile(containerType, dataName, profileKey.getPlayerName()); - configCache.invalidate(profileKey); - profileCache.invalidateAll(Sets.filter( - profileCache.asMap().keySet(), - key -> key.getPlayerUUID().equals(playerUUID) - && key.getContainerType().equals(containerType) - && key.getDataName().equals(dataName) - )); - playerProfileIO.queueAction(playerFile::delete); - } catch (Exception ignore) { + clearProfileCache(key -> key.getPlayerUUID().equals(profileKey.getPlayerUUID()) + && key.getContainerType().equals(profileKey.getContainerType()) + && key.getDataName().equals(profileKey.getDataName())); + File playerFile = getPlayerFile(profileKey); + if (!playerFile.exists()) { Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() - + " in " + containerType.name().toLowerCase() + " " + dataName); + + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); return false; } + playerProfileIO.queueAction(playerFile::delete); + return true; } - try { - profileCache.invalidate(profileKey); - playerProfileIO.queueAction(() -> processProfileRemove(profileKey)); - } catch (Exception e) { - Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; - } + profileCache.invalidate(profileKey); + playerProfileIO.queueAction(() -> processRemovePlayerData(profileKey)); return true; } - private void processProfileRemove(ProfileKey profileKey) { + private void processRemovePlayerData(ProfileKey profileKey) { try { File playerFile = getPlayerFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); - ProfileKey fileProfileKey = profileKey.forProfileType(null); - FileConfiguration playerData = configCache.getIfPresent(fileProfileKey); - if (playerData == null) { - if (!playerFile.exists()) { - return; - } - playerData = playerProfileIO.getConfigHandleNow(playerFile); - configCache.put(fileProfileKey, playerData); - } + FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); playerData.set(profileKey.getProfileType().getName(), null); - FileConfiguration finalPlayerData = playerData; - finalPlayerData.save(playerFile); - } catch (IOException | InvalidConfigurationException e) { - throw new RuntimeException(e); + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() + + " for " + profileKey.getContainerType() + ": " + profileKey.getDataName()); + Logging.severe(e.getMessage()); } } - private Map convertSection(ConfigurationSection section) { - Map resultMap = new HashMap(); - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj instanceof ConfigurationSection) { - resultMap.put(key, convertSection((ConfigurationSection) obj)); - } else { - resultMap.put(key, obj); + /** + * {@inheritDoc} + */ + @Override + public void migratePlayerData(String oldName, String newName, UUID uuid) throws IOException { + clearPlayerCache(uuid); + + File[] worldFolders = worldFolder.listFiles(File::isDirectory); + if (worldFolders == null) { + throw new IOException("Could not enumerate world folders"); + } + File[] groupFolders = groupFolder.listFiles(File::isDirectory); + if (groupFolders == null) { + throw new IOException("Could not enumerate group folders"); + } + + migrateForContainerType(worldFolders, ContainerType.WORLD, oldName, newName); + migrateForContainerType(groupFolders, ContainerType.GROUP, oldName, newName); + } + + private void migrateForContainerType(File[] folders, ContainerType containerType, String oldName, String newName) { + for (File folder : folders) { + File oldNameFile = getPlayerFile(containerType, folder.getName(), oldName); + File newNameFile = getPlayerFile(containerType, folder.getName(), newName); + if (!oldNameFile.exists()) { + Logging.fine("No old data for player %s in %s %s to migrate.", + oldName, containerType.name(), folder.getName()); + continue; + } + if (newNameFile.exists()) { + Logging.warning("Data already exists for player %s in %s %s. Not migrating.", + newName, containerType.name(), folder.getName()); + continue; + } + if (!oldNameFile.renameTo(newNameFile)) { + Logging.warning("Could not rename old data file for player %s in %s %s to %s.", + oldName, containerType.name(), folder.getName(), newName); + continue; } + Logging.fine("Migrated data for player %s in %s %s to %s.", + oldName, containerType.name(), folder.getName(), newName); } - return resultMap; } + @NotNull @Override public GlobalProfile getGlobalProfile(UUID playerUUID) { return getGlobalProfile(Bukkit.getOfflinePlayer(playerUUID)); } + @NotNull @Override public GlobalProfile getGlobalProfile(OfflinePlayer player) { - return getGlobalProfile(player.getName(), player.getUniqueId()); + return getGlobalProfile(player.getUniqueId(), player.getName()); } + @NotNull @Override - public @NotNull GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { - return getExistingGlobalProfile(playerName, playerUUID) - .getOrElse(() -> GlobalProfile.createGlobalProfile(playerName, playerUUID)); + public GlobalProfile getGlobalProfile(UUID playerUUID, String playerName) { + try { + return globalProfileCache.get(playerUUID, () -> getGlobalProfileFromDisk(playerUUID, playerName)); + } catch (ExecutionException e) { + Logging.severe("Unable to get global profile for player: " + playerName); + throw new RuntimeException(e); + } } @Override - public @NotNull Option getExistingGlobalProfile(String playerName, UUID playerUUID) { - GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); - if (cached != null) { - return Option.of(cached); + public @NotNull Option getExistingGlobalProfile(UUID playerUUID, String playerName) { + File uuidFile = getGlobalFile(playerUUID.toString()); + if (!uuidFile.exists()) { + return Option.none(); } + return Option.of(getGlobalProfile(playerUUID, playerName)); + } + private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName) { // Migrate from player name to uuid profile file File legacyFile = getGlobalFile(playerName); if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { @@ -440,12 +473,10 @@ public GlobalProfile getGlobalProfile(OfflinePlayer player) { // Load from existing profile file File uuidFile = getGlobalFile(playerUUID.toString()); - if (uuidFile.exists()) { - GlobalProfile globalProfile = loadGlobalProfile(uuidFile, playerName, playerUUID); - globalProfileCache.put(playerUUID, globalProfile); - return Option.of(globalProfile); + if (!uuidFile.exists()) { + return GlobalProfile.createGlobalProfile(playerUUID, playerName); } - return Option.none(); + return loadGlobalProfile(uuidFile, playerName, playerUUID); } private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { @@ -461,12 +492,25 @@ private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID return GlobalProfile.deserialize(playerName, playerUUID, section); } + public Future modifyGlobalProfile(UUID playerUUID, Consumer consumer) { + return modifyGlobalProfile(getGlobalProfile(playerUUID), consumer); + } + + public Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { + return modifyGlobalProfile(getGlobalProfile(offlinePlayer), consumer); + } + + private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { + consumer.accept(globalProfile); + return updateGlobalProfile(globalProfile); + } + /** * {@inheritDoc} */ @Override - public void updateGlobalProfile(GlobalProfile globalProfile) { - globalProfileIO.queueAction(() -> processGlobalProfileWrite(globalProfile)); + public Future updateGlobalProfile(GlobalProfile globalProfile) { + return globalProfileIO.queueAction(() -> processGlobalProfileWrite(globalProfile)); } private void processGlobalProfileWrite(GlobalProfile globalProfile) { @@ -491,61 +535,6 @@ private File getGlobalFile(String fileName) { return new File(playerFolder, fileName + JSON); } - @Override - public void updateLastWorld(UUID playerUUID, String worldName) { - GlobalProfile globalProfile = getGlobalProfile(playerUUID); - globalProfile.setLastWorld(worldName); - updateGlobalProfile(globalProfile); - } - - @Override - public void setLoadOnLogin(final UUID playerUUID, final boolean loadOnLogin) { - final GlobalProfile globalProfile = getGlobalProfile(playerUUID); - globalProfile.setLoadOnLogin(loadOnLogin); - updateGlobalProfile(globalProfile); - } - - @Override - public void migratePlayerData(String oldName, String newName, UUID uuid) throws IOException { - clearPlayerCache(uuid); - - File[] worldFolders = worldFolder.listFiles(File::isDirectory); - if (worldFolders == null) { - throw new IOException("Could not enumerate world folders"); - } - File[] groupFolders = groupFolder.listFiles(File::isDirectory); - if (groupFolders == null) { - throw new IOException("Could not enumerate group folders"); - } - - migrateForContainerType(worldFolders, ContainerType.WORLD, oldName, newName); - migrateForContainerType(groupFolders, ContainerType.GROUP, oldName, newName); - } - - private void migrateForContainerType(File[] folders, ContainerType containerType, String oldName, String newName) throws IOException { - for (File folder : folders) { - File oldNameFile = getPlayerFile(containerType, folder.getName(), oldName); - File newNameFile = getPlayerFile(containerType, folder.getName(), newName); - if (!oldNameFile.exists()) { - Logging.fine("No old data for player %s in %s %s to migrate.", - oldName, containerType.name(), folder.getName()); - continue; - } - if (newNameFile.exists()) { - Logging.warning("Data already exists for player %s in %s %s. Not migrating.", - newName, containerType.name(), folder.getName()); - continue; - } - if (!oldNameFile.renameTo(newNameFile)) { - Logging.warning("Could not rename old data file for player %s in %s %s to %s.", - oldName, containerType.name(), folder.getName(), newName); - continue; - } - Logging.fine("Migrated data for player %s in %s %s to %s.", - oldName, containerType.name(), folder.getName(), newName); - } - } - void clearPlayerCache(UUID playerUUID) { clearProfileCache(key -> key.getPlayerUUID().equals(playerUUID)); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index fb80d856..445b2d2a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -26,11 +26,11 @@ static GlobalProfile createGlobalProfile(OfflinePlayer player) { /** * Creates a global profile object for the given player with default values. * - * @param playerName the player to create the profile object for. * @param playerUUID the UUID of the player to create the profile for. + * @param playerName the player to create the profile object for. * @return a new GlobalProfile for the given player. */ - static GlobalProfile createGlobalProfile(String playerName, UUID playerUUID) { + static GlobalProfile createGlobalProfile(UUID playerUUID, String playerName) { return new GlobalProfile(playerName, playerUUID); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 3f05614d..197ec452 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -5,11 +5,12 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Option; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import java.io.IOException; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Future; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -24,31 +25,34 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * * @param playerProfile The profile for the player that is being updated. */ - void updatePlayerData(PlayerProfile playerProfile); + Future updatePlayerData(PlayerProfile playerProfile); /** * Retrieves a PlayerProfile from the data source. * - * @param containerType The type of container this profile is part of, world or group. - * @param dataName World/Group to retrieve from. - * @param profileType The type of profile to load data for, typically based on game mode. - * @param playerUUID UUID of the player to retrieve for. + * @param profileKey The key of the profile to retrieve. * @return The player as returned from data. If no data was found, a new PlayerProfile will be * created. */ - PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); + PlayerProfile getPlayerData(ProfileKey profileKey); /** * Removes the persisted data for a player for a specific profile. * - * @param containerType The type of container this profile is part of, world or group. - * @param dataName The name of the world/group the player's data is associated with. - * @param profileType The type of profile we're removing, as per {@link ProfileType}. If null, this will remove - * remove all profile types. - * @param playerUUID The UUID of the player whose data is being removed. + * @param profileKey The key of the profile to remove. * @return True if successfully removed. */ - boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); + boolean removePlayerData(ProfileKey profileKey); + + /** + * Copies all the data belonging to oldName to newName and removes the old data. + * + * @param oldName the previous name of the player. + * @param newName the new name of the player. + * @param playerUUID the UUID of the player. + * @throws IOException Thrown if something goes wrong while migrating the files. + */ + void migratePlayerData(String oldName, String newName, UUID playerUUID) throws IOException; /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -56,7 +60,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param playerUUID The UUID of the player. * @return The global profile for the specified player. */ - GlobalProfile getGlobalProfile(UUID playerUUID); + @NotNull GlobalProfile getGlobalProfile(UUID playerUUID); /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -64,59 +68,37 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param player The player. * @return The global profile for the specified player. */ - GlobalProfile getGlobalProfile(OfflinePlayer player); + @NotNull GlobalProfile getGlobalProfile(OfflinePlayer player); /** * Retrieves the global profile for a player which contains meta-data for the player. * Creates the profile if it doesn't exist. * - * @param playerName The name of the player. * @param playerUUID The UUID of the player. + * @param playerName The name of the player. * @return The global profile for the specified player. */ - @NotNull GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); + @NotNull GlobalProfile getGlobalProfile(UUID playerUUID, String playerName); /** * Retrieves the global profile for a player which contains meta-data for the player if it exists. * - * @param playerName The name of the player. - * @param playerUUID The UUID of the player. + * @param playerUUID The UUID of the player. + * @param playerName The name of the player. * @return The global profile for the specified player or empty if it doesn't exist. */ - @NotNull Option getExistingGlobalProfile(String playerName, UUID playerUUID); - - /** - * Update the file for a player's global profile. - * - * @param globalProfile The GlobalProfile object to update the file for. - */ - void updateGlobalProfile(GlobalProfile globalProfile); + @NotNull Option getExistingGlobalProfile(UUID playerUUID, String playerName); - /** - * A convenience method to update the GlobalProfile of a player with a specified world. - * - * @param playerUUID The player whose global profile this will update. - * @param worldName The world to update the global profile with. - */ - void updateLastWorld(UUID playerUUID, String worldName); + Future modifyGlobalProfile(UUID playerUUID, Consumer consumer); - /** - * A convenience method for setting whether player data should be loaded on login for the specified player. - * - * @param playerUUID The player whose data should be loaded. - * @param loadOnLogin Whether or not to load on login. - */ - void setLoadOnLogin(UUID playerUUID, boolean loadOnLogin); + Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer); /** - * Copies all the data belonging to oldName to newName and removes the old data. + * Update the file for a player's global profile. * - * @param oldName the previous name of the player. - * @param newName the new name of the player. - * @param playerUUID the UUID of the player. - * @throws IOException Thrown if something goes wrong while migrating the files. + * @param globalProfile The GlobalProfile object to update the file for. */ - void migratePlayerData(String oldName, String newName, UUID playerUUID) throws IOException; + Future updateGlobalProfile(GlobalProfile globalProfile); /** * Clears a single profile in cache. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 8af2ab26..4647848e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -1,11 +1,11 @@ package org.mvplugins.multiverse.inventories.profile; import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import org.bukkit.configuration.InvalidConfigurationException; +import com.dumptruckman.minecraft.util.Logging; import org.bukkit.configuration.file.FileConfiguration; +import org.mvplugins.multiverse.external.vavr.control.Try; import java.io.File; -import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -13,7 +13,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.function.Supplier; final class ProfileFileIO { @@ -23,48 +22,33 @@ final class ProfileFileIO { fileIOExecutorService = Executors.newSingleThreadExecutor(); } - FileConfiguration waitForConfigHandle(File file) { - Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - try { - return future.get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); - } + Future queueAction(Runnable action) { + return fileIOExecutorService.submit(action); } - FileConfiguration getConfigHandleNow(File file) throws IOException, InvalidConfigurationException { - JsonConfiguration jsonConfiguration = new JsonConfiguration(); - jsonConfiguration.options().continueOnSerializationError(true); - jsonConfiguration.load(file); - return jsonConfiguration; + Future queueCallable(Callable callable) { + return fileIOExecutorService.submit(callable); } - private class ConfigLoader implements Callable { - private final File file; - - private ConfigLoader(File file) { - this.file = file; - } - - @Override - public FileConfiguration call() throws Exception { - return getConfigHandleNow(file); + T waitForData(Callable callable) { + try { + return queueCallable(callable).get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); } } - void queueAction(Runnable action) { - fileIOExecutorService.submit(action); - } - - Future queueSupplier(Supplier supplier) { - return fileIOExecutorService.submit(supplier::get); + FileConfiguration waitForConfigHandle(File file) { + return waitForData(() -> getConfigHandleNow(file)); } - T waitForData(Supplier supplier) { - try { - return queueSupplier(supplier).get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { + FileConfiguration getConfigHandleNow(File file) { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(true); + Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { + Logging.severe("Could not load file: " + file); throw new RuntimeException(e); - } + }); + return jsonConfiguration; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java index ee01e459..81d8c3b9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.profile; import com.google.common.base.Objects; +import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -19,6 +20,14 @@ public static ProfileKey create( return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); } + public static ProfileKey create( + ContainerType containerType, + String dataName, + ProfileType profileType, + OfflinePlayer offlinePlayer) { + return new ProfileKey(containerType, dataName, profileType, offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + public static ProfileKey create( ContainerType containerType, String dataName, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 78fd90f8..748b9748 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -4,6 +4,7 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileKey; import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileType; @@ -75,8 +76,11 @@ public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player Map profileMap = this.getPlayerData(player.getName()); PlayerProfile playerProfile = profileMap.get(profileType); if (playerProfile == null) { - playerProfile = profileDataSource.getPlayerData(getContainerType(), - getContainerName(), profileType, player.getUniqueId()); + playerProfile = profileDataSource.getPlayerData(ProfileKey.create( + getContainerType(), + getContainerName(), + profileType, + player)); Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); profileMap.put(profileType, playerProfile); @@ -100,7 +104,11 @@ public void addPlayerData(PlayerProfile playerProfile) { */ public void removeAllPlayerData(OfflinePlayer player) { this.getPlayerData(player.getName()).clear(); - profileDataSource.removePlayerData(getContainerType(), getContainerName(), null, player.getUniqueId()); + profileDataSource.removePlayerData(ProfileKey.create( + getContainerType(), + getContainerName(), + null, + player.getUniqueId())); } /** @@ -111,7 +119,11 @@ public void removeAllPlayerData(OfflinePlayer player) { */ public void removePlayerData(ProfileType profileType, OfflinePlayer player) { this.getPlayerData(player.getName()).remove(profileType); - profileDataSource.removePlayerData(getContainerType(), getContainerName(), profileType, player.getUniqueId()); + profileDataSource.removePlayerData(ProfileKey.create( + getContainerType(), + getContainerName(), + profileType, + player.getUniqueId())); } /** diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index 7e7718bf..45002e5d 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -3,6 +3,7 @@ package org.mvplugins.multiverse.inventories.handleshare import org.mockbukkit.mockbukkit.entity.PlayerMock import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.ProfileDataSource +import org.mvplugins.multiverse.inventories.profile.ProfileKey import org.mvplugins.multiverse.inventories.profile.ProfileTypes import org.mvplugins.multiverse.inventories.profile.container.ContainerType import org.mvplugins.multiverse.inventories.share.Sharables @@ -28,7 +29,8 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { player.health = 4.4 player.maxHealth = 15.1 - val playerProfile = profileDataSource.getPlayerData(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + val playerProfile = profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfile)) assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) @@ -37,7 +39,8 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { @Test fun `Test updating player`() { - val playerProfile = profileDataSource.getPlayerData(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + val playerProfile = profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) playerProfile.set(Sharables.HEALTH, 4.4) playerProfile.set(Sharables.MAX_HEALTH, 15.1) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 6162324a..0825016c 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -41,7 +41,7 @@ class FilePerformanceTest : TestWithMockBukkit() { fun `Test 10K global profiles`() { val startTime = System.nanoTime() for (i in 0..9999) { - val globalProfile = profileDataSource.getGlobalProfile("player-$i", UUID.randomUUID()) + val globalProfile = profileDataSource.getGlobalProfile(UUID.randomUUID()) globalProfile.setLoadOnLogin(true) profileDataSource.updateGlobalProfile(globalProfile) } @@ -52,7 +52,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val startTime2 = System.nanoTime() for (i in 0..9999) { - val globalProfile = profileDataSource.getGlobalProfile("player-$i", UUID.randomUUID()) + val globalProfile = profileDataSource.getGlobalProfile(UUID.randomUUID()) globalProfile.setLoadOnLogin(false) profileDataSource.updateGlobalProfile(globalProfile) } @@ -67,7 +67,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { val playerProfile = profileDataSource.getPlayerData( - ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId) + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) playerProfile.set(Sharables.HEALTH, 5.0) playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) playerProfile.set(Sharables.INVENTORY, arrayOf( @@ -92,8 +92,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { val playerProfile = profileDataSource.getPlayerData( - ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId - ) + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) } @@ -105,11 +104,9 @@ class FilePerformanceTest : TestWithMockBukkit() { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { profileDataSource.removePlayerData( - ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId - ) + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) val playerProfile = profileDataSource.getPlayerData( - ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId - ) + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) assertNull(playerProfile.get(Sharables.HEALTH)) assertNull(playerProfile.get(Sharables.OFF_HAND)) } @@ -119,8 +116,10 @@ class FilePerformanceTest : TestWithMockBukkit() { Thread.sleep(1000) // Wait for files to write finish val cacheStats = profileDataSource.getCacheStats() - Logging.info("Cache stats: $cacheStats") - + Logging.info(cacheStats.values.toString()) + for (cacheStat in cacheStats) { + Logging.info(cacheStat.key + ": " + cacheStat.value.averageLoadPenalty() / 1000000 + "ms") + } } fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { From d2be8ef0ca9f2a25757ca57766f6a7edefc6f16c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 11:40:04 +0800 Subject: [PATCH 080/180] Add health stats test on world change --- .../multiverse/inventories/handleshare/WorldChangeTest.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt index 6cb4b5bd..2f14b0d7 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt @@ -28,13 +28,21 @@ class WorldChangeTest : TestWithMockBukkit() { Logging.fine("player world: " + server.getPlayer("Benji_0224")?.world?.name) val stack = ItemStack.of(Material.STONE_BRICKS, 64) player.inventory.setItem(0, stack) + player.health = 5.5 + val startTime = System.nanoTime() server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + server.getWorld("world4")?.let { player.teleport(it.spawnLocation) } assertNotEquals(stack, player.inventory.getItem(0)) + assertNotEquals(5.5, player.health) + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") } From de6a720cb76113438a3801946e07988e194f8d65 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 11:40:32 +0800 Subject: [PATCH 081/180] Remove useless put to cache --- .../inventories/profile/FlatFileProfileDataSource.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index b7d32222..1a44164d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -246,9 +246,7 @@ private PlayerProfile getPlayerDataFromDisk(ProfileKey key, File playerFile) { if (section == null) { section = playerData.createSection(key.getProfileType().getName()); } - PlayerProfile playerProfile = deserializePlayerProfile(key, convertSection(section)); - profileCache.put(key, playerProfile); - return playerProfile; + return deserializePlayerProfile(key, convertSection(section)); } @Deprecated From b41e21fd9377611b1208938402fa684af18d492e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 12:02:05 +0800 Subject: [PATCH 082/180] Return Future for threaded profile task --- .../profile/FlatFileProfileDataSource.java | 21 +++++++++---------- .../profile/ProfileDataSource.java | 10 ++++----- .../inventories/profile/ProfileFileIO.java | 4 ++-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 1a44164d..88123a38 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -154,7 +155,7 @@ private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playe * {@inheritDoc} */ @Override - public Future updatePlayerData(PlayerProfile playerProfile) { + public Future updatePlayerData(PlayerProfile playerProfile) { return playerProfileIO.queueAction(() -> processUpdatePlayerData(playerProfile.clone())); } @@ -354,7 +355,7 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile * {@inheritDoc} */ @Override - public boolean removePlayerData(ProfileKey profileKey) { + public Future removePlayerData(ProfileKey profileKey) { if (profileKey.getProfileType() == null) { clearProfileCache(key -> key.getPlayerUUID().equals(profileKey.getPlayerUUID()) && key.getContainerType().equals(profileKey.getContainerType()) @@ -363,14 +364,12 @@ public boolean removePlayerData(ProfileKey profileKey) { if (!playerFile.exists()) { Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); - return false; + return CompletableFuture.completedFuture(null); } - playerProfileIO.queueAction(playerFile::delete); - return true; + return playerProfileIO.queueAction(playerFile::delete); } profileCache.invalidate(profileKey); - playerProfileIO.queueAction(() -> processRemovePlayerData(profileKey)); - return true; + return playerProfileIO.queueAction(() -> processRemovePlayerData(profileKey)); } private void processRemovePlayerData(ProfileKey profileKey) { @@ -490,15 +489,15 @@ private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID return GlobalProfile.deserialize(playerName, playerUUID, section); } - public Future modifyGlobalProfile(UUID playerUUID, Consumer consumer) { + public Future modifyGlobalProfile(UUID playerUUID, Consumer consumer) { return modifyGlobalProfile(getGlobalProfile(playerUUID), consumer); } - public Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { + public Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { return modifyGlobalProfile(getGlobalProfile(offlinePlayer), consumer); } - private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { + private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { consumer.accept(globalProfile); return updateGlobalProfile(globalProfile); } @@ -507,7 +506,7 @@ private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer updateGlobalProfile(GlobalProfile globalProfile) { + public Future updateGlobalProfile(GlobalProfile globalProfile) { return globalProfileIO.queueAction(() -> processGlobalProfileWrite(globalProfile)); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 197ec452..e9e9d748 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -25,7 +25,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * * @param playerProfile The profile for the player that is being updated. */ - Future updatePlayerData(PlayerProfile playerProfile); + Future updatePlayerData(PlayerProfile playerProfile); /** * Retrieves a PlayerProfile from the data source. @@ -42,7 +42,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param profileKey The key of the profile to remove. * @return True if successfully removed. */ - boolean removePlayerData(ProfileKey profileKey); + Future removePlayerData(ProfileKey profileKey); /** * Copies all the data belonging to oldName to newName and removes the old data. @@ -89,16 +89,16 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { */ @NotNull Option getExistingGlobalProfile(UUID playerUUID, String playerName); - Future modifyGlobalProfile(UUID playerUUID, Consumer consumer); + Future modifyGlobalProfile(UUID playerUUID, Consumer consumer); - Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer); + Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer); /** * Update the file for a player's global profile. * * @param globalProfile The GlobalProfile object to update the file for. */ - Future updateGlobalProfile(GlobalProfile globalProfile); + Future updateGlobalProfile(GlobalProfile globalProfile); /** * Clears a single profile in cache. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 4647848e..2c0e3e6c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -22,8 +22,8 @@ final class ProfileFileIO { fileIOExecutorService = Executors.newSingleThreadExecutor(); } - Future queueAction(Runnable action) { - return fileIOExecutorService.submit(action); + Future queueAction(Runnable action) { + return (Future) fileIOExecutorService.submit(action); } Future queueCallable(Callable callable) { From 5a893e6d06efb914685fbec3b52a2c1c8072bfda Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 12:07:23 +0800 Subject: [PATCH 083/180] Remove handling of config file from ProfileFileIO --- .../profile/FlatFileProfileDataSource.java | 14 +++++++++++-- .../inventories/profile/ProfileFileIO.java | 20 ------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 88123a38..f35d266b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -139,11 +139,21 @@ private File getProfileContainerFolder(ContainerType type, String folderName) { return folder; } + private FileConfiguration parseToConfiguration(File file) { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(true); + Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { + Logging.severe("Could not load file: " + file); + throw new RuntimeException(e); + }); + return jsonConfiguration; + } + private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playerFile) { ProfileKey fileProfileKey = profileKey.forProfileType(null); return Try.of(() -> configCache.get(fileProfileKey, () -> playerFile.exists() - ? playerProfileIO.getConfigHandleNow(playerFile) + ? parseToConfiguration(playerFile) : new JsonConfiguration()) ).getOrElseThrow(e -> { Logging.severe("Could not load profile data for player: " + fileProfileKey); @@ -481,7 +491,7 @@ private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { } private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = globalProfileIO.waitForConfigHandle(globalFile); + FileConfiguration playerData = globalProfileIO.waitForData(() -> parseToConfiguration(globalFile)); ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { section = playerData.createSection(DataStrings.PLAYER_DATA); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 2c0e3e6c..e4bf520e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -1,11 +1,5 @@ package org.mvplugins.multiverse.inventories.profile; -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.dumptruckman.minecraft.util.Logging; -import org.bukkit.configuration.file.FileConfiguration; -import org.mvplugins.multiverse.external.vavr.control.Try; - -import java.io.File; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -37,18 +31,4 @@ T waitForData(Callable callable) { throw new RuntimeException(e); } } - - FileConfiguration waitForConfigHandle(File file) { - return waitForData(() -> getConfigHandleNow(file)); - } - - FileConfiguration getConfigHandleNow(File file) { - JsonConfiguration jsonConfiguration = new JsonConfiguration(); - jsonConfiguration.options().continueOnSerializationError(true); - Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { - Logging.severe("Could not load file: " + file); - throw new RuntimeException(e); - }); - return jsonConfiguration; - } } From af5ace4456ba7a0a7e84c1dd8f24ac4f8dc7305c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 12:42:23 +0800 Subject: [PATCH 084/180] Save spawn location to disk on change --- .../handleshare/SingleShareWriter.java | 39 +++++++++++++++---- .../handleshare/SpawnChangeListener.java | 3 +- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index b7ead4e7..27bdaa8e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -4,11 +4,15 @@ import org.bukkit.entity.Player; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharable; +import java.util.Objects; + /** * Write a single share to the relevant world and group profiles. * @@ -23,14 +27,20 @@ public static SingleShareWriter of(MultiverseInventories inventories, Pla private final MultiverseInventories inventories; private final Player player; private final Sharable sharable; + private final ProfileDataSource profileDataSource; private SingleShareWriter(MultiverseInventories inventories, Player player, Sharable sharable) { this.inventories = inventories; this.player = player; this.sharable = sharable; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); } public void write(T value) { + write(value, false); + } + + public void write(T value, boolean save) { if (sharable.isOptional() && !inventories.getServiceLocator().getService(InventoriesConfig.class).getOptionalShares().contains(sharable)) { Logging.finer("Skipping write for optional share: " + sharable); @@ -39,15 +49,30 @@ public void write(T value) { Logging.finer("Writing single share: " + sharable.getNames()[0]); String worldName = this.player.getWorld().getName(); var profileContainerStoreProvider = this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class); - profileContainerStoreProvider.getStore(ContainerType.WORLD) - .getContainer(worldName) - .getPlayerData(this.player) - .set(this.sharable, value); + writeNewValueToProfile( + profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(worldName) + .getPlayerData(this.player), + value, + save + ); this.inventories.getServiceLocator().getService(WorldGroupManager.class) .getGroupsForWorld(worldName) - .forEach(worldGroup -> - worldGroup.getGroupProfileContainer().getPlayerData(this.player) - .set(this.sharable, value)); + .forEach(worldGroup -> writeNewValueToProfile( + worldGroup.getGroupProfileContainer().getPlayerData(this.player), + value, + save + )); + } + + private void writeNewValueToProfile(PlayerProfile profile, T value, boolean save) { + if (Objects.equals(profile.get(sharable), value)) { + return; + } + profile.set(sharable, value); + if (save) { + profileDataSource.updatePlayerData(profile); + } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java index 8451dd56..45904e16 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java @@ -47,6 +47,7 @@ void onSpawnChange(PlayerSpawnChangeEvent event) { } private void updatePlayerSpawn(Player player, Location location) { - SingleShareWriter.of(this.inventories, player, Sharables.BED_SPAWN).write(location); + SingleShareWriter.of(this.inventories, player, Sharables.BED_SPAWN) + .write(location == null ? null : location.clone(), true); } } From 307251f4b3b99a13c8b68e43f36376b2938094d6 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 12:43:42 +0800 Subject: [PATCH 085/180] Clone last location object before storing it --- .../inventories/handleshare/ShareHandleListener.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 085184ee..f72eaf73 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -206,7 +206,7 @@ void playerQuit(final PlayerQuitEvent event) { )); profileDataSource.setLoadOnLogin(player.getUniqueId(), true); } - SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); } private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { @@ -233,7 +233,7 @@ void playerGameModeChange(PlayerGameModeChangeEvent event) { return; } Player player = event.getPlayer(); - SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation()); + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); new GameModeShareHandler(this.inventories, player, player.getGameMode(), event.getNewGameMode()).handleSharing(); } @@ -279,7 +279,7 @@ void playerTeleport(PlayerTeleportEvent event) { } Player player = event.getPlayer(); - SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(event.getFrom()); + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(event.getFrom().clone()); // Possibly prevents item duping exploit player.closeInventory(); From b0e2a8fbae207de576b21f6281f056d17ca8d510 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 13:51:34 +0800 Subject: [PATCH 086/180] Improve debug logs for SingleShareWriter --- .../inventories/handleshare/SingleShareWriter.java | 1 + .../inventories/handleshare/SpawnChangeListener.java | 3 --- .../multiverse/inventories/profile/PlayerProfile.java | 10 ++++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 27bdaa8e..f0ec0beb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -70,6 +70,7 @@ private void writeNewValueToProfile(PlayerProfile profile, T value, boolean save if (Objects.equals(profile.get(sharable), value)) { return; } + Logging.finest("Writing %s value: %s for profile %s", sharable, value, profile); profile.set(sharable, value); if (save) { profileDataSource.updatePlayerData(profile); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java index 45904e16..65f502d0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java @@ -32,9 +32,6 @@ public SpawnChangeListener(MultiverseInventories inventories) { @EventHandler(priority = EventPriority.MONITOR) void onSpawnChange(PlayerSpawnChangeEvent event) { Player player = event.getPlayer(); - - Logging.fine("Respawn cause: %s", event.getCause()); - if (event.getCause() == Cause.BED) { updatePlayerSpawn(player, findBedFromRespawnLocation(event.getNewSpawn())); return; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index e0faaabf..0f7b1fe9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -93,4 +93,14 @@ public PlayerProfile clone() { public Map getData() { return data; } + + @Override + public String toString() { + return "PlayerProfile{" + + "player=" + player.getName() + + ", containerType=" + containerType + + ", containerName='" + containerName + '\'' + + ", profileType=" + profileType + + '}'; + } } From 58d430ed94cabbaeb688e6fe6bd151ddaaaadaaf Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Feb 2025 16:12:29 +0800 Subject: [PATCH 087/180] Implement command completion for toggle and import command --- .../inventories/commands/ImportCommand.java | 2 +- .../inventories/commands/InfoCommand.java | 3 -- .../inventories/commands/ListCommand.java | 3 -- .../inventories/commands/ToggleCommand.java | 41 ++++++------------- .../commandtools/MVInvCommandCompletion.java | 10 +++++ 5 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index 21178ca2..c58bfd2e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -41,7 +41,7 @@ final class ImportCommand extends InventoriesCommand { @Subcommand("import") @Syntax("") @CommandPermission("multiverse.inventories.import") - @CommandCompletion("MultiInv|WorldInventories|PerWorldInventory") + @CommandCompletion("@dataimporters") @Description("Import inventories from MultiInv/WorldInventories/PerWorldInventory plugin.") void onImportCommand( MVCommandIssuer issuer, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index 99b89b75..c88ec70f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -31,18 +31,15 @@ @CommandAlias("mvinv") final class InfoCommand extends InventoriesCommand { - private final MultiverseInventories plugin; private final ProfileContainerStoreProvider profileContainerStoreProvider; private final WorldGroupManager worldGroupManager; @Inject InfoCommand( @NotNull MVCommandManager commandManager, - @NotNull MultiverseInventories plugin, @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, @NotNull WorldGroupManager worldGroupManager) { super(commandManager); - this.plugin = plugin; this.profileContainerStoreProvider = profileContainerStoreProvider; this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index d76c22e5..f440af48 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -22,16 +22,13 @@ @CommandAlias("mvinv") final class ListCommand extends InventoriesCommand { - private final MultiverseInventories plugin; private final WorldGroupManager worldGroupManager; @Inject ListCommand( @NotNull MVCommandManager commandManager, - @NotNull MultiverseInventories plugin, @NotNull WorldGroupManager worldGroupManager) { super(commandManager); - this.plugin = plugin; this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 3699b238..e7040002 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -1,12 +1,9 @@ package org.mvplugins.multiverse.inventories.commands; import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; -import org.bukkit.command.CommandSender; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; @@ -26,23 +23,20 @@ @CommandAlias("mvinv") final class ToggleCommand extends InventoriesCommand { - private final MultiverseInventories plugin; private final InventoriesConfig inventoriesConfig; @Inject ToggleCommand( @NotNull MVCommandManager commandManager, - @NotNull MultiverseInventories plugin, @NotNull InventoriesConfig inventoriesConfig) { super(commandManager); - this.plugin = plugin; this.inventoriesConfig = inventoriesConfig; } @CommandAlias("mvinvtoggle") @Subcommand("toggle") @CommandPermission("multiverse.inventories.addshares") - @CommandCompletion("economy|last_location") + @CommandCompletion("@sharables:scope=optional") @Syntax("") @Description("Toggles the usage of optional sharables") void onToggleCommand( @@ -51,32 +45,21 @@ void onToggleCommand( @Single @Syntax("") @Description("Share to toggle") - @NotNull String shareName + @NotNull Sharable sharable ) { - Shares shares = Sharables.lookup(shareName.toLowerCase()); - if (shares == null) { - issuer.sendError(MVInvi18n.ERROR_NOSHARESSPECIFIED); - return; - } - boolean foundOpt = false; Shares optionalShares = inventoriesConfig.getOptionalShares(); - for (Sharable sharable : shares) { - if (sharable.isOptional()) { - foundOpt = true; - if (optionalShares.contains(sharable)) { - optionalShares.remove(sharable); - issuer.sendInfo(MVInvi18n.TOGGLE_NOWNOTUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); - } else { - optionalShares.add(sharable); - issuer.sendInfo(MVInvi18n.TOGGLE_NOWUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); - } - } + if (!sharable.isOptional()) { + issuer.sendError(MVInvi18n.TOGGLE_NOOPTIONALSHARES, replace("{share}").with(sharable.toString())); + return; } - if (foundOpt) { - inventoriesConfig.setOptionalShares(optionalShares); - inventoriesConfig.save(); + if (optionalShares.contains(sharable)) { + optionalShares.remove(sharable); + issuer.sendInfo(MVInvi18n.TOGGLE_NOWNOTUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); } else { - issuer.sendError(MVInvi18n.TOGGLE_NOOPTIONALSHARES, replace("{share}").with(shareName)); + optionalShares.add(sharable); + issuer.sendInfo(MVInvi18n.TOGGLE_NOWUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); } + inventoriesConfig.setOptionalShares(optionalShares); + inventoriesConfig.save(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java index bfe3f892..2a306f4d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.commandtools; +import org.checkerframework.checker.units.qual.N; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandCompletions; @@ -9,6 +10,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -26,22 +28,30 @@ public final class MVInvCommandCompletion { private final InventoriesConfig inventoriesConfig; private final WorldGroupManager worldGroupManager; + private final DataImportManager dataImportManager; @Inject private MVInvCommandCompletion( @NotNull InventoriesConfig inventoriesConfig, @NotNull WorldGroupManager worldGroupManager, + @NotNull DataImportManager dataImportManager, @NotNull MVCommandManager mvCommandManager) { this.inventoriesConfig = inventoriesConfig; this.worldGroupManager = worldGroupManager; + this.dataImportManager = dataImportManager; MVCommandCompletions commandCompletions = mvCommandManager.getCommandCompletions(); + commandCompletions.registerAsyncCompletion("dataimporters", this::suggestDataImporters); commandCompletions.registerAsyncCompletion("sharables", this::suggestSharables); commandCompletions.registerAsyncCompletion("shares", this::suggestShares); commandCompletions.registerAsyncCompletion("worldGroups", this::suggestWorldGroups); commandCompletions.registerAsyncCompletion("worldGroupWorlds", this::suggestWorldGroupWorlds); } + private Collection suggestDataImporters(BukkitCommandCompletionContext context) { + return dataImportManager.getEnabledImporterNames(); + } + private Collection suggestSharables(BukkitCommandCompletionContext context) { String scope = context.getConfig("scope", "enabled"); From 1458781a57e7bf5e10cd1af3d13053542203ce48 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 26 Feb 2025 20:04:36 +0800 Subject: [PATCH 088/180] Update to 1.0.2 of gradle plugins. --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c6e91735..4f73d529 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'checkstyle' - id 'org.mvplugins.multiverse-plugin' version '1.0.1' - id 'org.mvplugins.kotlin-test-only' version '1.0.1' + id 'org.mvplugins.multiverse-plugin' version '1.0.2' + id 'org.mvplugins.kotlin-test-only' version '1.0.2' } group = 'org.mvplugins.multiverse.inventories' From 9e9dcc39a6aa60b92f0024f7b243e4d9a4e89db6 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 14:31:03 +0800 Subject: [PATCH 089/180] Restructure configuration --- .../inventories/MultiverseInventories.java | 9 +- .../inventories/commands/ToggleCommand.java | 4 +- .../commandtools/MVInvCommandCompletion.java | 4 +- .../inventories/config/InventoriesConfig.java | 184 ++++++++++++------ .../config/InventoriesConfigNodes.java | 145 ++++++++++---- .../perworldinventory/PwiImportHelper.java | 10 +- .../handleshare/ShareHandleListener.java | 26 +-- .../handleshare/ShareHandlingUpdater.java | 4 +- .../handleshare/SingleShareWriter.java | 2 +- .../handleshare/WorldChangeShareHandler.java | 2 +- .../profile/FlatFileProfileDataSource.java | 63 +++--- .../profile/container/ProfileContainer.java | 2 +- .../group/AbstractWorldGroupManager.java | 3 +- .../inventories/share/Sharable.java | 2 +- .../multiverse/inventories/util/Perm.java | 2 +- .../inventories/commands/ToggleCommandTest.kt | 12 +- .../inventories/config/ConfigTest.kt | 4 + 17 files changed, 308 insertions(+), 170 deletions(-) create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 513dee1f..7aa50833 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -90,6 +90,7 @@ public final void onEnable() { initializeDependencyInjection(); Perm.register(this); this.reloadConfig(); + inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); // Register Events PluginManager pluginManager = this.getServer().getPluginManager(); @@ -136,13 +137,15 @@ public void onDisable() { for (final Player player : getServer().getOnlinePlayers()) { final String world = player.getWorld().getName(); - if (inventoriesConfig.get().usingLoggingSaveLoad()) { + if (inventoriesConfig.get().getSavePlayerdataOnQuit()) { ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile( Sharables.allOf(), profileContainerStoreProvider.get().getStore(ContainerType.WORLD) .getContainer(world) .getPlayerData(player))); - profileDataSource.get().modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true)); + if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { + profileDataSource.get().modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true)); + } } } @@ -224,7 +227,7 @@ public void reloadConfig() { @Override public void run() { // Create initial World Group for first run IF NO GROUPS EXIST - if (inventoriesConfig.get().isFirstRun()) { + if (inventoriesConfig.get().getFirstRun()) { Logging.info("First run!"); if (worldGroupManager.get().getGroups().isEmpty()) { worldGroupManager.get().createDefaultGroup(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index e7040002..f7837626 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -47,7 +47,7 @@ void onToggleCommand( @Description("Share to toggle") @NotNull Sharable sharable ) { - Shares optionalShares = inventoriesConfig.getOptionalShares(); + Shares optionalShares = inventoriesConfig.getActiveOptionalShares(); if (!sharable.isOptional()) { issuer.sendError(MVInvi18n.TOGGLE_NOOPTIONALSHARES, replace("{share}").with(sharable.toString())); return; @@ -59,7 +59,7 @@ void onToggleCommand( optionalShares.add(sharable); issuer.sendInfo(MVInvi18n.TOGGLE_NOWUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); } - inventoriesConfig.setOptionalShares(optionalShares); + inventoriesConfig.setActiveOptionalShares(optionalShares); inventoriesConfig.save(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java index 2a306f4d..872e3edc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java @@ -1,11 +1,9 @@ package org.mvplugins.multiverse.inventories.commandtools; -import org.checkerframework.checker.units.qual.N; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandCompletions; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; @@ -58,7 +56,7 @@ private Collection suggestSharables(BukkitCommandCompletionContext conte return Sharables.all().stream() .filter(sharable -> switch (scope) { case "enabled" -> - !sharable.isOptional() || inventoriesConfig.getOptionalShares().contains(sharable); + !sharable.isOptional() || inventoriesConfig.getActiveOptionalShares().contains(sharable); case "optional" -> sharable.isOptional(); default -> true; }) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 616c4298..15460b8d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -4,6 +4,9 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.configuration.handle.CommentedConfigurationHandle; import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle; +import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator; +import org.mvplugins.multiverse.core.configuration.migration.MoveMigratorAction; +import org.mvplugins.multiverse.core.configuration.migration.VersionMigrator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; @@ -32,6 +35,17 @@ public final class InventoriesConfig { var configPath = Path.of(inventories.getDataFolder().getPath(), CONFIG_FILENAME); this.configHandle = CommentedConfigurationHandle.builder(configPath, this.configNodes.getNodes()) .logger(Logging.getLogger()) + .migrator(ConfigMigrator.builder(this.configNodes.version) + .addVersionMigrator(VersionMigrator.builder(5.0) + .addAction(MoveMigratorAction.of("settings.first_run", "first-run")) + .addAction(MoveMigratorAction.of("settings.use_bypass", "share-handling.enable-bypass-permissions")) + .addAction(MoveMigratorAction.of("settings.default_ungrouped_worlds", "share-handling.default-ungrouped-worlds")) + .addAction(MoveMigratorAction.of("settings.save_load_on_log_in_out", "performance.save-playerdata-on-quit")) + .addAction(MoveMigratorAction.of("settings.use_game_mode_profiles", "share-handling.enable-gamemode-share-handling")) + .addAction(MoveMigratorAction.of("shares.optionals_for_ungrouped_worlds", "share-handling.use-optionals-for-ungrouped-worlds")) + .addAction(MoveMigratorAction.of("shares.use_optionals", "share-handling.active-optional-shares")) + .build()) + .build()) .build(); this.stringPropertyHandle = new StringPropertyHandle(this.configHandle); } @@ -49,66 +63,63 @@ public StringPropertyHandle getStringPropertyHandle() { } /** - * Retrieves the locale string from the config. - * - * @return The locale string. + * @return True if we should check for bypass permissions. */ - public String getLocale() { - return this.configHandle.get(configNodes.locale); + public boolean getEnableBypassPermissions() { + return this.configHandle.get(configNodes.enableBypassPermissions); } - public Try setLocale(String locale) { - return this.configHandle.set(configNodes.locale, locale); + /** + * @param useBypass Whether or not to check for bypass permissions. + */ + public Try setEnableBypassPermissions(boolean useBypass) { + return this.configHandle.set(configNodes.enableBypassPermissions, useBypass); } /** - * Tells whether this is the first time the plugin has run as set by a config flag. - * - * @return True if first_run is set to true in config. + * @return True if using separate data for game modes. */ - public boolean isFirstRun() { - return this.configHandle.get(configNodes.firstRun); + public boolean getEnableGamemodeShareHandling() { + return this.configHandle.get(configNodes.enableGamemodeShareHandling); } /** - * Sets the first_run flag in the config so that the plugin no longer thinks it is the first run. - * - * @param firstRun What to set the flag to in the config. + * @param useGameModeProfile whether to use separate data for game modes. */ - public Try setFirstRun(boolean firstRun) { - return this.configHandle.set(configNodes.firstRun, firstRun); + public Try setEnableGamemodeShareHandling(boolean useGameModeProfile) { + return this.configHandle.set(configNodes.enableGamemodeShareHandling, useGameModeProfile); } /** - * @return True if we should check for bypass permissions. + * @return true if worlds with no group should be considered part of the default group. */ - public boolean isUsingBypass() { - return this.configHandle.get(configNodes.useBypass); + public boolean getDefaultUngroupedWorlds() { + return this.configHandle.get(configNodes.defaultUngroupedWorlds); } /** - * @param useBypass Whether or not to check for bypass permissions. + * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. */ - public Try setUsingBypass(boolean useBypass) { - return this.configHandle.set(configNodes.useBypass, useBypass); + public Try setDefaultUngroupedWorlds(boolean useDefaultGroup) { + return this.configHandle.set(configNodes.defaultUngroupedWorlds, useDefaultGroup); } /** - * Tells whether Multiverse-Inventories should save on player logout and load on player login. + * Whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. * - * @return True if should save and load on player log out and in. + * @return true if should utilize optional shares in worlds that are not grouped. */ - public boolean usingLoggingSaveLoad() { - return this.configHandle.get(configNodes.loggingSaveLoad); + public boolean getUseOptionalsForUngroupedWorlds() { + return this.configHandle.get(configNodes.useOptionalsForUngroupedWorlds); } /** - * Sets whether Multiverse-Inventories should save on player logout and load on player login. + * Sets whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. * - * @param useLoggingSaveLoad true if should save and load on player log out and in. + * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. */ - public Try setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { - return this.configHandle.set(configNodes.loggingSaveLoad, useLoggingSaveLoad); + public Try setUseOptionalsForUngroupedWorlds(final boolean usingOptionalsForUngrouped) { + return this.configHandle.set(configNodes.useOptionalsForUngroupedWorlds, usingOptionalsForUngrouped); } /** @@ -117,8 +128,8 @@ public Try setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { * A {@link Sharable} marked as optional is ignored if it is not * contained in this list. */ - public Shares getOptionalShares() { - return this.configHandle.get(configNodes.optionalShares); + public Shares getActiveOptionalShares() { + return this.configHandle.get(configNodes.activeOptionalShares); } /** @@ -127,54 +138,109 @@ public Shares getOptionalShares() { * @param shares The optional shares to be used. * @return True if successful. */ - public Try setOptionalShares(Shares shares) { - return this.configHandle.set(configNodes.optionalShares, shares); + public Try setActiveOptionalShares(Shares shares) { + return this.configHandle.set(configNodes.activeOptionalShares, shares); } /** - * @return true if worlds with no group should be considered part of the default group. + * Tells whether Multiverse-Inventories should save on player logout and load on player login. + * + * @return True if should save and load on player log out and in. */ - public boolean isDefaultingUngroupedWorlds() { - return this.configHandle.get(configNodes.defaultUngroupedWorlds); + public boolean getSavePlayerdataOnQuit() { + return this.configHandle.get(configNodes.savePlayerdataOnQuit); } /** - * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. + * Sets whether Multiverse-Inventories should save on player logout and load on player login. + * + * @param useLoggingSaveLoad true if should save and load on player log out and in. */ - public Try setDefaultingUngroupedWorlds(boolean useDefaultGroup) { - return this.configHandle.set(configNodes.defaultUngroupedWorlds, useDefaultGroup); + public Try setSavePlayerdataOnQuit(boolean useLoggingSaveLoad) { + return this.configHandle.set(configNodes.savePlayerdataOnQuit, useLoggingSaveLoad); } - /** - * @return True if using separate data for game modes. - */ - public boolean isUsingGameModeProfiles() { - return this.configHandle.get(configNodes.useGameModeProfiles); + public boolean getApplyPlayerdataOnJoin() { + return this.configHandle.get(configNodes.applyPlayerdataOnJoin); } - /** - * @param useGameModeProfile whether to use separate data for game modes. - */ - public Try setUsingGameModeProfiles(boolean useGameModeProfile) { - return this.configHandle.set(configNodes.useGameModeProfiles, useGameModeProfile); + public Try setApplyPlayerdataOnJoin(boolean applyPlayerdataOnJoin) { + return this.configHandle.set(configNodes.applyPlayerdataOnJoin, applyPlayerdataOnJoin); + } + + public boolean getAlwaysWriteWorldProfile() { + return this.configHandle.get(configNodes.alwaysWriteWorldProfile); + } + + public Try setAlwaysWriteWorldProfile(boolean alwaysWriteWorldProfile) { + return this.configHandle.set(configNodes.alwaysWriteWorldProfile, alwaysWriteWorldProfile); + } + + + public int getPlayerFileCacheSize() { + return this.configHandle.get(configNodes.playerFileCacheSize); + } + + public Try setPlayerFileCacheSize(int playerFileCacheSize) { + return this.configHandle.set(configNodes.playerFileCacheSize, playerFileCacheSize); + } + + public int getPlayerFileCacheExpiry() { + return this.configHandle.get(configNodes.playerFileCacheExpiry); + } + + public Try setPlayerFileCacheExpiry(int playerFileCacheExpiry) { + return this.configHandle.set(configNodes.playerFileCacheExpiry, playerFileCacheExpiry); + } + + public int getPlayerProfileCacheSize() { + return this.configHandle.get(configNodes.playerProfileCacheSize); + } + + public Try setPlayerProfileCacheSize(int playerProfileCacheSize) { + return this.configHandle.set(configNodes.playerProfileCacheSize, playerProfileCacheSize); + } + + public int getPlayerProfileCacheExpiry() { + return this.configHandle.get(configNodes.playerProfileCacheExpiry); + } + + public Try setPlayerProfileCacheExpiry(int playerProfileCacheExpiry) { + return this.configHandle.set(configNodes.playerProfileCacheExpiry, playerProfileCacheExpiry); + } + + public int getGlobalProfileCacheSize() { + return this.configHandle.get(configNodes.globalProfileCacheSize); + } + + public Try setGlobalProfileCacheSize(int globalProfileCacheSize) { + return this.configHandle.set(configNodes.globalProfileCacheSize, globalProfileCacheSize); + } + + public int getGlobalProfileCacheExpiry() { + return this.configHandle.get(configNodes.globalProfileCacheExpiry); + } + + public Try setGlobalProfileCacheExpiry(int globalProfileCacheExpiry) { + return this.configHandle.set(configNodes.globalProfileCacheExpiry, globalProfileCacheExpiry); } /** - * Whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. + * Tells whether this is the first time the plugin has run as set by a config flag. * - * @return true if should utilize optional shares in worlds that are not grouped. + * @return True if first_run is set to true in config. */ - public boolean usingOptionalsForUngrouped() { - return this.configHandle.get(configNodes.useOptionalsForUngrouped); + public boolean getFirstRun() { + return this.configHandle.get(configNodes.firstRun); } /** - * Sets whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. + * Sets the first_run flag in the config so that the plugin no longer thinks it is the first run. * - * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. + * @param firstRun What to set the flag to in the config. */ - public Try setUsingOptionalsForUngrouped(final boolean usingOptionalsForUngrouped) { - return this.configHandle.set(configNodes.useOptionalsForUngrouped, usingOptionalsForUngrouped); + public Try setFirstRun(boolean firstRun) { + return this.configHandle.set(configNodes.firstRun, firstRun); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 8f701531..0db1b3bb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -27,67 +27,61 @@ private N node(N node) { return node; } - private final ConfigHeaderNode settingsHeader = node(ConfigHeaderNode.builder("settings") - .comment("#######################################") - .comment("# Settings for Multiverse-Inventories #") - .comment("#######################################") + private final ConfigHeaderNode shareHandlingHeader = node(ConfigHeaderNode.builder("share-handling") + .comment("#-----------------------------------------------------------------------------------------------------------------#") + .comment("# #") + .comment("# __ __ _ _ _____ _ _____ _____ ___ ___ ___ ___ _ ___ _____ _ _ _____ ___ ___ ___ ___ ___ #") + .comment("# | \\/ | | | |_ _| | |_ _\\ \\ / / __| _ \\/ __| __| |_ _| \\| \\ \\ / / __| \\| |_ _/ _ \\| _ \\_ _| __/ __| #") + .comment("# | |\\/| | |_| | | | | |__ | | \\ V /| _|| /\\__ \\ _| | || .` |\\ V /| _|| .` | | || (_) | /| || _|\\__ \\ #") + .comment("# |_| |_|\\___/ |_| |____|___| \\_/ |___|_|_\\|___/___| |___|_|\\_| \\_/ |___|_|\\_| |_| \\___/|_|_\\___|___|___/ #") + .comment("# #") + .comment("# #") + .comment("# #") + .comment("# #") + .comment("# WIKI: https://github.com/Multiverse/Multiverse-Core/wiki/Basics-(Inventories) #") + .comment("# DISCORD: https://discord.gg/NZtfKky #") + .comment("# BUG REPORTS: https://github.com/Multiverse/Multiverse-Inventories/issues #") + .comment("# #") + .comment("# #") + .comment("# New options are added to this file automatically. If you manually made changes #") + .comment("# to this file while your server is running, please run `/mvinv reload` command. #") + .comment("# #") + .comment("#-----------------------------------------------------------------------------------------------------------------#") .comment("") .comment("") .build()); - final ConfigNode locale = node(ConfigNode.builder("settings.locale", String.class) - .comment("This is the locale you wish to use.") - .defaultValue("en") - .name("locale") - .build()); - - final ConfigNode firstRun = node(ConfigNode.builder("settings.first_run", Boolean.class) - .comment("") - .comment("If this is true it will generate world groups for you based on MV worlds.") - .defaultValue(true) - .name(null) - .build()); - - final ConfigNode useBypass = node(ConfigNode.builder("settings.use_bypass", Boolean.class) - .comment("") + final ConfigNode enableBypassPermissions = node(ConfigNode.builder("share-handling.enable-bypass-permissions", Boolean.class) .comment("If this is set to true, it will enable bypass permissions (Check the wiki for more info.)") .defaultValue(false) - .name("use-bypass") + .name("enable-bypass") .build()); - final ConfigNode defaultUngroupedWorlds = node(ConfigNode.builder("settings.default_ungrouped_worlds", Boolean.class) + final ConfigNode enableGamemodeShareHandling = node(ConfigNode.builder("share-handling.enable-gamemode-share-handling", Boolean.class) .comment("") - .comment("If set to true, any world not listed in a group will automatically use the settings for the default group!") + .comment("If this is set to true, players will have different inventories/stats for each game mode.") + .comment("Please note that old data migrated to the version that has this feature will have their data copied for both game modes.") .defaultValue(false) - .name("default-ungrouped-worlds") + .name("enable-gamemode-share-handling") .build()); - final ConfigNode loggingSaveLoad = node(ConfigNode.builder("settings.save_load_on_log_in_out", Boolean.class) + final ConfigNode defaultUngroupedWorlds = node(ConfigNode.builder("share-handling.default-ungrouped-worlds", Boolean.class) .comment("") - .comment("The default and suggested setting for this is FALSE.") - .comment("False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out.") - .comment("That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY.") - .comment("Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in.") - .comment("The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back!") + .comment("If set to true, any world not listed in a group will automatically be assigned to the 'default' group!") .defaultValue(false) - .name("save-load-on-log-in-out") + .name("default-ungrouped-worlds") .build()); - private final ConfigHeaderNode sharesHeader = node(ConfigHeaderNode.builder("shares") - .comment("") + final ConfigNode useOptionalsForUngroupedWorlds = node(ConfigNode.builder("share-handling.use-optionals-for-ungrouped-worlds", Boolean.class) .comment("") - .build()); - - - final ConfigNode useOptionalsForUngrouped = node(ConfigNode.builder("shares.optionals_for_ungrouped_worlds", Boolean.class) .comment("When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.") .comment("An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.") .comment("When set to false, optional shares WILL NOT be utilized in these cases, effectively disabling it for ungrouped worlds.") .defaultValue(true) - .name("optionals-for-ungrouped-worlds") + .name("use-optionals-for-ungrouped-worlds") .build()); - final ConfigNode optionalShares = node(ConfigNode.builder("shares.use_optionals", Shares.class) + final ConfigNode activeOptionalShares = node(ConfigNode.builder("share-handling.active-optional-shares", Shares.class) .comment("") .comment("You must specify optional shares you wish to use here or they will be ignored.") .comment("The only built-in optional shares are \"economy\" and \"last_location\".") @@ -109,11 +103,78 @@ public Object serialize(Shares sharables, Class aClass) { }) .build()); - final ConfigNode useGameModeProfiles = node(ConfigNode.builder("settings.use_game_mode_profiles", Boolean.class) + private final ConfigHeaderNode performanceHeader = node(ConfigHeaderNode.builder("performance") .comment("") - .comment("If this is set to true, players will have different inventories/stats for each game mode.") - .comment("Please note that old data migrated to the version that has this feature will have their data copied for both game modes.") + .comment("") + .build()); + + final ConfigNode savePlayerdataOnQuit = node(ConfigNode.builder("performance.save-playerdata-on-quit", Boolean.class) + .comment("This option may be useful if you want an up-to-date offline copy of the playerdata within mvinv.") + .comment("However, this will result in minor performance overhead on every player quit.") + .defaultValue(false) + .name("save-playerdata-on-quit") + .build()); + + final ConfigNode applyPlayerdataOnJoin = node(ConfigNode.builder("performance.apply-playerdata-on-join", Boolean.class) + .comment("") + .comment("This will only work if save-playerdata-on-quit is set to true.") + .comment("Minecraft will already load the most up-to-date player data and this option will generally be redundant.") + .comment("The only possible edge case uses is if you have a need to always modify the mvinv playerdata while the player is offline.") .defaultValue(false) - .name("use-game-mode-profiles") + .name("apply-playerdata-on-join") + .build()); + + final ConfigNode alwaysWriteWorldProfile = node(ConfigNode.builder("performance.always-write-world-profile", Boolean.class) + .comment("") + .defaultValue(true) + .name("always-write-world-profile") + .build()); + + private final ConfigHeaderNode cacheHeader = node(ConfigHeaderNode.builder("performance.cache") + .comment("") + .comment("NOTE: Cache options require a server restart to take effect.") + .build()); + + final ConfigNode playerFileCacheSize = node(ConfigNode.builder("performance.cache.player-file-cache-size", Integer.class) + .defaultValue(2000) + .name("player-file-cache-size") + .build()); + + final ConfigNode playerFileCacheExpiry = node(ConfigNode.builder("performance.cache.player-file-cache-expiry", Integer.class) + .defaultValue(60) + .name("player-file-cache-expiry") + .build()); + + final ConfigNode playerProfileCacheSize = node(ConfigNode.builder("performance.cache.player-profile-cache-size", Integer.class) + .defaultValue(6000) + .name("player-profile-cache-size") + .build()); + + final ConfigNode playerProfileCacheExpiry = node(ConfigNode.builder("performance.cache.player-profile-cache-expiry", Integer.class) + .defaultValue(60) + .name("player-profile-cache-expiry") + .build()); + + final ConfigNode globalProfileCacheSize = node(ConfigNode.builder("performance.cache.global-profile-cache-size", Integer.class) + .defaultValue(500) + .name("global-profile-cache-size") + .build()); + + final ConfigNode globalProfileCacheExpiry = node(ConfigNode.builder("performance.cache.global-profile-cache-expiry", Integer.class) + .defaultValue(60) + .name("global-profile-cache-expiry") + .build()); + + final ConfigNode firstRun = node(ConfigNode.builder("first-run", Boolean.class) + .comment("") + .comment("") + .comment("Do not edit the following values!!!!!") + .defaultValue(true) + .hidden() + .build()); + + final ConfigNode version = node(ConfigNode.builder("version", Double.class) + .defaultValue(0.0) + .hidden() .build()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 2666ea99..30b2c97b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -109,10 +109,10 @@ private void pwiSetUp() { * Set similar/supported config options in MultiverseInventories with the values used in PerWorldInventory. */ private void transferConfigOptions() { - inventoriesConfig.setUsingGameModeProfiles(this.pwiSettings.getProperty(PluginSettings.SEPARATE_GM_INVENTORIES)); - inventoriesConfig.setUsingLoggingSaveLoad(this.pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); - inventoriesConfig.setDefaultingUngroupedWorlds(this.pwiSettings.getProperty(PluginSettings.SHARE_IF_UNCONFIGURED)); - inventoriesConfig.getOptionalShares().setSharing(Sharables.ECONOMY, this.pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)); + inventoriesConfig.setEnableGamemodeShareHandling(this.pwiSettings.getProperty(PluginSettings.SEPARATE_GM_INVENTORIES)); + inventoriesConfig.setSavePlayerdataOnQuit(this.pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); + inventoriesConfig.setDefaultUngroupedWorlds(this.pwiSettings.getProperty(PluginSettings.SHARE_IF_UNCONFIGURED)); + inventoriesConfig.getActiveOptionalShares().setSharing(Sharables.ECONOMY, this.pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)); inventoriesConfig.save(); } @@ -138,7 +138,7 @@ private void findPlayersWithData() throws DataImportException { */ private Collection getPWIGroups() { Set groups = new HashSet<>(this.pwiGroupManager.getGroups().values()); - if (!inventoriesConfig.isDefaultingUngroupedWorlds()) { + if (!inventoriesConfig.getDefaultUngroupedWorlds()) { worldManager.getWorlds().forEach(world -> groups.add(this.pwiGroupManager.getGroupFromWorld(world.getName()))); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index c00ee02a..6d09ebf3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -97,14 +97,14 @@ void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { private String getDebugInfo() { StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + inventories.getDescription().getVersion() + '\n' + "[Multiverse-Inventories] === Settings ===" + '\n' - + "[Multiverse-Inventories] First Run: " + config.isFirstRun() + '\n' - + "[Multiverse-Inventories] Using Bypass: " + config.isUsingBypass() + '\n' - + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.isDefaultingUngroupedWorlds() + '\n' - + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.usingLoggingSaveLoad() + '\n' - + "[Multiverse-Inventories] Using GameMode Profiles: " + config.isUsingGameModeProfiles() + '\n' + + "[Multiverse-Inventories] First Run: " + config.getFirstRun() + '\n' + + "[Multiverse-Inventories] Using Bypass: " + config.getEnableBypassPermissions() + '\n' + + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.getDefaultUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.getSavePlayerdataOnQuit() + '\n' + + "[Multiverse-Inventories] Using GameMode Profiles: " + config.getEnableGamemodeShareHandling() + '\n' + "[Multiverse-Inventories] === Shares ===" + '\n' - + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.usingOptionalsForUngrouped() + '\n' - + "[Multiverse-Inventories] Enabled Optionals: " + config.getOptionalShares() + '\n' + + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.getUseOptionalsForUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Enabled Optionals: " + config.getActiveOptionalShares() + '\n' + "[Multiverse-Inventories] === Groups ===" + '\n'); for (WorldGroup group : worldGroupManager.getGroups()) { @@ -153,7 +153,7 @@ void playerJoin(final PlayerJoinEvent event) { final GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player); final String world = globalProfile.getLastWorld(); - if (config.usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { + if (config.getApplyPlayerdataOnJoin() && globalProfile.shouldLoadOnLogin()) { ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile( Sharables.allOf(), profileContainerStoreProvider.getStore(ContainerType.WORLD) @@ -198,14 +198,16 @@ void playerQuit(final PlayerQuitEvent event) { final String world = event.getPlayer().getWorld().getName(); GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player); globalProfile.setLastWorld(world); - if (config.usingLoggingSaveLoad()) { + if (config.getSavePlayerdataOnQuit()) { ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile( Sharables.allOf(), profileContainerStoreProvider.getStore(ContainerType.WORLD) .getContainer(world) .getPlayerData(player) )); - globalProfile.setLoadOnLogin(true); + if (config.getApplyPlayerdataOnJoin()) { + globalProfile.setLoadOnLogin(true); + } } profileDataSource.updateGlobalProfile(globalProfile); SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); @@ -231,7 +233,7 @@ private void verifyCorrectWorld(Player player, String world, GlobalProfile globa */ @EventHandler(priority = EventPriority.MONITOR) void playerGameModeChange(PlayerGameModeChangeEvent event) { - if (event.isCancelled() || !config.isUsingGameModeProfiles()) { + if (event.isCancelled() || !config.getEnableGamemodeShareHandling()) { return; } Player player = event.getPlayer(); @@ -276,7 +278,7 @@ void playerChangedWorld(PlayerChangedWorldEvent event) { void playerTeleport(PlayerTeleportEvent event) { if (event.isCancelled() || event.getFrom().getWorld().equals(event.getTo().getWorld()) - || !config.getOptionalShares().contains(Sharables.LAST_LOCATION)) { + || !config.getActiveOptionalShares().contains(Sharables.LAST_LOCATION)) { return; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 4677bc40..26e09cf0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -84,12 +84,12 @@ private void updatePlayer() { private boolean isSharableUsed(Sharable sharable) { var config = inventories.getServiceLocator().getService(InventoriesConfig.class); if (sharable.isOptional()) { - if (!config.getOptionalShares().contains(sharable)) { + if (!config.getActiveOptionalShares().contains(sharable)) { Logging.finest("Ignoring optional share: " + sharable.getNames()[0]); return false; } if (profile.profile().getContainerType() == ContainerType.WORLD - && !config.usingOptionalsForUngrouped()) { + && !config.getUseOptionalsForUngroupedWorlds()) { Logging.finest("Ignoring optional share '" + sharable.getNames()[0] + "' for ungrouped world!"); return false; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index f0ec0beb..198a4cc3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -42,7 +42,7 @@ public void write(T value) { public void write(T value, boolean save) { if (sharable.isOptional() && - !inventories.getServiceLocator().getService(InventoriesConfig.class).getOptionalShares().contains(sharable)) { + !inventories.getServiceLocator().getService(InventoriesConfig.class).getActiveOptionalShares().contains(sharable)) { Logging.finer("Skipping write for optional share: " + sharable); return; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index f1140fa9..6df4323d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -144,7 +144,7 @@ private boolean isPlayerBypassingChange(WorldGroup worldGroup) { } private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { - if (inventoriesConfig.isDefaultingUngroupedWorlds() + if (inventoriesConfig.getDefaultUngroupedWorlds() && !worldGroupManager.hasConfiguredGroup(fromWorld) && worldGroup.equals(worldGroupManager.getDefaultGroup())) { return false; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index f35d266b..b441bf49 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -15,6 +15,7 @@ import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.share.ProfileEntry; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -47,23 +48,9 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); // TODO these probably need configurable max sizes - private final Cache configCache = CacheBuilder.newBuilder() - .expireAfterAccess(60, TimeUnit.MINUTES) - .maximumSize(2000) - .recordStats() - .build(); - - private final Cache profileCache = CacheBuilder.newBuilder() - .expireAfterAccess(60, TimeUnit.MINUTES) - .maximumSize(6000) - .recordStats() - .build(); - - private final Cache globalProfileCache = CacheBuilder.newBuilder() - .expireAfterAccess(60, TimeUnit.MINUTES) - .maximumSize(500) - .recordStats() - .build(); + private final Cache playerFileCache; + private final Cache playerProfileCache; + private final Cache globalProfileCache; private final File worldFolder; private final File groupFolder; @@ -73,7 +60,25 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final ProfileFileIO globalProfileIO; @Inject - FlatFileProfileDataSource(@NotNull MultiverseInventories plugin) throws IOException { + FlatFileProfileDataSource(@NotNull MultiverseInventories plugin, @NotNull InventoriesConfig inventoriesConfig) throws IOException { + this.playerFileCache = CacheBuilder.newBuilder() + .expireAfterAccess(inventoriesConfig.getPlayerFileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getPlayerFileCacheSize()) + .recordStats() + .build(); + + this.playerProfileCache = CacheBuilder.newBuilder() + .expireAfterAccess(inventoriesConfig.getPlayerProfileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getPlayerProfileCacheSize()) + .recordStats() + .build(); + + this.globalProfileCache = CacheBuilder.newBuilder() + .expireAfterAccess(inventoriesConfig.getGlobalProfileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getGlobalProfileCacheSize()) + .recordStats() + .build(); + this.playerProfileIO = new ProfileFileIO(); this.globalProfileIO = new ProfileFileIO(); @@ -152,7 +157,7 @@ private FileConfiguration parseToConfiguration(File file) { private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playerFile) { ProfileKey fileProfileKey = profileKey.forProfileType(null); return Try.of(() -> - configCache.get(fileProfileKey, () -> playerFile.exists() + playerFileCache.get(fileProfileKey, () -> playerFile.exists() ? parseToConfiguration(playerFile) : new JsonConfiguration()) ).getOrElseThrow(e -> { @@ -224,7 +229,7 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) @Override public PlayerProfile getPlayerData(ProfileKey key) { try { - return profileCache.get(key, () -> { + return playerProfileCache.get(key, () -> { File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); if (!playerFile.exists()) { return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), @@ -378,7 +383,7 @@ public Future removePlayerData(ProfileKey profileKey) { } return playerProfileIO.queueAction(playerFile::delete); } - profileCache.invalidate(profileKey); + playerProfileCache.invalidate(profileKey); return playerProfileIO.queueAction(() -> processRemovePlayerData(profileKey)); } @@ -548,29 +553,29 @@ void clearPlayerCache(UUID playerUUID) { @Override public void clearProfileCache(ProfileKey key) { - configCache.invalidate(key); - profileCache.invalidate(key); + playerFileCache.invalidate(key); + playerProfileCache.invalidate(key); } @Override public void clearProfileCache(Predicate predicate) { - configCache.invalidateAll(Sets.filter(configCache.asMap().keySet(), predicate::test)); - profileCache.invalidateAll(Sets.filter(profileCache.asMap().keySet(), predicate::test)); + playerFileCache.invalidateAll(Sets.filter(playerFileCache.asMap().keySet(), predicate::test)); + playerProfileCache.invalidateAll(Sets.filter(playerProfileCache.asMap().keySet(), predicate::test)); } @Override public void clearAllCache() { - configCache.invalidateAll(); + playerFileCache.invalidateAll(); globalProfileCache.invalidateAll(); - profileCache.invalidateAll(); + playerProfileCache.invalidateAll(); } @Override public Map getCacheStats() { Map stats = new HashMap<>(); - stats.put("configCache", configCache.stats()); + stats.put("configCache", playerFileCache.stats()); stats.put("globalProfileCache", globalProfileCache.stats()); - stats.put("profileCache", profileCache.stats()); + stats.put("profileCache", playerProfileCache.stats()); return stats; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 748b9748..38173eff 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -57,7 +57,7 @@ private Map getPlayerData(String name) { */ public PlayerProfile getPlayerData(Player player) { ProfileType type; - if (config.isUsingGameModeProfiles()) { + if (config.getEnableGamemodeShareHandling()) { type = ProfileTypes.forGameMode(player.getGameMode()); } else { type = ProfileTypes.SURVIVAL; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index bf76efad..01994c7d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -17,7 +17,6 @@ import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -81,7 +80,7 @@ public List getGroupsForWorld(String worldName) { } } // Only use the default group for worlds managed by MV-Core - if (worldGroups.isEmpty() && inventoriesConfig.isDefaultingUngroupedWorlds() && + if (worldGroups.isEmpty() && inventoriesConfig.getDefaultUngroupedWorlds() && this.worldManager.isWorld(worldName)) { Logging.finer("Returning default group for world: " + worldName); worldGroups.add(getDefaultGroup()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java index f0b9c5a3..e0113e09 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java @@ -46,7 +46,7 @@ public interface Sharable { /** * @return True if this Sharable is optional. That is to say that it is completely ignored when share handling - * takes place UNLESS it is present in {@link InventoriesConfig#getOptionalShares()}. + * takes place UNLESS it is present in {@link InventoriesConfig#getActiveOptionalShares()}. */ boolean isOptional(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java index 2afe533f..244f500c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java @@ -184,7 +184,7 @@ public Permission getBypassPermission(String finalNode) { */ public boolean hasBypass(Player player, String name) { if (inventories != null && - !inventories.getServiceLocator().getService(InventoriesConfig.class).isUsingBypass()) { + !inventories.getServiceLocator().getService(InventoriesConfig.class).getEnableBypassPermissions()) { return false; } Permission bypassPerm = this.getBypassPermission(name); diff --git a/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt index 5a3d7497..8cc08158 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt @@ -19,19 +19,19 @@ class ToggleCommandTest : AbstractCommandTest() { @Test fun `Toggle last_location on and off`() { - assertFalse(config.optionalShares.contains(Sharables.LAST_LOCATION)) + assertFalse(config.activeOptionalShares.contains(Sharables.LAST_LOCATION)) server.dispatchCommand(console, "mvinv toggle last_location") - assertTrue(config.optionalShares.contains(Sharables.LAST_LOCATION)) + assertTrue(config.activeOptionalShares.contains(Sharables.LAST_LOCATION)) server.dispatchCommand(console, "mvinv toggle last_location") - assertFalse(config.optionalShares.contains(Sharables.LAST_LOCATION)) + assertFalse(config.activeOptionalShares.contains(Sharables.LAST_LOCATION)) } @Test fun `Toggle economy on and off`() { - assertFalse(config.optionalShares.contains(Sharables.ECONOMY)) + assertFalse(config.activeOptionalShares.contains(Sharables.ECONOMY)) server.dispatchCommand(console, "mvinv toggle economy") - assertTrue(config.optionalShares.contains(Sharables.ECONOMY)) + assertTrue(config.activeOptionalShares.contains(Sharables.ECONOMY)) server.dispatchCommand(console, "mvinv toggle economy") - assertFalse(config.optionalShares.contains(Sharables.ECONOMY)) + assertFalse(config.activeOptionalShares.contains(Sharables.ECONOMY)) } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt new file mode 100644 index 00000000..97141ee5 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt @@ -0,0 +1,4 @@ +package org.mvplugins.multiverse.inventories.config + +class ConfigTest { +} \ No newline at end of file From 98dc9093c90cff0984632c93ad58f411381e7a27 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:03:13 +0800 Subject: [PATCH 090/180] Refactor share handling to reduce complexity --- .../handleshare/GameModeShareHandler.java | 13 +-- .../inventories/handleshare/ShareHandler.java | 28 +---- .../handleshare/WorldChangeShareHandler.java | 109 ++++++------------ .../inventories/share/Sharables.java | 4 +- 4 files changed, 45 insertions(+), 109 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index f056a1e0..987c82de 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -36,8 +36,6 @@ final class GameModeShareHandler extends ShareHandler { this.toType = ProfileTypes.forGameMode(toGameMode); this.world = player.getWorld().getName(); this.worldGroups = getAffectedWorldGroups(); - - prepareProfiles(); } private List getAffectedWorldGroups() { @@ -49,11 +47,12 @@ protected ShareHandlingEvent createEvent() { return new GameModeChangeShareHandlingEvent(player, affectedProfiles, fromGameMode, toGameMode); } - private void prepareProfiles() { + @Override + protected void prepareProfiles() { Logging.finer("=== " + player.getName() + " changing game mode from: " + fromType + " to: " + toType + " for world: " + world + " ==="); - setAlwaysWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player)); + affectedProfiles.setAlwaysWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player)); if (isPlayerAffectedByChange()) { addProfiles(); @@ -78,7 +77,7 @@ private void addProfiles() { worldGroups.forEach(this::addProfilesForWorldGroup); } else { Logging.finer("No groups for world."); - addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), + affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), Sharables.allOf()); } } @@ -89,8 +88,8 @@ private boolean hasWorldGroups() { private void addProfilesForWorldGroup(WorldGroup worldGroup) { ProfileContainer container = worldGroup.getGroupProfileContainer(); - addWriteProfile(container.getPlayerData(fromType, player), Sharables.allOf()); - addReadProfile(container.getPlayerData(toType, player), Sharables.allOf()); + affectedProfiles.addWriteProfile(container.getPlayerData(fromType, player), Sharables.allOf()); + affectedProfiles.addReadProfile(container.getPlayerData(toType, player), Sharables.allOf()); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 8604e88a..00d5a735 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -26,7 +26,7 @@ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShar protected final InventoriesConfig inventoriesConfig; protected final WorldGroupManager worldGroupManager; protected final ProfileContainerStore worldProfileContainerStore; - final AffectedProfiles affectedProfiles; + protected final AffectedProfiles affectedProfiles; ShareHandler(MultiverseInventories inventories, Player player) { this.inventories = inventories; @@ -44,8 +44,8 @@ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShar * inventories/stats for a player and persisting the changes. */ final void handleSharing() { + this.prepareProfiles(); ShareHandlingEvent event = this.createEvent(); - Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { Logging.fine("Share handling has been cancelled by another plugin!"); @@ -54,28 +54,7 @@ final void handleSharing() { this.completeSharing(event); } - protected final void setAlwaysWriteProfile(PlayerProfile profile) { - affectedProfiles.setAlwaysWriteProfile(profile); - } - - /** - * @param profile The player profile that will need data saved to. - * @param shares What from this group needs to be saved. - */ - protected final void addWriteProfile(PlayerProfile profile, Shares shares) { - affectedProfiles.addWriteProfile(profile, shares); - } - - /** - * Finalizes the transfer from one world to another. This handles the switching - * inventories/stats for a player and persisting the changes. - * - * @param profile The player profile that will need data loaded from. - * @param shares What from this group needs to be loaded. - */ - protected final void addReadProfile(PlayerProfile profile, Shares shares) { - affectedProfiles.addReadProfile(profile, shares); - } + protected abstract void prepareProfiles(); protected abstract ShareHandlingEvent createEvent(); @@ -130,5 +109,4 @@ private void updatePlayer(Player player, List readProfiles) { private void logHandlingComplete(ShareHandlingEvent event) { Logging.finer("=== %s complete for %s ===", event.getPlayer().getName(), event.getEventName()); } - } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index 6df4323d..fb16923e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -30,15 +30,9 @@ final class WorldChangeShareHandler extends ShareHandler { this.toWorld = toWorld; // Get any groups we may need to save stuff to. - this.fromWorldGroups = getAffectedWorldGroups(fromWorld); + this.fromWorldGroups = worldGroupManager.getGroupsForWorld(fromWorld); // Get any groups we may need to load stuff from. - this.toWorldGroups = getAffectedWorldGroups(toWorld); - - prepareProfiles(); - } - - private List getAffectedWorldGroups(String world) { - return worldGroupManager.getGroupsForWorld(world); + this.toWorldGroups = worldGroupManager.getGroupsForWorld(toWorld); } @Override @@ -46,28 +40,24 @@ protected ShareHandlingEvent createEvent() { return new WorldChangeShareHandlingEvent(player, affectedProfiles, fromWorld, toWorld); } - private void prepareProfiles() { + @Override + protected void prepareProfiles() { Logging.finer("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); - setAlwaysWriteWorldProfile(); - if (isPlayerAffectedByChange()) { - addProfiles(); + addWriteProfiles(); + addReadProfiles(); } } private void setAlwaysWriteWorldProfile() { // We will always save everything to the world they come from. PlayerProfile fromWorldProfile = getWorldPlayerProfile(fromWorld, player); - setAlwaysWriteProfile(fromWorldProfile); + affectedProfiles.setAlwaysWriteProfile(fromWorldProfile); } private PlayerProfile getWorldPlayerProfile(String world, Player player) { - return getWorldProfile(world).getPlayerData(player); - } - - private ProfileContainer getWorldProfile(String world) { - return worldProfileContainerStore.getContainer(world); + return worldProfileContainerStore.getContainer(world).getPlayerData(player); } private boolean isPlayerAffectedByChange() { @@ -82,53 +72,47 @@ private boolean isPlayerBypassingChange() { return Perm.BYPASS_WORLD.hasBypass(player, fromWorld); } - private void addProfiles() { - addWriteProfiles(); - new ReadProfilesAggregator().addReadProfiles(); - } - private void addWriteProfiles() { - if (hasFromWorldGroups()) { - fromWorldGroups.forEach(wg -> new WorldGroupWrapper(wg).conditionallyAddWriteProfiles()); - } else { + if (fromWorldGroups.isEmpty()) { Logging.finer("No groups for fromWorld."); + return; } + fromWorldGroups.forEach(wg -> new WorldGroupWrapper(wg).conditionallyAddWriteProfiles()); } - private boolean hasFromWorldGroups() { - return !fromWorldGroups.isEmpty(); + private void addReadProfiles() { + new ReadProfilesAggregator().addReadProfiles(); } private class ReadProfilesAggregator { - private Shares sharesToRead; + private final Shares handledShares; + + private ReadProfilesAggregator() { + this.handledShares = Sharables.noneOf(); + } private void addReadProfiles() { - sharesToRead = Sharables.noneOf(); addReadProfilesFromToWorldGroups(); useToWorldForMissingShares(); } private void addReadProfilesFromToWorldGroups() { - if (hasToWorldGroups()) { - toWorldGroups.forEach(this::conditionallyAddReadProfileForWorldGroup); - } else { + if (toWorldGroups.isEmpty()) { Logging.finer("No groups for toWorld."); + return; } - } - - private boolean hasToWorldGroups() { - return !toWorldGroups.isEmpty(); + toWorldGroups.forEach(this::conditionallyAddReadProfileForWorldGroup); } private void conditionallyAddReadProfileForWorldGroup(WorldGroup worldGroup) { - if (isPlayerAffectedByChange(worldGroup)) { - if (isFromWorldNotInToWorldGroup(worldGroup)) { - addReadProfileForWorldGroup(worldGroup); - } else { - sharesToRead.addAll(worldGroup.getShares()); - } + if (!isPlayerAffectedByChange(worldGroup)) { + return; } + if (isFromWorldNotInToWorldGroup(worldGroup)) { + addReadProfileForWorldGroup(worldGroup); + } + handledShares.addAll(worldGroup.getShares()); } private boolean isPlayerAffectedByChange(WorldGroup worldGroup) { @@ -154,49 +138,25 @@ private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { private void addReadProfileForWorldGroup(WorldGroup worldGroup) { PlayerProfile playerProfile = getWorldGroupPlayerData(worldGroup); - Shares sharesToAdd = getWorldGroupShares(worldGroup); - - addReadProfile(playerProfile, sharesToAdd); - sharesToRead.addAll(sharesToAdd); + affectedProfiles.addReadProfile(playerProfile, worldGroup.getShares()); } private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { - return getWorldGroupProfileContainer(worldGroup).getPlayerData(player); - } - - private ProfileContainer getWorldGroupProfileContainer(WorldGroup worldGroup) { - return worldGroup.getGroupProfileContainer(); - } - - private Shares getWorldGroupShares(WorldGroup worldGroup) { - return Sharables.fromShares(worldGroup.getShares()); + return worldGroup.getGroupProfileContainer().getPlayerData(player); } private void useToWorldForMissingShares() { // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. - if (hasUnhandledShares()) { - addUnhandledSharesFromToWorld(); + Shares unhandledShares = Sharables.complimentOf(handledShares); + if (unhandledShares.isEmpty()) { + return; } - } - - private boolean hasUnhandledShares() { - return !sharesToRead.isSharing(Sharables.all()); - } - - private void addUnhandledSharesFromToWorld() { - Shares unhandledShares = Sharables.complimentOf(sharesToRead); - Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); - - addReadProfile(getToWorldPlayerData(), unhandledShares); + affectedProfiles.addReadProfile(getToWorldPlayerData(), unhandledShares); } private PlayerProfile getToWorldPlayerData() { - return getToWorldProfileContainer().getPlayerData(player); - } - - private ProfileContainer getToWorldProfileContainer() { - return worldProfileContainerStore.getContainer(toWorld); + return worldProfileContainerStore.getContainer(toWorld).getPlayerData(player); } } @@ -234,5 +194,4 @@ private Shares getWorldGroupShares() { return Sharables.fromShares(worldGroup.getShares()); } } - } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 6368ac44..34940f10 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -793,9 +793,9 @@ public static Shares noneOf() { * contained in the shares argument. */ public static Shares complimentOf(Shares shares) { - Set compliment = Sharables.allOf(); + Shares compliment = Sharables.allOf(); compliment.removeAll(shares); - return new Sharables(compliment); + return compliment; } /** From 0d63d5270b39effcb476dbb4b9cdf671e424d250 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:12:14 +0800 Subject: [PATCH 091/180] Move mvcore events handling to a seperate listener class --- .../inventories/MVEventsListener.java | 86 +++++++++++++++++++ .../inventories/MultiverseInventories.java | 3 + .../handleshare/ShareHandleListener.java | 65 ++------------ 3 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java new file mode 100644 index 00000000..2aeee039 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java @@ -0,0 +1,86 @@ +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; +import org.mvplugins.multiverse.core.event.MVDebugModeEvent; +import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.io.File; + +@Service +final class MVEventsListener implements Listener { + + private final MultiverseInventories inventories; + private final InventoriesConfig config; + private final WorldGroupManager worldGroupManager; + + @Inject + MVEventsListener(@NotNull MultiverseInventories inventories, @NotNull InventoriesConfig config, @NotNull WorldGroupManager worldGroupManager) { + this.inventories = inventories; + this.config = config; + this.worldGroupManager = worldGroupManager; + } + + /** + * Adds Multiverse-Inventories version info to /mv version. + * + * @param event The MVVersionEvent that this plugin will listen for. + */ + @EventHandler + void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { + event.appendDebugInfo(getDebugInfo()); + File configFile = new File(this.inventories.getDataFolder(), "config.yml"); + File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); + event.putDetailedDebugInfo("multiverse-inventories/config.yml", configFile); + event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); + } + + /** + * Builds a String containing Multiverse-Inventories' version info. + * + * @return The version info. + */ + private String getDebugInfo() { + StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + inventories.getDescription().getVersion() + '\n' + + "[Multiverse-Inventories] === Settings ===" + '\n' + + "[Multiverse-Inventories] First Run: " + config.getFirstRun() + '\n' + + "[Multiverse-Inventories] Using Bypass: " + config.getEnableBypassPermissions() + '\n' + + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.getDefaultUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.getSavePlayerdataOnQuit() + '\n' + + "[Multiverse-Inventories] Using GameMode Profiles: " + config.getEnableGamemodeShareHandling() + '\n' + + "[Multiverse-Inventories] === Shares ===" + '\n' + + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.getUseOptionalsForUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Enabled Optionals: " + config.getActiveOptionalShares() + '\n' + + "[Multiverse-Inventories] === Groups ===" + '\n'); + + for (WorldGroup group : worldGroupManager.getGroups()) { + versionInfo.append("[Multiverse-Inventories] ").append(group.toString()).append('\n'); + } + + return versionInfo.toString(); + } + + @EventHandler + void onDebugModeChange(MVDebugModeEvent event) { + Logging.setDebugLevel(event.getLevel()); + } + + /** + * Hooks Multiverse-Inventories into the Multiverse reload command. + * + * @param event The MVConfigReloadEvent that this plugin will listen for. + */ + @EventHandler + void configReload(MVConfigReloadEvent event) { + this.inventories.reloadConfig(); + event.addConfig("Multiverse-Inventories - config.yml"); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 7aa50833..6fba3a0a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -51,6 +51,8 @@ public class MultiverseInventories extends MultiversePlugin { @Inject private Provider respawnListener; @Inject + private Provider mvEventsListener; + @Inject private Provider worldGroupManager; @Inject private Provider profileDataSource; @@ -96,6 +98,7 @@ public final void onEnable() { PluginManager pluginManager = this.getServer().getPluginManager(); pluginManager.registerEvents(shareHandleListener.get(), this); pluginManager.registerEvents(respawnListener.get(), this); + pluginManager.registerEvents(mvEventsListener.get(), this); try { Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); pluginManager.registerEvents(new SpawnChangeListener(this), this); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 6d09ebf3..07aa63e0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -75,61 +75,6 @@ public final class ShareHandleListener implements Listener { this.profileContainerStoreProvider = profileContainerStoreProvider; } - /** - * Adds Multiverse-Inventories version info to /mv version. - * - * @param event The MVVersionEvent that this plugin will listen for. - */ - @EventHandler - void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { - event.appendDebugInfo(getDebugInfo()); - File configFile = new File(this.inventories.getDataFolder(), "config.yml"); - File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); - event.putDetailedDebugInfo("multiverse-inventories/config.yml", configFile); - event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); - } - - /** - * Builds a String containing Multiverse-Inventories' version info. - * - * @return The version info. - */ - private String getDebugInfo() { - StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + inventories.getDescription().getVersion() + '\n' - + "[Multiverse-Inventories] === Settings ===" + '\n' - + "[Multiverse-Inventories] First Run: " + config.getFirstRun() + '\n' - + "[Multiverse-Inventories] Using Bypass: " + config.getEnableBypassPermissions() + '\n' - + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.getDefaultUngroupedWorlds() + '\n' - + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.getSavePlayerdataOnQuit() + '\n' - + "[Multiverse-Inventories] Using GameMode Profiles: " + config.getEnableGamemodeShareHandling() + '\n' - + "[Multiverse-Inventories] === Shares ===" + '\n' - + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.getUseOptionalsForUngroupedWorlds() + '\n' - + "[Multiverse-Inventories] Enabled Optionals: " + config.getActiveOptionalShares() + '\n' - + "[Multiverse-Inventories] === Groups ===" + '\n'); - - for (WorldGroup group : worldGroupManager.getGroups()) { - versionInfo.append("[Multiverse-Inventories] ").append(group.toString()).append('\n'); - } - - return versionInfo.toString(); - } - - @EventHandler - void onDebugModeChange(MVDebugModeEvent event) { - Logging.setDebugLevel(event.getLevel()); - } - - /** - * Hooks Multiverse-Inventories into the Multiverse reload command. - * - * @param event The MVConfigReloadEvent that this plugin will listen for. - */ - @EventHandler - void configReload(MVConfigReloadEvent event) { - this.inventories.reloadConfig(); - event.addConfig("Multiverse-Inventories - config.yml"); - } - @EventHandler(priority = EventPriority.MONITOR) void playerPreLogin(AsyncPlayerPreLoginEvent event) { if (event.getLoginResult() != Result.ALLOWED) { @@ -339,6 +284,12 @@ void entityPortal(EntityPortalEvent event) { } World fromWorld = event.getFrom().getWorld(); + if (fromWorld == null) { + // Apparently this happens sometimes. + Logging.fine("Entity %s attempted to go from null world", entity); + return; + } + Location toLocation = event.getTo(); if (toLocation == null) { // Apparently this happens sometimes. @@ -359,8 +310,8 @@ void entityPortal(EntityPortalEvent event) { List fromGroups = worldGroupManager.getGroupsForWorld(fromWorld.getName()); List toGroups = worldGroupManager.getGroupsForWorld(toWorld.getName()); // We only care about the groups that have the inventory sharable - fromGroups = fromGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).collect(Collectors.toList()); - toGroups = toGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).collect(Collectors.toList()); + fromGroups = fromGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).toList(); + toGroups = toGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).toList(); for (WorldGroup fromGroup : fromGroups) { if (toGroups.contains(fromGroup)) { Logging.finest("Allowing item or inventory holding %s to go from world %s to world %s", entity, From 65342391770bb2200c90539e95ea6d6255b96e2c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:34:10 +0800 Subject: [PATCH 092/180] Add config options for last-location and PlayerSpawnChangeEvent --- .../inventories/MultiverseInventories.java | 17 +++++----- .../inventories/config/InventoriesConfig.java | 32 ++++++++++++++++--- .../config/InventoriesConfigNodes.java | 29 +++++++++++++++++ .../handleshare/ShareHandleListener.java | 20 +++++++----- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 6fba3a0a..4be3ed61 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -99,14 +99,15 @@ public final void onEnable() { pluginManager.registerEvents(shareHandleListener.get(), this); pluginManager.registerEvents(respawnListener.get(), this); pluginManager.registerEvents(mvEventsListener.get(), this); - try { - Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); - pluginManager.registerEvents(new SpawnChangeListener(this), this); - usingSpawnChangeEvent = true; - Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); - } catch (ClassNotFoundException e) { - Logging.fine("PlayerSpawnChangeEvent will not be used!"); - usingSpawnChangeEvent = false; + if (inventoriesConfig.get().getUseImprovedRespawnLocationDetection()) { + try { + Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); + pluginManager.registerEvents(new SpawnChangeListener(this), this); + usingSpawnChangeEvent = true; + Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); + } catch (ClassNotFoundException e) { + Logging.fine("PlayerSpawnChangeEvent will not be used!"); + } } // Register Commands diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 15460b8d..8730bddf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -142,19 +142,43 @@ public Try setActiveOptionalShares(Shares shares) { return this.configHandle.set(configNodes.activeOptionalShares, shares); } + public boolean getUseImprovedRespawnLocationDetection() { + return this.configHandle.get(configNodes.useImprovedRespawnLocationDetection); + } + + public Try setUseImprovedRespawnLocationDetection(boolean useImprovedRespawnLocationDetection) { + return this.configHandle.set(configNodes.useImprovedRespawnLocationDetection, useImprovedRespawnLocationDetection); + } + + public boolean getResetLastLocationOnDeath() { + return this.configHandle.get(configNodes.resetLastLocationOnDeath); + } + + public Try setResetLastLocationOnDeath(boolean resetLastLocationOnDeath) { + return this.configHandle.set(configNodes.resetLastLocationOnDeath, resetLastLocationOnDeath); + } + + public boolean getApplyLastLocationForAllTeleports() { + return this.configHandle.get(configNodes.applyLastLocationForAllTeleports); + } + + public Try setApplyLastLocationForAllTeleports(boolean applyLastLocationForAllTeleports) { + return this.configHandle.set(configNodes.applyLastLocationForAllTeleports, applyLastLocationForAllTeleports); + } + /** - * Tells whether Multiverse-Inventories should save on player logout and load on player login. + * Tells whether Multiverse-Inventories should save on player logout. * - * @return True if should save and load on player log out and in. + * @return True if should save on player log out. */ public boolean getSavePlayerdataOnQuit() { return this.configHandle.get(configNodes.savePlayerdataOnQuit); } /** - * Sets whether Multiverse-Inventories should save on player logout and load on player login. + * Sets whether Multiverse-Inventories should save on player logout. * - * @param useLoggingSaveLoad true if should save and load on player log out and in. + * @param useLoggingSaveLoad true if should save on player log out. */ public Try setSavePlayerdataOnQuit(boolean useLoggingSaveLoad) { return this.configHandle.set(configNodes.savePlayerdataOnQuit, useLoggingSaveLoad); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 0db1b3bb..c00eef5d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -103,6 +103,35 @@ public Object serialize(Shares sharables, Class aClass) { }) .build()); + private final ConfigHeaderNode sharablesHeader = node(ConfigHeaderNode.builder("sharables") + .comment("") + .comment("") + .build()); + + final ConfigNode useImprovedRespawnLocationDetection = node(ConfigNode.builder("sharables.use-improved-respawn-location-detection", Boolean.class) + .comment("") + .comment("When enabled, we will use 1.21's PlayerSpawnChangeEvent to better detect bed and anchor respawn locations.") + .comment("This options is not applicable for older minecraft server versions.") + .defaultValue(true) + .name("use-improved-respawn-location-detection") + .build()); + + final ConfigNode resetLastLocationOnDeath = node(ConfigNode.builder("sharables.reset-last-location-on-death", Boolean.class) + .comment("When set to true, the last location of the player will be reset when they die.") + .comment("This is useful if they respawn in a different world and you do not want them to return to their death location.") + .defaultValue(false) + .name("reset-last-location-on-death") + .build()); + + final ConfigNode applyLastLocationForAllTeleports = node(ConfigNode.builder("sharables.apply-last-location-for-all-teleports", Boolean.class) + .comment("") + .comment("When enabled, the last location of the player will be applied for any teleportation.") + .comment("This is useful as you want to use the last location for any teleportation, such as the warp system.") + .comment("When disabled, you can only use `/mvinv tplastlocation [player] ` to teleport to the player's last location.") + .defaultValue(true) + .name("apply-last-location-for-all-teleports") + .build()); + private final ConfigHeaderNode performanceHeader = node(ConfigHeaderNode.builder("performance") .comment("") .comment("") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 07aa63e0..60135610 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -245,20 +245,24 @@ void playerDeath(PlayerDeathEvent event) { String deathWorld = event.getEntity().getWorld().getName(); ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(deathWorld); PlayerProfile profile = worldProfileContainer.getPlayerData(event.getEntity()); - profile.set(Sharables.LEVEL, event.getNewLevel()); - profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); - profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); - profileDataSource.updatePlayerData(profile); + resetStatsOnDeath(event, profile); for (WorldGroup worldGroup : worldGroupManager.getGroupsForWorld(deathWorld)) { profile = worldGroup.getGroupProfileContainer().getPlayerData(event.getEntity()); - profile.set(Sharables.LEVEL, event.getNewLevel()); - profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); - profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); - profileDataSource.updatePlayerData(profile); + resetStatsOnDeath(event, profile); } Logging.finer("=== Finished handling PlayerDeathEvent for: " + event.getEntity().getName() + "! ==="); } + private void resetStatsOnDeath(PlayerDeathEvent event, PlayerProfile profile) { + profile.set(Sharables.LEVEL, event.getNewLevel()); + profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); + profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); + if (config.getResetLastLocationOnDeath()) { + profile.set(Sharables.LAST_LOCATION, null); + } + profileDataSource.updatePlayerData(profile); + } + @EventHandler(priority = EventPriority.MONITOR) void playerRespawn(PlayerRespawnEvent event) { Location respawnLoc = event.getRespawnLocation(); From c9d2c2a3c6b2d679420553f642a832dcde5d75e7 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:59:09 +0800 Subject: [PATCH 093/180] Implement disabled share feature --- .../inventories/MultiverseInventories.java | 2 +- .../config/InventoriesConfigNodes.java | 1 + .../handleshare/AffectedProfiles.java | 4 +- .../handleshare/GameModeShareHandler.java | 7 +- .../handleshare/ShareHandlingUpdater.java | 45 +++-------- .../handleshare/SingleShareWriter.java | 15 ++-- .../handleshare/WorldChangeShareHandler.java | 27 +++---- .../group/AbstractWorldGroupManager.java | 12 ++- .../inventories/profile/group/WorldGroup.java | 22 ++++++ .../profile/group/WorldGroupManager.java | 5 ++ .../profile/group/YamlWorldGroupManager.java | 16 +++- .../inventories/share/Sharables.java | 74 ++++++++++++++++++- .../handleshare/WorldChangeTest.kt | 4 + .../gameplay/world_change_groups.yml | 2 + 14 files changed, 166 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 4be3ed61..743c9cb4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -90,6 +90,7 @@ public final void onEnable() { super.onEnable(); initializeDependencyInjection(); + Sharables.init(this); Perm.register(this); this.reloadConfig(); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); @@ -114,7 +115,6 @@ public final void onEnable() { this.registerCommands(); // Hook plugins that can be imported from this.hookImportables(); - Sharables.init(this); this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); Logging.config("Version %s (API v%s) Enabled - By %s", diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index c00eef5d..c3d79f12 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -101,6 +101,7 @@ public Object serialize(Shares sharables, Class aClass) { return sharables.toStringList(); } }) + .onSetValue((oldValue, newValue) -> Sharables.recalculateEnabledShares()) .build()); private final ConfigHeaderNode sharablesHeader = node(ConfigHeaderNode.builder("sharables") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java index 7e98b7e9..7fffb1f1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -6,7 +6,7 @@ import java.util.LinkedList; import java.util.List; -import static org.mvplugins.multiverse.inventories.share.Sharables.allOf; +import static org.mvplugins.multiverse.inventories.share.Sharables.enabled; public final class AffectedProfiles { @@ -18,7 +18,7 @@ public final class AffectedProfiles { } void setAlwaysWriteProfile(PlayerProfile profile) { - alwaysWriteProfile = new PersistingProfile(allOf(), profile); + alwaysWriteProfile = new PersistingProfile(enabled(), profile); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 987c82de..568401e7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -78,7 +78,7 @@ private void addProfiles() { } else { Logging.finer("No groups for world."); affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), - Sharables.allOf()); + inventoriesConfig.getUseOptionalsForUngroupedWorlds() ? Sharables.enabled() : Sharables.standardOf()); } } @@ -88,8 +88,7 @@ private boolean hasWorldGroups() { private void addProfilesForWorldGroup(WorldGroup worldGroup) { ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(fromType, player), Sharables.allOf()); - affectedProfiles.addReadProfile(container.getPlayerData(toType, player), Sharables.allOf()); + affectedProfiles.addWriteProfile(container.getPlayerData(fromType, player), Sharables.enabled()); + affectedProfiles.addReadProfile(container.getPlayerData(toType, player), Sharables.enabled()); } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 26e09cf0..090812c5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -36,19 +36,16 @@ private ShareHandlingUpdater(MultiverseInventories inventories, Player player, P } private void updateProfile() { - final List> saved = new ArrayList<>(profile.shares().size()); - for (Sharable sharable : profile.shares()) { - if (isSharableUsed(sharable)) { - saved.add(sharable); - sharable.getHandler().updateProfile(profile.profile(), player); - } + if (profile.shares().isEmpty()) { + return; } - if (!saved.isEmpty()) { - Logging.finer("Persisted: " + saved + " to " - + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() - + " (" + profile.profile().getProfileType() + ")" - + " for player " + profile.profile().getPlayer().getName()); + for (Sharable sharable : profile.shares()) { + sharable.getHandler().updateProfile(profile.profile(), player); } + Logging.finer("Persisted: " + profile.shares() + " to " + + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() + + " (" + profile.profile().getProfileType() + ")" + + " for player " + profile.profile().getPlayer().getName()); inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(profile.profile()); } @@ -59,12 +56,10 @@ private void updatePlayer() { final List> defaulted = new ArrayList<>(profile.shares().size()); for (Sharable sharable : profile.shares()) { - if (isSharableUsed(sharable)) { - if (sharable.getHandler().updatePlayer(player, profile.profile())) { - loaded.add(sharable); - } else { - defaulted.add(sharable); - } + if (sharable.getHandler().updatePlayer(player, profile.profile())) { + loaded.add(sharable); + } else { + defaulted.add(sharable); } } if (!loaded.isEmpty()) { @@ -80,20 +75,4 @@ private void updatePlayer() { + " (" + profile.profile().getProfileType() + ")"); } } - - private boolean isSharableUsed(Sharable sharable) { - var config = inventories.getServiceLocator().getService(InventoriesConfig.class); - if (sharable.isOptional()) { - if (!config.getActiveOptionalShares().contains(sharable)) { - Logging.finest("Ignoring optional share: " + sharable.getNames()[0]); - return false; - } - if (profile.profile().getContainerType() == ContainerType.WORLD - && !config.getUseOptionalsForUngroupedWorlds()) { - Logging.finest("Ignoring optional share '" + sharable.getNames()[0] + "' for ungrouped world!"); - return false; - } - } - return true; - } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 198a4cc3..354af032 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -59,11 +59,16 @@ public void write(T value, boolean save) { this.inventories.getServiceLocator().getService(WorldGroupManager.class) .getGroupsForWorld(worldName) - .forEach(worldGroup -> writeNewValueToProfile( - worldGroup.getGroupProfileContainer().getPlayerData(this.player), - value, - save - )); + .forEach(worldGroup -> { + if (worldGroup.getDisabledShares().contains(sharable)) { + return; + } + writeNewValueToProfile( + worldGroup.getGroupProfileContainer().getPlayerData(this.player), + value, + save + ); + }); } private void writeNewValueToProfile(PlayerProfile profile, T value, boolean save) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index fb16923e..dd9a4d08 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -112,7 +112,8 @@ private void conditionallyAddReadProfileForWorldGroup(WorldGroup worldGroup) { if (isFromWorldNotInToWorldGroup(worldGroup)) { addReadProfileForWorldGroup(worldGroup); } - handledShares.addAll(worldGroup.getShares()); + handledShares.addAll(worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getDisabledShares()); } private boolean isPlayerAffectedByChange(WorldGroup worldGroup) { @@ -138,7 +139,7 @@ private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { private void addReadProfileForWorldGroup(WorldGroup worldGroup) { PlayerProfile playerProfile = getWorldGroupPlayerData(worldGroup); - affectedProfiles.addReadProfile(playerProfile, worldGroup.getShares()); + affectedProfiles.addReadProfile(playerProfile, worldGroup.getApplicableShares()); } private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { @@ -147,7 +148,11 @@ private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { private void useToWorldForMissingShares() { // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. - Shares unhandledShares = Sharables.complimentOf(handledShares); + Shares unhandledShares = Sharables.enabledOf(); + unhandledShares.removeAll(handledShares); + if (!inventoriesConfig.getUseOptionalsForUngroupedWorlds()) { + unhandledShares.removeAll(Sharables.optional()); + } if (unhandledShares.isEmpty()) { return; } @@ -168,30 +173,18 @@ public WorldGroupWrapper(WorldGroup worldGroup) { } private void conditionallyAddWriteProfiles() { - if (isEligibleForWrite()) { + if (!worldGroup.containsWorld(toWorld)) { addWriteProfiles(); } } - boolean isEligibleForWrite() { - return groupDoesNotContainWorld(toWorld) || isNotSharingAll(); - } - - private boolean groupDoesNotContainWorld(String world) { - return !worldGroup.containsWorld(world); - } - - private boolean isNotSharingAll() { - return !worldGroup.getShares().isSharing(Sharables.all()); - } - void addWriteProfiles() { ProfileContainer container = worldGroup.getGroupProfileContainer(); affectedProfiles.addWriteProfile(container.getPlayerData(player), getWorldGroupShares()); } private Shares getWorldGroupShares() { - return Sharables.fromShares(worldGroup.getShares()); + return Sharables.fromShares(worldGroup.getApplicableShares()); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index 01994c7d..69cd4083 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -109,6 +109,7 @@ protected Map getGroupNames() { @Override public void updateGroup(final WorldGroup worldGroup) { getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); + worldGroup.recalculateApplicableShares(); } /** @@ -127,7 +128,7 @@ public WorldGroup newEmptyGroup(String name) { if (getGroup(name) != null) { return null; } - return new WorldGroup(this, profileContainerStoreProvider, name); + return new WorldGroup(this, profileContainerStoreProvider, inventoriesConfig, name); } /** @@ -143,7 +144,7 @@ public void createDefaultGroup() { .fold(() -> Bukkit.getWorlds().get(0), world -> world); World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); - WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, DEFAULT_GROUP_NAME); + WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, inventoriesConfig, DEFAULT_GROUP_NAME); worldGroup.getShares().mergeShares(Sharables.allOf()); worldGroup.addWorld(defaultWorld); if (defaultNether != null) { @@ -153,7 +154,7 @@ public void createDefaultGroup() { worldGroup.addWorld(defaultEnd); } updateGroup(worldGroup); - inventoriesConfig.save(); + worldGroup.recalculateApplicableShares(); Logging.info("Created a default group for you containing all of your default worlds: " + String.join(", ", worldGroup.getWorlds())); } @@ -237,4 +238,9 @@ public void checkForConflicts(MVCommandIssuer issuer) { issuer.sendInfo(MVInvi18n.CONFLICT_NOTFOUND); } } + + @Override + public void recalculateApplicableShares() { + getGroupNames().values().forEach(WorldGroup::recalculateApplicableShares); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index c23ec62f..f58d7f53 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.profile.group; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; @@ -18,19 +19,24 @@ public final class WorldGroup { private final WorldGroupManager worldGroupManager; private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final InventoriesConfig inventoriesConfig; private final String name; private final HashSet worlds = new HashSet<>(); private final Shares shares = Sharables.noneOf(); + private final Shares disabledShares = Sharables.noneOf(); + private Shares applicableShares = Sharables.noneOf(); private String spawnWorld = null; private EventPriority spawnPriority = EventPriority.NORMAL; WorldGroup( final WorldGroupManager worldGroupManager, final ProfileContainerStoreProvider profileContainerStoreProvider, + final InventoriesConfig inventoriesConfig, final String name) { this.worldGroupManager = worldGroupManager; this.profileContainerStoreProvider = profileContainerStoreProvider; + this.inventoriesConfig = inventoriesConfig; this.name = name; } @@ -187,6 +193,22 @@ public Shares getShares() { return this.shares; } + public Shares getDisabledShares() { + return this.disabledShares; + } + + public Shares getApplicableShares() { + return this.applicableShares; + } + + public void recalculateApplicableShares() { + this.applicableShares = Sharables.fromShares(this.shares); + this.applicableShares.removeAll(this.disabledShares); + Shares disabledOptionalShares = Sharables.optionalOf(); + disabledOptionalShares.removeAll(this.inventoriesConfig.getActiveOptionalShares()); + this.applicableShares.removeAll(disabledOptionalShares); + } + /** * @param worldName Name of world to check for. * @return True if specified world is part of this group. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index 4188bebe..a905a3e3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -110,5 +110,10 @@ public sealed interface WorldGroupManager permits AbstractWorldGroupManager { * @param issuer The issuer to relay information to. If null, info only displayed in console. */ void checkForConflicts(MVCommandIssuer issuer); + + /** + * Recalculates the applicable shares for all groups removing disabled optional shares. + */ + void recalculateApplicableShares(); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index b91e099c..e7946a07 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -90,6 +90,7 @@ public Try load() { for (final WorldGroup worldGroup : worldGroups) { getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); } + recalculateApplicableShares(); Logging.fine("Loaded " + worldGroups.size() + " world groups from config."); }); } @@ -144,7 +145,7 @@ private List getGroupsFromConfig() { private WorldGroup deserializeGroup(final String name, final Map dataMap) throws DeserializationException { - WorldGroup profile = new WorldGroup(this, profileContainerStoreProvider, name); + WorldGroup profile = new WorldGroup(this, profileContainerStoreProvider, inventoriesConfig, name); if (dataMap.containsKey("worlds")) { Object worldListObj = dataMap.get("worlds"); if (worldListObj == null) { @@ -183,6 +184,14 @@ private WorldGroup deserializeGroup(final String name, final Map Logging.warning("Shares formatted incorrectly for group: " + name); } } + if (dataMap.containsKey("disabled-shares")) { + Object sharesListObj = dataMap.get("disabled-shares"); + if (sharesListObj instanceof List) { + profile.getDisabledShares().mergeShares(Sharables.fromList((List) sharesListObj)); + } else { + Logging.warning("Disabled shares formatted incorrectly for group: " + name); + } + } if (dataMap.containsKey("spawn")) { Object spawnPropsObj = dataMap.get("spawn"); if (spawnPropsObj instanceof ConfigurationSection) { @@ -205,6 +214,7 @@ private WorldGroup deserializeGroup(final String name, final Map Logging.warning("Spawn settings for group formatted incorrectly"); } } + profile.recalculateApplicableShares(); return profile; } @@ -220,6 +230,10 @@ private Map serializeWorldGroupProfile(WorldGroup profile) { if (!sharesList.isEmpty()) { results.put("shares", sharesList); } + List disabledSharesList = profile.getDisabledShares().toStringList(); + if (!disabledSharesList.isEmpty()) { + results.put("disabled-shares", disabledSharesList); + } Map spawnProps = new LinkedHashMap(); if (profile.getSpawnWorld() != null) { spawnProps.put("world", profile.getSpawnWorld()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 34940f10..f4e50816 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -7,7 +7,7 @@ import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.util.DataStrings; @@ -41,15 +41,22 @@ public final class Sharables implements Shares { private static final Shares ALL_SHARABLES = new Sharables(new LinkedHashSet<>()); + private static final Shares STANDARD_SHARABLES = new Sharables(new LinkedHashSet<>()); + private static final Shares OPTIONAL_SHARABLES = new Sharables(new LinkedHashSet<>()); /** * The map used to lookup a Sharable or set of Sharables by their name. */ static final Map LOOKUP_MAP = new HashMap(); + private static Shares enabledShares = noneOf(); + private static MultiverseInventories inventories = null; + private static InventoriesConfig inventoriesConfig = null; + private static WorldGroupManager worldGroupManager = null; private static MVEconomist economist = null; private static AsyncSafetyTeleporter safetyTeleporter = null; + private static Attribute maxHealthAttr = null; /** @@ -67,6 +74,12 @@ public static void init(MultiverseInventories inventories) { if (Sharables.safetyTeleporter == null) { Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); } + if (Sharables.inventoriesConfig == null) { + Sharables.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); + } + if (Sharables.worldGroupManager == null) { + Sharables.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + } Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); if (Sharables.maxHealthAttr == null) { Logging.warning("Could not find max_health attribute. Health related sharables may not work as expected."); @@ -591,7 +604,7 @@ public void updateProfile(PlayerProfile profile, Player player) { @Override public boolean updatePlayer(Player player, PlayerProfile profile) { Location loc = profile.get(LAST_LOCATION); - if (loc == null) { + if (loc == null || loc.getWorld() == null || loc.equals(player.getLocation())) { return false; } safetyTeleporter.to(loc).checkSafety(false).teleport(player); @@ -734,6 +747,11 @@ static boolean register(Sharable sharable) { } } if (ALL_SHARABLES.add(sharable)) { + if (sharable.isOptional()) { + OPTIONAL_SHARABLES.add(sharable); + } else { + STANDARD_SHARABLES.add(sharable); + } for (String name : sharable.getNames()) { String key = name.toLowerCase(); Shares shares = LOOKUP_MAP.get(key); @@ -773,18 +791,60 @@ public static Shares all() { return ALL_SHARABLES; } + /** + * @return A {@link Shares} collection containing ALL registered standard {@link Sharable}s. This is NOT to be modified and + * serves only as a reference. For a version you can do what you want with, see {@link #allOf()}. + */ + public static Shares standard() { + return STANDARD_SHARABLES; + } + + /** + * @return A {@link Shares} collection containing ALL registered enabled {@link Sharable}s. This is NOT to be modified and + * serves only as a reference. For a version you can do what you want with, see {@link #allOf()}. + */ + public static Shares enabled() { + return enabledShares; + } + + /** + * @return A {@link Shares} collection containing ALL registered optional {@link Sharable}s. This is NOT to be modified and + * serves only as a reference. For a version you can do what you want with, see {@link #optionalOf()}. + */ + public static Shares optional() { + return OPTIONAL_SHARABLES; + } + /** * @return A new {@link Shares} instance containing ALL registered {@link Sharable}s for your own devices. */ public static Shares allOf() { - return new Sharables(new LinkedHashSet(ALL_SHARABLES)); + return new Sharables(new LinkedHashSet<>(ALL_SHARABLES)); + } + + /** + * @return A new {@link Shares} instance containing ALL enabled optional {@link Sharable}s for your own devices. + */ + public static Shares enabledOf() { + return new Sharables(new LinkedHashSet<>(enabledShares)); + } + + public static Shares standardOf() { + return new Sharables(new LinkedHashSet<>(STANDARD_SHARABLES)); + } + + /** + * @return A new {@link Shares} instance containing ALL registered optional {@link Sharable}s for your own devices. + */ + public static Shares optionalOf() { + return new Sharables(new LinkedHashSet<>(OPTIONAL_SHARABLES)); } /** * @return A new empty {@link Shares} instance for your own devices. */ public static Shares noneOf() { - return new Sharables(new LinkedHashSet(ALL_SHARABLES.size())); + return new Sharables(new LinkedHashSet<>(ALL_SHARABLES.size())); } /** @@ -886,6 +946,12 @@ public static Shares fromList(List sharesList) { return shares; } + public static void recalculateEnabledShares() { + enabledShares = standardOf(); + enabledShares.addAll(inventoriesConfig.getActiveOptionalShares()); + worldGroupManager.recalculateApplicableShares(); + } + private Set sharables; private Sharables(Set sharableSet) { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt index 2f14b0d7..c50f9e12 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt @@ -29,19 +29,23 @@ class WorldChangeTest : TestWithMockBukkit() { val stack = ItemStack.of(Material.STONE_BRICKS, 64) player.inventory.setItem(0, stack) player.health = 5.5 + player.totalExperience = 10 val startTime = System.nanoTime() server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) server.getWorld("world4")?.let { player.teleport(it.spawnLocation) } assertNotEquals(stack, player.inventory.getItem(0)) assertNotEquals(5.5, player.health) + assertEquals(player.totalExperience, 0) server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) assertEquals(5.5, player.health) + assertEquals(player.totalExperience, 0) Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") } diff --git a/src/test/resources/gameplay/world_change_groups.yml b/src/test/resources/gameplay/world_change_groups.yml index f44e2bbb..e0faea3f 100644 --- a/src/test/resources/gameplay/world_change_groups.yml +++ b/src/test/resources/gameplay/world_change_groups.yml @@ -6,6 +6,8 @@ groups: shares: - inventory_contents - hit_points + disabled-shares: + - total_xp group2: worlds: - world3 From e7882eea0251485b5f54b52b7358b7604eb848bc Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 22:18:39 +0800 Subject: [PATCH 094/180] Rename to playerFileCache --- .../inventories/profile/FlatFileProfileDataSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index b441bf49..17da11d3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -573,7 +573,7 @@ public void clearAllCache() { @Override public Map getCacheStats() { Map stats = new HashMap<>(); - stats.put("configCache", playerFileCache.stats()); + stats.put("playerFileCache", playerFileCache.stats()); stats.put("globalProfileCache", globalProfileCache.stats()); stats.put("profileCache", playerProfileCache.stats()); return stats; From 1eeaa06371c96eeb4bffda7bfedcc06454b3ceec Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 1 Mar 2025 22:59:33 +0800 Subject: [PATCH 095/180] Add basic config test --- .../inventories/config/ConfigTest.kt | 32 +++++++++++++++++-- src/test/resources/config/fresh_config.yml | 26 +++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/config/fresh_config.yml diff --git a/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt index 97141ee5..8cd9a543 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt @@ -1,4 +1,32 @@ package org.mvplugins.multiverse.inventories.config -class ConfigTest { -} \ No newline at end of file +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import java.io.File +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertTrue + +class ConfigTest : TestWithMockBukkit() { + + private lateinit var config : InventoriesConfig + private lateinit var configFile : File + + @BeforeTest + fun setUp() { + configFile = File(Path.of(multiverseInventories.dataFolder.absolutePath, "config.yml").absolutePathString()) + if (configFile.exists()) configFile.delete() + + config = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") } + + assertTrue(config.load().isSuccess) + assertTrue(config.save().isSuccess) + } + + @Test + fun `Config is fresh`() { + assertConfigEquals("/config/fresh_config.yml", "config.yml") + } +} diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml new file mode 100644 index 00000000..34efd460 --- /dev/null +++ b/src/test/resources/config/fresh_config.yml @@ -0,0 +1,26 @@ +share-handling: + enable-bypass-permissions: false + enable-gamemode-share-handling: false + default-ungrouped-worlds: false + use-optionals-for-ungrouped-worlds: true + active-optional-shares: [] + +sharables: + use-improved-respawn-location-detection: true + reset-last-location-on-death: false + apply-last-location-for-all-teleports: true + +performance: + save-playerdata-on-quit: false + apply-playerdata-on-join: false + always-write-world-profile: true + cache: + player-file-cache-size: 2000 + player-file-cache-expiry: 60 + player-profile-cache-size: 6000 + player-profile-cache-expiry: 60 + global-profile-cache-size: 500 + global-profile-cache-expiry: 60 + +first-run: true +version: 5.0 From f88dd5e4f6b0853a9daf31aab94d1ad702590bb1 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 2 Mar 2025 10:26:52 +0800 Subject: [PATCH 096/180] Add config command --- .../inventories/commands/ConfigCommand.java | 70 +++++++++++++++++++ .../commandtools/MVInvCommandCompletion.java | 10 +++ .../config/InventoriesConfigNodes.java | 2 +- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java new file mode 100644 index 00000000..86f5ef76 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -0,0 +1,70 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.exceptions.MultiverseException; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Optional; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +@Service +@CommandAlias("mvinv") +final class ConfigCommand extends InventoriesCommand { + + private final InventoriesConfig config; + + @Inject + ConfigCommand(@NotNull MVCommandManager commandManager, @NotNull InventoriesConfig config) { + super(commandManager); + this.config = config; + } + + @Subcommand("config") + @CommandPermission("multiverse.core.config") + @CommandCompletion("@mvinvconfigs @mvinvconfigvalues") + @Syntax(" [value]") + @Description("Show or set a config value.") + void onConfigCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("The name of the config to set or show.") + String name, + + @Optional + @Syntax("[value]") + @Description("The value to set the config to. If not specified, the current value will be shown.") + String value) { + if (value == null) { + showConfigValue(issuer, name); + return; + } + updateConfigValue(issuer, name, value); + } + + private void showConfigValue(MVCommandIssuer issuer, String name) { + config.getStringPropertyHandle().getProperty(name) + .onSuccess(value -> issuer.sendMessage(name + "is currently set to " + value)) + .onFailure(e -> issuer.sendMessage(e.getMessage())); + } + + private void updateConfigValue(MVCommandIssuer issuer, String name, String value) { + // TODO: Update with localization + config.getStringPropertyHandle().setPropertyString(name, value) + .onSuccess(ignore -> { + config.save(); + issuer.sendMessage("Successfully set " + name + " to " + value); + }) + .onFailure(ignore -> issuer.sendMessage("Unable to set " + name + " to " + value + ".")) + .onFailure(MultiverseException.class, e -> Option.of(e.getMVMessage()).peek(issuer::sendError)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java index 872e3edc..71695822 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java @@ -4,6 +4,7 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandCompletions; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; @@ -40,6 +41,8 @@ private MVInvCommandCompletion( MVCommandCompletions commandCompletions = mvCommandManager.getCommandCompletions(); commandCompletions.registerAsyncCompletion("dataimporters", this::suggestDataImporters); + commandCompletions.registerStaticCompletion("mvinvconfigs", inventoriesConfig.getStringPropertyHandle().getAllPropertyNames()); + commandCompletions.registerAsyncCompletion("mvinvconfigvalues", this::suggestConfigValues); commandCompletions.registerAsyncCompletion("sharables", this::suggestSharables); commandCompletions.registerAsyncCompletion("shares", this::suggestShares); commandCompletions.registerAsyncCompletion("worldGroups", this::suggestWorldGroups); @@ -50,6 +53,13 @@ private Collection suggestDataImporters(BukkitCommandCompletionContext c return dataImportManager.getEnabledImporterNames(); } + private Collection suggestConfigValues(BukkitCommandCompletionContext context) { + return Try.of(() -> context.getContextValue(String.class)) + .map(propertyName -> inventoriesConfig.getStringPropertyHandle() + .getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET)) + .getOrElse(Collections.emptyList()); + } + private Collection suggestSharables(BukkitCommandCompletionContext context) { String scope = context.getConfig("scope", "enabled"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index c3d79f12..77c867d3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -86,7 +86,7 @@ private N node(N node) { .comment("You must specify optional shares you wish to use here or they will be ignored.") .comment("The only built-in optional shares are \"economy\" and \"last_location\".") .defaultValue(Sharables.noneOf()) - .name(null) + .hidden() .serializer(new NodeSerializer<>() { @Override public Shares deserialize(Object o, Class aClass) { From ab9c2318d55c3dd33af0ff5ba4efbcd2fceded52 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 2 Mar 2025 11:43:13 +0800 Subject: [PATCH 097/180] Add disable share command --- .../commands/AddDisabledSharesCommand.java | 55 +++++++++++++++++++ .../inventories/commands/ConfigCommand.java | 2 +- .../commands/RemoveDisabledSharesCommand.java | 55 +++++++++++++++++++ .../inventories/util/MVInvi18n.java | 2 + .../multiverse-inventories_en.properties | 3 + .../multiverse/inventories/InjectionTest.kt | 2 +- 6 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java new file mode 100644 index 00000000..768bc49c --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java @@ -0,0 +1,55 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class AddDisabledSharesCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + AddDisabledSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("adddisbledshares") + @CommandPermission("multiverse.inventories.adddisabledshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Add one or more disabled shares to a group.") + void onAddDisabledSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to add the disabled shares to.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to disable for the given group.") + Shares shares + ) { + group.getDisabledShares().mergeShares(shares); + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.DISABLEDSHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getDisabledShares().toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java index 86f5ef76..8a84d88e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -29,7 +29,7 @@ final class ConfigCommand extends InventoriesCommand { } @Subcommand("config") - @CommandPermission("multiverse.core.config") + @CommandPermission("multiverse.inventories.config") @CommandCompletion("@mvinvconfigs @mvinvconfigvalues") @Syntax(" [value]") @Description("Show or set a config value.") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java new file mode 100644 index 00000000..cd1e6095 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java @@ -0,0 +1,55 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +@CommandAlias("mvinv") +final class RemoveDisabledSharesCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + RemoveDisabledSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + super(commandManager); + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("removedisabledshares") + @CommandPermission("multiverse.inventories.removedisabledshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Remove one or more disabled shares from a group.") + void onRemoveSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to remove the shares from.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to remove.") + Shares shares + ) { + group.getDisabledShares().setSharing(shares, false); + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.DISABLEDSHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getDisabledShares().toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java index 44e9085f..827dd6fe 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java @@ -56,6 +56,8 @@ public enum MVInvi18n implements MessageKeyProvider { SHARES_NOWSHARING, + DISABLEDSHARES_NOWSHARING, + SPAWN_TELEPORTING, SPAWN_TELEPORTEDBY, SPAWN_TELEPORTCONSOLEERROR, diff --git a/src/main/resources/multiverse-inventories_en.properties b/src/main/resources/multiverse-inventories_en.properties index fc7b6294..cf197e1a 100644 --- a/src/main/resources/multiverse-inventories_en.properties +++ b/src/main/resources/multiverse-inventories_en.properties @@ -57,6 +57,9 @@ mv-inventories.removeworld.worldnotingroup=&6World:&f {world} &6is not part of G # Add/remove shares command mv-inventories.shares.nowsharing=&6Group: &f{group} &6is now sharing: &f{shares} &6and NOT sharing: &f{negativeshares} +# Add/remove disabled shares command +mv-inventories.disabledshares.nowsharing=&6Group &f{group} &6now has the following disabled shares: &f{shares} + # Spawn command mv-inventories.spawn.teleporting=Teleporting to this world group's spawn... mv-inventories.spawn.teleportedby=You were teleported by: {player} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 7e91eb2c..157d701f 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(14, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(17, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From c3c16cd67a80d604b37e17fad27d9280bd922b52 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 2 Mar 2025 17:17:07 +0800 Subject: [PATCH 098/180] Add safe multithreaded file io with CountDownLatch --- .../profile/FlatFileProfileDataSource.java | 38 +++++++------- .../inventories/profile/ProfileFileIO.java | 49 ++++++++++++++++--- .../inventories/profile/ProfileTypes.java | 21 ++++++-- .../profile/FilePerformanceTest.kt | 36 +++++++++----- 4 files changed, 103 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 17da11d3..f6d50c6b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -171,12 +171,12 @@ private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playe */ @Override public Future updatePlayerData(PlayerProfile playerProfile) { - return playerProfileIO.queueAction(() -> processUpdatePlayerData(playerProfile.clone())); - } - - private void processUpdatePlayerData(PlayerProfile playerProfile) { ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); File playerFile = getPlayerFile(profileKey); + return playerProfileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); + } + + private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, PlayerProfile playerProfile) { FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); Map serializedData = serializePlayerProfile(playerProfile); if (serializedData.isEmpty()) { @@ -235,7 +235,7 @@ public PlayerProfile getPlayerData(ProfileKey key) { return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID())); } - return playerProfileIO.waitForData(() -> getPlayerDataFromDisk(key, playerFile)); + return playerProfileIO.waitForData(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); }); } catch (ExecutionException e) { Logging.severe("Could not get data for player: " + key.getPlayerName() @@ -371,25 +371,25 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile */ @Override public Future removePlayerData(ProfileKey profileKey) { + File playerFile = getPlayerFile(profileKey); if (profileKey.getProfileType() == null) { - clearProfileCache(key -> key.getPlayerUUID().equals(profileKey.getPlayerUUID()) - && key.getContainerType().equals(profileKey.getContainerType()) - && key.getDataName().equals(profileKey.getDataName())); - File playerFile = getPlayerFile(profileKey); + for (var type : ProfileTypes.getTypes()) { + Option.of(playerProfileCache.getIfPresent(profileKey.forProfileType(type))) + .peek(profile -> profile.getData().clear()); + } if (!playerFile.exists()) { Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); return CompletableFuture.completedFuture(null); } - return playerProfileIO.queueAction(playerFile::delete); + return playerProfileIO.queueAction(playerFile, playerFile::delete); } - playerProfileCache.invalidate(profileKey); - return playerProfileIO.queueAction(() -> processRemovePlayerData(profileKey)); + Option.of(playerProfileCache.getIfPresent(profileKey)).peek(profile -> profile.getData().clear()); + return playerProfileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); } - private void processRemovePlayerData(ProfileKey profileKey) { + private void processRemovePlayerData(ProfileKey profileKey, File playerFile) { try { - File playerFile = getPlayerFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); playerData.set(profileKey.getProfileType().getName(), null); playerData.save(playerFile); @@ -496,7 +496,7 @@ private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { } private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = globalProfileIO.waitForData(() -> parseToConfiguration(globalFile)); + FileConfiguration playerData = globalProfileIO.waitForData(globalFile, () -> parseToConfiguration(globalFile)); ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { section = playerData.createSection(DataStrings.PLAYER_DATA); @@ -522,15 +522,15 @@ private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer updateGlobalProfile(GlobalProfile globalProfile) { - return globalProfileIO.queueAction(() -> processGlobalProfileWrite(globalProfile)); + File globalFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); + return globalProfileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); } - private void processGlobalProfileWrite(GlobalProfile globalProfile) { - File playerFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); + private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalFile) { FileConfiguration playerData = new JsonConfiguration(); playerData.createSection(DataStrings.PLAYER_DATA, globalProfile.serialize(globalProfile)); try { - playerData.save(playerFile); + playerData.save(globalFile); } catch (IOException e) { Logging.severe("Could not save global data for player: " + globalProfile); Logging.severe(e.getMessage()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index e4bf520e..6082c8c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -1,32 +1,67 @@ package org.mvplugins.multiverse.inventories.profile; +import java.io.File; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; final class ProfileFileIO { private final ExecutorService fileIOExecutorService; + private final Map fileLocks = new ConcurrentHashMap<>(); ProfileFileIO() { - fileIOExecutorService = Executors.newSingleThreadExecutor(); + fileIOExecutorService = Executors.newWorkStealingPool(); } - Future queueAction(Runnable action) { - return (Future) fileIOExecutorService.submit(action); + @SuppressWarnings("unchecked") + Future queueAction(File file, Runnable action) { + CountDownLatch thisLatch = new CountDownLatch(1); + CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); + return (Future) fileIOExecutorService.submit(() -> { + if (toWaitLatch != null) { + try { + toWaitLatch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + action.run(); + thisLatch.countDown(); + fileLocks.remove(file); + }); } - Future queueCallable(Callable callable) { - return fileIOExecutorService.submit(callable); + Future queueCallable(File file, Supplier callable) { + CountDownLatch thisLatch = new CountDownLatch(1); + CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); + return fileIOExecutorService.submit(() -> { + if (toWaitLatch != null) { + try { + toWaitLatch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + T result = callable.get(); + thisLatch.countDown(); + fileLocks.remove(file); + return result; + }); } - T waitForData(Callable callable) { + T waitForData(File file, Supplier callable) { try { - return queueCallable(callable).get(10, TimeUnit.SECONDS); + return queueCallable(file, callable).get(10, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java index 1db9cd1a..39329c4a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java @@ -2,25 +2,40 @@ import org.bukkit.GameMode; +import java.util.ArrayList; +import java.util.List; + /** * Static class for profile type lookup and protected registration. */ public final class ProfileTypes { + private static final List types = new ArrayList<>(); + + private static ProfileType createProfileType(String name) { + ProfileType type = ProfileType.createProfileType(name); + types.add(type); + return type; + } + + public static List getTypes() { + return types; + } + /** * The profile type for the SURVIVAL Game Mode. */ - public static final ProfileType SURVIVAL = ProfileType.createProfileType("SURVIVAL"); + public static final ProfileType SURVIVAL = createProfileType("SURVIVAL"); /** * The profile type for the CREATIVE Game Mode. */ - public static final ProfileType CREATIVE = ProfileType.createProfileType("CREATIVE"); + public static final ProfileType CREATIVE = createProfileType("CREATIVE"); /** * The profile type for the ADVENTURE Game Mode. */ - public static final ProfileType ADVENTURE = ProfileType.createProfileType("ADVENTURE"); + public static final ProfileType ADVENTURE = createProfileType("ADVENTURE"); /** * Returns the appropriate ProfileType for the given game mode. diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 0825016c..909306be 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -1,7 +1,6 @@ package org.mvplugins.multiverse.inventories.profile import com.dumptruckman.minecraft.util.Logging -import com.google.common.cache.CacheStats import org.bukkit.GameMode import org.bukkit.Material import org.bukkit.enchantments.Enchantment @@ -9,13 +8,14 @@ import org.bukkit.inventory.ItemStack import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.junit.jupiter.api.Test -import org.mockbukkit.mockbukkit.inventory.ItemStackMock +import org.mvplugins.multiverse.core.utils.CoreLogging import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.container.ContainerType import org.mvplugins.multiverse.inventories.share.Sharables import java.util.* +import java.util.concurrent.Future import java.util.function.Consumer import kotlin.test.BeforeTest import kotlin.test.assertEquals @@ -33,6 +33,7 @@ class FilePerformanceTest : TestWithMockBukkit() { throw IllegalStateException("WorldManager is not available as a service") } profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { throw IllegalStateException("ProfileDataSource is not available as a service") } + CoreLogging.setDebugLevel(0); Logging.setDebugLevel(0) assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) } @@ -40,21 +41,26 @@ class FilePerformanceTest : TestWithMockBukkit() { @Test fun `Test 10K global profiles`() { val startTime = System.nanoTime() + val futures = ArrayList>(10000) for (i in 0..9999) { val globalProfile = profileDataSource.getGlobalProfile(UUID.randomUUID()) globalProfile.setLoadOnLogin(true) - profileDataSource.updateGlobalProfile(globalProfile) + futures.add(profileDataSource.updateGlobalProfile(globalProfile)) + } + for (future in futures) { + future.get() } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - profileDataSource.clearAllCache(); - Thread.sleep(800) // Wait for files to write finish - val startTime2 = System.nanoTime() + val futures2 = ArrayList>(10000) for (i in 0..9999) { val globalProfile = profileDataSource.getGlobalProfile(UUID.randomUUID()) globalProfile.setLoadOnLogin(false) - profileDataSource.updateGlobalProfile(globalProfile) + futures2.add(profileDataSource.updateGlobalProfile(globalProfile)) + } + for (future in futures2) { + future.get() } Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") } @@ -63,6 +69,7 @@ class FilePerformanceTest : TestWithMockBukkit() { fun `Test 1K player profiles`() { server.setPlayers(1000) val startTime = System.nanoTime() + val futures = ArrayList>(1000) for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { @@ -82,9 +89,12 @@ class FilePerformanceTest : TestWithMockBukkit() { PotionEffect(PotionEffectType.POISON, 100, 1), PotionEffect(PotionEffectType.SPEED, 50, 1), )) - profileDataSource.updatePlayerData(playerProfile) + futures.add(profileDataSource.updatePlayerData(playerProfile)) } } + for (future in futures) { + future.get() + } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") val startTime2 = System.nanoTime() @@ -100,21 +110,23 @@ class FilePerformanceTest : TestWithMockBukkit() { Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") val startTime3 = System.nanoTime() + val futures3 = ArrayList>(1000) for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - profileDataSource.removePlayerData( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) + futures3.add(profileDataSource.removePlayerData( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) val playerProfile = profileDataSource.getPlayerData( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) assertNull(playerProfile.get(Sharables.HEALTH)) assertNull(playerProfile.get(Sharables.OFF_HAND)) } } + for (future in futures3) { + future.get() + } Logging.info("Time taken: " + (System.nanoTime() - startTime3) / 1000000 + "ms") - Thread.sleep(1000) // Wait for files to write finish - val cacheStats = profileDataSource.getCacheStats() Logging.info(cacheStats.values.toString()) for (cacheStat in cacheStats) { From 4e91c7fa31c73a7abd05dccbd0f9cd02fdcf990c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 2 Mar 2025 17:44:06 +0800 Subject: [PATCH 099/180] Use caffine asynccache --- build.gradle | 4 + .../inventories/commands/CacheCommand.java | 3 +- .../perworldinventory/PwiImportHelper.java | 4 +- .../profile/FlatFileProfileDataSource.java | 78 +++++++++++-------- .../profile/ProfileDataSource.java | 25 ++++-- .../inventories/profile/ProfileFileIO.java | 17 ++-- .../profile/container/ProfileContainer.java | 2 +- .../handleshare/ShareHandlingUpdaterTest.kt | 4 +- .../profile/FilePerformanceTest.kt | 6 +- 9 files changed, 86 insertions(+), 57 deletions(-) diff --git a/build.gradle b/build.gradle index 4f73d529..ea38b4ae 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,9 @@ dependencies { exclude group: 'junit', module: 'junit' } + // Caching + shadowed("com.github.ben-manes.caffeine:caffeine:3.2.0") + // Other plugins for import compileOnly('uk.co:MultiInv:3.0.6') { exclude group: '*', module: '*' @@ -64,6 +67,7 @@ shadowJar { relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' + relocate 'com.github.ben-manes', 'org.mvplugins.multiverse.inventories.utils.ben-manes' dependencies { exclude(dependency { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index 83f5a657..8d13683a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.commands; -import com.google.common.cache.CacheStats; +import com.github.benmanes.caffeine.cache.stats.CacheStats; import org.bukkit.entity.Player; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; @@ -38,7 +38,6 @@ void onCacheStatsCommand(MVCommandIssuer issuer) { issuer.sendMessage(" hits count: " + entry.getValue().hitCount()); issuer.sendMessage(" misses count: " + entry.getValue().missCount()); issuer.sendMessage(" loads count: " + entry.getValue().loadCount()); - issuer.sendMessage(" exceptions: " + entry.getValue().loadExceptionCount()); issuer.sendMessage(" evictions: " + entry.getValue().evictionCount()); issuer.sendMessage(" hit rate: " + entry.getValue().hitRate() * 100 + "%"); issuer.sendMessage(" miss rate: " + entry.getValue().missRate() * 100 + "%"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 30b2c97b..c8be31da 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -205,10 +205,10 @@ private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throw private List getMVPlayerData( @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { List profiles = new ArrayList<>(); - profiles.add(profileDataSource.getPlayerData(org.mvplugins.multiverse.inventories.profile.ProfileKey + profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.ProfileKey .create(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); for (var worldName : group.getWorlds()) { - profiles.add(profileDataSource.getPlayerData(org.mvplugins.multiverse.inventories.profile.ProfileKey + profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.ProfileKey .create(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); } return profiles; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index f6d50c6b..27fb5aad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -2,9 +2,10 @@ import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; import com.dumptruckman.minecraft.util.Logging; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheStats; +import com.github.benmanes.caffeine.cache.AsyncCache; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.stats.CacheStats; import com.google.common.collect.Sets; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; @@ -34,8 +35,8 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.logging.Level; @@ -47,9 +48,8 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); - // TODO these probably need configurable max sizes private final Cache playerFileCache; - private final Cache playerProfileCache; + private final AsyncCache playerProfileCache; private final Cache globalProfileCache; private final File worldFolder; @@ -61,19 +61,19 @@ final class FlatFileProfileDataSource implements ProfileDataSource { @Inject FlatFileProfileDataSource(@NotNull MultiverseInventories plugin, @NotNull InventoriesConfig inventoriesConfig) throws IOException { - this.playerFileCache = CacheBuilder.newBuilder() + this.playerFileCache = Caffeine.newBuilder() .expireAfterAccess(inventoriesConfig.getPlayerFileCacheExpiry(), TimeUnit.MINUTES) .maximumSize(inventoriesConfig.getPlayerFileCacheSize()) .recordStats() .build(); - this.playerProfileCache = CacheBuilder.newBuilder() + this.playerProfileCache = Caffeine.newBuilder() .expireAfterAccess(inventoriesConfig.getPlayerProfileCacheExpiry(), TimeUnit.MINUTES) .maximumSize(inventoriesConfig.getPlayerProfileCacheSize()) .recordStats() - .build(); + .buildAsync(); - this.globalProfileCache = CacheBuilder.newBuilder() + this.globalProfileCache = Caffeine.newBuilder() .expireAfterAccess(inventoriesConfig.getGlobalProfileCacheExpiry(), TimeUnit.MINUTES) .maximumSize(inventoriesConfig.getGlobalProfileCacheSize()) .recordStats() @@ -157,7 +157,7 @@ private FileConfiguration parseToConfiguration(File file) { private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playerFile) { ProfileKey fileProfileKey = profileKey.forProfileType(null); return Try.of(() -> - playerFileCache.get(fileProfileKey, () -> playerFile.exists() + playerFileCache.get(fileProfileKey, (key) -> playerFile.exists() ? parseToConfiguration(playerFile) : new JsonConfiguration()) ).getOrElseThrow(e -> { @@ -170,7 +170,7 @@ private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playe * {@inheritDoc} */ @Override - public Future updatePlayerData(PlayerProfile playerProfile) { + public CompletableFuture updatePlayerData(PlayerProfile playerProfile) { ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); File playerFile = getPlayerFile(profileKey); return playerProfileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); @@ -227,19 +227,31 @@ private Map serializePlayerProfile(PlayerProfile playerProfile) * {@inheritDoc} */ @Override - public PlayerProfile getPlayerData(ProfileKey key) { + public PlayerProfile getPlayerDataNow(ProfileKey profileKey) { + try { + return getPlayerData(profileKey).get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture getPlayerData(ProfileKey profileKey) { try { - return playerProfileCache.get(key, () -> { + return playerProfileCache.get(profileKey, (key, executor) -> { File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); if (!playerFile.exists()) { - return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), - key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID())); + return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), + key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID()))); } - return playerProfileIO.waitForData(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); + return playerProfileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); }); - } catch (ExecutionException e) { - Logging.severe("Could not get data for player: " + key.getPlayerName() - + " for " + key.getContainerType().toString() + ": " + key.getDataName()); + } catch (Exception e) { + Logging.severe("Could not get data for player: " + profileKey.getPlayerName() + + " for " + profileKey.getContainerType().toString() + ": " + profileKey.getDataName()); throw new RuntimeException(e); } } @@ -370,11 +382,11 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile * {@inheritDoc} */ @Override - public Future removePlayerData(ProfileKey profileKey) { + public CompletableFuture removePlayerData(ProfileKey profileKey) { File playerFile = getPlayerFile(profileKey); if (profileKey.getProfileType() == null) { for (var type : ProfileTypes.getTypes()) { - Option.of(playerProfileCache.getIfPresent(profileKey.forProfileType(type))) + Option.of(playerProfileCache.synchronous().getIfPresent(profileKey.forProfileType(type))) .peek(profile -> profile.getData().clear()); } if (!playerFile.exists()) { @@ -384,7 +396,7 @@ public Future removePlayerData(ProfileKey profileKey) { } return playerProfileIO.queueAction(playerFile, playerFile::delete); } - Option.of(playerProfileCache.getIfPresent(profileKey)).peek(profile -> profile.getData().clear()); + Option.of(playerProfileCache.synchronous().getIfPresent(profileKey)).peek(profile -> profile.getData().clear()); return playerProfileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); } @@ -460,8 +472,8 @@ public GlobalProfile getGlobalProfile(OfflinePlayer player) { @Override public GlobalProfile getGlobalProfile(UUID playerUUID, String playerName) { try { - return globalProfileCache.get(playerUUID, () -> getGlobalProfileFromDisk(playerUUID, playerName)); - } catch (ExecutionException e) { + return globalProfileCache.get(playerUUID, (key) -> getGlobalProfileFromDisk(playerUUID, playerName)); + } catch (Exception e) { Logging.severe("Unable to get global profile for player: " + playerName); throw new RuntimeException(e); } @@ -504,15 +516,15 @@ private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID return GlobalProfile.deserialize(playerName, playerUUID, section); } - public Future modifyGlobalProfile(UUID playerUUID, Consumer consumer) { + public CompletableFuture modifyGlobalProfile(UUID playerUUID, Consumer consumer) { return modifyGlobalProfile(getGlobalProfile(playerUUID), consumer); } - public Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { + public CompletableFuture modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { return modifyGlobalProfile(getGlobalProfile(offlinePlayer), consumer); } - private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { + private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { consumer.accept(globalProfile); return updateGlobalProfile(globalProfile); } @@ -521,7 +533,7 @@ private Future modifyGlobalProfile(GlobalProfile globalProfile, Consumer updateGlobalProfile(GlobalProfile globalProfile) { + public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { File globalFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); return globalProfileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); } @@ -554,20 +566,20 @@ void clearPlayerCache(UUID playerUUID) { @Override public void clearProfileCache(ProfileKey key) { playerFileCache.invalidate(key); - playerProfileCache.invalidate(key); + playerProfileCache.synchronous().invalidate(key); } @Override public void clearProfileCache(Predicate predicate) { playerFileCache.invalidateAll(Sets.filter(playerFileCache.asMap().keySet(), predicate::test)); - playerProfileCache.invalidateAll(Sets.filter(playerProfileCache.asMap().keySet(), predicate::test)); + playerProfileCache.synchronous().invalidateAll(Sets.filter(playerProfileCache.asMap().keySet(), predicate::test)); } @Override public void clearAllCache() { playerFileCache.invalidateAll(); globalProfileCache.invalidateAll(); - playerProfileCache.invalidateAll(); + playerProfileCache.synchronous().invalidateAll(); } @Override @@ -575,7 +587,7 @@ public Map getCacheStats() { Map stats = new HashMap<>(); stats.put("playerFileCache", playerFileCache.stats()); stats.put("globalProfileCache", globalProfileCache.stats()); - stats.put("profileCache", playerProfileCache.stats()); + stats.put("profileCache", playerProfileCache.synchronous().stats()); return stats; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index e9e9d748..92061329 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,6 +1,6 @@ package org.mvplugins.multiverse.inventories.profile; -import com.google.common.cache.CacheStats; +import com.github.benmanes.caffeine.cache.stats.CacheStats; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; @@ -9,7 +9,7 @@ import java.io.IOException; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Predicate; @@ -25,7 +25,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * * @param playerProfile The profile for the player that is being updated. */ - Future updatePlayerData(PlayerProfile playerProfile); + CompletableFuture updatePlayerData(PlayerProfile playerProfile); /** * Retrieves a PlayerProfile from the data source. @@ -34,7 +34,16 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @return The player as returned from data. If no data was found, a new PlayerProfile will be * created. */ - PlayerProfile getPlayerData(ProfileKey profileKey); + PlayerProfile getPlayerDataNow(ProfileKey profileKey); + + /** + * Retrieves a PlayerProfile from the data source. + * + * @param profileKey The key of the profile to retrieve. + * @return The player as returned from data. If no data was found, a new PlayerProfile will be + * created. + */ + CompletableFuture getPlayerData(ProfileKey profileKey); /** * Removes the persisted data for a player for a specific profile. @@ -42,7 +51,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param profileKey The key of the profile to remove. * @return True if successfully removed. */ - Future removePlayerData(ProfileKey profileKey); + CompletableFuture removePlayerData(ProfileKey profileKey); /** * Copies all the data belonging to oldName to newName and removes the old data. @@ -89,16 +98,16 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { */ @NotNull Option getExistingGlobalProfile(UUID playerUUID, String playerName); - Future modifyGlobalProfile(UUID playerUUID, Consumer consumer); + CompletableFuture modifyGlobalProfile(UUID playerUUID, Consumer consumer); - Future modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer); + CompletableFuture modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer); /** * Update the file for a player's global profile. * * @param globalProfile The GlobalProfile object to update the file for. */ - Future updateGlobalProfile(GlobalProfile globalProfile); + CompletableFuture updateGlobalProfile(GlobalProfile globalProfile); /** * Clears a single profile in cache. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 6082c8c0..d9865dc0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -23,11 +24,11 @@ final class ProfileFileIO { fileIOExecutorService = Executors.newWorkStealingPool(); } - @SuppressWarnings("unchecked") - Future queueAction(File file, Runnable action) { + CompletableFuture queueAction(File file, Runnable action) { CountDownLatch thisLatch = new CountDownLatch(1); CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); - return (Future) fileIOExecutorService.submit(() -> { + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { if (toWaitLatch != null) { try { toWaitLatch.await(10, TimeUnit.SECONDS); @@ -38,13 +39,16 @@ Future queueAction(File file, Runnable action) { action.run(); thisLatch.countDown(); fileLocks.remove(file); + future.complete(null); }); + return future; } - Future queueCallable(File file, Supplier callable) { + CompletableFuture queueCallable(File file, Supplier callable) { CountDownLatch thisLatch = new CountDownLatch(1); CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); - return fileIOExecutorService.submit(() -> { + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { if (toWaitLatch != null) { try { toWaitLatch.await(10, TimeUnit.SECONDS); @@ -55,8 +59,9 @@ Future queueCallable(File file, Supplier callable) { T result = callable.get(); thisLatch.countDown(); fileLocks.remove(file); - return result; + future.complete(result); }); + return future; } T waitForData(File file, Supplier callable) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 38173eff..3e3f3949 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -76,7 +76,7 @@ public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player Map profileMap = this.getPlayerData(player.getName()); PlayerProfile playerProfile = profileMap.get(profileType); if (playerProfile == null) { - playerProfile = profileDataSource.getPlayerData(ProfileKey.create( + playerProfile = profileDataSource.getPlayerDataNow(ProfileKey.create( getContainerType(), getContainerName(), profileType, diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index 45002e5d..78dd6aa1 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -29,7 +29,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { player.health = 4.4 player.maxHealth = 15.1 - val playerProfile = profileDataSource.getPlayerData( + val playerProfile = profileDataSource.getPlayerDataNow( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfile)) @@ -39,7 +39,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { @Test fun `Test updating player`() { - val playerProfile = profileDataSource.getPlayerData( + val playerProfile = profileDataSource.getPlayerDataNow( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) playerProfile.set(Sharables.HEALTH, 4.4) playerProfile.set(Sharables.MAX_HEALTH, 15.1) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 909306be..1e147c9a 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -73,7 +73,7 @@ class FilePerformanceTest : TestWithMockBukkit() { for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - val playerProfile = profileDataSource.getPlayerData( + val playerProfile = profileDataSource.getPlayerDataNow( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) playerProfile.set(Sharables.HEALTH, 5.0) playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) @@ -101,7 +101,7 @@ class FilePerformanceTest : TestWithMockBukkit() { for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - val playerProfile = profileDataSource.getPlayerData( + val playerProfile = profileDataSource.getPlayerDataNow( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) @@ -116,7 +116,7 @@ class FilePerformanceTest : TestWithMockBukkit() { for (gameMode in GameMode.entries) { futures3.add(profileDataSource.removePlayerData( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) - val playerProfile = profileDataSource.getPlayerData( + val playerProfile = profileDataSource.getPlayerDataNow( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) assertNull(playerProfile.get(Sharables.HEALTH)) assertNull(playerProfile.get(Sharables.OFF_HAND)) From d8ee4760167e3eba6acecf8c5390a1934fb56614 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:58:17 +0800 Subject: [PATCH 100/180] Remove use of weakhashmap in container --- .../handleshare/ShareHandleListener.java | 9 +-- .../profile/container/ProfileContainer.java | 65 +++++-------------- 2 files changed, 18 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 60135610..fa62be91 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -2,9 +2,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; -import org.mvplugins.multiverse.core.event.MVDebugModeEvent; -import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; @@ -41,11 +38,9 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * Events related to handling of player profile changes. @@ -338,11 +333,11 @@ void worldUnload(WorldUnloadEvent event) { ProfileContainer fromWorldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD) .getContainer(unloadWorldName); - fromWorldProfileContainer.clearContainer(); + fromWorldProfileContainer.clearContainerCache(); List fromGroups = worldGroupManager.getGroupsForWorld(unloadWorldName); for (WorldGroup fromGroup : fromGroups) { - fromGroup.getGroupProfileContainer().clearContainer(); + fromGroup.getGroupProfileContainer().clearContainerCache(); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 3e3f3949..21c0051e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -1,6 +1,5 @@ package org.mvplugins.multiverse.inventories.profile.container; -import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; @@ -11,42 +10,28 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + /** * A container for player profiles in a given world or world group (based on {@link #getContainerType()}), - * using WeakHashMaps to keep memory usage to a minimum. *
* Players may have separate profiles per game mode within this container if game mode profiles are enabled. */ public final class ProfileContainer { - private final Map> playerData = new WeakHashMap<>(); - private final MultiverseInventories inventories; private final String name; private final ContainerType type; private final ProfileDataSource profileDataSource; private final InventoriesConfig config; ProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { - this.inventories = inventories; this.name = name; this.type = type; this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); } - /** - * Gets the stored profiles for this player, mapped by ProfileType. - * - * @param name The name of player to get profile map for. - * @return The profile map for the given player. - */ - private Map getPlayerData(String name) { - return this.playerData.computeIfAbsent(name, k -> new HashMap<>()); - } - /** * Retrieves the profile for the given player. *

If game mode profiles are enabled, the profile for their current game mode will be returned, otherwise their @@ -73,38 +58,21 @@ public PlayerProfile getPlayerData(Player player) { * @return The profile of the given type for the given player. */ public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { - Map profileMap = this.getPlayerData(player.getName()); - PlayerProfile playerProfile = profileMap.get(profileType); - if (playerProfile == null) { - playerProfile = profileDataSource.getPlayerDataNow(ProfileKey.create( - getContainerType(), - getContainerName(), - profileType, - player)); - Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", - profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); - profileMap.put(profileType, playerProfile); - } - return playerProfile; - } - - /** - * Adds a player profile to this profile container. - * - * @param playerProfile Player player to add. - */ - public void addPlayerData(PlayerProfile playerProfile) { - this.getPlayerData(playerProfile.getPlayer().getName()).put(playerProfile.getProfileType(), playerProfile); + return profileDataSource.getPlayerDataNow(ProfileKey.create( + getContainerType(), + getContainerName(), + profileType, + player)); } /** * Removes all of the profile data for a given player in this profile container. * * @param player Player to remove data for. + * @return */ - public void removeAllPlayerData(OfflinePlayer player) { - this.getPlayerData(player.getName()).clear(); - profileDataSource.removePlayerData(ProfileKey.create( + public CompletableFuture removeAllPlayerData(OfflinePlayer player) { + return profileDataSource.removePlayerData(ProfileKey.create( getContainerType(), getContainerName(), null, @@ -115,11 +83,11 @@ public void removeAllPlayerData(OfflinePlayer player) { * Removes the profile data for a specific type of profile in this profile container. * * @param profileType The type of profile to remove data for. - * @param player Player to remove data for. + * @param player Player to remove data for. + * @return */ - public void removePlayerData(ProfileType profileType, OfflinePlayer player) { - this.getPlayerData(player.getName()).remove(profileType); - profileDataSource.removePlayerData(ProfileKey.create( + public CompletableFuture removePlayerData(ProfileType profileType, OfflinePlayer player) { + return profileDataSource.removePlayerData(ProfileKey.create( getContainerType(), getContainerName(), profileType, @@ -149,9 +117,8 @@ public ContainerType getContainerType() { /** * Clears all cached data in the container. */ - public void clearContainer() { + public void clearContainerCache() { profileDataSource.clearProfileCache(key -> key.getContainerType().equals(type) && key.getDataName().equals(name)); - this.playerData.clear(); } } From 0980c303583877e30cf421a7ffcc6f69c4856058 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Mar 2025 12:43:17 +0800 Subject: [PATCH 101/180] Revamp share handling order --- .../multiinv/MultiInvImportHelper.java | 4 +- .../WorldInventoriesImportHelper.java | 2 +- .../handleshare/AffectedProfiles.java | 7 +- .../handleshare/GameModeShareHandler.java | 2 +- .../handleshare/PersistingProfile.java | 22 ++++- .../handleshare/ShareHandleListener.java | 6 +- .../inventories/handleshare/ShareHandler.java | 91 +++++++++++-------- .../handleshare/ShareHandlingUpdater.java | 74 ++++++++------- .../handleshare/SingleShareWriter.java | 21 ++--- .../handleshare/WorldChangeShareHandler.java | 34 +++---- .../profile/FlatFileProfileDataSource.java | 5 +- .../inventories/profile/PlayerProfile.java | 37 +------- .../inventories/profile/ProfileData.java | 27 ++++++ .../profile/ProfileDataSnapshot.java | 54 +++++++++++ .../inventories/profile/ProfileFileIO.java | 3 +- .../profile/container/ProfileContainer.java | 31 +++++-- .../inventories/profile/group/WorldGroup.java | 2 + .../inventories/share/SharableHandler.java | 5 +- .../inventories/share/Sharables.java | 82 +++++++++-------- .../handleshare/ShareHandlingUpdaterTest.kt | 6 +- 20 files changed, 301 insertions(+), 214 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java index b0639d94..cbc1dd97 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java @@ -106,10 +106,10 @@ private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader Logging.warning("Could not import player data for group: " + dataName); return; } - playerProfile = group.getGroupProfileContainer().getPlayerData(ProfileTypes.SURVIVAL, player); + playerProfile = group.getGroupProfileContainer().getPlayerDataNow(ProfileTypes.SURVIVAL, player); } else { playerProfile = profileContainerStoreProvider.getStore(type) - .getContainer(dataName).getPlayerData(ProfileTypes.SURVIVAL, player); + .getContainer(dataName).getPlayerDataNow(ProfileTypes.SURVIVAL, player); } MIInventoryInterface inventoryInterface = playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java index 1fdc6ae6..1c74dfc7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java @@ -144,7 +144,7 @@ private Set getWorldsWithoutGroups() { } private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer profileContainer) { - PlayerProfile playerProfile = profileContainer.getPlayerData(ProfileTypes.SURVIVAL, player); + PlayerProfile playerProfile = profileContainer.getPlayerDataNow(ProfileTypes.SURVIVAL, player); WIPlayerInventory wiInventory = this.loadPlayerInventory(player, wiGroup); WIPlayerStats wiStats = this.loadPlayerStats(player, wiGroup); if (wiInventory != null) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java index 7fffb1f1..e8c51e0b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -5,6 +5,7 @@ import java.util.LinkedList; import java.util.List; +import java.util.concurrent.CompletableFuture; import static org.mvplugins.multiverse.inventories.share.Sharables.enabled; @@ -17,7 +18,7 @@ public final class AffectedProfiles { AffectedProfiles() { } - void setAlwaysWriteProfile(PlayerProfile profile) { + void setAlwaysWriteProfile(CompletableFuture profile) { alwaysWriteProfile = new PersistingProfile(enabled(), profile); } @@ -25,7 +26,7 @@ void setAlwaysWriteProfile(PlayerProfile profile) { * @param profile The player profile that will need data saved to. * @param shares What from this group needs to be saved. */ - void addWriteProfile(PlayerProfile profile, Shares shares) { + void addWriteProfile(CompletableFuture profile, Shares shares) { writeProfiles.add(new PersistingProfile(shares, profile)); } @@ -33,7 +34,7 @@ void addWriteProfile(PlayerProfile profile, Shares shares) { * @param profile The player profile that will need data loaded from. * @param shares What from this group needs to be loaded. */ - void addReadProfile(PlayerProfile profile, Shares shares) { + void addReadProfile(CompletableFuture profile, Shares shares) { readProfiles.add(new PersistingProfile(shares, profile)); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 568401e7..91deccfd 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -49,7 +49,7 @@ protected ShareHandlingEvent createEvent() { @Override protected void prepareProfiles() { - Logging.finer("=== " + player.getName() + " changing game mode from: " + fromType + Logging.fine("=== " + player.getName() + " changing game mode from: " + fromType + " to: " + toType + " for world: " + world + " ==="); affectedProfiles.setAlwaysWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player)); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java index ded31a71..575f367d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java @@ -3,11 +3,25 @@ import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.share.Shares; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + /** * Simple class for groups that are going to be saved/loaded. This is used specifically for when a user's world * change is being handled. */ -public record PersistingProfile(Shares shares, PlayerProfile profile) { +public final class PersistingProfile { + private final Shares shares; + private final CompletableFuture profile; + + public PersistingProfile(Shares shares, PlayerProfile profile) { + this(shares, CompletableFuture.completedFuture(profile)); + } + + public PersistingProfile(Shares shares, CompletableFuture profile) { + this.shares = shares; + this.profile = profile; + } /** * Gets the shares that will be saved/loaded for the profile. @@ -15,8 +29,7 @@ public record PersistingProfile(Shares shares, PlayerProfile profile) { * @return The shares that will be saved/loaded for the profile. This is the set of all Sharables that will be acted * upon when passed through the ShareHandler class, or any of its subclasses. */ - @Override - public Shares shares() { + public Shares getShares() { return this.shares; } @@ -25,8 +38,7 @@ public Shares shares() { * * @return The player profile for the world/group that will be saved/loaded for. */ - @Override - public PlayerProfile profile() { + public CompletableFuture getProfile() { return this.profile; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index fa62be91..159788da 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -203,10 +203,8 @@ void playerChangedWorld(PlayerChangedWorldEvent event) { Logging.fine("The from or to world is not managed by Multiverse-Core!"); } - long startTime = System.nanoTime(); new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); profileDataSource.modifyGlobalProfile(player, profile -> profile.setLastWorld(toWorld.getName())); - Logging.finest("WorldChangeShareHandler took " + (System.nanoTime() - startTime) / 1000000 + " ms."); } /** @@ -239,10 +237,10 @@ void playerDeath(PlayerDeathEvent event) { Logging.finer("=== Handling PlayerDeathEvent for: " + event.getEntity().getName() + " ==="); String deathWorld = event.getEntity().getWorld().getName(); ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(deathWorld); - PlayerProfile profile = worldProfileContainer.getPlayerData(event.getEntity()); + PlayerProfile profile = worldProfileContainer.getPlayerDataNow(event.getEntity()); resetStatsOnDeath(event, profile); for (WorldGroup worldGroup : worldGroupManager.getGroupsForWorld(deathWorld)) { - profile = worldGroup.getGroupProfileContainer().getPlayerData(event.getEntity()); + profile = worldGroup.getGroupProfileContainer().getPlayerDataNow(event.getEntity()); resetStatsOnDeath(event, profile); } Logging.finer("=== Finished handling PlayerDeathEvent for: " + event.getEntity().getName() + "! ==="); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 00d5a735..31b6953b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -2,36 +2,43 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSnapshot; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; -import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.share.Sharables; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Abstract class for handling sharing of data between worlds and game modes. */ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShareHandler { - protected final MultiverseInventories inventories; protected final Player player; + protected final AffectedProfiles affectedProfiles; + + protected final MultiverseInventories inventories; + protected final ProfileDataSource profileDataStore; protected final InventoriesConfig inventoriesConfig; protected final WorldGroupManager worldGroupManager; protected final ProfileContainerStore worldProfileContainerStore; - protected final AffectedProfiles affectedProfiles; ShareHandler(MultiverseInventories inventories, Player player) { - this.inventories = inventories; this.player = player; this.affectedProfiles = new AffectedProfiles(); + + this.inventories = inventories; + this.profileDataStore = inventories.getServiceLocator().getService(ProfileDataSource.class); this.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); this.worldProfileContainerStore = inventories.getServiceLocator() @@ -44,6 +51,7 @@ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShar * inventories/stats for a player and persisting the changes. */ final void handleSharing() { + long startTime = System.nanoTime(); this.prepareProfiles(); ShareHandlingEvent event = this.createEvent(); Bukkit.getPluginManager().callEvent(event); @@ -51,7 +59,13 @@ final void handleSharing() { Logging.fine("Share handling has been cancelled by another plugin!"); return; } - this.completeSharing(event); + logAffectedProfilesCount(); + ProfileDataSnapshot snapshot = getSnapshot(); + updatePlayer(); + updateAlwaysWriteProfile(snapshot); + updateProfiles(snapshot); + double timeTaken = (System.nanoTime() - startTime) / 1000000.0; + logHandlingComplete(timeTaken, event); } protected abstract void prepareProfiles(); @@ -62,51 +76,56 @@ protected void logBypass() { Logging.fine(player.getName() + " has bypass permission for 1 or more world/groups!"); } - private void completeSharing(ShareHandlingEvent event) { - logAffectedProfilesCount(event); - saveAlwaysWriteProfile(event); - handleProfileChanges(event); - logHandlingComplete(event); + private void logAffectedProfilesCount() { + PersistingProfile alwaysWriteProfile = affectedProfiles.getAlwaysWriteProfile(); + int writeProfiles = affectedProfiles.getWriteProfiles().size() + (alwaysWriteProfile != null ? 1 : 0); + + Logging.finer("Change affected by %d fromProfiles and %d toProfiles", writeProfiles, + affectedProfiles.getReadProfiles().size()); } - private void logAffectedProfilesCount(ShareHandlingEvent event) { - PersistingProfile alwaysWriteProfile = event.getAlwaysWriteProfile(); - int writeProfiles = event.getWriteProfiles().size() + (alwaysWriteProfile != null ? 1 : 0); + private ProfileDataSnapshot getSnapshot() { + ProfileDataSnapshot profileDataSnapshot = new ProfileDataSnapshot(); + Sharables.enabled().forEach(sharable -> sharable.getHandler().updateProfile(profileDataSnapshot, player)); + return profileDataSnapshot; + } - Logging.finer("Change affected by %d fromProfiles and %d toProfiles", writeProfiles, - event.getReadProfiles().size()); + private void updatePlayer() { + for (PersistingProfile readProfile : affectedProfiles.getReadProfiles()) { + ShareHandlingUpdater.updatePlayer(inventories, player, readProfile); + } } - private void saveAlwaysWriteProfile(ShareHandlingEvent event) { - if (event.getAlwaysWriteProfile() != null) { - ShareHandlingUpdater.updateProfile(inventories, event.getPlayer(), event.getAlwaysWriteProfile()); - } else { + private void updateAlwaysWriteProfile(ProfileDataSnapshot snapshot) { + if (affectedProfiles.getAlwaysWriteProfile() == null) { Logging.warning("No fromWorld to save to"); + return; } + updatePersistingProfile(affectedProfiles.getAlwaysWriteProfile(), snapshot); } - private void handleProfileChanges(ShareHandlingEvent event) { - if (event.getReadProfiles().isEmpty()) { + private void updateProfiles(ProfileDataSnapshot snapshot) { + if (affectedProfiles.getReadProfiles().isEmpty()) { Logging.finest("No profiles to read from - nothing more to do."); - } else { - updateProfiles(event.getPlayer(), event.getWriteProfiles()); - updatePlayer(event.getPlayer(), event.getReadProfiles()); + return; } - } - - private void updateProfiles(Player player, List writeProfiles) { - for (PersistingProfile writeProfile : writeProfiles) { - ShareHandlingUpdater.updateProfile(inventories, player, writeProfile); + for (PersistingProfile writeProfile : affectedProfiles.getWriteProfiles()) { + updatePersistingProfile(writeProfile, snapshot); } } - private void updatePlayer(Player player, List readProfiles) { - for (PersistingProfile readProfile : readProfiles) { - ShareHandlingUpdater.updatePlayer(inventories, player, readProfile); - } + private void updatePersistingProfile(PersistingProfile persistingProfile, ProfileDataSnapshot snapshot) { + persistingProfile.getProfile().thenAccept(playerProfile -> { + Logging.finer("Persisted: " + persistingProfile.getShares() + " to " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")" + + " for player " + playerProfile.getPlayer().getName()); + playerProfile.updateFromSnapshot(snapshot, persistingProfile.getShares()); + profileDataStore.updatePlayerData(playerProfile); + }); } - private void logHandlingComplete(ShareHandlingEvent event) { - Logging.finer("=== %s complete for %s ===", event.getPlayer().getName(), event.getEventName()); + private void logHandlingComplete(double timeTaken, ShareHandlingEvent event) { + Logging.fine("=== %s complete for %s | time taken: %4.4f ms ===", player.getName(), event.getEventName(), timeTaken); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 090812c5..e3990e14 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -1,15 +1,16 @@ package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.share.Sharable; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; public final class ShareHandlingUpdater { @@ -36,43 +37,50 @@ private ShareHandlingUpdater(MultiverseInventories inventories, Player player, P } private void updateProfile() { - if (profile.shares().isEmpty()) { + if (profile.getShares().isEmpty()) { return; } - for (Sharable sharable : profile.shares()) { - sharable.getHandler().updateProfile(profile.profile(), player); - } - Logging.finer("Persisted: " + profile.shares() + " to " - + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() - + " (" + profile.profile().getProfileType() + ")" - + " for player " + profile.profile().getPlayer().getName()); - inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(profile.profile()); + Try.of(() -> profile.getProfile().get(10, TimeUnit.SECONDS)) + .peek(playerProfile -> { + for (Sharable sharable : profile.getShares()) { + sharable.getHandler().updateProfile(playerProfile, player); + } + Logging.finer("Persisted: " + profile.getShares() + " to " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")" + + " for player " + playerProfile.getPlayer().getName()); + inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(playerProfile); + }) + .onFailure(e -> Logging.severe("Error getting playerdata: " + e.getMessage())); } private void updatePlayer() { player.closeInventory(); + Try.of(() -> profile.getProfile().get(10, TimeUnit.SECONDS)) + .peek(playerProfile -> { + List> loaded = new ArrayList<>(profile.getShares().size()); + List> defaulted = new ArrayList<>(profile.getShares().size()); - final List> loaded = new ArrayList<>(profile.shares().size()); - final List> defaulted = new ArrayList<>(profile.shares().size()); - - for (Sharable sharable : profile.shares()) { - if (sharable.getHandler().updatePlayer(player, profile.profile())) { - loaded.add(sharable); - } else { - defaulted.add(sharable); - } - } - if (!loaded.isEmpty()) { - Logging.finer("Updated: " + loaded + " for " - + profile.profile().getPlayer().getName() + " for " - + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() - + " (" + profile.profile().getProfileType() + ")"); - } - if (!defaulted.isEmpty()) { - Logging.finer("Defaulted: " + defaulted + " for " - + profile.profile().getPlayer().getName() + " for " - + profile.profile().getContainerType() + ":" + profile.profile().getContainerName() - + " (" + profile.profile().getProfileType() + ")"); - } + for (Sharable sharable : profile.getShares()) { + if (sharable.getHandler().updatePlayer(player, playerProfile)) { + loaded.add(sharable); + } else { + defaulted.add(sharable); + } + } + if (!loaded.isEmpty()) { + Logging.finer("Updated: " + loaded + " for " + + playerProfile.getPlayer().getName() + " for " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")"); + } + if (!defaulted.isEmpty()) { + Logging.finer("Defaulted: " + defaulted + " for " + + playerProfile.getPlayer().getName() + " for " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")"); + } + }) + .onFailure(e -> Logging.severe("Error getting playerdata: " + e.getMessage()));; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 354af032..80a5c555 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -49,25 +49,20 @@ public void write(T value, boolean save) { Logging.finer("Writing single share: " + sharable.getNames()[0]); String worldName = this.player.getWorld().getName(); var profileContainerStoreProvider = this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class); - writeNewValueToProfile( - profileContainerStoreProvider.getStore(ContainerType.WORLD) - .getContainer(worldName) - .getPlayerData(this.player), - value, - save - ); + profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(worldName) + .getPlayerData(this.player) + .thenAccept(profile -> writeNewValueToProfile(profile, value, save)); this.inventories.getServiceLocator().getService(WorldGroupManager.class) .getGroupsForWorld(worldName) .forEach(worldGroup -> { - if (worldGroup.getDisabledShares().contains(sharable)) { + if (!worldGroup.getApplicableShares().contains(sharable)) { return; } - writeNewValueToProfile( - worldGroup.getGroupProfileContainer().getPlayerData(this.player), - value, - save - ); + worldGroup.getGroupProfileContainer().getPlayerData(this.player).thenAccept(profile -> { + writeNewValueToProfile(profile, value, save); + }); }); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index dd9a4d08..efb3a836 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -42,7 +42,7 @@ protected ShareHandlingEvent createEvent() { @Override protected void prepareProfiles() { - Logging.finer("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); + Logging.fine("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); setAlwaysWriteWorldProfile(); if (isPlayerAffectedByChange()) { addWriteProfiles(); @@ -52,12 +52,7 @@ protected void prepareProfiles() { private void setAlwaysWriteWorldProfile() { // We will always save everything to the world they come from. - PlayerProfile fromWorldProfile = getWorldPlayerProfile(fromWorld, player); - affectedProfiles.setAlwaysWriteProfile(fromWorldProfile); - } - - private PlayerProfile getWorldPlayerProfile(String world, Player player) { - return worldProfileContainerStore.getContainer(world).getPlayerData(player); + affectedProfiles.setAlwaysWriteProfile(worldProfileContainerStore.getContainer(fromWorld).getPlayerData(player)); } private boolean isPlayerAffectedByChange() { @@ -138,12 +133,10 @@ private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { } private void addReadProfileForWorldGroup(WorldGroup worldGroup) { - PlayerProfile playerProfile = getWorldGroupPlayerData(worldGroup); - affectedProfiles.addReadProfile(playerProfile, worldGroup.getApplicableShares()); - } - - private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { - return worldGroup.getGroupProfileContainer().getPlayerData(player); + affectedProfiles.addReadProfile( + worldGroup.getGroupProfileContainer().getPlayerData(player), + worldGroup.getApplicableShares() + ); } private void useToWorldForMissingShares() { @@ -157,11 +150,10 @@ private void useToWorldForMissingShares() { return; } Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); - affectedProfiles.addReadProfile(getToWorldPlayerData(), unhandledShares); - } - - private PlayerProfile getToWorldPlayerData() { - return worldProfileContainerStore.getContainer(toWorld).getPlayerData(player); + affectedProfiles.addReadProfile( + worldProfileContainerStore.getContainer(toWorld).getPlayerData(player), + unhandledShares + ); } } @@ -180,11 +172,7 @@ private void conditionallyAddWriteProfiles() { void addWriteProfiles() { ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(player), getWorldGroupShares()); - } - - private Shares getWorldGroupShares() { - return Sharables.fromShares(worldGroup.getApplicableShares()); + affectedProfiles.addWriteProfile(container.getPlayerData(player), worldGroup.getApplicableShares()); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 27fb5aad..c1ce0fa1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -186,7 +186,7 @@ private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, Pla Try.run(() -> playerData.save(playerFile)).onFailure(e -> { Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); - Logging.severe(e.getMessage()); + e.printStackTrace(); }); } @@ -247,6 +247,7 @@ public CompletableFuture getPlayerData(ProfileKey profileKey) { return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID()))); } + Logging.finer("%s not cached. loading from disk...", profileKey); return playerProfileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); }); } catch (Exception e) { @@ -266,7 +267,7 @@ private PlayerProfile getPlayerDataFromDisk(ProfileKey key, File playerFile) { } catch (IOException e) { Logging.severe("Could not save data for player: " + key.getPlayerName() + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); - Logging.severe(e.getMessage()); + e.printStackTrace(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 0f7b1fe9..25f2cac3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -11,21 +11,20 @@ /** * Contains all the world/group specific data for a player. */ -public final class PlayerProfile implements Cloneable { +public final class PlayerProfile extends ProfileDataSnapshot { static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, OfflinePlayer player) { return new PlayerProfile(containerType, containerName, profileType, player); } - private final Map data = new HashMap<>(Sharables.all().size()); - private final OfflinePlayer player; private final ContainerType containerType; private final String containerName; private final ProfileType profileType; private PlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, OfflinePlayer player) { + super(); this.containerType = containerType; this.profileType = profileType; this.containerName = containerName; @@ -60,38 +59,8 @@ public ProfileType getProfileType() { return this.profileType; } - /** - * Retrieves the profile's value of the {@link Sharable} passed in. - * - * @param sharable Represents the key for the data wanted from the profile. - * @param This indicates the type of return value to be expected. - * @return The value of the sharable for this profile. Null if no value is set. - */ - public T get(Sharable sharable) { - return sharable.getType().cast(this.data.get(sharable)); - } - - /** - * Sets the profile's value for the {@link Sharable} passed in. - * - * @param sharable Represents the key for the data to store. - * @param value The value of the data. - * @param The type of value to be expected. - */ - public void set(Sharable sharable, T value) { - this.data.put(sharable, value); - } - public PlayerProfile clone() { - try { - return (PlayerProfile) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - } - - public Map getData() { - return data; + return (PlayerProfile) super.clone(); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java new file mode 100644 index 00000000..6da935a8 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java @@ -0,0 +1,27 @@ +package org.mvplugins.multiverse.inventories.profile; + +import org.mvplugins.multiverse.inventories.share.Sharable; + +import java.util.Map; + +public interface ProfileData { + /** + * Retrieves the profile's value of the {@link Sharable} passed in. + * + * @param sharable Represents the key for the data wanted from the profile. + * @param This indicates the type of return value to be expected. + * @return The value of the sharable for this profile. Null if no value is set. + */ + T get(Sharable sharable); + + /** + * Sets the profile's value for the {@link Sharable} passed in. + * + * @param sharable Represents the key for the data to store. + * @param value The value of the data. + * @param The type of value to be expected. + */ + void set(Sharable sharable, T value); + + Map getData(); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java new file mode 100644 index 00000000..17e5aa52 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java @@ -0,0 +1,54 @@ +package org.mvplugins.multiverse.inventories.profile; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.HashMap; +import java.util.Map; + +public class ProfileDataSnapshot implements Cloneable, ProfileData { + + private final Map data; + + public ProfileDataSnapshot() { + this.data = new HashMap<>(Sharables.all().size()); + } + + @Override + public T get(Sharable sharable) { + return sharable.getType().cast(this.data.get(sharable)); + } + + @Override + public void set(Sharable sharable, T value) { + this.data.put(sharable, value); + } + + @Override + public Map getData() { + return data; + } + + public void updateFromSnapshot(ProfileDataSnapshot snapshot) { + this.data.putAll(snapshot.getData()); + } + + public void updateFromSnapshot(ProfileDataSnapshot snapshot, Shares shares) { + shares.forEach(sharable -> { + Object data = snapshot.getData().get(sharable); + if (data != null) { + this.data.put(sharable, data); + } + }); + } + + @Override + public ProfileDataSnapshot clone() { + try { + return (ProfileDataSnapshot) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index d9865dc0..7712d41b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -17,11 +17,10 @@ final class ProfileFileIO { - private final ExecutorService fileIOExecutorService; + private final ExecutorService fileIOExecutorService = Executors.newWorkStealingPool(); private final Map fileLocks = new ConcurrentHashMap<>(); ProfileFileIO() { - fileIOExecutorService = Executors.newWorkStealingPool(); } CompletableFuture queueAction(File file, Runnable action) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 21c0051e..fa717833 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -11,7 +11,6 @@ import org.bukkit.entity.Player; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; /** * A container for player profiles in a given world or world group (based on {@link #getContainerType()}), @@ -32,6 +31,21 @@ public final class ProfileContainer { this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); } + public CompletableFuture getPlayerData(Player player) { + ProfileType type = config.getEnableGamemodeShareHandling() + ? ProfileTypes.forGameMode(player.getGameMode()) + : ProfileTypes.SURVIVAL; + return getPlayerData(type, player); + } + + public CompletableFuture getPlayerData(ProfileType profileType, OfflinePlayer player) { + return profileDataSource.getPlayerData(ProfileKey.create( + getContainerType(), + getContainerName(), + profileType, + player)); + } + /** * Retrieves the profile for the given player. *

If game mode profiles are enabled, the profile for their current game mode will be returned, otherwise their @@ -40,14 +54,11 @@ public final class ProfileContainer { * @param player Player to get profile for. * @return The profile for the given player. */ - public PlayerProfile getPlayerData(Player player) { - ProfileType type; - if (config.getEnableGamemodeShareHandling()) { - type = ProfileTypes.forGameMode(player.getGameMode()); - } else { - type = ProfileTypes.SURVIVAL; - } - return getPlayerData(type, player); + public PlayerProfile getPlayerDataNow(Player player) { + ProfileType type = config.getEnableGamemodeShareHandling() + ? ProfileTypes.forGameMode(player.getGameMode()) + : ProfileTypes.SURVIVAL; + return getPlayerDataNow(type, player); } /** @@ -57,7 +68,7 @@ public PlayerProfile getPlayerData(Player player) { * @param player Player to get profile for. * @return The profile of the given type for the given player. */ - public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { + public PlayerProfile getPlayerDataNow(ProfileType profileType, OfflinePlayer player) { return profileDataSource.getPlayerDataNow(ProfileKey.create( getContainerType(), getContainerName(), diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index f58d7f53..2c2e27de 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.profile.group; +import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; @@ -207,6 +208,7 @@ public void recalculateApplicableShares() { Shares disabledOptionalShares = Sharables.optionalOf(); disabledOptionalShares.removeAll(this.inventoriesConfig.getActiveOptionalShares()); this.applicableShares.removeAll(disabledOptionalShares); + Logging.finest("Applicable shares for " + this.getName() + ": " + this.applicableShares); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java index 30b3fd16..bf4a342b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java @@ -2,6 +2,7 @@ import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.profile.ProfileData; /** * This class is used to handle the transition of data from a player profile to a player and vice versa, typically @@ -19,7 +20,7 @@ public interface SharableHandler { * with the values of the player. * @param player The player whose values will be used to update the given profile. */ - void updateProfile(PlayerProfile profile, Player player); + void updateProfile(ProfileData profile, Player player); /** * This method is called during share handling (aka PlayerChangeWorldEvent). It will perform updates to @@ -30,5 +31,5 @@ public interface SharableHandler { * @param profile The profile whose values will be used to update the give player. * @return True if player was updated from existing profile. False if default was used (new profile). */ - boolean updatePlayer(Player player, PlayerProfile profile); + boolean updatePlayer(Player player, ProfileData profile); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index f4e50816..baeb14e7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -8,6 +8,7 @@ import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileData; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.util.DataStrings; @@ -92,12 +93,12 @@ public static void init(MultiverseInventories inventories) { public static final Sharable ENDER_CHEST = new Sharable.Builder("ender_chest", ItemStack[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(ENDER_CHEST, player.getEnderChest().getContents()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack[] value = profile.get(ENDER_CHEST); if (value == null) { player.getEnderChest().setContents(MinecraftTools.fillWithAir( @@ -116,12 +117,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable INVENTORY = new Sharable.Builder("inventory_contents", ItemStack[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(INVENTORY, player.getInventory().getContents()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack[] value = profile.get(INVENTORY); if (value == null) { player.getInventory().setContents(MinecraftTools.fillWithAir( @@ -142,12 +143,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable ARMOR = new Sharable.Builder("armor_contents", ItemStack[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(ARMOR, player.getInventory().getArmorContents()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack[] value = profile.get(ARMOR); if (value == null) { player.getInventory().setArmorContents(MinecraftTools.fillWithAir( @@ -168,12 +169,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable OFF_HAND = new Sharable.Builder("off_hand", ItemStack.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(OFF_HAND, player.getInventory().getItemInOffHand()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack value = profile.get(OFF_HAND); if (value == null) { player.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); @@ -193,12 +194,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable MAX_HEALTH = new Sharable.Builder<>("max_hit_points", Double.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(MAX_HEALTH, getMaxHealth(player)); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Double value = profile.get(MAX_HEALTH); if (value == null) { Option.of(maxHealthAttr).map(player::getAttribute) @@ -218,7 +219,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable HEALTH = new Sharable.Builder("hit_points", Double.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { double health = player.getHealth(); // Player is dead, so health should be regained to full. if (health <= 0) { @@ -228,7 +229,7 @@ public void updateProfile(PlayerProfile profile, Player player) { } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Double value = profile.get(HEALTH); if (value == null) { player.setHealth(PlayerStats.HEALTH); @@ -266,12 +267,12 @@ private static double getMaxHealth(Player player) { public static final Sharable REMAINING_AIR = new Sharable.Builder("remaining_air", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(REMAINING_AIR, player.getRemainingAir()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(REMAINING_AIR); if (value == null) { player.setRemainingAir(PlayerStats.REMAINING_AIR); @@ -294,12 +295,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable MAXIMUM_AIR = new Sharable.Builder("maximum_air", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(MAXIMUM_AIR, player.getMaximumAir()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(MAXIMUM_AIR); if (value == null) { player.setMaximumAir(PlayerStats.MAXIMUM_AIR); @@ -322,12 +323,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable FALL_DISTANCE = new Sharable.Builder("fall_distance", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(FALL_DISTANCE, player.getFallDistance()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(FALL_DISTANCE); if (value == null) { player.setFallDistance(PlayerStats.FALL_DISTANCE); @@ -351,12 +352,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable FIRE_TICKS = new Sharable.Builder("fire_ticks", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(FIRE_TICKS, player.getFireTicks()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(FIRE_TICKS); if (value == null) { player.setFireTicks(PlayerStats.FIRE_TICKS); @@ -381,12 +382,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable EXPERIENCE = new Sharable.Builder("xp", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(EXPERIENCE, player.getExp()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(EXPERIENCE); if (value == null) { player.setExp(PlayerStats.EXPERIENCE); @@ -409,12 +410,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable LEVEL = new Sharable.Builder("lvl", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(LEVEL, player.getLevel()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(LEVEL); if (value == null) { player.setLevel(PlayerStats.LEVEL); @@ -437,12 +438,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable TOTAL_EXPERIENCE = new Sharable.Builder("total_xp", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(TOTAL_EXPERIENCE, player.getTotalExperience()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(TOTAL_EXPERIENCE); if (value == null) { player.setTotalExperience(PlayerStats.TOTAL_EXPERIENCE); @@ -465,12 +466,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable FOOD_LEVEL = new Sharable.Builder("food_level", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(FOOD_LEVEL, player.getFoodLevel()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(FOOD_LEVEL); if (value == null) { player.setFoodLevel(PlayerStats.FOOD_LEVEL); @@ -494,12 +495,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable EXHAUSTION = new Sharable.Builder("exhaustion", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(EXHAUSTION, player.getExhaustion()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(EXHAUSTION); if (value == null) { player.setExhaustion(PlayerStats.EXHAUSTION); @@ -523,12 +524,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable SATURATION = new Sharable.Builder("saturation", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(SATURATION, player.getSaturation()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(SATURATION); if (value == null) { player.setSaturation(PlayerStats.SATURATION); @@ -552,7 +553,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable BED_SPAWN = new Sharable.Builder("bed_spawn", Location.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { if (inventories.isUsingSpawnChangeEvent()) { // Bed spawn location already updated during PlayerSpawnChangeEvent return; @@ -576,7 +577,7 @@ public void updateProfile(PlayerProfile profile, Player player) { } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Location loc = profile.get(BED_SPAWN); if (loc == null) { Logging.finer("No bed location saved"); @@ -596,13 +597,13 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable LAST_LOCATION = new Sharable.Builder("last_location", Location.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { /* It's too late to update the profile for last location here because the world change has already happened. The update occurs in the PlayerTeleportEvent handler in InventoriesListener. */ } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Location loc = profile.get(LAST_LOCATION); if (loc == null || loc.getWorld() == null || loc.equals(player.getLocation())) { return false; @@ -629,7 +630,7 @@ private boolean hasValidEconomyHandler() { } @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { if (!hasValidEconomyHandler()) { return; } @@ -637,7 +638,7 @@ public void updateProfile(PlayerProfile profile, Player player) { } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { if (!hasValidEconomyHandler()) { return false; } @@ -658,13 +659,13 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable POTIONS = new Sharable.Builder("potion_effects", PotionEffect[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { Collection potionEffects = player.getActivePotionEffects(); profile.set(POTIONS, potionEffects.toArray(new PotionEffect[potionEffects.size()])); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { PotionEffect[] effects = profile.get(POTIONS); for (PotionEffect effect : player.getActivePotionEffects()) { player.removePotionEffect(effect.getType()); @@ -947,6 +948,7 @@ public static Shares fromList(List sharesList) { } public static void recalculateEnabledShares() { + Logging.finer("Recalculating enabled shares..."); enabledShares = standardOf(); enabledShares.addAll(inventoriesConfig.getActiveOptionalShares()); worldGroupManager.recalculateApplicableShares(); diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index 78dd6aa1..2792cd79 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -29,10 +29,10 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { player.health = 4.4 player.maxHealth = 15.1 - val playerProfile = profileDataSource.getPlayerDataNow( + val playerProfileFuture = profileDataSource.getPlayerData( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) - ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfile)) - + ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfileFuture)) + val playerProfile = playerProfileFuture.get() assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) assertEquals(15.1, playerProfile.get(Sharables.MAX_HEALTH)) } From 49943c9ba3bce910f8f62130e5f00cda73d1a7eb Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:16:41 +0800 Subject: [PATCH 102/180] Add async methods for getGlobalProfile --- .../perworldinventory/PwiImportHelper.java | 2 +- .../handleshare/ShareHandleListener.java | 15 +-- .../inventories/handleshare/ShareHandler.java | 10 ++ .../profile/FlatFileProfileDataSource.java | 96 ++++++++++++------- .../profile/ProfileDataSnapshot.java | 4 +- .../profile/ProfileDataSource.java | 16 +++- .../inventories/profile/ProfileFileIO.java | 7 +- .../inventories/profile/ProfileKey.java | 2 +- .../inventories/share/Sharables.java | 6 -- .../inventories/TestWithMockBukkit.kt | 8 +- .../profile/FilePerformanceTest.kt | 18 +--- .../profile/PlayerNameChangeTest.kt | 2 +- .../profile/ProfileDataSourceTest.kt | 26 ++++- src/test/resources/playerdata.json | 1 + 14 files changed, 137 insertions(+), 76 deletions(-) create mode 100644 src/test/resources/playerdata.json diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index c8be31da..657a4b87 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -180,7 +180,7 @@ private void saveMVDataForGroup(Group group) throws DataImportException { } private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throws DataImportException { - GlobalProfile globalProfile = profileDataSource.getGlobalProfile(offlinePlayer); + GlobalProfile globalProfile = profileDataSource.getGlobalProfileNow(offlinePlayer); globalProfile.setLoadOnLogin(pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); profileDataSource.updateGlobalProfile(globalProfile); for (GameMode gameMode : GameMode.values()) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 159788da..63ad5630 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; /** * Events related to handling of player profile changes. @@ -91,7 +92,7 @@ void playerJoin(final PlayerJoinEvent event) { // Just in case AsyncPlayerPreLoginEvent was still the old name verifyCorrectPlayerName(player.getUniqueId(), player.getName()); - final GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player); + final GlobalProfile globalProfile = profileDataSource.getGlobalProfileNow(player); final String world = globalProfile.getLastWorld(); if (config.getApplyPlayerdataOnJoin() && globalProfile.shouldLoadOnLogin()) { ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile( @@ -107,7 +108,7 @@ void playerJoin(final PlayerJoinEvent event) { } private void verifyCorrectPlayerName(UUID uuid, String name) { - profileDataSource.getExistingGlobalProfile(uuid, name).peek(globalProfile -> { + profileDataSource.getExistingGlobalProfileNow(uuid, name).peek(globalProfile -> { if (globalProfile.getLastKnownName().equals(name)) { return; } @@ -136,8 +137,8 @@ private void verifyCorrectPlayerName(UUID uuid, String name) { void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); - GlobalProfile globalProfile = profileDataSource.getGlobalProfile(player); - globalProfile.setLastWorld(world); + CompletableFuture globalProfile = profileDataSource.getGlobalProfile(player); + globalProfile.thenAccept(p -> p.setLastWorld(world)); if (config.getSavePlayerdataOnQuit()) { ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile( Sharables.allOf(), @@ -146,10 +147,10 @@ void playerQuit(final PlayerQuitEvent event) { .getPlayerData(player) )); if (config.getApplyPlayerdataOnJoin()) { - globalProfile.setLoadOnLogin(true); + globalProfile.thenAccept(p -> p.setLoadOnLogin(true)); } } - profileDataSource.updateGlobalProfile(globalProfile); + globalProfile.thenAccept(profileDataSource::updateGlobalProfile); SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); } @@ -269,7 +270,7 @@ void playerRespawn(PlayerRespawnEvent event) { () -> verifyCorrectWorld( player, player.getWorld().getName(), - profileDataSource.getGlobalProfile(player)), + profileDataSource.getGlobalProfileNow(player)), 2L); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 31b6953b..73b720f3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -52,18 +52,28 @@ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShar */ final void handleSharing() { long startTime = System.nanoTime(); + long s1 = System.nanoTime(); this.prepareProfiles(); + Logging.finest("Prepared profiles in %4.4f ms", (System.nanoTime() - s1) / 1000000.0); + long s2 = System.nanoTime(); ShareHandlingEvent event = this.createEvent(); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { Logging.fine("Share handling has been cancelled by another plugin!"); return; } + Logging.finest("Share handling event took %4.4f ms", (System.nanoTime() - s2) / 1000000.0); logAffectedProfilesCount(); + long s3 = System.nanoTime(); ProfileDataSnapshot snapshot = getSnapshot(); + Logging.finest("Got snapshot in %4.4f ms", (System.nanoTime() - s3) / 1000000.0); + long s4 = System.nanoTime(); updatePlayer(); + Logging.finest("Updated player in %4.4f ms", (System.nanoTime() - s4) / 1000000.0); + long s5 = System.nanoTime(); updateAlwaysWriteProfile(snapshot); updateProfiles(snapshot); + Logging.finest("Updated profiles in %4.4f ms", (System.nanoTime() - s5) / 1000000.0); double timeTaken = (System.nanoTime() - startTime) / 1000000.0; logHandlingComplete(timeTaken, event); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index c1ce0fa1..7afa3970 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -48,19 +48,20 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); + private final ProfileFileIO profileFileIO; + private final Cache playerFileCache; private final AsyncCache playerProfileCache; - private final Cache globalProfileCache; + private final AsyncCache globalProfileCache; private final File worldFolder; private final File groupFolder; private final File playerFolder; - private final ProfileFileIO playerProfileIO; - private final ProfileFileIO globalProfileIO; - @Inject FlatFileProfileDataSource(@NotNull MultiverseInventories plugin, @NotNull InventoriesConfig inventoriesConfig) throws IOException { + this.profileFileIO = new ProfileFileIO(); + this.playerFileCache = Caffeine.newBuilder() .expireAfterAccess(inventoriesConfig.getPlayerFileCacheExpiry(), TimeUnit.MINUTES) .maximumSize(inventoriesConfig.getPlayerFileCacheSize()) @@ -70,17 +71,16 @@ final class FlatFileProfileDataSource implements ProfileDataSource { this.playerProfileCache = Caffeine.newBuilder() .expireAfterAccess(inventoriesConfig.getPlayerProfileCacheExpiry(), TimeUnit.MINUTES) .maximumSize(inventoriesConfig.getPlayerProfileCacheSize()) + .executor(profileFileIO.getExecutor()) .recordStats() .buildAsync(); this.globalProfileCache = Caffeine.newBuilder() .expireAfterAccess(inventoriesConfig.getGlobalProfileCacheExpiry(), TimeUnit.MINUTES) .maximumSize(inventoriesConfig.getGlobalProfileCacheSize()) + .executor(profileFileIO.getExecutor()) .recordStats() - .build(); - - this.playerProfileIO = new ProfileFileIO(); - this.globalProfileIO = new ProfileFileIO(); + .buildAsync(); // Make the data folders plugin.getDataFolder().mkdirs(); @@ -148,7 +148,7 @@ private FileConfiguration parseToConfiguration(File file) { JsonConfiguration jsonConfiguration = new JsonConfiguration(); jsonConfiguration.options().continueOnSerializationError(true); Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { - Logging.severe("Could not load file: " + file); + Logging.severe("Could not load file %s : %s", file, e.getMessage()); throw new RuntimeException(e); }); return jsonConfiguration; @@ -173,7 +173,7 @@ private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playe public CompletableFuture updatePlayerData(PlayerProfile playerProfile) { ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); File playerFile = getPlayerFile(profileKey); - return playerProfileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); + return profileFileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); } private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, PlayerProfile playerProfile) { @@ -248,7 +248,7 @@ public CompletableFuture getPlayerData(ProfileKey profileKey) { key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID()))); } Logging.finer("%s not cached. loading from disk...", profileKey); - return playerProfileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); + return profileFileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); }); } catch (Exception e) { Logging.severe("Could not get data for player: " + profileKey.getPlayerName() @@ -395,10 +395,10 @@ public CompletableFuture removePlayerData(ProfileKey profileKey) { + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); return CompletableFuture.completedFuture(null); } - return playerProfileIO.queueAction(playerFile, playerFile::delete); + return profileFileIO.queueAction(playerFile, playerFile::delete); } Option.of(playerProfileCache.synchronous().getIfPresent(profileKey)).peek(profile -> profile.getData().clear()); - return playerProfileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); + return profileFileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); } private void processRemovePlayerData(ProfileKey profileKey, File playerFile) { @@ -459,37 +459,70 @@ private void migrateForContainerType(File[] folders, ContainerType containerType @NotNull @Override - public GlobalProfile getGlobalProfile(UUID playerUUID) { - return getGlobalProfile(Bukkit.getOfflinePlayer(playerUUID)); + public GlobalProfile getGlobalProfileNow(UUID playerUUID) { + return getGlobalProfileNow(Bukkit.getOfflinePlayer(playerUUID)); } @NotNull @Override - public GlobalProfile getGlobalProfile(OfflinePlayer player) { + public GlobalProfile getGlobalProfileNow(OfflinePlayer player) { + return getGlobalProfileNow(player.getUniqueId(), player.getName()); + } + + @NotNull + @Override + public GlobalProfile getGlobalProfileNow(UUID playerUUID, String playerName) { + try { + return getGlobalProfile(playerUUID, playerName).get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + + @Override + public @NotNull Option getExistingGlobalProfileNow(UUID playerUUID, String playerName) { + try { + return getExistingGlobalProfile(playerUUID, playerName).get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + + @Override + public CompletableFuture getGlobalProfile(UUID playerUUID) { + return getGlobalProfile(Bukkit.getOfflinePlayer(playerUUID)); + } + + @Override + public CompletableFuture getGlobalProfile(OfflinePlayer player) { return getGlobalProfile(player.getUniqueId(), player.getName()); } @NotNull @Override - public GlobalProfile getGlobalProfile(UUID playerUUID, String playerName) { + public CompletableFuture getGlobalProfile(UUID playerUUID, String playerName) { try { - return globalProfileCache.get(playerUUID, (key) -> getGlobalProfileFromDisk(playerUUID, playerName)); + File globalFile = getGlobalFile(playerUUID.toString()); + return globalProfileCache.get(playerUUID, (key, executor) -> + profileFileIO.queueCallable(globalFile, () -> getGlobalProfileFromDisk(playerUUID, playerName, globalFile))); } catch (Exception e) { Logging.severe("Unable to get global profile for player: " + playerName); throw new RuntimeException(e); } } + @NotNull @Override - public @NotNull Option getExistingGlobalProfile(UUID playerUUID, String playerName) { + public CompletableFuture> getExistingGlobalProfile(UUID playerUUID, String playerName) { File uuidFile = getGlobalFile(playerUUID.toString()); if (!uuidFile.exists()) { - return Option.none(); + return CompletableFuture.completedFuture(Option.none()); } - return Option.of(getGlobalProfile(playerUUID, playerName)); + return getGlobalProfile(playerUUID, playerName).thenApply(Option::of); } - private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName) { + private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { + Logging.finer("Global profile for player %s not in cached. Loading...", playerName); // Migrate from player name to uuid profile file File legacyFile = getGlobalFile(playerName); if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { @@ -497,11 +530,10 @@ private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerNam } // Load from existing profile file - File uuidFile = getGlobalFile(playerUUID.toString()); - if (!uuidFile.exists()) { + if (!globalFile.exists()) { return GlobalProfile.createGlobalProfile(playerUUID, playerName); } - return loadGlobalProfile(uuidFile, playerName, playerUUID); + return loadGlobalProfile(globalFile, playerName, playerUUID); } private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { @@ -509,20 +541,20 @@ private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { } private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = globalProfileIO.waitForData(globalFile, () -> parseToConfiguration(globalFile)); + FileConfiguration playerData = parseToConfiguration(globalFile); ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { - section = playerData.createSection(DataStrings.PLAYER_DATA); + return GlobalProfile.createGlobalProfile(playerUUID, playerName); } return GlobalProfile.deserialize(playerName, playerUUID, section); } public CompletableFuture modifyGlobalProfile(UUID playerUUID, Consumer consumer) { - return modifyGlobalProfile(getGlobalProfile(playerUUID), consumer); + return getGlobalProfile(playerUUID).thenCompose(globalProfile -> modifyGlobalProfile(globalProfile, consumer)); } public CompletableFuture modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { - return modifyGlobalProfile(getGlobalProfile(offlinePlayer), consumer); + return getGlobalProfile(offlinePlayer).thenCompose(globalProfile -> modifyGlobalProfile(globalProfile, consumer)); } private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { @@ -536,7 +568,7 @@ private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, @Override public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { File globalFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); - return globalProfileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); + return profileFileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); } private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalFile) { @@ -579,7 +611,7 @@ public void clearProfileCache(Predicate predicate) { @Override public void clearAllCache() { playerFileCache.invalidateAll(); - globalProfileCache.invalidateAll(); + globalProfileCache.synchronous().invalidateAll(); playerProfileCache.synchronous().invalidateAll(); } @@ -587,7 +619,7 @@ public void clearAllCache() { public Map getCacheStats() { Map stats = new HashMap<>(); stats.put("playerFileCache", playerFileCache.stats()); - stats.put("globalProfileCache", globalProfileCache.stats()); + stats.put("globalProfileCache", globalProfileCache.synchronous().stats()); stats.put("profileCache", playerProfileCache.synchronous().stats()); return stats; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java index 17e5aa52..dbcd74b9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java @@ -12,12 +12,12 @@ public class ProfileDataSnapshot implements Cloneable, ProfileData { private final Map data; public ProfileDataSnapshot() { - this.data = new HashMap<>(Sharables.all().size()); + this.data = new HashMap<>(Sharables.all().size(), 1); } @Override public T get(Sharable sharable) { - return sharable.getType().cast(this.data.get(sharable)); + return (T) this.data.get(sharable); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 92061329..e569922f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -69,7 +69,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param playerUUID The UUID of the player. * @return The global profile for the specified player. */ - @NotNull GlobalProfile getGlobalProfile(UUID playerUUID); + @NotNull GlobalProfile getGlobalProfileNow(UUID playerUUID); /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -77,7 +77,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param player The player. * @return The global profile for the specified player. */ - @NotNull GlobalProfile getGlobalProfile(OfflinePlayer player); + @NotNull GlobalProfile getGlobalProfileNow(OfflinePlayer player); /** * Retrieves the global profile for a player which contains meta-data for the player. @@ -87,7 +87,7 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param playerName The name of the player. * @return The global profile for the specified player. */ - @NotNull GlobalProfile getGlobalProfile(UUID playerUUID, String playerName); + @NotNull GlobalProfile getGlobalProfileNow(UUID playerUUID, String playerName); /** * Retrieves the global profile for a player which contains meta-data for the player if it exists. @@ -96,7 +96,15 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param playerName The name of the player. * @return The global profile for the specified player or empty if it doesn't exist. */ - @NotNull Option getExistingGlobalProfile(UUID playerUUID, String playerName); + @NotNull Option getExistingGlobalProfileNow(UUID playerUUID, String playerName); + + CompletableFuture getGlobalProfile(UUID playerUUID); + + CompletableFuture getGlobalProfile(OfflinePlayer player); + + @NotNull CompletableFuture getGlobalProfile(UUID playerUUID, String playerName); + + @NotNull CompletableFuture> getExistingGlobalProfile(UUID playerUUID, String playerName); CompletableFuture modifyGlobalProfile(UUID playerUUID, Consumer consumer); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 7712d41b..51decaef 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -1,16 +1,13 @@ package org.mvplugins.multiverse.inventories.profile; import java.io.File; -import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; @@ -70,4 +67,8 @@ T waitForData(File file, Supplier callable) { throw new RuntimeException(e); } } + + ExecutorService getExecutor() { + return fileIOExecutorService; + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java index 81d8c3b9..3e2c51f1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java @@ -55,7 +55,7 @@ private ProfileKey(ContainerType containerType, String dataName, ProfileType pro this.profileType = profileType; this.playerUUID = playerUUID; this.playerName = playerName; - this.hashCode = Objects.hashCode(getContainerType(), getDataName(), getProfileType(), getPlayerName(), getPlayerUUID()); + this.hashCode = Objects.hashCode(playerUUID, containerType, dataName, profileType); } public ProfileKey forProfileType(@Nullable ProfileType profileType) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index baeb14e7..ea892d19 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -127,11 +127,9 @@ public boolean updatePlayer(Player player, ProfileData profile) { if (value == null) { player.getInventory().setContents(MinecraftTools.fillWithAir( new ItemStack[PlayerStats.INVENTORY_SIZE])); - player.updateInventory(); return false; } player.getInventory().setContents(value); - player.updateInventory(); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_INVENTORY_CONTENTS), @@ -153,11 +151,9 @@ public boolean updatePlayer(Player player, ProfileData profile) { if (value == null) { player.getInventory().setArmorContents(MinecraftTools.fillWithAir( new ItemStack[PlayerStats.ARMOR_SIZE])); - player.updateInventory(); return false; } player.getInventory().setArmorContents(value); - player.updateInventory(); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_ARMOR_CONTENTS), @@ -178,11 +174,9 @@ public boolean updatePlayer(Player player, ProfileData profile) { ItemStack value = profile.get(OFF_HAND); if (value == null) { player.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); - player.updateInventory(); return false; } player.getInventory().setItemInOffHand(value); - player.updateInventory(); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_OFF_HAND_ITEM), diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt index 0a9edad4..6fe147d8 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -53,8 +53,12 @@ abstract class TestWithMockBukkit { fun writeResourceToConfigFile(resourcePath: String, configPath: String) { val configResource = getResourceAsText(resourcePath) assertNotNull(configResource) - File(Path.of(multiverseInventories.dataFolder.absolutePath, configPath).absolutePathString()) - .writeText(configResource) + val configFile = File(Path.of(multiverseInventories.dataFolder.absolutePath, configPath).absolutePathString()) + if (!configFile.exists()) { + configFile.parentFile.mkdirs() + configFile.createNewFile() + } + configFile.writeText(configResource) } fun assertConfigEquals(expectedPath: String, actualPath: String) { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 1e147c9a..1aff5d3e 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -43,26 +43,14 @@ class FilePerformanceTest : TestWithMockBukkit() { val startTime = System.nanoTime() val futures = ArrayList>(10000) for (i in 0..9999) { - val globalProfile = profileDataSource.getGlobalProfile(UUID.randomUUID()) - globalProfile.setLoadOnLogin(true) - futures.add(profileDataSource.updateGlobalProfile(globalProfile)) + futures.add(profileDataSource.modifyGlobalProfile(UUID.randomUUID(), { globalProfile -> + globalProfile.setLoadOnLogin(true) + })) } for (future in futures) { future.get() } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - - val startTime2 = System.nanoTime() - val futures2 = ArrayList>(10000) - for (i in 0..9999) { - val globalProfile = profileDataSource.getGlobalProfile(UUID.randomUUID()) - globalProfile.setLoadOnLogin(false) - futures2.add(profileDataSource.updateGlobalProfile(globalProfile)) - } - for (future in futures2) { - future.get() - } - Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") } @Test diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt index a34ec36a..51f0270e 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt @@ -75,6 +75,6 @@ class PlayerNameChangeTest : TestWithMockBukkit() { assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "Benji_0224.json").toFile().exists()) // check player profile - assertEquals("benthecat10", profileDataSource.getGlobalProfile(player)?.lastKnownName) + assertEquals("benthecat10", profileDataSource.getGlobalProfileNow(player)?.lastKnownName) } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt index 599011c0..22ba2a19 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt @@ -1,7 +1,29 @@ package org.mvplugins.multiverse.inventories.profile +import com.dumptruckman.minecraft.util.Logging +import org.junit.jupiter.api.Test import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.container.ContainerType +import kotlin.test.BeforeTest class ProfileDataSourceTest : TestWithMockBukkit() { - //TODO -} \ No newline at end of file + + private lateinit var profileDataSource: ProfileDataSource + + @BeforeTest + fun setUp() { + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") } + } + + @Test + fun `getPlayerData called twice`() { + server.setPlayers(1) + writeResourceToConfigFile("/playerdata.json", "worlds/world/Player0.json") + val key = ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, server.getPlayer("Player0")) + profileDataSource.getPlayerData(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerData(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerData(key).thenAccept { profile -> Logging.info(profile.toString()) } + Logging.info("Getting player data...") + } +} diff --git a/src/test/resources/playerdata.json b/src/test/resources/playerdata.json new file mode 100644 index 00000000..f300f216 --- /dev/null +++ b/src/test/resources/playerdata.json @@ -0,0 +1 @@ +{"SURVIVAL":{"bedSpawnLocation":{"==":"org.bukkit.Location","world":"world","x":-40.0,"y":72.0,"z":0.0,"pitch":0.0,"yaw":0.0},"armorContents":{},"enderChestContents":{},"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"AIR","amount":0},"potions":[],"inventoryContents":{},"lastLocation":{"==":"org.bukkit.Location","world":"world","x":-39.5,"y":72.0,"z":0.5,"pitch":0.30000004,"yaw":-0.45000005},"stats":{"ex":"0.4980147","ma":"300","mhp":"20.0","fl":"13","el":"0","hp":"9.0","xp":"0.0","txp":"0","fd":"0.0","sa":"0.0","ft":"-20","ra":"300"}},"CREATIVE":{"armorContents":{},"stats":{"ex":"0.0","ma":"300","mhp":"20.0","fl":"20","el":"0","hp":"20.0","xp":"0.0","txp":"0","sa":"5.0","ft":"-20","fd":"0.0","ra":"300"},"enderChestContents":{},"potions":[],"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"AIR","amount":0},"inventoryContents":{"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_TORCH"},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"COMPARATOR"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"LEVER"},"4":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"STONE_BUTTON"},"5":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE"},"6":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"}}}} \ No newline at end of file From 06a51747320bc74786e0e84e175f4b84d486db35 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:11:27 +0800 Subject: [PATCH 103/180] Implement always write world profile config option --- build.gradle | 3 +- .../inventories/event/ShareHandlingEvent.java | 14 +---- .../handleshare/AffectedProfiles.java | 11 ---- .../handleshare/GameModeShareHandler.java | 41 ++++++++----- .../inventories/handleshare/ShareHandler.java | 22 +++---- .../handleshare/WorldChangeShareHandler.java | 58 ++++++++++--------- .../profile/FlatFileProfileDataSource.java | 1 + .../inventories/share/SharableGroup.java | 4 +- .../inventories/share/Sharables.java | 6 +- .../multiverse/inventories/share/Shares.java | 4 +- .../handleshare/GameModeChangeTest.kt | 56 +++++++++++++++++- .../profile/PlayerNameChangeTest.kt | 3 + .../gameplay/gamemode_change_groups.yml | 8 +++ 13 files changed, 143 insertions(+), 88 deletions(-) create mode 100644 src/test/resources/gameplay/gamemode_change_groups.yml diff --git a/build.gradle b/build.gradle index ea38b4ae..8f9422c0 100644 --- a/build.gradle +++ b/build.gradle @@ -67,7 +67,8 @@ shadowJar { relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' - relocate 'com.github.ben-manes', 'org.mvplugins.multiverse.inventories.utils.ben-manes' + relocate 'com.github.benmanes', 'org.mvplugins.multiverse.inventories.utils.benmanes' + relocate 'come.google.errorprone', 'org.mvplugins.multiverse.inventories.utils.errorprone' dependencies { exclude(dependency { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index 079e866e..414a8857 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -16,13 +16,11 @@ public abstract class ShareHandlingEvent extends Event implements Cancellable { private boolean cancelled; private final Player player; - private final PersistingProfile alwaysWriteProfile; private final List writeProfiles; private final List readProfiles; ShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { this.player = player; - this.alwaysWriteProfile = affectedProfiles.getAlwaysWriteProfile(); this.writeProfiles = affectedProfiles.getWriteProfiles(); this.readProfiles = affectedProfiles.getReadProfiles(); } @@ -44,17 +42,7 @@ public void setCancelled(boolean cancel) { } /** - * Returns the profile that will always be saved to. By default, this is a profile for the world the player was in. - * - * @return The profile that will always be saved to when this event occurs. - */ - public PersistingProfile getAlwaysWriteProfile() { - return alwaysWriteProfile; - } - - /** - * @return The profiles for the world/groups the player is coming from that data will be saved to in addition to - * the profile returned by {@link #getAlwaysWriteProfile()}. + * @return The profiles for the world/groups the player is coming from that data will be saved to. */ public List getWriteProfiles() { return this.writeProfiles; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java index e8c51e0b..dd89aa7b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -7,21 +7,14 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -import static org.mvplugins.multiverse.inventories.share.Sharables.enabled; - public final class AffectedProfiles { - private PersistingProfile alwaysWriteProfile; private final List writeProfiles = new LinkedList<>(); private final List readProfiles = new LinkedList<>(); AffectedProfiles() { } - void setAlwaysWriteProfile(CompletableFuture profile) { - alwaysWriteProfile = new PersistingProfile(enabled(), profile); - } - /** * @param profile The player profile that will need data saved to. * @param shares What from this group needs to be saved. @@ -38,10 +31,6 @@ void addReadProfile(CompletableFuture profile, Shares shares) { readProfiles.add(new PersistingProfile(shares, profile)); } - public PersistingProfile getAlwaysWriteProfile() { - return alwaysWriteProfile; - } - public List getWriteProfiles() { return writeProfiles; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 91deccfd..8e167c93 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -9,6 +9,7 @@ import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; import org.mvplugins.multiverse.inventories.util.Perm; import org.bukkit.GameMode; import org.bukkit.entity.Player; @@ -52,10 +53,16 @@ protected void prepareProfiles() { Logging.fine("=== " + player.getName() + " changing game mode from: " + fromType + " to: " + toType + " for world: " + world + " ==="); - affectedProfiles.setAlwaysWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player)); - if (isPlayerAffectedByChange()) { addProfiles(); + } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { + // Write to world profile to ensure data is saved incase bypass is removed + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(world).getPlayerData(player), + (worldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standard() + : Sharables.enabled() + ); } } @@ -73,22 +80,28 @@ private boolean isPlayerBypassingChange() { } private void addProfiles() { - if (hasWorldGroups()) { - worldGroups.forEach(this::addProfilesForWorldGroup); - } else { - Logging.finer("No groups for world."); - affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), - inventoriesConfig.getUseOptionalsForUngroupedWorlds() ? Sharables.enabled() : Sharables.standardOf()); + Shares handledShares = Sharables.noneOf(); + worldGroups.forEach(worldGroup -> addProfilesForWorldGroup(handledShares,worldGroup)); + Shares unhandledShares = Sharables.enabledOf().setSharing(handledShares, false); + if (!unhandledShares.isEmpty()) { + affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), unhandledShares); } - } - private boolean hasWorldGroups() { - return !worldGroups.isEmpty(); + if (inventoriesConfig.getAlwaysWriteWorldProfile()) { + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), + inventoriesConfig.getUseOptionalsForUngroupedWorlds() ? Sharables.enabled() : Sharables.standard()); + } else { + if (!unhandledShares.isEmpty()) { + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), unhandledShares); + } + } } - private void addProfilesForWorldGroup(WorldGroup worldGroup) { + private void addProfilesForWorldGroup(Shares handledShares, WorldGroup worldGroup) { ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(fromType, player), Sharables.enabled()); - affectedProfiles.addReadProfile(container.getPlayerData(toType, player), Sharables.enabled()); + affectedProfiles.addWriteProfile(container.getPlayerData(fromType, player), worldGroup.getApplicableShares()); + affectedProfiles.addReadProfile(container.getPlayerData(toType, player), worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getDisabledShares()); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 73b720f3..44a09e1a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -71,7 +71,6 @@ final void handleSharing() { updatePlayer(); Logging.finest("Updated player in %4.4f ms", (System.nanoTime() - s4) / 1000000.0); long s5 = System.nanoTime(); - updateAlwaysWriteProfile(snapshot); updateProfiles(snapshot); Logging.finest("Updated profiles in %4.4f ms", (System.nanoTime() - s5) / 1000000.0); double timeTaken = (System.nanoTime() - startTime) / 1000000.0; @@ -87,8 +86,7 @@ protected void logBypass() { } private void logAffectedProfilesCount() { - PersistingProfile alwaysWriteProfile = affectedProfiles.getAlwaysWriteProfile(); - int writeProfiles = affectedProfiles.getWriteProfiles().size() + (alwaysWriteProfile != null ? 1 : 0); + int writeProfiles = affectedProfiles.getWriteProfiles().size(); Logging.finer("Change affected by %d fromProfiles and %d toProfiles", writeProfiles, affectedProfiles.getReadProfiles().size()); @@ -106,17 +104,9 @@ private void updatePlayer() { } } - private void updateAlwaysWriteProfile(ProfileDataSnapshot snapshot) { - if (affectedProfiles.getAlwaysWriteProfile() == null) { - Logging.warning("No fromWorld to save to"); - return; - } - updatePersistingProfile(affectedProfiles.getAlwaysWriteProfile(), snapshot); - } - private void updateProfiles(ProfileDataSnapshot snapshot) { - if (affectedProfiles.getReadProfiles().isEmpty()) { - Logging.finest("No profiles to read from - nothing more to do."); + if (affectedProfiles.getWriteProfiles().isEmpty()) { + Logging.finest("No profiles to write - nothing more to do."); return; } for (PersistingProfile writeProfile : affectedProfiles.getWriteProfiles()) { @@ -125,7 +115,11 @@ private void updateProfiles(ProfileDataSnapshot snapshot) { } private void updatePersistingProfile(PersistingProfile persistingProfile, ProfileDataSnapshot snapshot) { - persistingProfile.getProfile().thenAccept(playerProfile -> { + if (persistingProfile.getShares().isEmpty()) { + Logging.finest("No shares to write - nothing more to do."); + return; + } + persistingProfile.getProfile().thenAcceptAsync(playerProfile -> { Logging.finer("Persisted: " + persistingProfile.getShares() + " to " + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")" diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index efb3a836..9c7399ca 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -5,7 +5,6 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.event.WorldChangeShareHandlingEvent; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; @@ -43,18 +42,20 @@ protected ShareHandlingEvent createEvent() { @Override protected void prepareProfiles() { Logging.fine("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); - setAlwaysWriteWorldProfile(); if (isPlayerAffectedByChange()) { addWriteProfiles(); addReadProfiles(); + } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { + // Write to world profile to ensure data is saved incase bypass is removed + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(fromWorld).getPlayerData(player), + (fromWorldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standard() + : Sharables.enabled() + ); } } - private void setAlwaysWriteWorldProfile() { - // We will always save everything to the world they come from. - affectedProfiles.setAlwaysWriteProfile(worldProfileContainerStore.getContainer(fromWorld).getPlayerData(player)); - } - private boolean isPlayerAffectedByChange() { if (isPlayerBypassingChange()) { logBypass(); @@ -68,11 +69,7 @@ private boolean isPlayerBypassingChange() { } private void addWriteProfiles() { - if (fromWorldGroups.isEmpty()) { - Logging.finer("No groups for fromWorld."); - return; - } - fromWorldGroups.forEach(wg -> new WorldGroupWrapper(wg).conditionallyAddWriteProfiles()); + new WriteProfileAggregator().conditionallyAddWriteProfiles(); } private void addReadProfiles() { @@ -81,11 +78,7 @@ private void addReadProfiles() { private class ReadProfilesAggregator { - private final Shares handledShares; - - private ReadProfilesAggregator() { - this.handledShares = Sharables.noneOf(); - } + private final Shares handledShares = Sharables.noneOf(); private void addReadProfiles() { addReadProfilesFromToWorldGroups(); @@ -141,11 +134,9 @@ private void addReadProfileForWorldGroup(WorldGroup worldGroup) { private void useToWorldForMissingShares() { // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. - Shares unhandledShares = Sharables.enabledOf(); + Shares unhandledShares = (toWorldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standardOf() : Sharables.enabledOf(); unhandledShares.removeAll(handledShares); - if (!inventoriesConfig.getUseOptionalsForUngroupedWorlds()) { - unhandledShares.removeAll(Sharables.optional()); - } if (unhandledShares.isEmpty()) { return; } @@ -157,20 +148,31 @@ private void useToWorldForMissingShares() { } } - private class WorldGroupWrapper { - private final WorldGroup worldGroup; + private class WriteProfileAggregator { - public WorldGroupWrapper(WorldGroup worldGroup) { - this.worldGroup = worldGroup; - } + private final Shares handledShares = Sharables.noneOf(); private void conditionallyAddWriteProfiles() { + fromWorldGroups.forEach(this::conditionallyAddWriteProfileForGroup); + Shares sharesToWrite = inventoriesConfig.getAlwaysWriteWorldProfile() + ? Sharables.enabled() + : Sharables.enabledOf().setSharing(handledShares, false); + if (!sharesToWrite.isEmpty()) { + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(fromWorld).getPlayerData(player), + sharesToWrite); + } + } + + private void conditionallyAddWriteProfileForGroup(WorldGroup worldGroup) { if (!worldGroup.containsWorld(toWorld)) { - addWriteProfiles(); + addWriteProfileForGroup(worldGroup); } + handledShares.addAll(worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getDisabledShares()); } - void addWriteProfiles() { + void addWriteProfileForGroup(WorldGroup worldGroup) { ProfileContainer container = worldGroup.getGroupProfileContainer(); affectedProfiles.addWriteProfile(container.getPlayerData(player), worldGroup.getApplicableShares()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 7afa3970..2af30cff 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -149,6 +149,7 @@ private FileConfiguration parseToConfiguration(File file) { jsonConfiguration.options().continueOnSerializationError(true); Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { Logging.severe("Could not load file %s : %s", file, e.getMessage()); + e.printStackTrace(); throw new RuntimeException(e); }); return jsonConfiguration; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java index 3f2e9b45..5d9a973d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java @@ -53,12 +53,12 @@ public Shares compare(Shares shares) { } @Override - public void setSharing(Sharable sharable, boolean sharing) { + public Shares setSharing(Sharable sharable, boolean sharing) { throw new IllegalStateException("May not alter SharableGroup!"); } @Override - public void setSharing(Shares sharables, boolean sharing) { + public Shares setSharing(Shares sharables, boolean sharing) { throw new IllegalStateException("May not alter SharableGroup!"); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index ea892d19..06132320 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1046,19 +1046,20 @@ public void mergeShares(Shares newShares) { * {@inheritDoc} */ @Override - public void setSharing(Sharable sharable, boolean sharing) { + public Shares setSharing(Sharable sharable, boolean sharing) { if (sharing) { this.add(sharable); } else { this.remove(sharable); } + return this; } /** * {@inheritDoc} */ @Override - public void setSharing(Shares sharables, boolean sharing) { + public Shares setSharing(Shares sharables, boolean sharing) { for (Sharable sharable : sharables) { if (sharing) { this.add(sharable); @@ -1066,6 +1067,7 @@ public void setSharing(Shares sharables, boolean sharing) { this.remove(sharable); } } + return this; } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java index 7f289126..631a8eae 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java @@ -40,13 +40,13 @@ public interface Shares extends Cloneable, Iterable, Collection Date: Mon, 3 Mar 2025 22:36:09 +0800 Subject: [PATCH 104/180] Fix relocate dep issue --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8f9422c0..1dc4c9f5 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ shadowJar { relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' relocate 'com.github.benmanes', 'org.mvplugins.multiverse.inventories.utils.benmanes' - relocate 'come.google.errorprone', 'org.mvplugins.multiverse.inventories.utils.errorprone' + relocate 'com.google.errorprone', 'org.mvplugins.multiverse.inventories.utils.errorprone' dependencies { exclude(dependency { From d6538546b3a502f28de7d4c8ec35bf29ad682004 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Mar 2025 22:36:31 +0800 Subject: [PATCH 105/180] Add ability to preload profile data on player join --- .../inventories/config/InventoriesConfig.java | 16 ++++++++ .../config/InventoriesConfigNodes.java | 16 ++++++++ .../handleshare/ShareHandleListener.java | 16 ++++++++ .../profile/FlatFileProfileDataSource.java | 38 +++++++++---------- .../inventories/profile/ProfileFileIO.java | 9 ++++- .../profile/FilePerformanceTest.kt | 14 +++++-- 6 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 8730bddf..cca41065 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.List; /** * Provides methods for interacting with the configuration of Multiverse-Inventories. @@ -200,6 +201,21 @@ public Try setAlwaysWriteWorldProfile(boolean alwaysWriteWorldProfile) { return this.configHandle.set(configNodes.alwaysWriteWorldProfile, alwaysWriteWorldProfile); } + public List getPreloadDataOnJoinWorlds() { + return this.configHandle.get(configNodes.preloadDataOnJoinWorlds); + } + + public Try setPreloadDataOnJoinWorlds(List preloadDataOnJoinWorlds) { + return this.configHandle.set(configNodes.preloadDataOnJoinWorlds, preloadDataOnJoinWorlds); + } + + public List getPreloadDataOnJoinGroups() { + return this.configHandle.get(configNodes.preloadDataOnJoinGroups); + } + + public Try setPreloadDataOnJoinGroups(List preloadDataOnJoinGroups) { + return this.configHandle.set(configNodes.preloadDataOnJoinGroups, preloadDataOnJoinGroups); + } public int getPlayerFileCacheSize() { return this.configHandle.get(configNodes.playerFileCacheSize); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 77c867d3..98a9291f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -3,11 +3,13 @@ import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; import org.mvplugins.multiverse.core.configuration.node.ConfigHeaderNode; import org.mvplugins.multiverse.core.configuration.node.ConfigNode; +import org.mvplugins.multiverse.core.configuration.node.ListConfigNode; import org.mvplugins.multiverse.core.configuration.node.Node; import org.mvplugins.multiverse.core.configuration.node.NodeGroup; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -160,6 +162,20 @@ public Object serialize(Shares sharables, Class aClass) { .name("always-write-world-profile") .build()); + private final ConfigHeaderNode preloadHeader = node(ConfigHeaderNode.builder("performance.preload-data-on-join") + .comment("") + .build()); + + final ListConfigNode preloadDataOnJoinWorlds = node(ListConfigNode.listBuilder("performance.preload-data-on-join.worlds", String.class) + .defaultValue(ArrayList::new) + .name("preload-data-on-join-worlds") + .build()); + + final ListConfigNode preloadDataOnJoinGroups = node(ListConfigNode.listBuilder("performance.preload-data-on-join.groups", String.class) + .defaultValue(ArrayList::new) + .name("preload-data-on-join-groups") + .build()); + private final ConfigHeaderNode cacheHeader = node(ConfigHeaderNode.builder("performance.cache") .comment("") .comment("NOTE: Cache options require a server restart to take effect.") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 63ad5630..1da6a7c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -3,9 +3,12 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; @@ -39,9 +42,11 @@ import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; /** * Events related to handling of player profile changes. @@ -79,6 +84,17 @@ void playerPreLogin(AsyncPlayerPreLoginEvent event) { Logging.finer("Loading global profile for Player{name:'%s', uuid:'%s'}.", event.getName(), event.getUniqueId()); verifyCorrectPlayerName(event.getUniqueId(), event.getName()); + + long startTime = System.nanoTime(); + List> profileFutures = new ArrayList<>(); + config.getPreloadDataOnJoinWorlds().forEach(worldName -> profileFutures.add(profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.WORLD, worldName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); + config.getPreloadDataOnJoinGroups().forEach(groupName -> profileFutures.add(profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.GROUP, groupName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); + Try.run(() -> CompletableFuture.allOf(profileFutures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS)) + .onSuccess(ignore -> Logging.finer("Preloaded data for Player{name:'%s', uuid:'%s'}. Time taken: %4.4f ms", + event.getName(), event.getUniqueId(), (System.nanoTime() - startTime) / 1000000.0)) + .onFailure(e -> Logging.warning("Preload data errored out: %s", e.getMessage())); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 2af30cff..aadca0cd 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -46,8 +47,6 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private static final String JSON = ".json"; - private final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); - private final ProfileFileIO profileFileIO; private final Cache playerFileCache; @@ -367,7 +366,7 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile } JSONObject jsonStats = null; try { - jsonStats = (JSONObject) JSON_PARSER.parse(stats); + jsonStats = (JSONObject) new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE).parse(stats); } catch (ParseException | ClassCastException e) { Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); @@ -504,8 +503,20 @@ public CompletableFuture getGlobalProfile(OfflinePlayer player) { public CompletableFuture getGlobalProfile(UUID playerUUID, String playerName) { try { File globalFile = getGlobalFile(playerUUID.toString()); - return globalProfileCache.get(playerUUID, (key, executor) -> - profileFileIO.queueCallable(globalFile, () -> getGlobalProfileFromDisk(playerUUID, playerName, globalFile))); + return globalProfileCache.get(playerUUID, (key, executor) -> { + Logging.finer("Global profile for player %s not in cached. Loading...", playerName); + // Migrate from player name to uuid profile file + File legacyFile = getGlobalFile(playerName); + if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } + + // Load from existing profile file + if (!globalFile.exists()) { + return CompletableFuture.completedFuture(GlobalProfile.createGlobalProfile(playerUUID, playerName)); + } + return profileFileIO.queueCallable(globalFile, () -> getGlobalProfileFromDisk(playerUUID, playerName, globalFile)); + }); } catch (Exception e) { Logging.severe("Unable to get global profile for player: " + playerName); throw new RuntimeException(e); @@ -522,26 +533,11 @@ public CompletableFuture> getExistingGlobalProfile(UUID pl return getGlobalProfile(playerUUID, playerName).thenApply(Option::of); } - private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { - Logging.finer("Global profile for player %s not in cached. Loading...", playerName); - // Migrate from player name to uuid profile file - File legacyFile = getGlobalFile(playerName); - if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - - // Load from existing profile file - if (!globalFile.exists()) { - return GlobalProfile.createGlobalProfile(playerUUID, playerName); - } - return loadGlobalProfile(globalFile, playerName, playerUUID); - } - private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { return legacyFile.renameTo(getGlobalFile(playerUUID.toString())); } - private GlobalProfile loadGlobalProfile(File globalFile, String playerName, UUID playerUUID) { + private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { FileConfiguration playerData = parseToConfiguration(globalFile); ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 51decaef..82e1cf45 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -1,5 +1,7 @@ package org.mvplugins.multiverse.inventories.profile; +import com.dumptruckman.minecraft.util.Logging; + import java.io.File; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -8,6 +10,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; @@ -25,8 +28,9 @@ CompletableFuture queueAction(File file, Runnable action) { CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); CompletableFuture future = new CompletableFuture<>(); fileIOExecutorService.submit(() -> { - if (toWaitLatch != null) { + if (toWaitLatch != null && toWaitLatch.getCount() > 0) { try { + Logging.finest("Waiting for lock on " + file); toWaitLatch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); @@ -45,8 +49,9 @@ CompletableFuture queueCallable(File file, Supplier callable) { CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); CompletableFuture future = new CompletableFuture<>(); fileIOExecutorService.submit(() -> { - if (toWaitLatch != null) { + if (toWaitLatch != null && toWaitLatch.getCount() > 0) { try { + Logging.finest("Waiting for lock on " + file); toWaitLatch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 1aff5d3e..69f896ec 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -15,6 +15,7 @@ import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.container.ContainerType import org.mvplugins.multiverse.inventories.share.Sharables import java.util.* +import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import java.util.function.Consumer import kotlin.test.BeforeTest @@ -84,17 +85,22 @@ class FilePerformanceTest : TestWithMockBukkit() { future.get() } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + profileDataSource.clearAllCache() val startTime2 = System.nanoTime() + val futures2 = ArrayList>(1000) for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - val playerProfile = profileDataSource.getPlayerDataNow( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) - assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) - assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) + futures2.add(profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) } } + for (future in futures2) { + val playerProfile = future.get() + assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) + assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) + } Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") val startTime3 = System.nanoTime() From f1589fa724ddf1ec0069a7caa3a06ed78e7bbb81 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 3 Mar 2025 22:39:57 +0800 Subject: [PATCH 106/180] Fix config test issue --- src/test/resources/config/fresh_config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml index 34efd460..ceec69b5 100644 --- a/src/test/resources/config/fresh_config.yml +++ b/src/test/resources/config/fresh_config.yml @@ -14,6 +14,9 @@ performance: save-playerdata-on-quit: false apply-playerdata-on-join: false always-write-world-profile: true + preload-data-on-join: + worlds: [] + groups: [] cache: player-file-cache-size: 2000 player-file-cache-expiry: 60 From 82ff8ca1392ffea6a0c7570ad6bc2c282031406d Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Mar 2025 20:07:16 +0800 Subject: [PATCH 107/180] Ignore PlayerSpawnChangeEvent if its called by sharable --- .../handleshare/SpawnChangeListener.java | 8 +++----- .../multiverse/inventories/share/Sharables.java | 14 +++++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java index 65f502d0..fcad9fae 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java @@ -1,9 +1,6 @@ package org.mvplugins.multiverse.inventories.handleshare; -import com.dumptruckman.minecraft.util.Logging; import org.bukkit.Location; -import org.bukkit.block.data.type.Bed; -import org.bukkit.block.data.type.RespawnAnchor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -13,8 +10,6 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.share.Sharables; -import javax.annotation.Nullable; - import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findAnchorFromRespawnLocation; import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; @@ -31,6 +26,9 @@ public SpawnChangeListener(MultiverseInventories inventories) { @EventHandler(priority = EventPriority.MONITOR) void onSpawnChange(PlayerSpawnChangeEvent event) { + if (Sharables.isIgnoringSpawnListener(event.getPlayer())) { + return; + } Player player = event.getPlayer(); if (event.getCause() == Cause.BED) { updatePlayerSpawn(player, findBedFromRespawnLocation(event.getNewSpawn())); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 06132320..183c9752 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -13,7 +13,6 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.util.DataStrings; import org.mvplugins.multiverse.inventories.util.PlayerStats; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.Location; import org.bukkit.Material; @@ -23,6 +22,7 @@ import org.bukkit.potion.PotionEffect; import org.mvplugins.multiverse.core.economy.MVEconomist; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; @@ -575,16 +576,27 @@ public boolean updatePlayer(Player player, ProfileData profile) { Location loc = profile.get(BED_SPAWN); if (loc == null) { Logging.finer("No bed location saved"); + ignoreSpawnListener.add(player.getUniqueId()); player.setBedSpawnLocation(player.getWorld().getSpawnLocation(), true); + ignoreSpawnListener.remove(player.getUniqueId()); return false; } + ignoreSpawnListener.add(player.getUniqueId()); player.setBedSpawnLocation(loc, true); + ignoreSpawnListener.remove(player.getUniqueId()); Logging.finer("updating bed: " + player.getBedSpawnLocation()); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_BED_SPAWN_LOCATION), new LocationSerializer()) .altName("bedspawn").altName("bed").altName("beds").altName("bedspawns").build(); + // todo: handle this somewhere better + private static List ignoreSpawnListener = new ArrayList<>(); + + public static boolean isIgnoringSpawnListener(Player player) { + return ignoreSpawnListener.contains(player.getUniqueId()); + } + /** * Sharing Last Location. */ From 8a785d267d96de58ee4ed8baeb24ee00040cc51b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Mar 2025 20:07:41 +0800 Subject: [PATCH 108/180] Remove debugging messages --- .../inventories/handleshare/ShareHandler.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 44a09e1a..1504bcb0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -1,8 +1,6 @@ package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; -import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; @@ -16,9 +14,6 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import java.util.List; -import java.util.concurrent.TimeUnit; - /** * Abstract class for handling sharing of data between worlds and game modes. */ @@ -52,27 +47,17 @@ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShar */ final void handleSharing() { long startTime = System.nanoTime(); - long s1 = System.nanoTime(); this.prepareProfiles(); - Logging.finest("Prepared profiles in %4.4f ms", (System.nanoTime() - s1) / 1000000.0); - long s2 = System.nanoTime(); ShareHandlingEvent event = this.createEvent(); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { Logging.fine("Share handling has been cancelled by another plugin!"); return; } - Logging.finest("Share handling event took %4.4f ms", (System.nanoTime() - s2) / 1000000.0); logAffectedProfilesCount(); - long s3 = System.nanoTime(); ProfileDataSnapshot snapshot = getSnapshot(); - Logging.finest("Got snapshot in %4.4f ms", (System.nanoTime() - s3) / 1000000.0); - long s4 = System.nanoTime(); updatePlayer(); - Logging.finest("Updated player in %4.4f ms", (System.nanoTime() - s4) / 1000000.0); - long s5 = System.nanoTime(); updateProfiles(snapshot); - Logging.finest("Updated profiles in %4.4f ms", (System.nanoTime() - s5) / 1000000.0); double timeTaken = (System.nanoTime() - startTime) / 1000000.0; logHandlingComplete(timeTaken, event); } From 5c85eb0d97df99e68551abc7a820ed50fbd1a1e2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Mar 2025 20:43:31 +0800 Subject: [PATCH 109/180] Improve teleport performance test with actual playerdata --- .../inventories/handleshare/WorldChangeTest.kt | 10 ---------- .../inventories/profile/FilePerformanceTest.kt | 18 ++++++++++++++++++ src/test/resources/playerdata.json | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt index c50f9e12..6d619da0 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt @@ -49,14 +49,4 @@ class WorldChangeTest : TestWithMockBukkit() { Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") } - - @Test - fun `World change performance with 50 players`() { - server.setPlayers(50) - val startTime = System.nanoTime() - for (player in server.playerList.onlinePlayers) { - server.getWorld("world3")?.let { player.teleport(it.spawnLocation) } - } - Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 69f896ec..c5a52b6b 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -37,6 +37,7 @@ class FilePerformanceTest : TestWithMockBukkit() { CoreLogging.setDebugLevel(0); Logging.setDebugLevel(0) assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) } @Test @@ -140,4 +141,21 @@ class FilePerformanceTest : TestWithMockBukkit() { server.setPlayers(50) Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") } + + @Test + fun `Teleport 50 players consecutively`() { + for (i in 0..49) { + writeResourceToConfigFile("/playerdata.json", "worlds/world2/Player$i.json") + } + server.setPlayers(50) + val startTime = System.nanoTime() + for (player in server.playerList.onlinePlayers) { + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + val cacheStats = profileDataSource.getCacheStats() + for (cacheStat in cacheStats) { + Logging.info(cacheStat.key + ": " + cacheStat.value.averageLoadPenalty() / 1000000 + "ms") + } + } } diff --git a/src/test/resources/playerdata.json b/src/test/resources/playerdata.json index f300f216..3de7e0a9 100644 --- a/src/test/resources/playerdata.json +++ b/src/test/resources/playerdata.json @@ -1 +1 @@ -{"SURVIVAL":{"bedSpawnLocation":{"==":"org.bukkit.Location","world":"world","x":-40.0,"y":72.0,"z":0.0,"pitch":0.0,"yaw":0.0},"armorContents":{},"enderChestContents":{},"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"AIR","amount":0},"potions":[],"inventoryContents":{},"lastLocation":{"==":"org.bukkit.Location","world":"world","x":-39.5,"y":72.0,"z":0.5,"pitch":0.30000004,"yaw":-0.45000005},"stats":{"ex":"0.4980147","ma":"300","mhp":"20.0","fl":"13","el":"0","hp":"9.0","xp":"0.0","txp":"0","fd":"0.0","sa":"0.0","ft":"-20","ra":"300"}},"CREATIVE":{"armorContents":{},"stats":{"ex":"0.0","ma":"300","mhp":"20.0","fl":"20","el":"0","hp":"20.0","xp":"0.0","txp":"0","sa":"5.0","ft":"-20","fd":"0.0","ra":"300"},"enderChestContents":{},"potions":[],"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"AIR","amount":0},"inventoryContents":{"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_TORCH"},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"COMPARATOR"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"LEVER"},"4":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"STONE_BUTTON"},"5":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE"},"6":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"}}}} \ No newline at end of file +{"SURVIVAL":{"inventoryContents":{"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_TORCH"},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"COMPARATOR"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"LEVER"},"4":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"STONE_BUTTON"},"5":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE"},"8":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"}},"lastLocation":{"==":"org.bukkit.Location","world":"world","x":-39.5,"y":72.0,"z":0.5,"pitch":0.0,"yaw":0.0},"armorContents":{},"potions":[],"bedSpawnLocation":{"==":"org.bukkit.Location","world":"world","x":-40.0,"y":72.0,"z":0.0,"pitch":0.0,"yaw":0.0},"enderChestContents":{},"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"AIR","amount":0},"stats":{"ex":"0.0","ma":"300","mhp":"20.0","fl":"20","el":"0","hp":"20.0","xp":"0.0","txp":"0","sa":"5.0","ft":"-20","fd":"0.0","ra":"300"}},"CREATIVE":{"inventoryContents":{"22":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"23":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"24":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"25":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"26":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"27":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"28":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"29":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"30":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"31":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"10":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"32":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"11":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"33":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"12":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND","amount":40},"34":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"13":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"35":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"14":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"36":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_BOOTS"},"15":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"37":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_LEGGINGS"},"16":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"38":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_CHESTPLATE"},"17":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"39":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_HELMET"},"18":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"19":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"END_STONE","amount":10},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"BONE","amount":2},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"ARROW"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"CRAFTING_TABLE","amount":63},"4":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"5":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"6":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"7":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"8":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"9":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"40":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"20":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"21":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"}},"lastLocation":{"==":"org.bukkit.Location","world":"world","x":-39.227095053544765,"y":72.0,"z":3.5331800520689565,"pitch":26.250015,"yaw":128.1001},"armorContents":{"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_BOOTS"},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_LEGGINGS"},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_CHESTPLATE"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_HELMET"}},"potions":[],"bedSpawnLocation":{"==":"org.bukkit.Location","world":"world","x":-40.0,"y":72.0,"z":0.0,"pitch":0.0,"yaw":0.0},"enderChestContents":{},"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"stats":{"ex":"1.0499994","ma":"300","mhp":"20.0","fl":"17","el":"0","hp":"3.3333358764648438","xp":"0.0","txp":"0","sa":"0.0","ft":"-20","fd":"0.0","ra":"300"}}} \ No newline at end of file From cd89bfa265702024db6df5107f5d9ebbf39f6ab6 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Mar 2025 21:27:19 +0800 Subject: [PATCH 110/180] Improve config comments --- .../inventories/config/InventoriesConfigNodes.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 98a9291f..3793958c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -86,7 +86,7 @@ private N node(N node) { final ConfigNode activeOptionalShares = node(ConfigNode.builder("share-handling.active-optional-shares", Shares.class) .comment("") .comment("You must specify optional shares you wish to use here or they will be ignored.") - .comment("The only built-in optional shares are \"economy\" and \"last_location\".") + .comment("Built-in optional shares are: \"economy\" and \"last_location\".") .defaultValue(Sharables.noneOf()) .hidden() .serializer(new NodeSerializer<>() { @@ -112,7 +112,6 @@ public Object serialize(Shares sharables, Class aClass) { .build()); final ConfigNode useImprovedRespawnLocationDetection = node(ConfigNode.builder("sharables.use-improved-respawn-location-detection", Boolean.class) - .comment("") .comment("When enabled, we will use 1.21's PlayerSpawnChangeEvent to better detect bed and anchor respawn locations.") .comment("This options is not applicable for older minecraft server versions.") .defaultValue(true) @@ -120,6 +119,7 @@ public Object serialize(Shares sharables, Class aClass) { .build()); final ConfigNode resetLastLocationOnDeath = node(ConfigNode.builder("sharables.reset-last-location-on-death", Boolean.class) + .comment("") .comment("When set to true, the last location of the player will be reset when they die.") .comment("This is useful if they respawn in a different world and you do not want them to return to their death location.") .defaultValue(false) @@ -158,12 +158,17 @@ public Object serialize(Shares sharables, Class aClass) { final ConfigNode alwaysWriteWorldProfile = node(ConfigNode.builder("performance.always-write-world-profile", Boolean.class) .comment("") + .comment("By default, even when the group shares all or going to a world within the same group, the world profile will still be written to disk.") + .comment("This will ensure that the world profile is always up-to-date, so when removing the world from the group, it will not be missing data.") + .comment("However, if you are certain that your world will always be in a group, you can set this to false to slightly improve performance.") .defaultValue(true) .name("always-write-world-profile") .build()); private final ConfigHeaderNode preloadHeader = node(ConfigHeaderNode.builder("performance.preload-data-on-join") .comment("") + .comment("Pre-loads player data into caches when joining the server.") + .comment("This will reduce the load time on first teleport to the world/group, with the cost of increased memory usage and join time.") .build()); final ListConfigNode preloadDataOnJoinWorlds = node(ListConfigNode.listBuilder("performance.preload-data-on-join.worlds", String.class) From c0879aa65ecbb7c0b7a36c7da14bb98e3e94120b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:21:45 +0800 Subject: [PATCH 111/180] Implement apply-last-location-for-all-teleports config option --- .../inventories/MultiverseInventories.java | 13 ++- .../config/InventoriesConfigNodes.java | 2 +- .../destination/LastLocationDestination.java | 61 ++++++++++++++ .../LastLocationDestinationInstance.java | 81 +++++++++++++++++++ .../handleshare/WorldChangeShareHandler.java | 14 +++- .../multiverse/inventories/DestinationTest.kt | 31 +++++++ 6 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java create mode 100644 src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 743c9cb4..a7a4fcd8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -6,6 +6,7 @@ import org.mvplugins.multiverse.core.MultiverseCoreApi; import org.mvplugins.multiverse.core.MultiversePlugin; import org.mvplugins.multiverse.core.config.MVCoreConfig; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; @@ -14,6 +15,7 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; import org.mvplugins.multiverse.inventories.dataimport.DataImporter; +import org.mvplugins.multiverse.inventories.destination.LastLocationDestination; import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; import org.mvplugins.multiverse.inventories.handleshare.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; @@ -40,13 +42,15 @@ public class MultiverseInventories extends MultiversePlugin { private static final int PROTOCOL = 50; - @Inject - private Provider inventoriesConfig; @Inject private Provider commandManager; @Inject private Provider mvCoreConfig; @Inject + private Provider destinationsProvider; + @Inject + private Provider inventoriesConfig; + @Inject private Provider shareHandleListener; @Inject private Provider respawnListener; @@ -113,6 +117,7 @@ public final void onEnable() { // Register Commands this.registerCommands(); + this.registerDestinations(); // Hook plugins that can be imported from this.hookImportables(); this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); @@ -175,6 +180,10 @@ private void registerCommands() { }); } + private void registerDestinations() { + destinationsProvider.get().registerDestination(serviceLocator.getService(LastLocationDestination.class)); + } + private void hookImportables() { serviceLocator.getAllServices(DataImporter.class).forEach(dataImporter -> { dataImportManager.get().register(dataImporter); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 3793958c..9aa56b33 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -130,7 +130,7 @@ public Object serialize(Shares sharables, Class aClass) { .comment("") .comment("When enabled, the last location of the player will be applied for any teleportation.") .comment("This is useful as you want to use the last location for any teleportation, such as the warp system.") - .comment("When disabled, you can only use `/mvinv tplastlocation [player] ` to teleport to the player's last location.") + .comment("When disabled, you can only use `/mv tp ll:worldname` to teleport to the player's last location.") .defaultValue(true) .name("apply-last-location-for-all-teleports") .build()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java new file mode 100644 index 00000000..dcc1305e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java @@ -0,0 +1,61 @@ +package org.mvplugins.multiverse.inventories.destination; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.destination.Destination; +import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.Collection; + +@Service +public final class LastLocationDestination implements Destination { + + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + + @Inject + LastLocationDestination( + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider) { + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileContainerStoreProvider = profileContainerStoreProvider; + } + + @Override + public @NotNull String getIdentifier() { + return "ll"; + } + + @Override + public LastLocationDestinationInstance getDestinationInstance(@Nullable String destinationParams) { + try { + Logging.warning("LastLocationDestination: destinationParams: " + destinationParams); + String worldName = destinationParams; + if (!worldManager.isLoadedWorld(worldName)) { + return null; + } + return new LastLocationDestinationInstance(this, worldGroupManager, profileContainerStoreProvider, worldName); + } catch (Exception e) { + Logging.severe(e.getMessage()); + e.printStackTrace(); + return null; + } + } + + @Override + public @NotNull Collection suggestDestinations(@NotNull CommandSender commandSender, @Nullable String destinationParams) { + return worldManager.getLoadedWorlds().stream() + .map(world -> new DestinationSuggestionPacket(world.getName(), world.getName())) + .toList(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java new file mode 100644 index 00000000..9d254389 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java @@ -0,0 +1,81 @@ +package org.mvplugins.multiverse.inventories.destination; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; + +public final class LastLocationDestinationInstance extends DestinationInstance { + + private final WorldGroupManager worldGroupManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final String worldName; + + LastLocationDestinationInstance( + @NotNull LastLocationDestination destination, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull String worldName) { + super(destination); + this.worldGroupManager = worldGroupManager; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.worldName = worldName; + } + + @Override + public @NotNull Option getLocation(@NotNull Entity teleportee) { + Logging.warning("LastLocationDestination: teleportee: " + teleportee); + if (!(teleportee instanceof Player player)) { + return Option.none(); + } + + var playerWorld = player.getWorld().getName(); + if (playerWorld.equals(worldName)) { + return Option.none(); + } + + for (var group : worldGroupManager.getGroupsForWorld(worldName)) { + Logging.warning("LastLocationDestination: group: " + group); + if (!group.containsWorld(playerWorld) && group.getApplicableShares().contains(Sharables.LAST_LOCATION)) { + return Option.of(profileContainerStoreProvider.getStore(ContainerType.GROUP) + .getContainer(group.getName()) + .getPlayerDataNow(player) + .get(Sharables.LAST_LOCATION)); + } + } + + // Means last location isn't shared by any group, and will be read directly for world profile + return Option.of(profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(worldName) + .getPlayerDataNow(player) + .get(Sharables.LAST_LOCATION)); + } + + @Override + public @NotNull Option getVelocity(@NotNull Entity teleportee) { + return Option.none(); + } + + @Override + public boolean checkTeleportSafety() { + return false; + } + + @Override + public @NotNull Option getFinerPermissionSuffix() { + return Option.of(worldName); + } + + @Override + protected @NotNull String serialise() { + return worldName; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index 9c7399ca..96616274 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -126,10 +126,12 @@ private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { } private void addReadProfileForWorldGroup(WorldGroup worldGroup) { - affectedProfiles.addReadProfile( - worldGroup.getGroupProfileContainer().getPlayerData(player), - worldGroup.getApplicableShares() - ); + Shares applicableShares = Sharables.fromShares(worldGroup.getApplicableShares()); + if (!inventoriesConfig.getApplyLastLocationForAllTeleports()) { + Logging.finer("Removing lastLocation from applicableShares as it is not applied for all teleports"); + applicableShares.remove(Sharables.LAST_LOCATION); + } + affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getPlayerData(player), applicableShares); } private void useToWorldForMissingShares() { @@ -137,6 +139,10 @@ private void useToWorldForMissingShares() { Shares unhandledShares = (toWorldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) ? Sharables.standardOf() : Sharables.enabledOf(); unhandledShares.removeAll(handledShares); + if (!inventoriesConfig.getApplyLastLocationForAllTeleports()) { + Logging.finer("Removing lastLocation from unhandledShares as it is not applied for all teleports"); + unhandledShares.remove(Sharables.LAST_LOCATION); + } if (unhandledShares.isEmpty()) { return; } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt new file mode 100644 index 00000000..119edd91 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt @@ -0,0 +1,31 @@ +package org.mvplugins.multiverse.inventories + +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.core.destination.DestinationsProvider +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import kotlin.test.BeforeTest +import kotlin.test.Test + +class DestinationTest : TestWithMockBukkit() { + + lateinit var destinationsProvider: DestinationsProvider + lateinit var player : PlayerMock + + @BeforeTest + fun setUp() { + destinationsProvider = serviceLocator.getService(DestinationsProvider::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("DestinationsProvider is not available as a service") } + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + worldManager.createWorld(CreateWorldOptions.worldName("world")) + worldManager.createWorld(CreateWorldOptions.worldName("world2")) + player = server.addPlayer("Benji_0224") + } + + @Test + fun `Last location destination`() { + destinationsProvider.parseDestination("ll:world").peek { it.getLocation(player) } + destinationsProvider.parseDestination("ll:world2").peek { it.getLocation(player) } + } +} \ No newline at end of file From 2f2fd0d32ef0ddfaa91254215ae743473c041ca2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:58:24 +0800 Subject: [PATCH 112/180] Use runTaskLater instead of scheduleSyncDelayTask --- .../inventories/InventoriesDupingPatch.java | 4 ++-- .../inventories/MultiverseInventories.java | 21 ++++++++----------- .../handleshare/ShareHandleListener.java | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java index 86034fa5..3460e0e3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java @@ -91,8 +91,8 @@ public void onCreativeSlotChange(InventoryCreativeEvent event) { */ public void enable(Plugin plugin) { Bukkit.getPluginManager().registerEvents(this.listener, plugin); - this.updateTimeoutsTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask( - plugin, new UpdateTimeoutsTask(), 1, 1); + this.updateTimeoutsTaskId = Bukkit.getScheduler().runTaskTimer( + plugin, new UpdateTimeoutsTask(), 1, 1).getTaskId(); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index a7a4fcd8..b1fd08c7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -236,20 +236,17 @@ public void reloadConfig() { return; } - this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() { - @Override - public void run() { - // Create initial World Group for first run IF NO GROUPS EXIST - if (inventoriesConfig.get().getFirstRun()) { - Logging.info("First run!"); - if (worldGroupManager.get().getGroups().isEmpty()) { - worldGroupManager.get().createDefaultGroup(); - } - - inventoriesConfig.get().setFirstRun(false); + this.getServer().getScheduler().runTaskLater(this, () -> { + // Create initial World Group for first run IF NO GROUPS EXIST + if (inventoriesConfig.get().getFirstRun()) { + Logging.info("First run!"); + if (worldGroupManager.get().getGroups().isEmpty()) { + worldGroupManager.get().createDefaultGroup(); } - worldGroupManager.get().checkForConflicts(null); + + inventoriesConfig.get().setFirstRun(false); } + worldGroupManager.get().checkForConflicts(null); }, 1L); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 1da6a7c0..ab34710a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -281,7 +281,7 @@ void playerRespawn(PlayerRespawnEvent event) { return; } final Player player = event.getPlayer(); - Bukkit.getScheduler().scheduleSyncDelayedTask( + Bukkit.getScheduler().runTaskLater( inventories, () -> verifyCorrectWorld( player, From d0d2ab5c2df1bbb23a6efaa45f744b9858ec1429 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:59:53 +0800 Subject: [PATCH 113/180] Remove old test --- .../inventories/FlatFileDataHelper.java | 23 - .../multiverse/inventories/TestCommands.java | 165 ------ .../TestCommentedYamlConfiguration.java | 103 ---- .../inventories/TestPerformance.java | 319 ----------- .../inventories/TestPlayerNameChange.java | 204 ------- .../inventories/TestResetWorld.java | 142 ----- .../inventories/TestWSharableAPI.java | 207 ------- .../inventories/TestWorldChanged.java | 539 ------------------ .../inventories/util/MVTestLogFormatter.java | 42 -- .../inventories/util/MockItemMeta.java | 94 --- .../inventories/util/MockPlayerFactory.java | 312 ---------- .../inventories/util/MockPlayerInventory.java | 328 ----------- .../inventories/util/MockWorldFactory.java | 163 ------ .../inventories/util/TestInstanceCreator.java | 413 -------------- .../multiverse/inventories/util/Util.java | 62 -- .../inventories/util/WorldCreatorMatcher.java | 55 -- .../org.mockito.plugins.MockMaker | 1 - 17 files changed, 3172 deletions(-) delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestCommands.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestPerformance.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/Util.java delete mode 100644 src/old-test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java delete mode 100644 src/old-test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java b/src/old-test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java deleted file mode 100644 index 07f8e31c..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/FlatFileDataHelper.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; - -import java.io.File; -import java.io.IOException; - -public class FlatFileDataHelper { - - private final FlatFileProfileDataSource data; - - public FlatFileDataHelper(ProfileDataSource data) { - if (!(data instanceof FlatFileProfileDataSource)) { - throw new ClassCastException("Must be instance of FlatFilePlayerData"); - } - this.data = (FlatFileProfileDataSource) data; - } - - public File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - return data.getPlayerFile(type, dataName, playerName); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommands.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommands.java deleted file mode 100644 index 1218cb3f..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommands.java +++ /dev/null @@ -1,165 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestCommands { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - @Test - public void testDebugReload() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Make a fake server folder to fool MV into thinking a world folder exists. - File serverDirectory = new File(creator.getPlugin().getServerFolder(), "world"); - serverDirectory.mkdirs(); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] debugArgs = new String[]{"debug", "3"}; - plugin.onCommand(mockCommandSender, mockCoreCommand, "", debugArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] reloadArgs = new String[] { "reload" }; - plugin.onCommand(mockCommandSender, mockCommand, "", reloadArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - } - - @Test - public void testInfoCommand() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - // Send the debug command. - String[] debugArgs = new String[]{ "info", "default"}; - plugin.onCommand(mockCommandSender, mockCommand, "", debugArgs); - } - - @Test - public void testToggleCommand() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - assertFalse(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - // Send the debug command. - String[] cmdArgs = new String[]{ "toggle", "economy" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertTrue(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - cmdArgs = new String[]{ "reload" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertTrue(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - cmdArgs = new String[]{ "toggle", "economy" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertFalse(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - } - - @Test - public void testGroupNoWorlds() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - String[] cmdArgs = new String[]{ "rmworld", "world", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - cmdArgs = new String[]{ "rmworld", "world_nether", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - cmdArgs = new String[]{ "rmworld", "world_the_end", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - cmdArgs = new String[]{ "reload" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - cmdArgs = new String[]{ "info", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java deleted file mode 100644 index 600c8ab7..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestCommentedYamlConfiguration.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.junit.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Arrays; - -import static org.junit.Assert.assertEquals; - -public class TestCommentedYamlConfiguration { - private static final char LINE_SEPARATOR = '\n'; - private static final File TEST_CONFIG = new File("bin/test/testconfig.yml"); - - private static final String TEST_CONTENTS_1 = "# A Test Yaml File" + LINE_SEPARATOR + LINE_SEPARATOR + - "test: 123" + LINE_SEPARATOR + - "a_map:" + LINE_SEPARATOR + - " something: yep" + LINE_SEPARATOR + - " something_else: 42" + LINE_SEPARATOR + - " one_more_something_else: 24" + LINE_SEPARATOR + - " a_list:" + LINE_SEPARATOR + - " - 1" + LINE_SEPARATOR + - " - 2" + LINE_SEPARATOR + - " a_child_map:" + LINE_SEPARATOR + - " another_map:" + LINE_SEPARATOR + - " test: 123" + LINE_SEPARATOR + - " two_steps_back:" + LINE_SEPARATOR + - " test: true" + LINE_SEPARATOR + - "back_to_root: true" + LINE_SEPARATOR; - - private static final String COMMENTED_TEST_CONTENTS_1 = "# A Test Yaml File" + LINE_SEPARATOR + - LINE_SEPARATOR + - "# Yay" + LINE_SEPARATOR + - "test: 123" + LINE_SEPARATOR + - LINE_SEPARATOR + - "# They seem to be" + LINE_SEPARATOR + - "# Working" + LINE_SEPARATOR + - "a_map:" + LINE_SEPARATOR + - " # Yeah, they're working" + LINE_SEPARATOR + - " something: yep" + LINE_SEPARATOR + - " something_else: 42" + LINE_SEPARATOR + - " one_more_something_else: 24" + LINE_SEPARATOR + - LINE_SEPARATOR + - " # Aww yeah, comments on a list" + LINE_SEPARATOR + - " a_list:" + LINE_SEPARATOR + - " - 1" + LINE_SEPARATOR + - " - 2" + LINE_SEPARATOR + - " a_child_map:" + LINE_SEPARATOR + - " # Comments on a child child map" + LINE_SEPARATOR + - " another_map:" + LINE_SEPARATOR + - " test: 123" + LINE_SEPARATOR + - LINE_SEPARATOR + - " # Two steps back comments" + LINE_SEPARATOR + - " two_steps_back:" + LINE_SEPARATOR + - " test: true" + LINE_SEPARATOR + - LINE_SEPARATOR + - "# Back to root comments" + LINE_SEPARATOR + - "back_to_root: true" + LINE_SEPARATOR; - - private CommentedYamlConfiguration createConfig(boolean doComments) { - TEST_CONFIG.getParentFile().mkdirs(); - try (PrintWriter out = new PrintWriter(TEST_CONFIG)) { - out.println(TEST_CONTENTS_1); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - - CommentedYamlConfiguration testConfig = new CommentedYamlConfiguration(TEST_CONFIG, doComments); - - testConfig.getConfig().options().header("A Test Yaml File\n"); - - testConfig.addComment("test", Arrays.asList("# Yay")); - testConfig.addComment("a_map", Arrays.asList("# They seem to be", "# Working")); - testConfig.addComment("a_map.something", Arrays.asList("# Yeah, they're working")); - testConfig.addComment("a_map.a_list", Arrays.asList("# Aww yeah, comments on a list")); - testConfig.addComment("a_map.a_child_map.another_map", Arrays.asList("# Comments on a child child map")); - testConfig.addComment("a_map.two_steps_back", Arrays.asList("# Two steps back comments")); - testConfig.addComment("back_to_root", Arrays.asList("# Back to root comments")); - - return testConfig; - } - - @Test - public void testNoComments() throws Exception { - CommentedYamlConfiguration testConfig = createConfig(false); - testConfig.save(); - - String uncommentedConfigFile = new String(Files.readAllBytes(TEST_CONFIG.getCanonicalFile().toPath()), StandardCharsets.UTF_8); - assertEquals(TEST_CONTENTS_1, uncommentedConfigFile); - } - - @Test - public void testWithComments() throws Exception { - CommentedYamlConfiguration testConfig = createConfig(true); - testConfig.save(); - - String commentedConfigFile = new String(Files.readAllBytes(TEST_CONFIG.getCanonicalFile().toPath()), StandardCharsets.UTF_8); - assertEquals(COMMENTED_TEST_CONTENTS_1, commentedConfigFile); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestPerformance.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestPerformance.java deleted file mode 100644 index 3612b514..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestPerformance.java +++ /dev/null @@ -1,319 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestPerformance { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - private Player getPreparedPlayer() { - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - Map fillerItems = new HashMap(); - for (int i = 0; i < PlayerStats.INVENTORY_SIZE; i++) { - ItemStack item = new ItemStack(Material.STONE_BRICKS, 64); - Enchantment mockEnchantment = mock(Enchantment.class); - when(mockEnchantment.getName()).thenReturn("Protection"); - item.addUnsafeEnchantment(mockEnchantment, 3); - mockEnchantment = mock(Enchantment.class); - when(mockEnchantment.getName()).thenReturn("Respiration"); - item.addUnsafeEnchantment(mockEnchantment, 3); - mockEnchantment = mock(Enchantment.class); - when(mockEnchantment.getName()).thenReturn("Smite"); - item.addUnsafeEnchantment(mockEnchantment, 3); - fillerItems.put(i, item); - } - addToInventory(player.getInventory(), fillerItems); - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 50, 3)); - player.addPotionEffect(new PotionEffect(PotionEffectType.FAST_DIGGING, 50, 3)); - player.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 50, 3)); - return player; - } - - @Test - public void testOverallWorldChangePerformance() { - int numTests = 100; - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - inventories.getGroupManager().updateGroup(newGroup); - - newGroup = inventories.getGroupManager().newEmptyGroup("test2"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - newGroup = inventories.getGroupManager().newEmptyGroup("test3"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - newGroup = inventories.getGroupManager().newEmptyGroup("test4"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - newGroup = inventories.getGroupManager().newEmptyGroup("test5"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - String[] cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = getPreparedPlayer(); - - long startTime = 0; - long endTime = 0; - double[] timeTaken = new double[numTests]; - double total = 0; - - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - } - double cachedAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - inventories.reloadConfig(); - } - double uncachedAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - new WorldChangeShareHandler(inventories, player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - double groupCollectionAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - ShareHandlingEvent event = new WorldChangeShareHandler(inventories, player, "world", "world2").createEvent(); - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - ShareHandlingUpdater.updateProfile(inventories, player, event.getWriteProfiles().get(0)); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - double profileUpdateAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - event = new WorldChangeShareHandler(inventories, player, "world", "world2").createEvent(); - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - ShareHandlingUpdater.updatePlayer(inventories, player, event.getReadProfiles().get(0)); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - double playerUpdateAverage = (total / numTests); - - System.out.println("Average Time for group collection: " + groupCollectionAverage + "ms"); - System.out.println("Average Time for cached world change: " + cachedAverage + "ms"); - System.out.println("Average Time for cached profile update: " + profileUpdateAverage + "ms"); - System.out.println("Average Time for cached player update: " + playerUpdateAverage + "ms"); - System.out.println("Average Time for uncached world change: " + uncachedAverage + "ms"); - } - - @Test - public void testIndividualSharesPerformance() { - int numTests = 100; - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - inventories.getGroupManager().updateGroup(newGroup); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - String[] cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = getPreparedPlayer(); - changeWorld(player, "world", "world2"); - changeWorld(player, "world2", "world"); - - Map averageUpdatePlayer = new HashMap(); - Map averageUpdateProfile = new HashMap(); - PlayerProfile profile = inventories.getGroupManager().getDefaultGroup().getGroupProfileContainer().getPlayerData(player); - for (Sharable share : Sharables.all()) { - if (share.isOptional()) { - continue; - } - long startTime = 0; - long endTime = 0; - double[] timeTaken = new double[numTests]; - double total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - share.getHandler().updateProfile(profile, player); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - averageUpdateProfile.put(share, (total / numTests)); - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - share.getHandler().updatePlayer(player, profile); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - averageUpdatePlayer.put(share, (total / numTests)); - } - - for (Sharable share : Sharables.all()) { - System.out.println("Average Time for " + share.getNames()[0] + ".updatePlayer(): " + averageUpdatePlayer.get(share)); - System.out.println("Average Time for " + share.getNames()[0] + ".updateProfile(): " + averageUpdateProfile.get(share)); - } - } - - @Test - public void testLargeGroupCollectionPerformance() { - int numTests = 100; - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - inventories.getGroupManager().addGroup(newGroup, true); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - String[] cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = getPreparedPlayer(); - - long startTime = 0; - long endTime = 0; - double[] timeTaken = new double[numTests]; - double total = 0; - - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - } - double average = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - } - System.out.println("Average Time for cached world change: " + average + "ms"); - System.out.println("Average Time for uncached world change: " + (total / numTests) + "ms"); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java deleted file mode 100644 index 1c16ff52..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestPlayerNameChange.java +++ /dev/null @@ -1,204 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.profile.GlobalProfile; -import org.mvplugins.multiverse.inventories.util.MockPlayerFactory; -import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestPlayerNameChange { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - public static Map getFillerInv() { - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.BOW, 1)); - fillerItems.put(13, new ItemStack(Material.DIRT, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.WRITTEN_BOOK, 1); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.LEATHER_BOOTS, 1); - fillerItems.put(2, leather); - return fillerItems; - } - - public static Map getFillerInv2() { - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.DIAMOND_PICKAXE, 1)); - fillerItems.put(13, new ItemStack(Material.STONE, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.BOOK, 1); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.GOLDEN_BOOTS, 1); - fillerItems.put(2, leather); - return fillerItems; - } - - public void doPlayerJoin(Player player) throws UnknownHostException { - this.listener.playerPreLogin(new AsyncPlayerPreLoginEvent(player.getName(), - InetAddress.getLocalHost(), player.getUniqueId())); - this.listener.playerJoin(new PlayerJoinEvent(player, null)); - } - - public void doPlayerQuit(Player player) { - this.listener.playerQuit(new PlayerQuitEvent(player, null)); - } - - public void changePlayerName(Player player, String targetName) { - assertNotNull(player); - - String oldName = player.getName(); - UUID oldUUID = player.getUniqueId(); - - MockPlayerFactory.changeName(player, targetName); - - String newName = player.getName(); - UUID newUUID = player.getUniqueId(); - - assertEquals(oldName, "dumptruckman"); - assertEquals(newName, "benwoo1110"); - assertEquals(oldUUID, newUUID); - } - - public GlobalProfile getAndCheckGlobalProfile(Player player) { - GlobalProfile globalProfile = this.inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId()); - assertEquals(globalProfile.getLastKnownName(), player.getName()); - assertEquals(globalProfile.getPlayerName(), player.getName()); - assertEquals(globalProfile.getPlayerUUID(), player.getUniqueId()); - - return globalProfile; - } - - /** - * Ensure that player data is migrated on name change. - */ - @Test - public void TestPlayerNameChangeMigration() throws UnknownHostException { - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, this.inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - this.inventories.onCommand(this.mockCommandSender, mockCoreCommand, "", cmdArgs); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // Assert debug mode is on - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - // Getting player - Player player = this.mockServer.getPlayerExact("dumptruckman"); - assertNotNull(player); - - doPlayerJoin(player); - GlobalProfile globalProfile = getAndCheckGlobalProfile(player); - - // Set inv for world - addToInventory(player.getInventory(), getFillerInv()); - String worldInvData = player.getInventory().toString(); - - changeWorld(player, "world", "world2"); - assertNotSame(player.getInventory().toString(), worldInvData); - - // Set inv for world2 - addToInventory(player.getInventory(), getFillerInv2()); - String world2InvData = player.getInventory().toString(); - - doPlayerQuit(player); - changePlayerName(player, "benwoo1110"); - doPlayerJoin(player); - - globalProfile = getAndCheckGlobalProfile(player); - assertNotSame(player.getInventory().toString(), worldInvData); - assertEquals(player.getInventory().toString(), world2InvData); - - // Go back to world_nether which is in group default - // i.e. Should be the same inv as world. - changeWorld(player, "world2", "world_nether"); - assertNotSame(player.getInventory().toString(), world2InvData); - assertEquals(player.getInventory().toString(), worldInvData); - - // Go back to world - changeWorld(player, "world_nether", "world"); - assertNotSame(player.getInventory().toString(), world2InvData); - assertEquals(player.getInventory().toString(), worldInvData); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java deleted file mode 100644 index 2beb4a78..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestResetWorld.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.onarandombox.MultiverseAdventure.event.MVAResetFinishedEvent; -import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; -import org.bukkit.Color; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestResetWorld { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - AdventureListener aListener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("adventureListener"); - field.setAccessible(true); - aListener = new AdventureListener(inventories); - field.set(inventories, aListener); - field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - @Test - public void testWorldReset() { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - changeWorld(player, "world", "world2"); - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.BOW, 1)); - fillerItems.put(13, new ItemStack(Material.DIRT, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.WRITTEN_BOOK, 1); - BookMeta bookMeta = (BookMeta) book.getItemMeta(); - bookMeta.setAuthor("dumptruckman"); - bookMeta.setPages("This is my freaking", "book", "man"); - bookMeta.setDisplayName("Super Book"); - book.setItemMeta(bookMeta); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.LEATHER_BOOTS, 1); - LeatherArmorMeta leatherMeta = (LeatherArmorMeta) leather.getItemMeta(); - leatherMeta.setColor(Color.PURPLE); - leatherMeta.setLore(Arrays.asList("Aww fuck yeah", "Lore")); - leather.setItemMeta(leatherMeta); - fillerItems.put(2, leather); - addToInventory(player.getInventory(), fillerItems); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world2", "world"); - String newInventory = player.getInventory().toString(); - - assertNotSame(originalInventory, newInventory); - - listener.worldUnload(new WorldUnloadEvent(mockServer.getWorld("world2"))); - aListener.worldReset(new MVAResetFinishedEvent("world2")); - changeWorld(player, "world", "world2"); - String inventoryAfterReset = player.getInventory().toString(); - - assertEquals(newInventory, inventoryAfterReset); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java deleted file mode 100644 index 3dbd0338..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestWSharableAPI.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.share.ProfileEntry; -import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.share.SharableHandler; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestWSharableAPI { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - public final static Sharable CUSTOM = new Sharable.Builder("custom", Double.class, - new SharableHandler() { - @Override - public void updateProfile(PlayerProfile profile, Player player) { - profile.set(CUSTOM, (double) player.getMaximumNoDamageTicks()); - } - - @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { - Double value = profile.get(CUSTOM); - if (value == null) { - // Specify default value - player.setMaximumNoDamageTicks(0); - return false; - } - player.setMaximumNoDamageTicks((int) Double.doubleToLongBits(value)); - return true; - } - }).defaultSerializer(new ProfileEntry(false, "custom")).build(); - public final static Sharable OPTIONAL = new Sharable.Builder("optional", Integer.class, - new SharableHandler() { - @Override - public void updateProfile(PlayerProfile profile, Player player) { - profile.set(CUSTOM, player.getLastDamage()); - } - - @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { - Double value = profile.get(CUSTOM); - if (value == null) { - // Specify default value - player.setLastDamage(0); - return false; - } - player.setLastDamage(value); - return true; - } - }).defaultSerializer(new ProfileEntry(false, "optional")).optional().build(); - - public final static Sharable CUSTOM_MAP = new Sharable.Builder("custom_map", Map.class, - new SharableHandler() { - @Override - public void updateProfile(PlayerProfile profile, Player player) { - Map data = new HashMap(); - data.put("maxNoDamageTick", player.getMaximumNoDamageTicks()); - data.put("displayName", player.getDisplayName()); - profile.set(CUSTOM_MAP, data); - } - - @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { - Map data = profile.get(CUSTOM_MAP); - if (data == null) { - // Specify default value - player.setMaximumNoDamageTicks(0); - player.setDisplayName("poop"); - return false; - } - player.setMaximumNoDamageTicks((Integer) data.get("maxNoDamageTick")); - player.setDisplayName(data.get("displayName").toString()); - return true; - } - }).defaultSerializer(new ProfileEntry(false, "custom_map")).build(); - - @Test - public void testSharableAPI() { - - assertTrue(Sharables.all().contains(CUSTOM)); - assertTrue(Sharables.all().contains(OPTIONAL)); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - newGroup.getShares().remove(OPTIONAL); - inventories.getGroupManager().updateGroup(newGroup); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - //changeWorld(player, "world", "world2"); - //changeWorld(player, "world2", "world"); - - //cmdArgs = new String[]{"addshare", "-optional", "default"}; - //inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - addToInventory(player.getInventory(), TestWorldChanged.getFillerInv()); - player.setMaximumNoDamageTicks(10); - int lastDamage = 10; - player.setLastDamage(lastDamage); - assertEquals(10, player.getMaximumNoDamageTicks()); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world", "world_nether"); - String newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - assertEquals(10, player.getMaximumNoDamageTicks()); - assertEquals(lastDamage, player.getLastDamage(), 0); - - changeWorld(player, "world_nether", "world2"); - assertEquals(0, player.getMaximumNoDamageTicks()); - assertNotSame(originalInventory, newInventory); - assertEquals(lastDamage, player.getLastDamage(), 0); - changeWorld(player, "world2", "world"); - assertEquals(10, player.getMaximumNoDamageTicks()); - assertEquals(originalInventory, newInventory); - assertEquals(lastDamage, player.getLastDamage(), 0); - } - -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java b/src/old-test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java deleted file mode 100644 index 026ea1c4..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/TestWorldChanged.java +++ /dev/null @@ -1,539 +0,0 @@ -package org.mvplugins.multiverse.inventories; - -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.mvplugins.multiverse.inventories.share.Sharables; -import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.inventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestWorldChanged { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - public static Map getFillerInv() { - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.BOW, 1)); - fillerItems.put(13, new ItemStack(Material.DIRT, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.WRITTEN_BOOK, 1); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.LEATHER_BOOTS, 1); - fillerItems.put(2, leather); - return fillerItems; - } - - @Test - public void testBasicWorldChange() throws IOException { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world", "world_nether"); - - String newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - - changeWorld(player, "world_nether", "world2"); - - assertNotSame(originalInventory, newInventory); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - } - - @Test - public void testNegativeSharables() { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // add inventory shares (inv and armor) to default group - cmdArgs = new String[]{"addshares", "-saturation", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Shares shares = Sharables.allOf(); - shares.remove(Sharables.SATURATION); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(shares)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(shares)); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - float satTest = 0.349F; - player.setSaturation(satTest); - double hpTest = 13D; - player.setHealth(hpTest); - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world", "world_nether"); - String newInventory = player.getInventory().toString(); - - // Inventory and health should be same, saturation different (from original values) - assertEquals(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - assertEquals(hpTest, player.getHealth(), 0); - - changeWorld(player, "world_nether", "world2"); - newInventory = player.getInventory().toString(); - - // Inventory, health and saturation should be different (from original values) - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - assertNotSame(hpTest, player.getHealth()); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - newInventory = player.getInventory().toString(); - - // Inventory, health and saturation should be same (from original values) - assertEquals(originalInventory, newInventory); - assertEquals(satTest, player.getSaturation(), 0); - assertEquals(hpTest, player.getHealth(), 0); - - changeWorld(player, "world", "world2"); - newInventory = player.getInventory().toString(); - - // Inventory, health and saturation should be different (from original values) - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - assertNotSame(hpTest, player.getHealth()); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - } - - @Test - public void testGroupedSharesWorldChange() throws Exception { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // remove all shares from default group - cmdArgs = new String[]{"rmshares", "all", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add inventory shares (inv and armor) to default group - cmdArgs = new String[]{"addshares", "inventory,saturation", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.INVENTORY)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.ARMOR)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.SATURATION)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.INVENTORY)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.ARMOR)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.SATURATION)); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - float satTest = 0.349F; - player.setSaturation(satTest); - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - // Changing to world within same group, nothing should change. - changeWorld(player, "world", "world_nether"); - - String newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - assertEquals(satTest, player.getSaturation(), 0); - - changeWorld(player, "world_nether", "world2"); - newInventory = player.getInventory().toString(); - - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - assertEquals(satTest, player.getSaturation(), 0); - - changeWorld(player, "world", "world2"); - originalInventory = player.getInventory().toString(); - - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - - FlatFileDataHelper dataHelper = new FlatFileDataHelper(inventories.getData()); - File playerFile = dataHelper.getPlayerFile(ContainerType.GROUP, "default", "dumptruckman"); - FileConfiguration playerConfig = JsonConfiguration.loadConfiguration(playerFile); - playerConfig.set("SURVIVAL." + DataStrings.PLAYER_INVENTORY_CONTENTS, null); - playerConfig.set("SURVIVAL." + DataStrings.PLAYER_ARMOR_CONTENTS, null); - try { - playerConfig.save(playerFile); - } catch (IOException e) { - e.printStackTrace(); - } - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - } - - @Test - public void testLastLocation() throws Exception { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // remove all shares from default group - cmdArgs = new String[]{"rmshares", "all", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // enable last_location share - cmdArgs = new String[]{"toggle", "last_location"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add last_location share to default group - cmdArgs = new String[]{"addshares", "last_location", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - // Move player within group and to a different location than spawn - changeWorld(player, "world", "world_nether"); - Location lastLocation = new Location(mockServer.getWorld("world_nether"), 10, 10, 10); - player.teleport(lastLocation); - - // Move player out of group - changeWorld(player, "world_nether", "world2"); - assertNotSame(lastLocation, player.getLocation()); - - // Move player back to group - changeWorld(player, "world2", "world_nether"); - assertEquals(lastLocation, player.getLocation()); - } - - @Test - public void testOptionalsForUngroupedWorlds() throws Exception { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Assert UseOptionalsForUngroupedWorlds is set to true - assertTrue(inventories.getMVIConfig().usingOptionalsForUngrouped()); - - // Change UseOptionalsForUngroupedWorlds to false, then assert that it is false - inventories.getMVIConfig().setUsingOptionalsForUngrouped(false); - assertFalse(inventories.getMVIConfig().usingOptionalsForUngrouped()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // remove all shares from default group - cmdArgs = new String[]{"rmshares", "all", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // enable last_location share - cmdArgs = new String[]{"toggle", "last_location"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add last_location share to default group - cmdArgs = new String[]{"addshares", "last_location", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add inventory share to default group - cmdArgs = new String[]{"addshares", "inventory", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - assertFalse(inventories.getMVIConfig().usingOptionalsForUngrouped()); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - // get inventories as strings - String emptyInventory = player.getInventory().toString(); - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - // Move player within group and to a different location than spawn - changeWorld(player, "world", "world_nether"); - Location lastLocation = new Location(mockServer.getWorld("world_nether"), 10, 10, 10); - player.teleport(lastLocation); - - // make sure inventory is the same - assertEquals(originalInventory, player.getInventory().toString()); - - // Move player out of group - changeWorld(player, "world_nether", "world2"); - assertNotSame(lastLocation, player.getLocation()); - assertEquals(emptyInventory, player.getInventory().toString()); - - // Move player back to group - changeWorld(player, "world2", "world_nether"); - assertEquals(lastLocation, player.getLocation()); - assertEquals(originalInventory, player.getInventory().toString()); - - // Move player within group again - // Note: newLocation must match the location given made in changeWorld() - Location newLocation = new Location(mockServer.getWorld("world"), 0, 70, 0); - changeWorld(player, "world_nether", "world"); - assertEquals(originalInventory, player.getInventory().toString()); - // The following two assertions mean the same thing (they're redundant) - // but they help in understanding what is being tested. - assertNotSame(lastLocation, player.getLocation()); - assertEquals(newLocation, player.getLocation()); - - } - - @Test - public void testGroupingConflictChecker() { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - // remove world2 from default group - String[] cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - WorldGroup group = inventories.getGroupManager().newEmptyGroup("test"); - group.addWorld("world"); - group.addWorld("world_nether"); - group.addWorld("world_the_end"); - group.addWorld("world2"); - group.getShares().setSharing(Sharables.allOf(), true); - inventories.getGroupManager().updateGroup(group); - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertTrue(inventories.getGroupManager().checkGroups().isEmpty()); - - cmdArgs = new String[]{"rmworld", "world_the_end", "test"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().checkGroups().isEmpty()); - - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java deleted file mode 100644 index 7a234a98..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MVTestLogFormatter.java +++ /dev/null @@ -1,42 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.logging.Formatter; -import java.util.logging.LogRecord; - -/** - * Formatter to format log-messages in tests - * - */ -public class MVTestLogFormatter extends Formatter { - private static final DateFormat df = new SimpleDateFormat("HH:mm:ss"); - - public String format(LogRecord record) { - StringBuilder ret = new StringBuilder(); - - ret.append("[").append(df.format(record.getMillis())).append("] [") - //.append(record.getLoggerName()).append("] [") - .append(record.getLevel().getLocalizedName()).append("] "); - ret.append(record.getMessage()); - ret.append('\n'); - - if (record.getThrown() != null) { - // An Exception was thrown! Let's print the StackTrace! - StringWriter writer = new StringWriter(); - record.getThrown().printStackTrace(new PrintWriter(writer)); - ret.append(writer); - } - - return ret.toString(); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java deleted file mode 100644 index 76c50097..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockItemMeta.java +++ /dev/null @@ -1,94 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2019. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ -package org.mvplugins.multiverse.inventories.util; - -import org.bukkit.Material; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MockItemMeta { - - private static final Map itemMetaData = new HashMap<>(); - - public static ItemFactory mockItemFactory() { - ItemFactory itemFactory = mock(ItemFactory.class); - - when(itemFactory.equals(any(), any())).thenReturn(true); - - doAnswer(invocation -> { - Material material = invocation.getArgument(0); - switch (material) { - case WRITTEN_BOOK: - return mockItemMeta(material, BookMeta.class); - case LEATHER_BOOTS: - return mockItemMeta(material, LeatherArmorMeta.class); - default: - return mockItemMeta(material, ItemMeta.class); - } - - }).when(itemFactory).getItemMeta(any(Material.class)); - - doReturn(true).when(itemFactory).isApplicable(any(ItemMeta.class), any(ItemStack.class)); - doReturn(true).when(itemFactory).isApplicable(any(ItemMeta.class), any(Material.class)); - - doAnswer(invocation -> invocation.getArgument(0)).when(itemFactory).asMetaFor(any(ItemMeta.class), any(ItemStack.class)); - doAnswer(invocation -> invocation.getArgument(0)).when(itemFactory).asMetaFor(any(ItemMeta.class), any(Material.class)); - - doAnswer(invocation -> invocation.getArgument(1)).when(itemFactory).updateMaterial(any(ItemMeta.class), any(Material.class)); - - return itemFactory; - } - - private static T mockItemMeta(Material type, Class itemMetaClass) { - Map data = new HashMap<>(); - - T itemMeta = mock(itemMetaClass, invocation -> { - String methodName = invocation.getMethod().getName(); - if (methodName.startsWith("set")) { - if (invocation.getArguments().length > 1) { - data.put(methodName.substring(3), Arrays.asList(invocation.getArguments())); - } else { - data.put(methodName.substring(3), invocation.getArguments()[0]); - } - } else if (methodName.startsWith("get")) { - return data.get(methodName.substring(3)); - } - - return null; - }); - - when(itemMeta.toString()).thenAnswer(i -> data.toString()); - - when(itemMeta.serialize()).thenAnswer(i -> data); - - when(itemMeta.clone()).thenReturn(itemMeta); - - MockItemMeta mockItemMeta = new MockItemMeta(type); - itemMetaData.put(itemMeta, mockItemMeta); - - return itemMeta; - } - - private final Material type; - - private MockItemMeta(Material type) { - this.type = type; - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java deleted file mode 100644 index a113d0ff..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerFactory.java +++ /dev/null @@ -1,312 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2020. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories.util; - -import org.mvplugins.multiverse.inventories.PlayerStats; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyDouble; -import static org.mockito.Mockito.anyFloat; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MockPlayerFactory { - - private static final Map createdPlayers = new HashMap<>(); - private static final Map playerUIDs = new HashMap<>(); - - public static Player makeMockPlayer(String name, Server server) { - Player player = new MockPlayerFactory(name, UUID.randomUUID(), server).getMockPlayer(); - registerPlayer(player); - return player; - } - - public static Player makeMockPlayer(UUID uuid, Server server) { - Player player = new MockPlayerFactory(uuid.toString(), uuid, server).getMockPlayer(); - registerPlayer(player); - return player; - } - - public static Player getMockPlayer(String name) { - return createdPlayers.get(name); - } - - public static Player getOrCreateMockPlayer(String name, Server server) { - Player player = getMockPlayer(name); - if (player == null) { - player = makeMockPlayer(name, server); - } - return player; - } - - public static Player getMockPlayer(UUID uuid) { - return playerUIDs.get(uuid); - } - - public static Player getOrCreateMockPlayer(UUID uuid, Server server) { - Player player = getMockPlayer(uuid); - if (player == null) { - player = makeMockPlayer(uuid, server); - } - return player; - } - - public static void clearAllPlayers() { - createdPlayers.clear(); - playerUIDs.clear(); - } - - public static Collection getAllPlayers() { - return createdPlayers.values(); - } - - public static Player changeName(Player player, String newName) { - createdPlayers.remove(player.getName()); - when(player.getName()).thenReturn(newName); - - registerPlayer(player); - return player; - } - - private static void registerPlayer(Player player) { - createdPlayers.put(player.getName(), player); - playerUIDs.put(player.getUniqueId(), player); - } - - private final Player player; - private final PlayerData data = new PlayerData(); - - private MockPlayerFactory(String name, UUID uuid, Server server) { - player = mock(Player.class); - mockName(name); - mockUid(uuid); - mockServer(server); - mockPlayerData(); - } - - private Player getMockPlayer() { - return player; - } - - private void mockName(String name) { - when(player.getName()).thenReturn(name); - when(player.getDisplayName()).thenReturn(name); - when(player.getPlayerListName()).thenReturn(name); - } - - private void mockUid(UUID uuid) { - when(player.getUniqueId()).thenReturn(uuid); - } - - private void mockServer(Server server) { - when(player.getServer()).thenReturn(server); - } - - private void mockPlayerData() { - mockCompassTarget(); - mockExp(); - mockLevel(); - mockTotalExperience(); - mockExhaustion(); - mockSaturation(); - mockFoodLevel(); - mockBedSpawnLocation(); - mockInventory(); - mockEnderChest(); - mockHealth(); - mockMaximumNoDamageTicks(); - mockLastDamage(); - mockMaximumAir(); - mockPotionEffects(); - mockLocation(); - mockTeleport(); - } - - private void mockCompassTarget() { - when(player.getCompassTarget()).thenAnswer(i -> data.compassTarget); - doAnswer(invocation -> { - data.compassTarget = invocation.getArgument(0); - return null; - }).when(player).setCompassTarget(any(Location.class)); - } - - private void mockExp() { - when(player.getExp()).thenAnswer(i -> data.exp); - doAnswer(invocation -> { - data.exp = invocation.getArgument(0); - return null; - }).when(player).setExp(anyFloat()); - } - - private void mockLevel() { - when(player.getLevel()).thenAnswer(i -> data.level); - doAnswer(invocation -> { - data.level = invocation.getArgument(0); - return null; - }).when(player).setLevel(anyInt()); - } - - private void mockTotalExperience() { - when(player.getTotalExperience()).thenAnswer(i -> data.totalExperience); - doAnswer(invocation -> { - data.totalExperience = invocation.getArgument(0); - return null; - }).when(player).setTotalExperience(anyInt()); - } - - private void mockExhaustion() { - when(player.getExhaustion()).thenAnswer(i -> data.exhaustion); - doAnswer(invocation -> { - data.exhaustion = invocation.getArgument(0); - return null; - }).when(player).setExhaustion(anyFloat()); - } - - private void mockSaturation() { - when(player.getSaturation()).thenAnswer(i -> data.saturation); - doAnswer(invocation -> { - data.saturation = invocation.getArgument(0); - return null; - }).when(player).setSaturation(anyFloat()); - } - - private void mockFoodLevel() { - when(player.getFoodLevel()).thenAnswer(i -> data.foodLevel); - doAnswer(invocation -> { - data.foodLevel = invocation.getArgument(0); - return null; - }).when(player).setFoodLevel(anyInt()); - } - - private void mockBedSpawnLocation() { - when(player.getBedSpawnLocation()).thenAnswer(i -> data.bedSpawnLocation); - doAnswer(invocation -> { - data.bedSpawnLocation = invocation.getArgument(0); - return null; - }).when(player).setBedSpawnLocation(any(Location.class)); - doAnswer(invocation -> { - data.bedSpawnLocation = invocation.getArgument(0); - return null; - }).when(player).setBedSpawnLocation(any(Location.class), anyBoolean()); - } - - private void mockInventory() { - when(player.getInventory()).thenReturn(data.inventory); - } - - private void mockEnderChest() { - when(player.getEnderChest()).thenReturn(data.enderChest); - } - - private void mockHealth() { - when(player.getHealth()).thenAnswer(i -> data.health); - doAnswer(invocation -> { - data.health = invocation.getArgument(0); - return null; - }).when(player).setHealth(anyDouble()); - } - - private void mockMaximumNoDamageTicks() { - when(player.getMaximumNoDamageTicks()).thenAnswer(i -> data.maximumNoDamageTicks); - doAnswer(invocation -> { - data.maximumNoDamageTicks = invocation.getArgument(0); - return null; - }).when(player).setMaximumNoDamageTicks(anyInt()); - } - - private void mockLastDamage() { - when(player.getLastDamage()).thenAnswer(i -> data.lastDamage); - doAnswer(invocation -> { - data.lastDamage = invocation.getArgument(0); - return null; - }).when(player).setLastDamage(anyDouble()); - } - - private void mockMaximumAir() { - when(player.getMaximumAir()).thenAnswer(i -> data.maximumAir); - doAnswer(invocation -> { - data.maximumAir = invocation.getArgument(0); - return null; - }).when(player).setMaximumAir(anyInt()); - } - - private void mockPotionEffects() { - when(player.getActivePotionEffects()).thenAnswer(i -> new ArrayList<>(data.potionEffects.values())); - - doAnswer(invocation -> { - PotionEffect effect = invocation.getArgument(0); - data.potionEffects.put(effect.getType().getId(), effect); - return true; - }).when(player).addPotionEffect(any(PotionEffect.class)); - - doAnswer(invocation -> { - PotionEffectType type = invocation.getArgument(0); - data.potionEffects.remove(type.getId()); - return null; - }).when(player).removePotionEffect(any(PotionEffectType.class)); - } - - private void mockLocation() { - when(player.getLocation()).thenAnswer(i -> data.location); - when(player.getWorld()).thenAnswer(i -> data.location.getWorld()); - } - - private void mockTeleport() { - doAnswer(invocation -> { - data.location = invocation.getArgument(0); - return true; - }).when(player).teleport(any(Location.class)); - - doAnswer(invocation -> { - data.location = invocation.getArgument(0); - return true; - }).when(player).teleport(any(Location.class), any(PlayerTeleportEvent.TeleportCause.class)); - } - - private static class PlayerData { - Location compassTarget = null; - - float exp = PlayerStats.EXPERIENCE; - int level = PlayerStats.LEVEL; - int totalExperience = PlayerStats.TOTAL_EXPERIENCE; - float exhaustion = PlayerStats.EXHAUSTION; - float saturation = PlayerStats.SATURATION; - int foodLevel = PlayerStats.FOOD_LEVEL; - - Location bedSpawnLocation = null; - - PlayerInventory inventory = new MockPlayerInventory(); - PlayerInventory enderChest = new MockPlayerInventory(); - - double health = PlayerStats.HEALTH; - int maximumNoDamageTicks = 0; - double lastDamage = 0D; - - int maximumAir = 20; - - Map potionEffects = new HashMap<>(); - - Location location = new Location(MockWorldFactory.getWorld("world"), 0, 70, 0); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java deleted file mode 100644 index 55d06252..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockPlayerInventory.java +++ /dev/null @@ -1,328 +0,0 @@ -package org.mvplugins.multiverse.inventories.util; - -import org.mvplugins.multiverse.inventories.PlayerStats; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.HumanEntity; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Spliterator; -import java.util.function.Consumer; - -public class MockPlayerInventory implements PlayerInventory { - - ItemStack[] armorContents = new ItemStack[PlayerStats.ARMOR_SIZE]; - ItemStack[] inventoryContents = new ItemStack[PlayerStats.INVENTORY_SIZE]; - - @Override - public ItemStack[] getArmorContents() { - return armorContents; - } - - @Override - public ItemStack getHelmet() { - return armorContents[0]; - } - - @Override - public ItemStack getChestplate() { - return armorContents[1]; - } - - @Override - public ItemStack getLeggings() { - return armorContents[2]; - } - - @Override - public ItemStack getBoots() { - return armorContents[3]; - } - - @Override - public void setArmorContents(ItemStack[] itemStacks) { - this.armorContents = itemStacks; - } - - @Override - public void setHelmet(ItemStack itemStack) { - this.armorContents[0] = itemStack; - } - - @Override - public void setChestplate(ItemStack itemStack) { - this.armorContents[1] = itemStack; - } - - @Override - public void setLeggings(ItemStack itemStack) { - this.armorContents[2] = itemStack; - } - - @Override - public void setBoots(ItemStack itemStack) { - this.armorContents[3] = itemStack; - } - - @Override - public void setHeldItemSlot(int i) { - - } - - @Override - public ItemStack getItemInHand() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void setItemInHand(ItemStack itemStack) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int getHeldItemSlot() { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HumanEntity getHolder() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int getSize() { - return inventoryContents.length + armorContents.length; - } - - public String getName() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ItemStack getItem(int i) { - if (i >= 0 && i < PlayerStats.INVENTORY_SIZE) { - return inventoryContents[i]; - } else if (i >= PlayerStats.INVENTORY_SIZE && i < PlayerStats.INVENTORY_SIZE + PlayerStats.ARMOR_SIZE) { - return armorContents[i - PlayerStats.INVENTORY_SIZE]; - } else { - throw new ArrayIndexOutOfBoundsException(); - } - } - - @Override - public void setItem(int i, ItemStack itemStack) { - if (i >= 0 && i < PlayerStats.INVENTORY_SIZE) { - inventoryContents[i] = itemStack; - } else if (i >= PlayerStats.INVENTORY_SIZE && i < PlayerStats.INVENTORY_SIZE + PlayerStats.ARMOR_SIZE) { - armorContents[i - PlayerStats.INVENTORY_SIZE] = itemStack; - } else { - throw new ArrayIndexOutOfBoundsException(); - } - } - - @Override - public HashMap addItem(ItemStack... itemStacks) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HashMap removeItem(ItemStack... itemStacks) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ItemStack[] getContents() { - return this.inventoryContents; - } - - @Override - public void setContents(ItemStack[] itemStacks) { - this.inventoryContents = itemStacks; - } - - @Override - public boolean contains(Material material) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean contains(ItemStack itemStack) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean contains(Material material, int i) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean contains(ItemStack itemStack, int i) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HashMap all(Material material) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HashMap all(ItemStack itemStack) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int first(Material material) { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int first(ItemStack itemStack) { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int firstEmpty() { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void remove(Material material) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void remove(ItemStack itemStack) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void clear(int i) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void clear() { - Arrays.fill(armorContents, null); - Arrays.fill(inventoryContents, null); - } - - @Override - public List getViewers() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public String getTitle() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public InventoryType getType() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ListIterator iterator() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int getMaxStackSize() { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void setMaxStackSize(int i) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ListIterator iterator(int i) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean containsAtLeast(final ItemStack itemStack, final int i) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - private static Map makeMap(ItemStack[] items) { - Map contents = new LinkedHashMap(items.length); - for (int i = 0; i < items.length; i++) { - if (items[i] != null && items[i].getType() != Material.AIR) { - contents.put(Integer.valueOf(i).toString(), items[i]); - } - } - return contents; - } - - public String toString() { - return "{\"inventoryContents\":" + makeMap(getContents()) - + ",\"armorContents\":" + makeMap(getArmorContents()) - + "}"; - } - - // TODO update these once rest of MV-Inv is compatible. - - @Override - public ItemStack[] getExtraContents() { - return new ItemStack[0]; - } - - @Override - public void setExtraContents(ItemStack[] itemStacks) { - - } - - @Override - public ItemStack getItemInMainHand() { - return null; - } - - @Override - public void setItemInMainHand(ItemStack itemStack) { - - } - - @Override - public ItemStack getItemInOffHand() { - return null; - } - - @Override - public void setItemInOffHand(ItemStack itemStack) { - - } - - @Override - public ItemStack[] getStorageContents() { - return new ItemStack[0]; - } - - @Override - public void setStorageContents(ItemStack[] itemStacks) throws IllegalArgumentException { - - } - - @Override - public Location getLocation() { - return null; - } - - @Override - public void forEach(Consumer action) { - - } - - @Override - public Spliterator spliterator() { - return null; - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java deleted file mode 100644 index 6356fe87..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/MockWorldFactory.java +++ /dev/null @@ -1,163 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories.util; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.WorldType; -import org.bukkit.block.Block; -import org.bukkit.generator.ChunkGenerator; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MockWorldFactory { - - private static final Map worldUIDS = new HashMap(); - private static final Map createdWorlds = new LinkedHashMap(); - - private MockWorldFactory() { - } - - private static void registerWorld(World world) { - worldUIDS.put(world.getUID(), world); - createdWorlds.put(world.getName(), world); - } - - private static World basics(String world, World.Environment env, WorldType type) { - World mockWorld = mock(World.class); - when(mockWorld.getName()).thenReturn(world); - when(mockWorld.getEnvironment()).thenReturn(env); - when(mockWorld.getWorldType()).thenReturn(type); - when(mockWorld.getSpawnLocation()).thenReturn(new Location(mockWorld, 0, 64, 0)); - when(mockWorld.getWorldFolder()).thenAnswer(new Answer() { - public File answer(InvocationOnMock invocation) throws Throwable { - if (!(invocation.getMock() instanceof World)) - return null; - - World thiss = (World) invocation.getMock(); - return new File(TestInstanceCreator.serverDirectory, thiss.getName()); - } - }); - when(mockWorld.getBlockAt(any(Location.class))).thenAnswer(new Answer() { - public Block answer(InvocationOnMock invocation) throws Throwable { - Location loc; - try { - loc = (Location) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - Material blockType = Material.AIR; - Block mockBlock = mock(Block.class); - if (loc.getBlockY() < 64) { - blockType = Material.DIRT; - } - - when(mockBlock.getType()).thenReturn(blockType); - when(mockBlock.getWorld()).thenReturn(loc.getWorld()); - when(mockBlock.getX()).thenReturn(loc.getBlockX()); - when(mockBlock.getY()).thenReturn(loc.getBlockY()); - when(mockBlock.getZ()).thenReturn(loc.getBlockZ()); - when(mockBlock.getLocation()).thenReturn(loc); - when(mockBlock.isEmpty()).thenReturn(blockType == Material.AIR); - return mockBlock; - } - }); - when(mockWorld.getUID()).thenReturn(UUID.randomUUID()); - return mockWorld; - } - - private static World nullWorld(String world, World.Environment env, WorldType type) { - World mockWorld = mock(World.class); - when(mockWorld.getName()).thenReturn(world); - when(mockWorld.getEnvironment()).thenReturn(env); - when(mockWorld.getWorldType()).thenReturn(type); - when(mockWorld.getSpawnLocation()).thenReturn(new Location(mockWorld, 0, 64, 0)); - when(mockWorld.getWorldFolder()).thenAnswer(new Answer() { - public File answer(InvocationOnMock invocation) throws Throwable { - if (!(invocation.getMock() instanceof World)) - return null; - - World thiss = (World) invocation.getMock(); - return new File(TestInstanceCreator.serverDirectory, thiss.getName()); - } - }); - when(mockWorld.getBlockAt(any(Location.class))).thenAnswer(new Answer() { - public Block answer(InvocationOnMock invocation) throws Throwable { - Location loc; - try { - loc = (Location) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - - Block mockBlock = mock(Block.class); - Material blockType = Material.AIR; - - when(mockBlock.getType()).thenReturn(blockType); - when(mockBlock.getWorld()).thenReturn(loc.getWorld()); - when(mockBlock.getX()).thenReturn(loc.getBlockX()); - when(mockBlock.getY()).thenReturn(loc.getBlockY()); - when(mockBlock.getZ()).thenReturn(loc.getBlockZ()); - when(mockBlock.getLocation()).thenReturn(loc); - when(mockBlock.isEmpty()).thenReturn(blockType == Material.AIR); - return mockBlock; - } - }); - return mockWorld; - } - - public static World makeNewMockWorld(String world, World.Environment env, WorldType type) { - World w = basics(world, env, type); - registerWorld(w); - return w; - } - - public static World makeNewNullMockWorld(String world, World.Environment env, WorldType type) { - World w = nullWorld(world, env, type); - registerWorld(w); - return w; - } - - public static World makeNewMockWorld(String world, World.Environment env, WorldType type, long seed, - ChunkGenerator generator) { - World mockWorld = basics(world, env, type); - when(mockWorld.getGenerator()).thenReturn(generator); - when(mockWorld.getSeed()).thenReturn(seed); - registerWorld(mockWorld); - return mockWorld; - } - - public static World getWorld(String name) { - return createdWorlds.get(name); - } - - public static World getWorld(UUID worldUID) { - return worldUIDS.get(worldUID); - } - - public static List getWorlds() { - return new ArrayList(createdWorlds.values()); - } - - public static void clearWorlds() { - createdWorlds.clear(); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java deleted file mode 100644 index 3740c644..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/TestInstanceCreator.java +++ /dev/null @@ -1,413 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories.util; - -import com.onarandombox.MultiverseCore.MultiverseCore; -import com.onarandombox.MultiverseCore.api.MVWorldManager; -import com.onarandombox.MultiverseCore.listeners.MVEntityListener; -import com.onarandombox.MultiverseCore.listeners.MVPlayerListener; -import com.onarandombox.MultiverseCore.listeners.MVWeatherListener; -import com.onarandombox.MultiverseCore.utils.FileUtils; -import com.onarandombox.MultiverseCore.utils.TestingMode; -import com.onarandombox.MultiverseCore.world.SimpleMVWorldManager; -import org.mvplugins.multiverse.inventories.InventoriesListener; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Server; -import org.bukkit.UnsafeValues; -import org.bukkit.World; -import org.bukkit.WorldCreator; -import org.bukkit.WorldType; -import org.bukkit.World.Environment; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.permissions.Permission; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.java.JavaPluginLoader; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitScheduler; -import org.mockito.internal.util.reflection.ReflectionMemberAccessor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.lang.reflect.Field; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -public class TestInstanceCreator { - private MultiverseInventories plugin; - private MultiverseCore core; - private Server mockServer; - private CommandSender commandSender; - - public static final File invDirectory = new File("bin/test/server/plugins/inventories-test"); - public static final File coreDirectory = new File("bin/test/server/plugins/core-test"); - public static final File serverDirectory = new File("bin/test/server"); - public static final File worldsDirectory = new File("bin/test/server"); - - public boolean setUp() { - TestingMode.enable(); - try { - FileUtils.deleteFolder(invDirectory); - FileUtils.deleteFolder(serverDirectory); - assertFalse(invDirectory.exists()); - invDirectory.mkdirs(); - assertTrue(invDirectory.exists()); - - // Initialize the Mock server. - mockServer = mock(Server.class); - when(mockServer.getName()).thenReturn("TestBukkit"); - when(mockServer.getVersion()).thenReturn("git-TestBukkit-0 (MC: 1.18.1)"); - Logger.getLogger("Minecraft").setParent(Util.logger); - when(mockServer.getLogger()).thenReturn(Util.logger); - when(mockServer.getWorldContainer()).thenReturn(worldsDirectory); - - // Initialize the Mock Plugin Loader. - JavaPluginLoader mockPluginLoader = mock(JavaPluginLoader.class); - new ReflectionMemberAccessor().set(JavaPluginLoader.class.getDeclaredField("server"), mockPluginLoader, mockServer); - - // Return a fake PDF file. - PluginDescriptionFile pdf = spy(new PluginDescriptionFile("Multiverse-Inventories", "2.4-test", - "com.onarandombox.multiverseinventories.MultiverseInventories")); - when(pdf.getAuthors()).thenReturn(new ArrayList()); - plugin = spy(new MultiverseInventories(mockPluginLoader, pdf, invDirectory, new File(invDirectory, "testPluginFile"))); - doReturn(pdf).when(plugin).getDescription(); - doReturn(true).when(plugin).isEnabled(); - PluginDescriptionFile pdfCore = spy(new PluginDescriptionFile("Multiverse-Core", "2.2-Test", - "com.onarandombox.MultiverseCore.MultiverseCore")); - when(pdfCore.getAuthors()).thenReturn(new ArrayList()); - core = spy(new MultiverseCore(mockPluginLoader, pdf, coreDirectory, new File(coreDirectory, "testPluginFile"))); - doReturn(pdfCore).when(core).getDescription(); - doReturn(true).when(core).isEnabled(); - doReturn(Util.logger).when(core).getLogger(); - plugin.setServerFolder(serverDirectory); - doReturn(core).when(plugin).getCore(); - - // Let's let all MV files go to bin/test - doReturn(invDirectory).when(plugin).getDataFolder(); - // Let's let all MV files go to bin/test - doReturn(coreDirectory).when(core).getDataFolder(); - - // Add Core to the list of loaded plugins - JavaPlugin[] plugins = new JavaPlugin[]{plugin, core}; - - // Mock the Plugin Manager - PluginManager mockPluginManager = mock(PluginManager.class); - when(mockPluginManager.getPlugins()).thenReturn(plugins); - when(mockPluginManager.getPlugin("Multiverse-Inventories")).thenReturn(plugin); - when(mockPluginManager.getPlugin("Multiverse-Core")).thenReturn(core); - when(mockPluginManager.getPermission(anyString())).thenReturn(null); - - // Make some fake folders to fool the fake MV into thinking these worlds exist - File worldNormalFile = new File(plugin.getServerFolder(), "world"); - Util.log("Creating world-folder: " + worldNormalFile.getAbsolutePath()); - worldNormalFile.mkdirs(); - MockWorldFactory.makeNewMockWorld("world", Environment.NORMAL, WorldType.NORMAL); - File worldNetherFile = new File(plugin.getServerFolder(), "world_nether"); - Util.log("Creating world-folder: " + worldNetherFile.getAbsolutePath()); - worldNetherFile.mkdirs(); - MockWorldFactory.makeNewMockWorld("world_nether", Environment.NETHER, WorldType.NORMAL); - File worldSkylandsFile = new File(plugin.getServerFolder(), "world_the_end"); - Util.log("Creating world-folder: " + worldSkylandsFile.getAbsolutePath()); - worldSkylandsFile.mkdirs(); - MockWorldFactory.makeNewMockWorld("world_the_end", Environment.THE_END, WorldType.NORMAL); - File world2File = new File(plugin.getServerFolder(), "world2"); - Util.log("Creating world-folder: " + world2File.getAbsolutePath()); - world2File.mkdirs(); - MockWorldFactory.makeNewMockWorld("world2", Environment.NORMAL, WorldType.NORMAL); - - // Finish initializing. - when(plugin.getServer()).thenReturn(mockServer); - when(core.getServer()).thenReturn(mockServer); - when(mockServer.getPluginManager()).thenReturn(mockPluginManager); - Answer playerAnswer = invocationOnMock -> { - String name = invocationOnMock.getArgument(0); - if (name == null) return null; - return MockPlayerFactory.getOrCreateMockPlayer(name, mockServer); - }; - when(mockServer.getPlayerExact(anyString())).thenAnswer(playerAnswer); - when(mockServer.getOfflinePlayer(anyString())).thenAnswer(playerAnswer); - when(mockServer.getOfflinePlayers()).thenAnswer( - (Answer) invocation -> MockPlayerFactory.getAllPlayers().toArray(new Player[0])); - when(mockServer.getOnlinePlayers()).thenAnswer( - (Answer>) invocation -> MockPlayerFactory.getAllPlayers()); - Answer uuidPlayerAnswer = invocationOnMock -> { - UUID uuid = invocationOnMock.getArgument(0); - if (uuid == null) return null; - return MockPlayerFactory.getOrCreateMockPlayer(uuid, mockServer); - }; - doAnswer(uuidPlayerAnswer).when(mockServer).getPlayer(any(UUID.class)); - doAnswer(uuidPlayerAnswer).when(mockServer).getOfflinePlayer(any(UUID.class)); - - try { - PotionEffectType.registerPotionEffectType(mockPotionEffectType(1, "SPEED")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(2, "SLOW")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(3, "FAST_DIGGING")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(4, "SLOW_DIGGING")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(5, "INCREASE_DAMAGE")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(6, "HEAL")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(7, "HARM")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(8, "JUMP")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(9, "CONFUSION")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(10, "REGENERATION")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(11, "DAMAGE_RESISTANCE")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(12, "FIRE_RESISTANCE")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(13, "WATER_BREATHING")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(14, "INVISIBILITY")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(15, "BLINDNESS")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(16, "NIGHT_VISION")); - } catch (IllegalArgumentException ignore) { - // Already registered in this context. - } - - // Give the server some worlds - when(mockServer.getWorld(anyString())).thenAnswer(new Answer() { - public World answer(InvocationOnMock invocation) throws Throwable { - String arg; - try { - arg = (String) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - return MockWorldFactory.getWorld(arg); - } - }); - - when(mockServer.getWorld(any(UUID.class))).thenAnswer(new Answer() { - @Override - public World answer(InvocationOnMock invocation) throws Throwable { - UUID arg; - try { - arg = (UUID) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - return MockWorldFactory.getWorld(arg); - } - }); - - when(mockServer.getWorlds()).thenAnswer(new Answer>() { - public List answer(InvocationOnMock invocation) throws Throwable { - return MockWorldFactory.getWorlds(); - } - }); - - - - when(mockServer.createWorld(isA(WorldCreator.class))).thenAnswer( - new Answer() { - public World answer(InvocationOnMock invocation) throws Throwable { - WorldCreator arg; - try { - arg = (WorldCreator) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - // Add special case for creating null worlds. - // Not sure I like doing it this way, but this is a special case - if (arg.name().equalsIgnoreCase("nullworld")) { - return MockWorldFactory.makeNewNullMockWorld(arg.name(), arg.environment(), arg.type()); - } - return MockWorldFactory.makeNewMockWorld(arg.name(), arg.environment(), arg.type()); - } - }); - - when(mockServer.unloadWorld(anyString(), anyBoolean())).thenReturn(true); - - // add mock scheduler - BukkitScheduler mockScheduler = mock(BukkitScheduler.class); - when(mockScheduler.scheduleSyncDelayedTask(any(Plugin.class), any(Runnable.class), anyLong())). - thenAnswer(new Answer() { - public Integer answer(InvocationOnMock invocation) throws Throwable { - Runnable arg; - try { - arg = (Runnable) invocation.getArguments()[1]; - } catch (Exception e) { - return null; - } - arg.run(); - return null; - } - }); - when(mockScheduler.scheduleSyncDelayedTask(any(Plugin.class), any(Runnable.class))). - thenAnswer(new Answer() { - public Integer answer(InvocationOnMock invocation) throws Throwable { - Runnable arg; - try { - arg = (Runnable) invocation.getArguments()[1]; - } catch (Exception e) { - return null; - } - arg.run(); - return null; - } - }); - when(mockServer.getScheduler()).thenReturn(mockScheduler); - - ItemFactory itemFactory = MockItemMeta.mockItemFactory(); - when(mockServer.getItemFactory()).thenReturn(itemFactory); - - - UnsafeValues unsafeValues = mock(UnsafeValues.class); - doAnswer(i -> Material.getMaterial(i.getArgument(0))).when(unsafeValues).getMaterial(any(), anyInt()); - when(mockServer.getUnsafe()).thenReturn(unsafeValues); - - // Set InventoriesListener - InventoriesListener il = spy(new InventoriesListener(plugin)); - Field inventoriesListenerField = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - inventoriesListenerField.setAccessible(true); - inventoriesListenerField.set(plugin, il); - - // Set Core - Field coreField = MultiverseInventories.class.getDeclaredField("core"); - coreField.setAccessible(true); - coreField.set(plugin, core); - - // Set server - Field serverfield = JavaPlugin.class.getDeclaredField("server"); - serverfield.setAccessible(true); - serverfield.set(plugin, mockServer); - - // Set worldManager - MVWorldManager wm = spy(new SimpleMVWorldManager(core)); - Field worldmanagerfield = MultiverseCore.class.getDeclaredField("worldManager"); - worldmanagerfield.setAccessible(true); - worldmanagerfield.set(core, wm); - - // Set playerListener - MVPlayerListener pl = spy(new MVPlayerListener(core)); - Field playerlistenerfield = MultiverseCore.class.getDeclaredField("playerListener"); - playerlistenerfield.setAccessible(true); - playerlistenerfield.set(core, pl); - - // Set entityListener - MVEntityListener el = spy(new MVEntityListener(core)); - Field entitylistenerfield = MultiverseCore.class.getDeclaredField("entityListener"); - entitylistenerfield.setAccessible(true); - entitylistenerfield.set(core, el); - - // Set weatherListener - MVWeatherListener wl = spy(new MVWeatherListener(core)); - Field weatherlistenerfield = MultiverseCore.class.getDeclaredField("weatherListener"); - weatherlistenerfield.setAccessible(true); - weatherlistenerfield.set(core, wl); - - // Init our command sender - final Logger commandSenderLogger = Logger.getLogger("CommandSender"); - commandSenderLogger.setParent(Util.logger); - commandSender = mock(CommandSender.class); - doAnswer(new Answer() { - public Void answer(InvocationOnMock invocation) throws Throwable { - commandSenderLogger.info(ChatColor.stripColor((String) invocation.getArguments()[0])); - return null; - } - }).when(commandSender).sendMessage(anyString()); - when(commandSender.getServer()).thenReturn(mockServer); - when(commandSender.getName()).thenReturn("MockCommandSender"); - when(commandSender.isPermissionSet(anyString())).thenReturn(true); - when(commandSender.isPermissionSet(isA(Permission.class))).thenReturn(true); - when(commandSender.hasPermission(anyString())).thenReturn(true); - when(commandSender.hasPermission(isA(Permission.class))).thenReturn(true); - when(commandSender.addAttachment(plugin)).thenReturn(null); - when(commandSender.isOp()).thenReturn(true); - - Bukkit.setServer(mockServer); - - // Load Multiverse Core - core.onLoad(); - plugin.onLoad(); - - // Enable it. - core.onEnable(); - plugin.onEnable(); - - return true; - } catch (Exception e) { - e.printStackTrace(); - } - - return false; - } - - public boolean tearDown() { - PluginManager pluginManager = getServer().getPluginManager(); - Plugin plugin = pluginManager.getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - inventories.onDisable(); - - MockPlayerFactory.clearAllPlayers(); - MockWorldFactory.clearWorlds(); - - plugin = getServer().getPluginManager().getPlugin("Multiverse-Core"); - MultiverseCore core = (MultiverseCore) plugin; - core.onDisable(); - - try { - Field serverField = Bukkit.class.getDeclaredField("server"); - serverField.setAccessible(true); - serverField.set(Class.forName("org.bukkit.Bukkit"), null); - } catch (Exception e) { - Util.log(Level.SEVERE, - "Error while trying to unregister the server from Bukkit. Has Bukkit changed?"); - e.printStackTrace(); - fail(e.getMessage()); - return false; - } - - // Dont remove so that we can see the data result after the test. - // FileUtils.deleteFolder(serverDirectory); - - return true; - } - - public MultiverseInventories getPlugin() { - return this.plugin; - } - - public Server getServer() { - return this.mockServer; - } - - public CommandSender getCommandSender() { - return commandSender; - } - - private PotionEffectType mockPotionEffectType(int id, String name) throws Exception { - PotionEffectType potionEffectType = mock(PotionEffectType.class); - Field idField = PotionEffectType.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(potionEffectType, id); - when(potionEffectType.getId()).thenReturn(id); - when(potionEffectType.getName()).thenReturn(name); - return potionEffectType; - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/Util.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/Util.java deleted file mode 100644 index e690c05e..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/Util.java +++ /dev/null @@ -1,62 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories.util; - -import com.dumptruckman.minecraft.util.Logging; - -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.logging.LogRecord; - -public class Util { - private Util() {} - - //public static final Logger logger = Logger.getLogger("MV-Test"); - public static final Logger logger = Logging.getLogger(); - - static { - logger.setUseParentHandlers(false); - - Handler handler = new ConsoleHandler(); - handler.setFormatter(new MVTestLogFormatter()); - Handler[] handlers = logger.getHandlers(); - - for (Handler h : handlers) - logger.removeHandler(h); - - logger.addHandler(handler); - } - - public static void log(Throwable t) { - log(Level.WARNING, t.getLocalizedMessage(), t); - } - - public static void log(Level level, Throwable t) { - log(level, t.getLocalizedMessage(), t); - } - - public static void log(String message, Throwable t) { - log(Level.WARNING, message, t); - } - - public static void log(Level level, String message, Throwable t) { - LogRecord record = new LogRecord(level, message); - record.setThrown(t); - logger.log(record); - } - - public static void log(String message) { - log(Level.INFO, message); - } - - public static void log(Level level, String message) { - logger.log(level, message); - } -} diff --git a/src/old-test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java b/src/old-test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java deleted file mode 100644 index 0b91bf3b..00000000 --- a/src/old-test/java/org/mvplugins/multiverse/inventories/util/WorldCreatorMatcher.java +++ /dev/null @@ -1,55 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package org.mvplugins.multiverse.inventories.util; - -import org.bukkit.WorldCreator; -import org.mockito.ArgumentMatcher; - -public class WorldCreatorMatcher implements ArgumentMatcher { - private final WorldCreator worldCreator; - private boolean careAboutSeeds = false; - private boolean careAboutGenerators = false; - - public WorldCreatorMatcher(WorldCreator creator) { - Util.log("Creating NEW world matcher.(" + creator.name() + ")"); - this.worldCreator = creator; - } - - public void careAboutSeeds(boolean doICare) { - this.careAboutSeeds = doICare; - } - - public void careAboutGenerators(boolean doICare) { - this.careAboutGenerators = doICare; - } - - @Override - public boolean matches(WorldCreator creator) { - Util.log("Checking world creators."); - if (creator == null) { - Util.log("The given creator was null, but I was checking: " + this.worldCreator.name()); - return false; - } - Util.log("Checking Names...(" + creator.name() + ") vs (" + this.worldCreator.name() + ")"); - Util.log("Checking Envs...(" + creator.environment() + ") vs (" + this.worldCreator.environment() + ")"); - if (!creator.name().equals(this.worldCreator.name())) { - return false; - } else if (!creator.environment().equals(this.worldCreator.environment())) { - Util.log("Checking Environments..."); - return false; - } else if (careAboutSeeds && creator.seed() != this.worldCreator.seed()) { - Util.log("Checking Seeds..."); - return false; - } else if (careAboutGenerators && creator.generator().equals(this.worldCreator.generator())) { - Util.log("Checking Gens..."); - return false; - } - Util.log("Creators matched!!!"); - return true; - } -} diff --git a/src/old-test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/old-test/resources/mockito-extensions/org.mockito.plugins.MockMaker deleted file mode 100644 index 1f0955d4..00000000 --- a/src/old-test/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ /dev/null @@ -1 +0,0 @@ -mock-maker-inline From 85a36d375638a6df6376bda26b8a20e1badd29c7 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 6 Mar 2025 22:02:03 +0800 Subject: [PATCH 114/180] Add tests config migration --- .../inventories/config/ConfigTest.kt | 8 +++++ src/test/resources/config/migrated_config.yml | 30 ++++++++++++++++ src/test/resources/config/old_config.yml | 35 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/test/resources/config/migrated_config.yml create mode 100644 src/test/resources/config/old_config.yml diff --git a/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt index 8cd9a543..8ea2d001 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt @@ -29,4 +29,12 @@ class ConfigTest : TestWithMockBukkit() { fun `Config is fresh`() { assertConfigEquals("/config/fresh_config.yml", "config.yml") } + + @Test + fun `Migrate from old config`() { + writeResourceToConfigFile("/config/old_config.yml", "config.yml") + assertTrue(config.load().isSuccess) + assertTrue(config.save().isSuccess) + assertConfigEquals("/config/migrated_config.yml", "config.yml") + } } diff --git a/src/test/resources/config/migrated_config.yml b/src/test/resources/config/migrated_config.yml new file mode 100644 index 00000000..31f7575e --- /dev/null +++ b/src/test/resources/config/migrated_config.yml @@ -0,0 +1,30 @@ +share-handling: + enable-bypass-permissions: true + enable-gamemode-share-handling: true + default-ungrouped-worlds: true + use-optionals-for-ungrouped-worlds: false + active-optional-shares: + - last_location + +sharables: + use-improved-respawn-location-detection: true + reset-last-location-on-death: false + apply-last-location-for-all-teleports: true + +performance: + save-playerdata-on-quit: true + apply-playerdata-on-join: false + always-write-world-profile: true + preload-data-on-join: + worlds: [] + groups: [] + cache: + player-file-cache-size: 2000 + player-file-cache-expiry: 60 + player-profile-cache-size: 6000 + player-profile-cache-expiry: 60 + global-profile-cache-size: 500 + global-profile-cache-expiry: 60 + +first-run: true +version: 5.0 diff --git a/src/test/resources/config/old_config.yml b/src/test/resources/config/old_config.yml new file mode 100644 index 00000000..c45b8251 --- /dev/null +++ b/src/test/resources/config/old_config.yml @@ -0,0 +1,35 @@ +# Multiverse-Inventories Settings + +settings: + # This is the locale you wish to use. + locale: en + + # If this is true it will generate world groups for you based on MV worlds. + first_run: true + + # If this is set to true, it will enable bypass permissions (Check the wiki for more info.) + use_bypass: true + + # If set to true, any world not listed in a group will automatically use the settings for the default group! + default_ungrouped_worlds: true + + # The default and suggested setting for this is FALSE. + # False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out. + # That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY. + # Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in. + # The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back! + save_load_on_log_in_out: true + + # If this is set to true, players will have different inventories/stats for each game mode. + # Please note that old data migrated to the version that has this feature will have their data copied for both game modes. + use_game_mode_profiles: true +shares: + # When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world. + # An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world. + # When set to false, optional shares WILL NOt be utilized in these cases, effectively disabling it for ungrouped worlds. + optionals_for_ungrouped_worlds: false + + # You must specify optional shares you wish to use here or they will be ignored. + # The only built in optional shares are "economy" and "last_location". + use_optionals: + - last_location From 3ee9564103a9667fc5326c9b54aece17f805926b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 6 Mar 2025 22:16:21 +0800 Subject: [PATCH 115/180] Add cache stats to debug dumps info --- .../inventories/MVEventsListener.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java index 2aeee039..6ed513da 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java @@ -10,6 +10,7 @@ import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; @@ -21,12 +22,18 @@ final class MVEventsListener implements Listener { private final MultiverseInventories inventories; private final InventoriesConfig config; private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; @Inject - MVEventsListener(@NotNull MultiverseInventories inventories, @NotNull InventoriesConfig config, @NotNull WorldGroupManager worldGroupManager) { + MVEventsListener( + @NotNull MultiverseInventories inventories, + @NotNull InventoriesConfig config, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource) { this.inventories = inventories; this.config = config; this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; } /** @@ -41,6 +48,24 @@ void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); event.putDetailedDebugInfo("multiverse-inventories/config.yml", configFile); event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); + event.putDetailedDebugInfo("multiverse-inventories/cachestats.md", generateCacheStatsContent()); + } + + private String generateCacheStatsContent() { + var builder = new StringBuilder(); + profileDataSource.getCacheStats().forEach((cacheName, stats) -> { + builder.append("# ").append(cacheName).append("\n") + .append("- hits count: ").append(stats.hitCount()).append("\n") + .append("- misses count: ").append(stats.missCount()).append("\n") + .append("- loads count: ").append(stats.loadCount()).append("\n") + .append("- misses count: ").append(stats.missCount()).append("\n") + .append("- evictions: ").append(stats.evictionCount()).append("\n") + .append("- hit rate: ").append(stats.hitRate() * 100).append("%\n") + .append("- miss rate: ").append(stats.missRate() * 100).append("%\n") + .append("- avg load penalty: ").append(stats.averageLoadPenalty() / 1000000).append("ms\n") + .append("\n"); + }); + return builder.toString(); } /** From 356a77626f08ab44ccf3a24b7c02b22a40fc453d Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Mar 2025 16:51:25 +0800 Subject: [PATCH 116/180] Add luckperms context support for worldgroup --- build.gradle | 3 ++ .../inventories/MultiverseInventories.java | 12 +++++ .../WorldGroupContextCalculator.java | 53 +++++++++++++++++++ src/main/resources/plugin.yml | 2 +- 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java diff --git a/build.gradle b/build.gradle index 1dc4c9f5..883a401c 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,9 @@ dependencies { // TODO update to correct version once we have it published externalPlugin 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' + // Luckperms for group context + externalPlugin 'net.luckperms:api:5.4' + // Config shadowed 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' shadowed 'net.minidev:json-smart:2.5.1' diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index b1fd08c7..fcda795a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -120,6 +120,9 @@ public final void onEnable() { this.registerDestinations(); // Hook plugins that can be imported from this.hookImportables(); + + // Init other extensions + this.hookLuckPerms(); this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); Logging.config("Version %s (API v%s) Enabled - By %s", @@ -190,6 +193,15 @@ private void hookImportables() { }); } + private void hookLuckPerms() { + Try.run(() -> Class.forName("net.luckperms.api.LuckPerms")) + .onFailure(e -> Logging.fine("Luckperms is not installed!")) + .andThenTry(() -> { + Logging.fine("Found luckperms!"); + serviceLocator.getService(WorldGroupContextCalculator.class); + }); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java b/src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java new file mode 100644 index 00000000..56c2378b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.context.ContextCalculator; +import net.luckperms.api.context.ContextConsumer; +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.ImmutableContextSet; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.annotation.PostConstruct; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +@Service +final class WorldGroupContextCalculator implements ContextCalculator { + + private static final String WORLD_GROUP_CONTEXT_KEY = "mvinv:worldgroup"; + private final WorldGroupManager worldGroupManager; + + @Inject + WorldGroupContextCalculator(WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @PostConstruct + private void registerCalculator() { + Try.of(LuckPermsProvider::get) + .peek(luckPerms -> luckPerms.getContextManager().registerCalculator(this)) + .onFailure(e -> Logging.warning("Failed to hook LuckPerms! %s", e.getMessage())); + } + + @Override + public void calculate(@NotNull Player player, @NotNull ContextConsumer contextConsumer) { + ImmutableContextSet.Builder contextBuilder = ImmutableContextSet.builder(); + worldGroupManager.getGroupsForWorld(player.getWorld().getName()) + .forEach(worldGroup -> contextBuilder.add(WORLD_GROUP_CONTEXT_KEY, worldGroup.getName())); + + contextConsumer.accept(contextBuilder.build()); + } + + @NotNull + @Override + public ContextSet estimatePotentialContexts() { + ImmutableContextSet.Builder contextBuilder = ImmutableContextSet.builder(); + worldGroupManager.getGroups() + .forEach(worldGroup -> contextBuilder.add(WORLD_GROUP_CONTEXT_KEY, worldGroup.getName())); + + return contextBuilder.build(); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 368aa413..493d7f9c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,4 +5,4 @@ api-version: 1.13 authors: ['dumptruckman', 'benwoo1110'] website: 'https://dev.bukkit.org/projects/multiverse-inventories' depend: ['Multiverse-Core'] -softdepend: [MultiInv, WorldInventories, PerWorldInventory] +softdepend: [LuckPerms, MultiInv, WorldInventories, PerWorldInventory] From 441a8674cf968964acc21b826f1b140e9a39e0ce Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Mar 2025 22:09:44 +0800 Subject: [PATCH 117/180] Make luckperms dependency compileOnly --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 883a401c..f901c8bb 100644 --- a/build.gradle +++ b/build.gradle @@ -30,9 +30,6 @@ dependencies { // TODO update to correct version once we have it published externalPlugin 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' - // Luckperms for group context - externalPlugin 'net.luckperms:api:5.4' - // Config shadowed 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' shadowed 'net.minidev:json-smart:2.5.1' @@ -45,6 +42,9 @@ dependencies { // Caching shadowed("com.github.ben-manes.caffeine:caffeine:3.2.0") + // Luckperms for group context + compileOnly 'net.luckperms:api:5.4' + // Other plugins for import compileOnly('uk.co:MultiInv:3.0.6') { exclude group: '*', module: '*' From fe309f722977e0033884794d43940110d8d12373 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 22 Mar 2025 22:17:33 +0800 Subject: [PATCH 118/180] Implement advancement, recipe and gamestats optional sharables. --- .../profile/group/YamlWorldGroupManager.java | 1 + .../inventories/share/Sharables.java | 150 +++++++++++++++++- .../handleshare/ShareHandlingUpdaterTest.kt | 4 +- 3 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index e7946a07..09a17032 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -188,6 +188,7 @@ private WorldGroup deserializeGroup(final String name, final Map Object sharesListObj = dataMap.get("disabled-shares"); if (sharesListObj instanceof List) { profile.getDisabledShares().mergeShares(Sharables.fromList((List) sharesListObj)); + profile.getDisabledShares().removeAll(Sharables.negativeFromList((List) sharesListObj)); } else { Logging.warning("Disabled shares formatted incorrectly for group: " + name); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 183c9752..a0aeb56a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1,9 +1,8 @@ package org.mvplugins.multiverse.inventories.share; import com.dumptruckman.minecraft.util.Logging; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.attribute.AttributeInstance; +import com.google.common.collect.Sets; +import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; @@ -12,20 +11,26 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.util.DataStrings; -import org.mvplugins.multiverse.inventories.util.PlayerStats; import org.mvplugins.multiverse.inventories.util.MinecraftTools; +import org.mvplugins.multiverse.inventories.util.PlayerStats; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Statistic; +import org.bukkit.advancement.Advancement; import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; -import org.mvplugins.multiverse.core.economy.MVEconomist; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -33,6 +38,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; @@ -687,6 +693,135 @@ public boolean updatePlayer(Player player, ProfileData profile) { }).serializer(new ProfileEntry(false, "potions"), new PotionEffectSerializer()) .altName("potion").altName("potions").build(); + /** + * Sharing Advancements. + */ + public static final Sharable ADVANCEMENTS = new Sharable.Builder<>("advancements", List.class, + new SharableHandler<>() { + @Override + public void updateProfile(ProfileData profile, Player player) { + Set completedAdvancements = new HashSet<>(); + Iterator advancementIterator = inventories.getServer().advancementIterator(); + + while (advancementIterator.hasNext()) { + Advancement advancement = advancementIterator.next(); + Collection awardedCriteria = player.getAdvancementProgress(advancement).getAwardedCriteria(); + completedAdvancements.addAll(awardedCriteria); + } + + profile.set(ADVANCEMENTS, new ArrayList<>(completedAdvancements)); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + List advancements = profile.get(ADVANCEMENTS); + Set processedCriteria = new HashSet<>(); + Set completedCriteria = (advancements != null) ? new HashSet<>(advancements) : new HashSet<>(); + + int totalExperience = player.getTotalExperience(); + int level = player.getLevel(); + float exp = player.getExp(); + + Iterator advancementIterator = Bukkit.advancementIterator(); + while (advancementIterator.hasNext()) { + Advancement advancement = advancementIterator.next(); + + for (String criteria : advancement.getCriteria()) { + if (processedCriteria.contains(criteria)) { + continue; + } else if (completedCriteria.contains(criteria)) { + player.getAdvancementProgress(advancement).awardCriteria(criteria); + } else { + player.getAdvancementProgress(advancement).revokeCriteria(criteria); + } + + processedCriteria.add(criteria); + } + } + + player.setExp(exp); + player.setLevel(level); + player.setTotalExperience(totalExperience); + + return advancements != null; + } + }).defaultSerializer(new ProfileEntry(false, "advancements")).altName("achievements").optional().build(); + + /** + * Sharing Statistics. + */ + public static final Sharable GAME_STATISTICS = new Sharable.Builder<>("game_statistics", Map.class, + new SharableHandler<>() { + @Override + public void updateProfile(ProfileData profile, Player player) { + Map playerStats = new HashMap<>(); + for (Statistic stat: Statistic.values()) { + if (stat.getType() == Statistic.Type.UNTYPED) { + int val = player.getStatistic(stat); + // no need to save values of 0, that's the default! + if (val != 0) { + playerStats.put(stat.name(), val); + } + } + } + profile.set(GAME_STATISTICS, playerStats); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + Map playerStats = profile.get(GAME_STATISTICS); + if (playerStats == null) { + // Set all to 0 + for (Statistic stat : Statistic.values()) { + if (stat.getType() == Statistic.Type.UNTYPED) { + player.setStatistic(stat, 0); + } + } + return false; + } + + for (Statistic stat : Statistic.values()) { + if (stat.getType() == Statistic.Type.UNTYPED) { + player.setStatistic(stat, playerStats.getOrDefault(stat.name(), 0)); + } + } + + return true; + } + }).defaultSerializer(new ProfileEntry(false, "game_statistics")).altName("game_stats").optional().build(); + + /** + * Sharing Recipes. + */ + public static final Sharable RECIPES = new Sharable.Builder<>("recipes", List.class, + new SharableHandler<>() { + @Override + public void updateProfile(ProfileData profile, Player player) { + List recipes = player.getDiscoveredRecipes().stream() + .map(NamespacedKey::toString) + .toList(); + profile.set(RECIPES, recipes); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + List recipes = profile.get(RECIPES); + if (recipes == null) { + player.undiscoverRecipes(player.getDiscoveredRecipes()); + return false; + } + + Set discoveredRecipes = player.getDiscoveredRecipes(); + Set toDiscover = recipes.stream().map(NamespacedKey::fromString) + .collect(Collectors.toSet()); + + player.undiscoverRecipes(Sets.difference(discoveredRecipes, toDiscover)); + player.discoverRecipes(Sets.difference(toDiscover, discoveredRecipes)); + + return true; + } + }).defaultSerializer(new ProfileEntry(false, "recipes")).optional().build(); + /** * Grouping for inventory sharables. */ @@ -722,7 +857,7 @@ public boolean updatePlayer(Player player, ProfileData profile) { */ public static final SharableGroup STATS = new SharableGroup("stats", fromSharables(HEALTH, MAX_HEALTH, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, - REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS)); + REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, GAME_STATISTICS, ADVANCEMENTS)); /** * Grouping for ALL default sharables. @@ -730,7 +865,8 @@ public boolean updatePlayer(Player player, ProfileData profile) { */ public static final SharableGroup ALL_DEFAULT = new SharableGroup("all", fromSharables(HEALTH, MAX_HEALTH, ECONOMY, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, INVENTORY, ARMOR, BED_SPAWN, - MAXIMUM_AIR, REMAINING_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, LAST_LOCATION, ENDER_CHEST, OFF_HAND), + MAXIMUM_AIR, REMAINING_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, LAST_LOCATION, ENDER_CHEST, OFF_HAND, + GAME_STATISTICS, ADVANCEMENTS, RECIPES), "*", "everything"); diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index 2792cd79..06b3e45d 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -31,7 +31,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { val playerProfileFuture = profileDataSource.getPlayerData( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) - ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfileFuture)) + ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfileFuture)) val playerProfile = playerProfileFuture.get() assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) assertEquals(15.1, playerProfile.get(Sharables.MAX_HEALTH)) @@ -44,7 +44,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { playerProfile.set(Sharables.HEALTH, 4.4) playerProfile.set(Sharables.MAX_HEALTH, 15.1) - ShareHandlingUpdater.updatePlayer(multiverseInventories, player, PersistingProfile(Sharables.allOf(), playerProfile)) + ShareHandlingUpdater.updatePlayer(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfile)) assertEquals(4.4, player.health) assertEquals(15.1, player.maxHealth) From 0112f505e5fd48977ad15ba92182c7d286218264 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:20:38 +0800 Subject: [PATCH 119/180] Update to suppport core api changes --- .../inventories/MultiverseInventories.java | 6 +++--- .../commandtools/MVInvCommandCompletion.java | 2 +- .../inventories/config/InventoriesConfig.java | 10 +++++----- .../inventories/config/InventoriesConfigNodes.java | 12 ++++++------ .../multiverse/inventories/TestWithMockBukkit.kt | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index fcda795a..bfe54972 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -5,7 +5,7 @@ import org.bukkit.plugin.PluginManager; import org.mvplugins.multiverse.core.MultiverseCoreApi; import org.mvplugins.multiverse.core.MultiversePlugin; -import org.mvplugins.multiverse.core.config.MVCoreConfig; +import org.mvplugins.multiverse.core.config.CoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.utils.StringFormatter; @@ -45,7 +45,7 @@ public class MultiverseInventories extends MultiversePlugin { @Inject private Provider commandManager; @Inject - private Provider mvCoreConfig; + private Provider coreConfig; @Inject private Provider destinationsProvider; @Inject @@ -224,7 +224,7 @@ public PluginServiceLocator getServiceLocator() { @Override public void reloadConfig() { try { - Logging.setDebugLevel(mvCoreConfig.get().getGlobalDebug()); + Logging.setDebugLevel(coreConfig.get().getGlobalDebug()); inventoriesConfig.get().load().onFailure(e -> { Logging.severe("Failed to load config file!"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java index 71695822..12e16bb4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java @@ -4,7 +4,7 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandCompletions; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction; +import org.mvplugins.multiverse.core.confighandle.PropertyModifyAction; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index cca41065..56726d40 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -2,11 +2,11 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.configuration.handle.CommentedConfigurationHandle; -import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle; -import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator; -import org.mvplugins.multiverse.core.configuration.migration.MoveMigratorAction; -import org.mvplugins.multiverse.core.configuration.migration.VersionMigrator; +import org.mvplugins.multiverse.core.confighandle.CommentedConfigurationHandle; +import org.mvplugins.multiverse.core.confighandle.StringPropertyHandle; +import org.mvplugins.multiverse.core.confighandle.migration.ConfigMigrator; +import org.mvplugins.multiverse.core.confighandle.migration.MoveMigratorAction; +import org.mvplugins.multiverse.core.confighandle.migration.VersionMigrator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 9aa56b33..b892784e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -1,11 +1,11 @@ package org.mvplugins.multiverse.inventories.config; -import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer; -import org.mvplugins.multiverse.core.configuration.node.ConfigHeaderNode; -import org.mvplugins.multiverse.core.configuration.node.ConfigNode; -import org.mvplugins.multiverse.core.configuration.node.ListConfigNode; -import org.mvplugins.multiverse.core.configuration.node.Node; -import org.mvplugins.multiverse.core.configuration.node.NodeGroup; +import org.mvplugins.multiverse.core.confighandle.functions.NodeSerializer; +import org.mvplugins.multiverse.core.confighandle.node.ConfigHeaderNode; +import org.mvplugins.multiverse.core.confighandle.node.ConfigNode; +import org.mvplugins.multiverse.core.confighandle.node.ListConfigNode; +import org.mvplugins.multiverse.core.confighandle.node.Node; +import org.mvplugins.multiverse.core.confighandle.node.NodeGroup; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt index 6fe147d8..5eae4579 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -7,7 +7,7 @@ import org.bukkit.configuration.serialization.ConfigurationSerialization import org.mockbukkit.mockbukkit.MockBukkit import org.mockbukkit.mockbukkit.inventory.ItemStackMock import org.mvplugins.multiverse.core.MultiverseCore -import org.mvplugins.multiverse.core.config.MVCoreConfig +import org.mvplugins.multiverse.core.config.CoreConfig import org.mvplugins.multiverse.core.inject.PluginServiceLocator import org.mvplugins.multiverse.inventories.mock.MVServerMock import java.io.File @@ -35,7 +35,7 @@ abstract class TestWithMockBukkit { server = MockBukkit.mock(MVServerMock()) multiverseCore = MockBukkit.load(MultiverseCore::class.java) - multiverseCore.serviceLocator.getService(MVCoreConfig::class.java).globalDebug = 3 + multiverseCore.serviceLocator.getService(CoreConfig::class.java).globalDebug = 3 multiverseInventories = MockBukkit.load(MultiverseInventories::class.java) serviceLocator = multiverseInventories.serviceLocator assertNotNull(server.commandMap) From 3b0c567240c8b12426427b185de2929a8a8e7a7e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 24 Mar 2025 22:04:18 +0800 Subject: [PATCH 120/180] Improve advancement sharable logic --- .../inventories/share/Sharables.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index a0aeb56a..0eac7f95 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -2,6 +2,7 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.collect.Sets; +import org.bukkit.advancement.AdvancementProgress; import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.external.vavr.control.Option; @@ -19,7 +20,6 @@ import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.Statistic; -import org.bukkit.advancement.Advancement; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.entity.Player; @@ -701,14 +701,10 @@ public boolean updatePlayer(Player player, ProfileData profile) { @Override public void updateProfile(ProfileData profile, Player player) { Set completedAdvancements = new HashSet<>(); - Iterator advancementIterator = inventories.getServer().advancementIterator(); - - while (advancementIterator.hasNext()) { - Advancement advancement = advancementIterator.next(); + Bukkit.advancementIterator().forEachRemaining(advancement -> { Collection awardedCriteria = player.getAdvancementProgress(advancement).getAwardedCriteria(); completedAdvancements.addAll(awardedCriteria); - } - + }); profile.set(ADVANCEMENTS, new ArrayList<>(completedAdvancements)); } @@ -718,27 +714,26 @@ public boolean updatePlayer(Player player, ProfileData profile) { Set processedCriteria = new HashSet<>(); Set completedCriteria = (advancements != null) ? new HashSet<>(advancements) : new HashSet<>(); + // Advancements may cause the player to level up, which we don't want to happen int totalExperience = player.getTotalExperience(); int level = player.getLevel(); float exp = player.getExp(); - Iterator advancementIterator = Bukkit.advancementIterator(); - while (advancementIterator.hasNext()) { - Advancement advancement = advancementIterator.next(); - + Bukkit.advancementIterator().forEachRemaining(advancement -> { + AdvancementProgress advancementProgress = player.getAdvancementProgress(advancement); for (String criteria : advancement.getCriteria()) { if (processedCriteria.contains(criteria)) { continue; } else if (completedCriteria.contains(criteria)) { - player.getAdvancementProgress(advancement).awardCriteria(criteria); + advancementProgress.awardCriteria(criteria); } else { - player.getAdvancementProgress(advancement).revokeCriteria(criteria); + advancementProgress.revokeCriteria(criteria); } - processedCriteria.add(criteria); } - } + }); + // Set back the level from before applying the advancements player.setExp(exp); player.setLevel(level); player.setTotalExperience(totalExperience); From b005151603244818532eab74b9175d4b970b5078 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 24 Mar 2025 22:07:07 +0800 Subject: [PATCH 121/180] Fix creategroup CommandCompletion string --- .../multiverse/inventories/commands/CreateGroupCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java index 177babeb..9800e83b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -36,7 +36,7 @@ final class CreateGroupCommand extends InventoriesCommand { @Subcommand("creategroup") @CommandPermission("multiverse.inventories.creategroup") - @CommandCompletion("@empty @mvworlds:multiple,scope=both, @shares") + @CommandCompletion("@empty @mvworlds:multiple,scope=both @shares") @Syntax(" [share[,extra]] [world[,extra]]") @Description("Creates a new empty World Group with no worlds and no shares.") void onCreateGroupCommand( From 661f3d3647473fa116a657b03efc4ed4531bfe04 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 00:06:07 +0800 Subject: [PATCH 122/180] Add support for paper's better ItemStack byte serialization --- build.gradle | 4 ++ .../inventories/MultiverseInventories.java | 4 +- .../inventories/config/InventoriesConfig.java | 8 +++ .../config/InventoriesConfigNodes.java | 7 +++ .../share/InventorySerializer.java | 21 +++++-- .../share/ItemStackSerializer.java | 17 ++++++ .../inventories/share/Sharables.java | 2 +- .../inventories/util/ItemStackConverter.java | 56 +++++++++++++++++++ 8 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java diff --git a/build.gradle b/build.gradle index f901c8bb..2e5f862d 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,10 @@ configure(apiDependencies) { } dependencies { + // Server API + // TODO make our custom plugin target paper instead of spigot + externalPlugin 'io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT' + // Core // TODO update to correct version once we have it published externalPlugin 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index bfe54972..b4ea4711 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -25,6 +25,7 @@ import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.ItemStackConverter; import org.mvplugins.multiverse.inventories.util.Perm; import org.bukkit.Bukkit; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; @@ -94,9 +95,10 @@ public final void onEnable() { super.onEnable(); initializeDependencyInjection(); - Sharables.init(this); + ItemStackConverter.init(this); Perm.register(this); this.reloadConfig(); + Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); Sharables.init(this); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); // Register Events diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 56726d40..8c06652d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -167,6 +167,14 @@ public Try setApplyLastLocationForAllTeleports(boolean applyLastLocationFo return this.configHandle.set(configNodes.applyLastLocationForAllTeleports, applyLastLocationForAllTeleports); } + public boolean getUseByteSerializationForInventoryData() { + return this.configHandle.get(configNodes.useByteSerializationForInventoryData); + } + + public Try setUseByteSerializationForInventoryData(boolean useByteSerializationForInventoryData) { + return this.configHandle.set(configNodes.useByteSerializationForInventoryData, useByteSerializationForInventoryData); + } + /** * Tells whether Multiverse-Inventories should save on player logout. * diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index b892784e..30cb011e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -135,6 +135,13 @@ public Object serialize(Shares sharables, Class aClass) { .name("apply-last-location-for-all-teleports") .build()); + final ConfigNode useByteSerializationForInventoryData = node(ConfigNode.builder("sharables.use-byte-serialization-for-inventory-data", Boolean.class) + .comment("") + .comment("When enabled, we will use byte serialization for inventory data.") + .defaultValue(false) + .name("use-byte-serialization-for-inventory-data") + .build()); + private final ConfigHeaderNode performanceHeader = node(ConfigHeaderNode.builder("performance") .comment("") .comment("") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index 513274b2..5c982c64 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.share; +import org.mvplugins.multiverse.inventories.util.ItemStackConverter; import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -29,11 +30,14 @@ public Object serialize(ItemStack[] itemStacks) { return mapSlots(itemStacks); } - private Map mapSlots(ItemStack[] itemStacks) { - Map result = new HashMap<>(itemStacks.length); + private Map mapSlots(ItemStack[] itemStacks) { + Map result = new HashMap<>(itemStacks.length); for (int i = 0; i < itemStacks.length; i++) { if (itemStacks[i] != null && itemStacks[i].getType() != Material.AIR) { - result.put(Integer.toString(i), itemStacks[i]); + Object serialize = ItemStackConverter.serialize(itemStacks[i]); + if (serialize != null) { + result.put(Integer.toString(i), serialize); + } } } return result; @@ -46,7 +50,16 @@ private ItemStack[] unmapSlots(Object obj) { } for (int i = 0; i < inventory.length; i++) { Object value = invMap.get(Integer.toString(i)); - inventory[i] = value instanceof ItemStack item ? item : new ItemStack(Material.AIR); + if (value == null) { + inventory[i] = new ItemStack(Material.AIR); + continue; + } + ItemStack item = ItemStackConverter.deserialize(value); + if (item == null) { + inventory[i] = new ItemStack(Material.AIR); + continue; + } + inventory[i] = item; } return inventory; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java new file mode 100644 index 00000000..b60cbba4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java @@ -0,0 +1,17 @@ +package org.mvplugins.multiverse.inventories.share; + +import org.bukkit.inventory.ItemStack; +import org.mvplugins.multiverse.inventories.util.ItemStackConverter; + +final class ItemStackSerializer implements SharableSerializer { + + @Override + public ItemStack deserialize(Object obj) { + return ItemStackConverter.deserialize(obj); + } + + @Override + public Object serialize(ItemStack itemStack) { + return ItemStackConverter.serialize(itemStack); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 0eac7f95..01aa4714 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -187,7 +187,7 @@ public boolean updatePlayer(Player player, ProfileData profile) { return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_OFF_HAND_ITEM), - new DefaultSerializer<>(ItemStack.class)).altName("shield").build(); + new ItemStackSerializer()).altName("shield").build(); /** * Sharing Max Health. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java new file mode 100644 index 00000000..10d65846 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +import java.util.Base64; + +public class ItemStackConverter { + + public final static boolean hasByteSerializeSupport; + + static { + hasByteSerializeSupport = Try.run(() -> ItemStack.class.getMethod("deserializeBytes", byte[].class)) + .map(ignore -> true) + .recover(ignore -> false) + .getOrElse(false); + } + + private static InventoriesConfig config = null; + + public static void init(MultiverseInventories plugin) { + config = plugin.getServiceLocator().getService(InventoriesConfig.class); + } + + @Nullable + public static ItemStack deserialize(Object obj) { + if (obj instanceof ItemStack itemStack) { + // Already handled by ConfigurationSerialization + return itemStack; + } + if (hasByteSerializeSupport && obj instanceof String string) { + byte[] bytes = Base64.getDecoder().decode(string); + return ItemStack.deserializeBytes(bytes); + } + return null; + } + + @Nullable + public static Object serialize(ItemStack itemStack) { + if (config != null && config.getUseByteSerializationForInventoryData() && hasByteSerializeSupport) { + if (itemStack.getType() == Material.AIR) { + return null; + } + return Try.of(() -> Base64.getEncoder().encodeToString(itemStack.serializeAsBytes())) + .onFailure(e -> Logging.severe("Could not serialize item stack: %s", e.getMessage())) + .getOrNull(); + } + // let ConfigurationSerialization handle it + return itemStack; + } +} From 0b29e1d06c88acce69551ba8ec592e39efa7edf3 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 10:58:39 +0800 Subject: [PATCH 123/180] Fix Sharables and ItemStackConverter init --- .../multiverse/inventories/MultiverseInventories.java | 5 +++-- .../multiverse/inventories/util/ItemStackConverter.java | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index b4ea4711..4fcf906c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -95,10 +95,11 @@ public final void onEnable() { super.onEnable(); initializeDependencyInjection(); - ItemStackConverter.init(this); + Sharables.init(this); Perm.register(this); + ItemStackConverter.init(this); + Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); this.reloadConfig(); - Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); Sharables.init(this); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); // Register Events diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java index 10d65846..a15b8cda 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -10,7 +10,7 @@ import java.util.Base64; -public class ItemStackConverter { +public final class ItemStackConverter { public final static boolean hasByteSerializeSupport; @@ -53,4 +53,8 @@ public static Object serialize(ItemStack itemStack) { // let ConfigurationSerialization handle it return itemStack; } + + private ItemStackConverter() { + // no instantiation + } } From 431b1c6d5655211217448d55a0ba0790841f5f2c Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:04:33 +0800 Subject: [PATCH 124/180] Add colour to log share handling time taken --- .../multiverse/inventories/handleshare/ShareHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 1504bcb0..f57c155c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -115,6 +115,7 @@ private void updatePersistingProfile(PersistingProfile persistingProfile, Profil } private void logHandlingComplete(double timeTaken, ShareHandlingEvent event) { - Logging.fine("=== %s complete for %s | time taken: %4.4f ms ===", player.getName(), event.getEventName(), timeTaken); + Logging.fine("=== %s complete for %s | \u001B[32mtime taken: %4.4f ms\u001B[0m ===", + player.getName(), event.getEventName(), timeTaken); } } From 2f049a96ecea1ed085bb489df4862ae74d848a5a Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:15:05 +0800 Subject: [PATCH 125/180] Optimise recipe NamespacedKey storage --- .../mvplugins/multiverse/inventories/share/Sharables.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 0eac7f95..aa628a83 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -36,6 +36,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -793,7 +794,9 @@ public boolean updatePlayer(Player player, ProfileData profile) { @Override public void updateProfile(ProfileData profile, Player player) { List recipes = player.getDiscoveredRecipes().stream() - .map(NamespacedKey::toString) + // Save space by removing the namespace if its default minecraft + .map(key -> NamespacedKey.MINECRAFT.equals(key.getNamespace()) + ? key.getKey() : key.toString()) .toList(); profile.set(RECIPES, recipes); } @@ -808,6 +811,7 @@ public boolean updatePlayer(Player player, ProfileData profile) { Set discoveredRecipes = player.getDiscoveredRecipes(); Set toDiscover = recipes.stream().map(NamespacedKey::fromString) + .filter(Objects::nonNull) .collect(Collectors.toSet()); player.undiscoverRecipes(Sets.difference(discoveredRecipes, toDiscover)); From 3e69bd01a4418a45a4b72888eca9d0fd5707a48a Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:24:03 +0800 Subject: [PATCH 126/180] Fix config tests --- src/test/resources/config/fresh_config.yml | 1 + src/test/resources/config/migrated_config.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml index ceec69b5..77727f68 100644 --- a/src/test/resources/config/fresh_config.yml +++ b/src/test/resources/config/fresh_config.yml @@ -9,6 +9,7 @@ sharables: use-improved-respawn-location-detection: true reset-last-location-on-death: false apply-last-location-for-all-teleports: true + use-byte-serialization-for-inventory-data: false performance: save-playerdata-on-quit: false diff --git a/src/test/resources/config/migrated_config.yml b/src/test/resources/config/migrated_config.yml index 31f7575e..167bb13b 100644 --- a/src/test/resources/config/migrated_config.yml +++ b/src/test/resources/config/migrated_config.yml @@ -10,6 +10,7 @@ sharables: use-improved-respawn-location-detection: true reset-last-location-on-death: false apply-last-location-for-all-teleports: true + use-byte-serialization-for-inventory-data: false performance: save-playerdata-on-quit: true From f30c5daac3f45b3b088776344ffeee48b208c6ce Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:41:48 +0800 Subject: [PATCH 127/180] Refactor CountDownLatch checking and properly pass CompletableFuture error --- .../inventories/profile/ProfileFileIO.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java index 82e1cf45..2ecad391 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java @@ -1,6 +1,8 @@ package org.mvplugins.multiverse.inventories.profile; import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.external.vavr.CheckedRunnable; +import org.mvplugins.multiverse.external.vavr.control.Try; import java.io.File; import java.util.Map; @@ -28,43 +30,41 @@ CompletableFuture queueAction(File file, Runnable action) { CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); CompletableFuture future = new CompletableFuture<>(); fileIOExecutorService.submit(() -> { - if (toWaitLatch != null && toWaitLatch.getCount() > 0) { - try { - Logging.finest("Waiting for lock on " + file); - toWaitLatch.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - action.run(); - thisLatch.countDown(); + waitForLock(file, toWaitLatch); + Try tryResult = Try.runRunnable(action); fileLocks.remove(file); - future.complete(null); + thisLatch.countDown(); + tryResult.onFailure(future::completeExceptionally).onSuccess(ignore -> future.complete(null)); }); return future; } - CompletableFuture queueCallable(File file, Supplier callable) { + CompletableFuture queueCallable(File file, Supplier supplier) { CountDownLatch thisLatch = new CountDownLatch(1); CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); CompletableFuture future = new CompletableFuture<>(); fileIOExecutorService.submit(() -> { - if (toWaitLatch != null && toWaitLatch.getCount() > 0) { - try { - Logging.finest("Waiting for lock on " + file); - toWaitLatch.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - T result = callable.get(); - thisLatch.countDown(); + waitForLock(file, toWaitLatch); + Try tryResult = Try.ofSupplier(supplier); fileLocks.remove(file); - future.complete(result); + thisLatch.countDown(); + tryResult.onFailure(future::completeExceptionally).onSuccess(future::complete); }); return future; } + private void waitForLock(File file, CountDownLatch toWaitLatch) { + if (toWaitLatch != null && toWaitLatch.getCount() > 0) { + try { + Logging.finest("Waiting for lock on " + file); + toWaitLatch.await(10, TimeUnit.SECONDS); + Logging.finest("Aquired lock on " + file); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + T waitForData(File file, Supplier callable) { try { return queueCallable(file, callable).get(10, TimeUnit.SECONDS); From b645c10c1b2887d030f2525d3a3bdfe343867585 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:45:20 +0800 Subject: [PATCH 128/180] Add sleep to gamemode test --- .../multiverse/inventories/handleshare/GameModeChangeTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt index f0f61f18..3a554f15 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt @@ -38,6 +38,7 @@ class GameModeChangeTest : TestWithMockBukkit() { player.inventory.contents = survivalItems player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(10) assertNotEquals(survivalItems[0], player.inventory.getItem(0)) assertNotEquals(survivalItems[1], player.inventory.getItem(1)) val creativeItems = arrayOf( @@ -47,12 +48,14 @@ class GameModeChangeTest : TestWithMockBukkit() { player.inventory.contents = creativeItems player.gameMode = org.bukkit.GameMode.SURVIVAL + Thread.sleep(10) assertEquals(survivalItems[0], player.inventory.getItem(0)) assertEquals(survivalItems[1], player.inventory.getItem(1)) assertNotEquals(creativeItems[0], player.inventory.getItem(0)) assertNotEquals(creativeItems[1], player.inventory.getItem(1)) player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(10) assertEquals(creativeItems[0], player.inventory.getItem(0)) assertEquals(creativeItems[1], player.inventory.getItem(1)) assertNotEquals(survivalItems[0], player.inventory.getItem(0)) From 5d4fbd142e31918e1d7d0835faadb6f6c0f62497 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 12:00:19 +0800 Subject: [PATCH 129/180] Improve comments explanation on byte serialisation --- .../inventories/config/InventoriesConfigNodes.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 30cb011e..7ada9db4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -137,7 +137,15 @@ public Object serialize(Shares sharables, Class aClass) { final ConfigNode useByteSerializationForInventoryData = node(ConfigNode.builder("sharables.use-byte-serialization-for-inventory-data", Boolean.class) .comment("") - .comment("When enabled, we will use byte serialization for inventory data.") + .comment("When enabled, we will use paper's improved byte serialization for inventory data.") + .comment("When disabled, we will use the legacy configuration serialization method.") + .comment("!!!!!BIG NOTE:") + .comment(" This option is only applicable on PAPERMC.") + .comment(" Once you enable this option, you cannot change your server software back to SPIGOT.") + .comment("------------") + .comment("Byte serialization will use minecraft's NBT format. NBT is safer for data migrations as it will use the built in ") + .comment("data converter instead of bukkits dangerous serialization system. This will fix various issues with the inventory data") + .comment("such as Skulker Box data loss, equip-sound crash, FoodEffect error, and more.") .defaultValue(false) .name("use-byte-serialization-for-inventory-data") .build()); From 959c152ef6d7ed22f07946a37953b06020dc9cce Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 18:12:03 +0800 Subject: [PATCH 130/180] Refactor some class access modifiers --- .../multiverse/inventories/MultiverseInventories.java | 6 +++--- .../{commandtools => command}/MVInvCommandCompletion.java | 2 +- .../{commandtools => command}/MVInvCommandConditions.java | 2 +- .../{commandtools => command}/MVInvCommandContexts.java | 2 +- .../inventories/commands/prompts/GroupControlPrompt.java | 2 +- .../inventories/commands/prompts/GroupCreatePrompt.java | 2 +- .../inventories/commands/prompts/GroupDeletePrompt.java | 2 +- .../inventories/commands/prompts/GroupEditPrompt.java | 2 +- .../inventories/commands/prompts/GroupModifyPrompt.java | 2 +- .../inventories/commands/prompts/GroupSharesPrompt.java | 2 +- .../inventories/commands/prompts/GroupWorldsPrompt.java | 2 +- .../perworldinventory/PerWorldInventoryImporter.java | 2 +- .../dataimport/perworldinventory/PwiImportHelper.java | 2 +- .../inventories/event/GameModeChangeShareHandlingEvent.java | 2 +- .../multiverse/inventories/event/ShareHandlingEvent.java | 2 +- .../inventories/event/WorldChangeShareHandlingEvent.java | 2 +- .../multiverse/inventories/handleshare/ShareHandler.java | 2 +- .../multiverse/inventories/profile/ProfileDataSnapshot.java | 4 ++-- .../multiverse/inventories/util/ItemStackConverter.java | 2 +- .../mvplugins/multiverse/inventories/util/MVInvi18n.java | 3 +++ .../multiverse/inventories/util/MinecraftTools.java | 6 ++++-- .../mvplugins/multiverse/inventories/util/PlayerStats.java | 2 +- 22 files changed, 30 insertions(+), 25 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/{commandtools => command}/MVInvCommandCompletion.java (98%) rename src/main/java/org/mvplugins/multiverse/inventories/{commandtools => command}/MVInvCommandConditions.java (97%) rename src/main/java/org/mvplugins/multiverse/inventories/{commandtools => command}/MVInvCommandContexts.java (98%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 4fcf906c..15f44733 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -10,8 +10,8 @@ import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; -import org.mvplugins.multiverse.inventories.commandtools.MVInvCommandCompletion; -import org.mvplugins.multiverse.inventories.commandtools.MVInvCommandContexts; +import org.mvplugins.multiverse.inventories.command.MVInvCommandCompletion; +import org.mvplugins.multiverse.inventories.command.MVInvCommandContexts; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; import org.mvplugins.multiverse.inventories.dataimport.DataImporter; @@ -39,7 +39,7 @@ * Multiverse-Inventories plugin main class. */ @Service -public class MultiverseInventories extends MultiversePlugin { +public final class MultiverseInventories extends MultiversePlugin { private static final int PROTOCOL = 50; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java similarity index 98% rename from src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java rename to src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java index 12e16bb4..bf831f2e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.commandtools; +package org.mvplugins.multiverse.inventories.command; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java similarity index 97% rename from src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java rename to src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java index e47db16b..a3a8cbf7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandConditions.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.commandtools; +package org.mvplugins.multiverse.inventories.command; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java similarity index 98% rename from src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java rename to src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java index dd98879f..155f3872 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commandtools/MVInvCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.commandtools; +package org.mvplugins.multiverse.inventories.command; import com.google.common.base.Strings; import org.jvnet.hk2.annotations.Service; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java index 55379059..03230fa0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java @@ -9,7 +9,7 @@ import org.bukkit.conversations.Prompt; import org.mvplugins.multiverse.inventories.util.MVInvi18n; -public class GroupControlPrompt extends InventoriesPrompt { +public final class GroupControlPrompt extends InventoriesPrompt { public GroupControlPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { super(plugin, issuer); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java index 355cd3c0..f34fcd87 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java @@ -11,7 +11,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; -class GroupCreatePrompt extends InventoriesPrompt { +final class GroupCreatePrompt extends InventoriesPrompt { public GroupCreatePrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { super(plugin, issuer); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java index 885e9792..d08c1bf6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java @@ -12,7 +12,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; -class GroupDeletePrompt extends InventoriesPrompt { +final class GroupDeletePrompt extends InventoriesPrompt { public GroupDeletePrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { super(plugin, issuer); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java index 63ab32ec..6cc281a9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java @@ -12,7 +12,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; -class GroupEditPrompt extends InventoriesPrompt { +final class GroupEditPrompt extends InventoriesPrompt { public GroupEditPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { super(plugin, issuer); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java index 347404b2..2138793e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java @@ -11,7 +11,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; -class GroupModifyPrompt extends InventoriesPrompt { +final class GroupModifyPrompt extends InventoriesPrompt { protected final WorldGroup group; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java index 4387a4c0..a3d230c5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -15,7 +15,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; -class GroupSharesPrompt extends InventoriesPrompt { +final class GroupSharesPrompt extends InventoriesPrompt { protected final WorldGroup group; protected final Prompt nextPrompt; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java index ffff3d19..45c2ea37 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java @@ -17,7 +17,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; -class GroupWorldsPrompt extends InventoriesPrompt { +final class GroupWorldsPrompt extends InventoriesPrompt { protected final WorldGroup group; protected final Prompt nextPrompt; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java index b85d1a88..54fb88f5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java @@ -14,7 +14,7 @@ import java.util.Objects; @Service -public class PerWorldInventoryImporter extends AbstractDataImporter { +final class PerWorldInventoryImporter extends AbstractDataImporter { private final InventoriesConfig inventoriesConfig; private final WorldManager worldManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 657a4b87..b2003497 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -47,7 +47,7 @@ import java.util.Set; import java.util.UUID; -class PwiImportHelper { +final class PwiImportHelper { private final PerWorldInventoryAPI pwiAPI; private final InventoriesConfig inventoriesConfig; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java index 75d3c697..fb94f830 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java @@ -6,7 +6,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; -public class GameModeChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { +public final class GameModeChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { private static final HandlerList HANDLERS = new HandlerList(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index 414a8857..d81617af 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -11,7 +11,7 @@ /** * Called when a player has changed from one world to another. Cancellable. */ -public abstract class ShareHandlingEvent extends Event implements Cancellable { +public abstract sealed class ShareHandlingEvent extends Event implements Cancellable permits WorldChangeShareHandlingEvent, GameModeChangeShareHandlingEvent { private boolean cancelled; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java index 4602d052..a1bc66cd 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java @@ -5,7 +5,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; -public class WorldChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { +public final class WorldChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { private static final HandlerList HANDLERS = new HandlerList(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index f57c155c..3a65f791 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -109,7 +109,7 @@ private void updatePersistingProfile(PersistingProfile persistingProfile, Profil + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")" + " for player " + playerProfile.getPlayer().getName()); - playerProfile.updateFromSnapshot(snapshot, persistingProfile.getShares()); + playerProfile.update(snapshot, persistingProfile.getShares()); profileDataStore.updatePlayerData(playerProfile); }); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java index dbcd74b9..7a9167cb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java @@ -30,11 +30,11 @@ public Map getData() { return data; } - public void updateFromSnapshot(ProfileDataSnapshot snapshot) { + public void update(ProfileData snapshot) { this.data.putAll(snapshot.getData()); } - public void updateFromSnapshot(ProfileDataSnapshot snapshot, Shares shares) { + public void update(ProfileData snapshot, Shares shares) { shares.forEach(sharable -> { Object data = snapshot.getData().get(sharable); if (data != null) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java index a15b8cda..b8ff1046 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -55,6 +55,6 @@ public static Object serialize(ItemStack itemStack) { } private ItemStackConverter() { - // no instantiation + throw new IllegalStateException(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java index 827dd6fe..2a2b707b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java @@ -6,6 +6,9 @@ import org.mvplugins.multiverse.external.acf.locales.MessageKey; import org.mvplugins.multiverse.external.acf.locales.MessageKeyProvider; +/** + * Locales keys for Multiverse-Inventories + */ public enum MVInvi18n implements MessageKeyProvider { TEST_STRING, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java index a4bf794a..0fa4771d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java @@ -16,8 +16,6 @@ public final class MinecraftTools { private static final int TICKS_PER_SECOND = 20; - private MinecraftTools() { } - /** * Converts an amount of seconds to the appropriate amount of ticks. * @@ -82,4 +80,8 @@ public static ItemStack[] fillWithAir(ItemStack[] items) { Logging.warning("Unable to anchor, respawn may not work as expected!"); return respawnLocation; } + + private MinecraftTools() { + throw new IllegalStateException(); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java index 679d7db7..1bafb4b2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java @@ -3,7 +3,7 @@ /** * A collection of values relating to a Minecraft player. */ -public class PlayerStats { +public final class PlayerStats { /** * Number of slots in Minecraft player inventory. From f906395704877f2f7c976a20575e1b25d7fe1097 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 18:17:31 +0800 Subject: [PATCH 131/180] Update for latest core api changes --- .../inventories/MultiverseInventories.java | 10 +++++----- .../inventories/command/MVInvCommandCompletion.java | 6 +++--- .../inventories/command/MVInvCommandConditions.java | 2 +- .../inventories/command/MVInvCommandContexts.java | 2 +- .../commands/AddDisabledSharesCommand.java | 4 ++-- .../inventories/commands/AddSharesCommand.java | 4 ++-- .../inventories/commands/AddWorldsCommand.java | 4 ++-- .../inventories/commands/CacheCommand.java | 4 ++-- .../inventories/commands/ConfigCommand.java | 4 ++-- .../inventories/commands/CreateGroupCommand.java | 4 ++-- .../inventories/commands/DeleteGroupCommand.java | 8 ++++---- .../inventories/commands/GroupCommand.java | 4 ++-- .../inventories/commands/ImportCommand.java | 8 ++++---- .../multiverse/inventories/commands/InfoCommand.java | 4 ++-- .../inventories/commands/InventoriesCommand.java | 4 ++-- .../multiverse/inventories/commands/ListCommand.java | 4 ++-- .../inventories/commands/ReloadCommand.java | 4 ++-- .../commands/RemoveDisabledSharesCommand.java | 4 ++-- .../inventories/commands/RemoveSharesCommand.java | 4 ++-- .../inventories/commands/RemoveWorldsCommand.java | 4 ++-- .../inventories/commands/ToggleCommand.java | 4 ++-- .../inventories/commands/UsageCommand.java | 2 +- .../commands/prompts/GroupControlPrompt.java | 2 +- .../commands/prompts/GroupCreatePrompt.java | 2 +- .../commands/prompts/GroupDeletePrompt.java | 2 +- .../commands/prompts/GroupEditPrompt.java | 2 +- .../commands/prompts/GroupModifyPrompt.java | 2 +- .../commands/prompts/GroupSharesPrompt.java | 2 +- .../commands/prompts/GroupWorldsPrompt.java | 2 +- .../commands/prompts/InventoriesPrompt.java | 4 ++-- .../inventories/config/InventoriesConfig.java | 10 +++++----- .../inventories/config/InventoriesConfigNodes.java | 12 ++++++------ .../destination/LastLocationDestination.java | 2 +- .../profile/group/AbstractWorldGroupManager.java | 4 ++-- .../inventories/profile/group/WorldGroupManager.java | 3 +-- .../profile/group/YamlWorldGroupManager.java | 2 +- .../mvplugins/multiverse/inventories/LocaleTest.kt | 2 +- 37 files changed, 75 insertions(+), 76 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 15f44733..4ac2eb03 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -28,7 +28,7 @@ import org.mvplugins.multiverse.inventories.util.ItemStackConverter; import org.mvplugins.multiverse.inventories.util.Perm; import org.bukkit.Bukkit; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jakarta.inject.Provider; @@ -41,7 +41,7 @@ @Service public final class MultiverseInventories extends MultiversePlugin { - private static final int PROTOCOL = 50; + private static final double TARGET_CORE_API_VERSION = 5.0; @Inject private Provider commandManager; @@ -129,7 +129,7 @@ public final void onEnable() { this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); Logging.config("Version %s (API v%s) Enabled - By %s", - this.getDescription().getVersion(), getTargetCoreProtocolVersion(), StringFormatter.joinAnd(this.getDescription().getAuthors())); + this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(this.getDescription().getAuthors())); } private void initializeDependencyInjection() { @@ -209,8 +209,8 @@ private void hookLuckPerms() { * {@inheritDoc} */ @Override - public int getTargetCoreProtocolVersion() { - return PROTOCOL; + public double getTargetCoreVersion() { + return TARGET_CORE_API_VERSION; } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java index bf831f2e..ba25c35a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java @@ -2,9 +2,9 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandCompletions; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.confighandle.PropertyModifyAction; +import org.mvplugins.multiverse.core.command.MVCommandCompletions; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java index a3a8cbf7..b6e28aea 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.command; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandIssuer; import org.mvplugins.multiverse.external.acf.commands.BukkitConditionContext; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java index 155f3872..5b0a0de0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -2,7 +2,7 @@ import com.google.common.base.Strings; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; import org.mvplugins.multiverse.external.acf.commands.CommandContexts; import org.mvplugins.multiverse.external.acf.commands.InvalidCommandArgument; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java index 768bc49c..4c510b98 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java @@ -2,8 +2,8 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java index 724b358b..f87f55d8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandIssuer; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java index 161b0c42..9ec6dce9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index 8d13683a..3e67b8d2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -3,8 +3,8 @@ import com.github.benmanes.caffeine.cache.stats.CacheStats; import org.bukkit.entity.Player; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java index 8a84d88e..a6600b28 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -2,8 +2,8 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.exceptions.MultiverseException; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java index 9800e83b..bcc30b26 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java index 17703501..975241a4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java @@ -1,10 +1,10 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.commandtools.queue.CommandQueueManager; -import org.mvplugins.multiverse.core.commandtools.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index a6793789..3167e856 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -1,13 +1,13 @@ package org.mvplugins.multiverse.inventories.commands; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.commands.prompts.GroupControlPrompt; import org.bukkit.command.CommandSender; import org.bukkit.conversations.Conversable; import org.bukkit.conversations.Conversation; import org.bukkit.conversations.ConversationFactory; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index c58bfd2e..e09248b1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -2,10 +2,10 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.commandtools.queue.CommandQueueManager; -import org.mvplugins.multiverse.core.commandtools.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index c88ec70f..bb69ea9b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -1,12 +1,12 @@ package org.mvplugins.multiverse.inventories.commands; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.bukkit.Bukkit; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java index cd905e12..83223463 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Contract; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.MultiverseCommand; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index f440af48..92b1d8dc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -1,9 +1,9 @@ package org.mvplugins.multiverse.inventories.commands; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index 2324a206..8a4f1e64 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java index cd1e6095..dcab48be 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java index b6f037ca..2e3e8515 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java index 7f728a08..539654f7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.commands; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index f7837626..0bc65f34 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -1,10 +1,10 @@ package org.mvplugins.multiverse.inventories.commands; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Shares; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java index 7eb96bff..a7e90118 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.CommandHelp; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java index 03230fa0..b3c44fdf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java @@ -2,7 +2,7 @@ import org.bukkit.ChatColor; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.bukkit.conversations.ConversationContext; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java index f34fcd87..e2cf9306 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java index d08c1bf6..a21022ba 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java index 6cc281a9..b700f608 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java index 2138793e..f60ec7f8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java index a3d230c5..408778ec 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java index 45c2ea37..0ce588c0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories.commands.prompts; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java index fd4c0115..1a85eb3f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java @@ -2,8 +2,8 @@ import org.bukkit.ChatColor; import org.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.locale.PluginLocales; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.inventories.MultiverseInventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 8c06652d..9b3d7019 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -2,11 +2,11 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.confighandle.CommentedConfigurationHandle; -import org.mvplugins.multiverse.core.confighandle.StringPropertyHandle; -import org.mvplugins.multiverse.core.confighandle.migration.ConfigMigrator; -import org.mvplugins.multiverse.core.confighandle.migration.MoveMigratorAction; -import org.mvplugins.multiverse.core.confighandle.migration.VersionMigrator; +import org.mvplugins.multiverse.core.config.handle.CommentedConfigurationHandle; +import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; +import org.mvplugins.multiverse.core.config.migration.ConfigMigrator; +import org.mvplugins.multiverse.core.config.migration.MoveMigratorAction; +import org.mvplugins.multiverse.core.config.migration.VersionMigrator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 7ada9db4..a861fe48 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -1,11 +1,11 @@ package org.mvplugins.multiverse.inventories.config; -import org.mvplugins.multiverse.core.confighandle.functions.NodeSerializer; -import org.mvplugins.multiverse.core.confighandle.node.ConfigHeaderNode; -import org.mvplugins.multiverse.core.confighandle.node.ConfigNode; -import org.mvplugins.multiverse.core.confighandle.node.ListConfigNode; -import org.mvplugins.multiverse.core.confighandle.node.Node; -import org.mvplugins.multiverse.core.confighandle.node.NodeGroup; +import org.mvplugins.multiverse.core.config.node.ConfigHeaderNode; +import org.mvplugins.multiverse.core.config.node.ConfigNode; +import org.mvplugins.multiverse.core.config.node.ListConfigNode; +import org.mvplugins.multiverse.core.config.node.Node; +import org.mvplugins.multiverse.core.config.node.NodeGroup; +import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java index dcc1305e..f1ad5d63 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java @@ -55,7 +55,7 @@ public LastLocationDestinationInstance getDestinationInstance(@Nullable String d @Override public @NotNull Collection suggestDestinations(@NotNull CommandSender commandSender, @Nullable String destinationParams) { return worldManager.getLoadedWorlds().stream() - .map(world -> new DestinationSuggestionPacket(world.getName(), world.getName())) + .map(world -> new DestinationSuggestionPacket(this, world.getName(), world.getName())) .toList(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index 69cd4083..fc477965 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -2,8 +2,8 @@ import com.dumptruckman.minecraft.util.Logging; import org.jvnet.hk2.annotations.Contract; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index a905a3e3..a75f8365 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -1,8 +1,7 @@ package org.mvplugins.multiverse.inventories.profile.group; -import org.bukkit.command.CommandSender; import org.jvnet.hk2.annotations.Contract; -import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.external.vavr.control.Try; import java.util.List; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index 09a17032..f0833a07 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -3,7 +3,7 @@ import com.dumptruckman.minecraft.util.Logging; import com.google.common.collect.Lists; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; import org.mvplugins.multiverse.external.jakarta.inject.Inject; diff --git a/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt index 2405130a..e2278ce0 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt @@ -1,7 +1,7 @@ package org.mvplugins.multiverse.inventories import org.mockbukkit.mockbukkit.entity.PlayerMock -import org.mvplugins.multiverse.core.commandtools.MVCommandManager +import org.mvplugins.multiverse.core.command.MVCommandManager import org.mvplugins.multiverse.core.locale.message.Message import org.mvplugins.multiverse.inventories.util.MVInvi18n import kotlin.test.BeforeTest From 20abba60e766aefbca6e0dd22c49040129fdfe1f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 29 Mar 2025 18:24:01 +0800 Subject: [PATCH 132/180] Spectator gamemode has their own profile --- .../multiverse/inventories/profile/ProfileTypes.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java index 39329c4a..2cfd1538 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java @@ -37,6 +37,11 @@ public static List getTypes() { */ public static final ProfileType ADVENTURE = createProfileType("ADVENTURE"); + /** + * The profile type for the SPECTATOR Game Mode. + */ + public static final ProfileType SPECTATOR = createProfileType("SPECTATOR"); + /** * Returns the appropriate ProfileType for the given game mode. * @@ -48,6 +53,7 @@ public static ProfileType forGameMode(GameMode mode) { case SURVIVAL -> SURVIVAL; case CREATIVE -> CREATIVE; case ADVENTURE -> ADVENTURE; + case SPECTATOR -> SPECTATOR; default -> SURVIVAL; }; } From d17179e1281da795f64f1f425218a65eccc8ef57 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 30 Mar 2025 19:47:20 +0800 Subject: [PATCH 133/180] Improve handling of save on exit and apply on login --- .../inventories/MultiverseInventories.java | 12 +--- .../config/InventoriesConfigNodes.java | 2 +- .../event/ReadOnlyShareHandlingEvent.java | 28 +++++++++ .../inventories/event/ShareHandlingEvent.java | 2 +- .../event/WriteOnlyShareHandlingEvent.java | 28 +++++++++ .../handleshare/ReadOnlyShareHandler.java | 38 +++++++++++++ .../handleshare/ShareHandleListener.java | 22 +++---- .../inventories/handleshare/ShareHandler.java | 4 +- .../handleshare/SingleShareWriter.java | 46 +++++++++------ .../handleshare/WriteOnlyShareHandler.java | 57 +++++++++++++++++++ .../inventories/profile/ProfileTypes.java | 15 +++++ .../profile/container/ProfileContainer.java | 10 +--- src/test/resources/config/fresh_config.yml | 2 +- 13 files changed, 213 insertions(+), 53 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 4ac2eb03..a0a6ee1e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -17,11 +17,9 @@ import org.mvplugins.multiverse.inventories.dataimport.DataImporter; import org.mvplugins.multiverse.inventories.destination.LastLocationDestination; import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; -import org.mvplugins.multiverse.inventories.handleshare.ShareHandlingUpdater; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; -import org.mvplugins.multiverse.inventories.handleshare.PersistingProfile; +import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -39,7 +37,7 @@ * Multiverse-Inventories plugin main class. */ @Service -public final class MultiverseInventories extends MultiversePlugin { +public class MultiverseInventories extends MultiversePlugin { private static final double TARGET_CORE_API_VERSION = 5.0; @@ -153,11 +151,7 @@ public void onDisable() { for (final Player player : getServer().getOnlinePlayers()) { final String world = player.getWorld().getName(); if (inventoriesConfig.get().getSavePlayerdataOnQuit()) { - ShareHandlingUpdater.updateProfile(this, player, new PersistingProfile( - Sharables.allOf(), - profileContainerStoreProvider.get().getStore(ContainerType.WORLD) - .getContainer(world) - .getPlayerData(player))); + new WriteOnlyShareHandler(this, player).handleSharing(); if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { profileDataSource.get().modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true)); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index a861fe48..95e3bd90 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -158,7 +158,7 @@ public Object serialize(Shares sharables, Class aClass) { final ConfigNode savePlayerdataOnQuit = node(ConfigNode.builder("performance.save-playerdata-on-quit", Boolean.class) .comment("This option may be useful if you want an up-to-date offline copy of the playerdata within mvinv.") .comment("However, this will result in minor performance overhead on every player quit.") - .defaultValue(false) + .defaultValue(true) .name("save-playerdata-on-quit") .build()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java new file mode 100644 index 00000000..f756f6d5 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; + +public final class ReadOnlyShareHandlingEvent extends ShareHandlingEvent { + + private static final HandlerList HANDLERS = new HandlerList(); + + /** + * Gets the handler list. This is required by the event system. + * @return A list of HANDLERS. + */ + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public ReadOnlyShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { + super(player, affectedProfiles); + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index d81617af..882f7f5c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -11,7 +11,7 @@ /** * Called when a player has changed from one world to another. Cancellable. */ -public abstract sealed class ShareHandlingEvent extends Event implements Cancellable permits WorldChangeShareHandlingEvent, GameModeChangeShareHandlingEvent { +public abstract sealed class ShareHandlingEvent extends Event implements Cancellable permits GameModeChangeShareHandlingEvent, ReadOnlyShareHandlingEvent, WorldChangeShareHandlingEvent, WriteOnlyShareHandlingEvent { private boolean cancelled; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java new file mode 100644 index 00000000..803f63a4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; + +public final class WriteOnlyShareHandlingEvent extends ShareHandlingEvent { + + private static final HandlerList HANDLERS = new HandlerList(); + + /** + * Gets the handler list. This is required by the event system. + * @return A list of HANDLERS. + */ + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public WriteOnlyShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { + super(player, affectedProfiles); + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java new file mode 100644 index 00000000..294285e3 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java @@ -0,0 +1,38 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.event.ReadOnlyShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; + +final class ReadOnlyShareHandler extends ShareHandler { + ReadOnlyShareHandler(MultiverseInventories inventories, Player player) { + super(inventories, player); + } + + @Override + protected void prepareProfiles() { + List worldGroups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()); + Shares unhandledShares = Sharables.enabledOf(); + for (WorldGroup worldGroup : worldGroups) { + affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getPlayerData(player), worldGroup.getApplicableShares()); + unhandledShares.removeAll(worldGroup.getApplicableShares()); + } + if (!unhandledShares.isEmpty()) { + affectedProfiles.addReadProfile( + worldProfileContainerStore.getContainer(player.getWorld().getName()).getPlayerData(player), + unhandledShares + ); + } + } + + @Override + protected ShareHandlingEvent createEvent() { + return new ReadOnlyShareHandlingEvent(player, affectedProfiles); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index ab34710a..63faf4de 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -109,14 +109,8 @@ void playerJoin(final PlayerJoinEvent event) { verifyCorrectPlayerName(player.getUniqueId(), player.getName()); final GlobalProfile globalProfile = profileDataSource.getGlobalProfileNow(player); - final String world = globalProfile.getLastWorld(); if (config.getApplyPlayerdataOnJoin() && globalProfile.shouldLoadOnLogin()) { - ShareHandlingUpdater.updatePlayer(inventories, player, new PersistingProfile( - Sharables.allOf(), - profileContainerStoreProvider.getStore(ContainerType.WORLD) - .getContainer(world) - .getPlayerData(player) - )); + new ReadOnlyShareHandler(inventories, player).handleSharing(); } globalProfile.setLoadOnLogin(false); verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); @@ -153,21 +147,21 @@ private void verifyCorrectPlayerName(UUID uuid, String name) { void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); + CompletableFuture globalProfile = profileDataSource.getGlobalProfile(player); globalProfile.thenAccept(p -> p.setLastWorld(world)); + + // Write last location as its possible for players to join at a different world + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION) + .write(player.getLocation().clone(), !config.getSavePlayerdataOnQuit()); + if (config.getSavePlayerdataOnQuit()) { - ShareHandlingUpdater.updateProfile(inventories, player, new PersistingProfile( - Sharables.allOf(), - profileContainerStoreProvider.getStore(ContainerType.WORLD) - .getContainer(world) - .getPlayerData(player) - )); + new WriteOnlyShareHandler(inventories, player).handleSharing(); if (config.getApplyPlayerdataOnJoin()) { globalProfile.thenAccept(p -> p.setLoadOnLogin(true)); } } globalProfile.thenAccept(profileDataSource::updateGlobalProfile); - SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); } private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 3a65f791..ceb7c757 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -17,7 +17,7 @@ /** * Abstract class for handling sharing of data between worlds and game modes. */ -sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShareHandler { +sealed abstract class ShareHandler permits GameModeShareHandler, ReadOnlyShareHandler, WorldChangeShareHandler, WriteOnlyShareHandler { protected final Player player; protected final AffectedProfiles affectedProfiles; @@ -45,7 +45,7 @@ sealed abstract class ShareHandler permits WorldChangeShareHandler, GameModeShar * Finalizes the transfer from one world to another. This handles the switching * inventories/stats for a player and persisting the changes. */ - final void handleSharing() { + public final void handleSharing() { long startTime = System.nanoTime(); this.prepareProfiles(); ShareHandlingEvent event = this.createEvent(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 80a5c555..33b893d3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -6,32 +6,43 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharable; import java.util.Objects; +import java.util.concurrent.CompletableFuture; /** * Write a single share to the relevant world and group profiles. * * @param The sharable type. */ -final class SingleShareWriter { +public final class SingleShareWriter { public static SingleShareWriter of(MultiverseInventories inventories, Player player, Sharable sharable) { - return new SingleShareWriter<>(inventories, player, sharable); + return new SingleShareWriter<>(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player), sharable); + } + + public static SingleShareWriter of(MultiverseInventories inventories, Player player, String worldName, ProfileType profileType, Sharable sharable) { + return new SingleShareWriter<>(inventories, player, worldName, profileType, sharable); } private final MultiverseInventories inventories; private final Player player; + private final String worldName; + private final ProfileType profileType; private final Sharable sharable; private final ProfileDataSource profileDataSource; - private SingleShareWriter(MultiverseInventories inventories, Player player, Sharable sharable) { + private SingleShareWriter(MultiverseInventories inventories, Player player, String worldName, ProfileType profileType, Sharable sharable) { this.inventories = inventories; this.player = player; + this.worldName = worldName; + this.profileType = profileType; this.sharable = sharable; this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); } @@ -40,40 +51,41 @@ public void write(T value) { write(value, false); } - public void write(T value, boolean save) { + public CompletableFuture write(T value, boolean save) { if (sharable.isOptional() && !inventories.getServiceLocator().getService(InventoriesConfig.class).getActiveOptionalShares().contains(sharable)) { Logging.finer("Skipping write for optional share: " + sharable); - return; + return CompletableFuture.completedFuture(null); } Logging.finer("Writing single share: " + sharable.getNames()[0]); - String worldName = this.player.getWorld().getName(); var profileContainerStoreProvider = this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class); profileContainerStoreProvider.getStore(ContainerType.WORLD) .getContainer(worldName) - .getPlayerData(this.player) + .getPlayerData(profileType, this.player) .thenAccept(profile -> writeNewValueToProfile(profile, value, save)); - this.inventories.getServiceLocator().getService(WorldGroupManager.class) + return CompletableFuture.allOf(this.inventories.getServiceLocator().getService(WorldGroupManager.class) .getGroupsForWorld(worldName) - .forEach(worldGroup -> { + .stream() + .map(worldGroup -> { if (!worldGroup.getApplicableShares().contains(sharable)) { - return; + return CompletableFuture.completedFuture(null); } - worldGroup.getGroupProfileContainer().getPlayerData(this.player).thenAccept(profile -> { - writeNewValueToProfile(profile, value, save); - }); - }); + return worldGroup.getGroupProfileContainer().getPlayerData(profileType, this.player) + .thenCompose(profile -> writeNewValueToProfile(profile, value, save)); + }) + .toArray(CompletableFuture[]::new)); } - private void writeNewValueToProfile(PlayerProfile profile, T value, boolean save) { + private CompletableFuture writeNewValueToProfile(PlayerProfile profile, T value, boolean save) { if (Objects.equals(profile.get(sharable), value)) { - return; + return CompletableFuture.completedFuture(null); } Logging.finest("Writing %s value: %s for profile %s", sharable, value, profile); profile.set(sharable, value); if (save) { - profileDataSource.updatePlayerData(profile); + return profileDataSource.updatePlayerData(profile); } + return CompletableFuture.completedFuture(null); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java new file mode 100644 index 00000000..383d34b6 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java @@ -0,0 +1,57 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.WriteOnlyShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; + +public final class WriteOnlyShareHandler extends ShareHandler { + + private final String worldName; + private final ProfileType profileType; + + public WriteOnlyShareHandler(MultiverseInventories inventories, Player player) { + this(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player)); + } + + public WriteOnlyShareHandler(MultiverseInventories inventories, Player player, String worldName, ProfileType profileType) { + super(inventories, player); + this.worldName = worldName; + this.profileType = profileType; + } + + @Override + protected void prepareProfiles() { + List worldGroups = worldGroupManager.getGroupsForWorld(worldName); + + Shares unhandledShares = Sharables.enabledOf(); + for (WorldGroup worldGroup : worldGroups) { + affectedProfiles.addWriteProfile( + worldGroup.getGroupProfileContainer().getPlayerData(profileType, player), + worldGroup.getApplicableShares() + ); + unhandledShares.removeAll(worldGroup.getApplicableShares()); + } + Shares sharesToWrite = inventoriesConfig.getAlwaysWriteWorldProfile() + ? Sharables.enabled() + : unhandledShares; + if (!sharesToWrite.isEmpty()) { + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(worldName).getPlayerData(profileType, player), + sharesToWrite + ); + } + } + + @Override + protected ShareHandlingEvent createEvent() { + return new WriteOnlyShareHandlingEvent(player, affectedProfiles); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java index 2cfd1538..cd3ba8ff 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java @@ -1,6 +1,9 @@ package org.mvplugins.multiverse.inventories.profile; import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import java.util.ArrayList; import java.util.List; @@ -11,6 +14,11 @@ public final class ProfileTypes { private static final List types = new ArrayList<>(); + private static InventoriesConfig config; + + public static void init(MultiverseInventories plugin) { + config = plugin.getServiceLocator().getService(InventoriesConfig.class); + } private static ProfileType createProfileType(String name) { ProfileType type = ProfileType.createProfileType(name); @@ -42,6 +50,13 @@ public static List getTypes() { */ public static final ProfileType SPECTATOR = createProfileType("SPECTATOR"); + public static ProfileType forPlayer(Player player) { + if (config != null && config.getEnableGamemodeShareHandling()) { + return forGameMode(player.getGameMode()); + } + return SURVIVAL; + } + /** * Returns the appropriate ProfileType for the given game mode. * diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index fa717833..420d4a47 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -32,10 +32,7 @@ public final class ProfileContainer { } public CompletableFuture getPlayerData(Player player) { - ProfileType type = config.getEnableGamemodeShareHandling() - ? ProfileTypes.forGameMode(player.getGameMode()) - : ProfileTypes.SURVIVAL; - return getPlayerData(type, player); + return getPlayerData(ProfileTypes.forPlayer(player), player); } public CompletableFuture getPlayerData(ProfileType profileType, OfflinePlayer player) { @@ -55,10 +52,7 @@ public CompletableFuture getPlayerData(ProfileType profileType, O * @return The profile for the given player. */ public PlayerProfile getPlayerDataNow(Player player) { - ProfileType type = config.getEnableGamemodeShareHandling() - ? ProfileTypes.forGameMode(player.getGameMode()) - : ProfileTypes.SURVIVAL; - return getPlayerDataNow(type, player); + return getPlayerDataNow(ProfileTypes.forPlayer(player), player); } /** diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml index 77727f68..3d5f7475 100644 --- a/src/test/resources/config/fresh_config.yml +++ b/src/test/resources/config/fresh_config.yml @@ -12,7 +12,7 @@ sharables: use-byte-serialization-for-inventory-data: false performance: - save-playerdata-on-quit: false + save-playerdata-on-quit: true apply-playerdata-on-join: false always-write-world-profile: true preload-data-on-join: From 09a3237270a6fde92db544b88c76d625e639e8d5 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:47:10 +0800 Subject: [PATCH 134/180] Implement give command --- .../inventories/MultiverseInventories.java | 3 +- .../inventories/commands/GiveCommand.java | 182 ++++++++++++++++++ .../handleshare/SingleShareReader.java | 53 +++++ .../handleshare/SingleShareWriter.java | 7 +- .../multiverse/inventories/InjectionTest.kt | 2 +- 5 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index a0a6ee1e..a9d58899 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -20,6 +20,7 @@ import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -93,6 +94,7 @@ public final void onEnable() { super.onEnable(); initializeDependencyInjection(); + ProfileTypes.init(this); Sharables.init(this); Perm.register(this); ItemStackConverter.init(this); @@ -149,7 +151,6 @@ public void onDisable() { super.onDisable(); for (final Player player : getServer().getOnlinePlayers()) { - final String world = player.getWorld().getName(); if (inventoriesConfig.get().getSavePlayerdataOnQuit()) { new WriteOnlyShareHandler(this, player).handleSharing(); if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java new file mode 100644 index 00000000..45d92cfe --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -0,0 +1,182 @@ +package org.mvplugins.multiverse.inventories.commands; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.utils.REPatterns; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +@CommandAlias("mvinv") +final class GiveCommand extends InventoriesCommand { + + private final MultiverseInventories inventories; + private final ProfileDataSource profileDataSource; + private final InventoriesConfig inventoriesConfig; + + @Inject + GiveCommand( + @NotNull MVCommandManager commandManager, + @NotNull MultiverseInventories inventories, + @NotNull ProfileDataSource profileDataSource, + @NotNull InventoriesConfig inventoriesConfig + ) { + super(commandManager); + this.inventories = inventories; + this.profileDataSource = profileDataSource; + this.inventoriesConfig = inventoriesConfig; + } + + @CommandAlias("mvinvgive") + @Subcommand("give") + @CommandPermission("multiverse.inventories.give") + @CommandCompletion("@players @mvworlds @gamemodes @materials @range:64") + @Syntax("") + @Description("World and Group Information") + void onGiveCommand( + MVCommandIssuer issuer, + OfflinePlayer player, + MultiverseWorld world, + GameMode gameMode, + String item + ) { + if (!player.isOnline() && !inventoriesConfig.getSavePlayerdataOnQuit()) { + issuer.sendError("You need to enable save-playerdata-on-quit in your config to give an offline player inventory items."); + return; + } + + ItemStack itemStack = parseItemFromString(issuer, item); + if (itemStack == null) { + return; + } + Logging.finer("Giving player " + player.getName() + " item: " + itemStack); + + // Giving online player in same world + // TODO check for gamemode as well if gamemode-profile is enabled. + Player onlinePlayer = player.getPlayer(); + if (onlinePlayer != null && world.getName().equals(onlinePlayer.getWorld().getName())) { + onlinePlayer.getInventory().addItem(itemStack); + issuer.sendInfo("Gave player %s %s %s in world %s." + .formatted(player.getName(), itemStack.getAmount(), itemStack, world.getName())); + return; + } + + ProfileType profileType = ProfileTypes.forGameMode(gameMode); + SingleShareReader.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) + .read() + .thenCompose(inventory -> updatePlayerInventory(issuer, player, world, profileType, inventory, itemStack)) + .exceptionally(throwable -> { + issuer.sendError(throwable.getMessage()); + return null; + }); + } + + private ItemStack parseItemFromString(MVCommandIssuer issuer, String item) { + // Get amount + int amount = 1; + AtomicBoolean endIsAmount = new AtomicBoolean(false); + int lastSpace = item.lastIndexOf(' '); + if (lastSpace != -1) { + String amountString = item.substring(lastSpace + 1); + amount = Try.of(() -> Integer.parseInt(amountString)) + .peek(ignore -> endIsAmount.set(true)) + .getOrElse(1); + } + if (amount < 1) { + issuer.sendError("You have to give at least 1 item."); + return null; + } + if (amount > 6400) { + issuer.sendError("Cannot give more than 6400 items at once."); + return null; + } + // Remove amount string from item + if (endIsAmount.get()) { + item = item.substring(0, lastSpace); + } + + // Get material + String[] split = REPatterns.get("\\[").split(item, 2); + String itemName = split[0]; + Material material = Material.matchMaterial(itemName); + if (material == null) { + issuer.sendError("Invalid Material: " + split[0]); + return null; + } + + // Create item and parse additional vanilla component data + ItemStack itemStack = new ItemStack(material, amount); + if (split.length < 2) { + return itemStack; + } + String additionalData = split[1]; + return Try.of(() -> Bukkit.getUnsafe().modifyItemStack(itemStack, itemStack.getType().getKey() + "[" + additionalData)) + .onFailure(throwable -> issuer.sendError(throwable.getMessage())) + .getOrNull(); + } + + private CompletableFuture updatePlayerInventory( + MVCommandIssuer issuer, + OfflinePlayer player, + MultiverseWorld world, + ProfileType profileType, + ItemStack[] inventory, + ItemStack itemStack + ) { + putItemInInventory(inventory, itemStack); + return SingleShareWriter.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) + .write(inventory, true) + .thenCompose(ignore -> player.isOnline() + ? CompletableFuture.completedFuture(null) + : profileDataSource.modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true))) + .thenRun(() -> issuer.sendInfo("Gave player %s %s %s in world %s." + .formatted(player.getName(), itemStack.getAmount(), itemStack.getI18NDisplayName(), world.getName()))); + } + + private void putItemInInventory(ItemStack[] inventory, ItemStack itemStack) { + int amountLeft = itemStack.getAmount(); + for (int i = 0; i < inventory.length; i++) { + if (inventory[i] == null || inventory[i].getType() == Material.AIR) { + int amountToGive = Math.min(amountLeft, itemStack.getMaxStackSize()); + inventory[i] = itemStack.clone(); + inventory[i].setAmount(amountToGive); + amountLeft -= amountToGive; + } else if (inventory[i].isSimilar(itemStack)) { + int amountToGive = Math.min(amountLeft, itemStack.getMaxStackSize() - inventory[i].getAmount()); + inventory[i].setAmount(inventory[i].getAmount() + amountToGive); + amountLeft -= amountToGive; + } + + if (amountLeft == 0) { + break; + } + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java new file mode 100644 index 00000000..24a32724 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.OfflinePlayer; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharable; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public final class SingleShareReader { + + public static SingleShareReader of(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { + return new SingleShareReader<>(inventories, player, worldName, profileType, sharable); + } + + private final MultiverseInventories inventories; + private final OfflinePlayer player; + private final String worldName; + private final ProfileType profileType; + private final Sharable sharable; + + public SingleShareReader(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { + this.inventories = inventories; + this.player = player; + this.worldName = worldName; + this.profileType = profileType; + this.sharable = sharable; + } + + public CompletableFuture read() { + WorldGroupManager worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + List worldGroups = worldGroupManager.getGroupsForWorld(worldName); + for (WorldGroup worldGroup : worldGroups) { + if (worldGroup.isSharing(sharable)) { + return getSharableFromProfile(ContainerType.GROUP, worldGroup.getName()); + } + } + return getSharableFromProfile(ContainerType.WORLD, worldName); + } + + private CompletableFuture getSharableFromProfile(ContainerType containerType, String containerName) { + return this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class) + .getStore(containerType) + .getContainer(containerName) + .getPlayerData(profileType, player) + .thenApply(playerProfile -> playerProfile.get(sharable)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 33b893d3..73de6ab2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; @@ -27,18 +28,18 @@ public static SingleShareWriter of(MultiverseInventories inventories, Pla return new SingleShareWriter<>(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player), sharable); } - public static SingleShareWriter of(MultiverseInventories inventories, Player player, String worldName, ProfileType profileType, Sharable sharable) { + public static SingleShareWriter of(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { return new SingleShareWriter<>(inventories, player, worldName, profileType, sharable); } private final MultiverseInventories inventories; - private final Player player; + private final OfflinePlayer player; private final String worldName; private final ProfileType profileType; private final Sharable sharable; private final ProfileDataSource profileDataSource; - private SingleShareWriter(MultiverseInventories inventories, Player player, String worldName, ProfileType profileType, Sharable sharable) { + private SingleShareWriter(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { this.inventories = inventories; this.player = player; this.worldName = worldName; diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 157d701f..1b5f9b22 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(17, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(18, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From d75fa6dd0d7e73d88904d9ce8fdb8feb9d633a03 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:12:44 +0800 Subject: [PATCH 135/180] Fix GameModeShareHandler profile type logic error --- .../inventories/handleshare/GameModeShareHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 8e167c93..95ba52b3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -58,7 +58,7 @@ protected void prepareProfiles() { } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { // Write to world profile to ensure data is saved incase bypass is removed affectedProfiles.addWriteProfile( - worldProfileContainerStore.getContainer(world).getPlayerData(player), + worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), (worldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) ? Sharables.standard() : Sharables.enabled() @@ -84,15 +84,15 @@ private void addProfiles() { worldGroups.forEach(worldGroup -> addProfilesForWorldGroup(handledShares,worldGroup)); Shares unhandledShares = Sharables.enabledOf().setSharing(handledShares, false); if (!unhandledShares.isEmpty()) { - affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), unhandledShares); + affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), unhandledShares); } if (inventoriesConfig.getAlwaysWriteWorldProfile()) { - affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), inventoriesConfig.getUseOptionalsForUngroupedWorlds() ? Sharables.enabled() : Sharables.standard()); } else { if (!unhandledShares.isEmpty()) { - affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), unhandledShares); + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), unhandledShares); } } } From 3b25d3d9e8ab9ded83d5e62d197e38360d85b3bc Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:13:16 +0800 Subject: [PATCH 136/180] Initial implementation of nbt migration --- .../inventories/MultiverseInventories.java | 2 +- .../inventories/commands/BulkEditCommand.java | 80 +++++++++++++++++++ .../inventories/handleshare/ShareHandler.java | 2 +- .../handleshare/ShareHandlingUpdater.java | 6 +- .../profile/FlatFileProfileDataSource.java | 64 +++++++++++++-- .../inventories/profile/PlayerProfile.java | 33 ++++---- .../profile/ProfileDataSource.java | 8 ++ .../inventories/profile/ProfileKey.java | 2 +- .../profile/container/ProfileContainer.java | 5 ++ .../multiverse/inventories/InjectionTest.kt | 2 +- 10 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index a9d58899..0d8b7955 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -98,7 +98,7 @@ public final void onEnable() { Sharables.init(this); Perm.register(this); ItemStackConverter.init(this); - Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); + Logging.warning("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); this.reloadConfig(); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java new file mode 100644 index 00000000..7c243c95 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java @@ -0,0 +1,80 @@ +package org.mvplugins.multiverse.inventories.commands; + +import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; + +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@Service +@CommandAlias("mvinv") +final class BulkEditCommand extends InventoriesCommand { + + private final ProfileDataSource profileDataSource; + private final InventoriesConfig inventoriesConfig; + + @Inject + BulkEditCommand(MVCommandManager commandManager, ProfileDataSource profileDataSource, InventoriesConfig inventoriesConfig) { + super(commandManager); + this.profileDataSource = profileDataSource; + this.inventoriesConfig = inventoriesConfig; + } + + @Subcommand("bulkedit migrate inventory-serialization nbt") + @CommandPermission("multiverse.inventories.bulkedit") + void onBulkEditCommand(MVCommandIssuer issuer) { + inventoriesConfig.setUseByteSerializationForInventoryData(true); + inventoriesConfig.save(); + + Collection globalPlayersList = profileDataSource.getGlobalPlayersList(); + Collection worldContainerNames = profileDataSource.getContainerNames(ContainerType.WORLD); + Collection groupContainerNames = profileDataSource.getContainerNames(ContainerType.GROUP); + Logging.fine(String.join(", ", worldContainerNames)); + CompletableFuture.allOf(globalPlayersList + .stream() + .map(playerUUID -> profileDataSource.getGlobalProfile(playerUUID, "") + .thenApply(profile -> { + profile.setLoadOnLogin(true); + profileDataSource.updateGlobalProfile(profile); + return profile; + }) + .thenCompose(profile -> { + return CompletableFuture.allOf(worldContainerNames.stream().flatMap(worldName -> { + return ProfileTypes.getTypes().stream().map(profileType -> { + return profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.WORLD, worldName, profileType, profile.getPlayerUUID(), profile.getLastKnownName()) + ).thenCompose(profileDataSource::updatePlayerData); + }); + }).toArray(CompletableFuture[]::new)).thenCompose(ignore -> { + return CompletableFuture.allOf(groupContainerNames.stream().flatMap(worldName -> { + return ProfileTypes.getTypes().stream().map(profileType -> { + return profileDataSource.getPlayerData( + ProfileKey.create(ContainerType.GROUP, worldName, profileType, profile.getPlayerUUID(), profile.getLastKnownName()) + ).thenCompose(profileDataSource::updatePlayerData); + }); + }).toArray(CompletableFuture[]::new)); + }); + }) + .exceptionally(throwable -> { + issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); + return null; + })) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> { + issuer.sendMessage("Bulk edit complete."); + issuer.sendMessage("Please restart your server to complete the migration."); + }); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index ceb7c757..de90d136 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -108,7 +108,7 @@ private void updatePersistingProfile(PersistingProfile persistingProfile, Profil Logging.finer("Persisted: " + persistingProfile.getShares() + " to " + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")" - + " for player " + playerProfile.getPlayer().getName()); + + " for player " + playerProfile.getPlayerName()); playerProfile.update(snapshot, persistingProfile.getShares()); profileDataStore.updatePlayerData(playerProfile); }); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index e3990e14..6e622e99 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -48,7 +48,7 @@ private void updateProfile() { Logging.finer("Persisted: " + profile.getShares() + " to " + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")" - + " for player " + playerProfile.getPlayer().getName()); + + " for player " + playerProfile.getPlayerName()); inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(playerProfile); }) .onFailure(e -> Logging.severe("Error getting playerdata: " + e.getMessage())); @@ -70,13 +70,13 @@ private void updatePlayer() { } if (!loaded.isEmpty()) { Logging.finer("Updated: " + loaded + " for " - + playerProfile.getPlayer().getName() + " for " + + playerProfile.getPlayerName() + " for " + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")"); } if (!defaulted.isEmpty()) { Logging.finer("Defaulted: " + defaulted + " for " - + playerProfile.getPlayer().getName() + " for " + + playerProfile.getPlayerName() + " for " + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")"); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index aadca0cd..dea9d75a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -29,8 +29,13 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -41,6 +46,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.logging.Level; +import java.util.stream.Stream; @Service final class FlatFileProfileDataSource implements ProfileDataSource { @@ -145,7 +151,7 @@ private File getProfileContainerFolder(ContainerType type, String folderName) { private FileConfiguration parseToConfiguration(File file) { JsonConfiguration jsonConfiguration = new JsonConfiguration(); - jsonConfiguration.options().continueOnSerializationError(true); + jsonConfiguration.options().continueOnSerializationError(false); Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { Logging.severe("Could not load file %s : %s", file, e.getMessage()); e.printStackTrace(); @@ -184,7 +190,7 @@ private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, Pla } playerData.createSection(playerProfile.getProfileType().getName(), serializedData); Try.run(() -> playerData.save(playerFile)).onFailure(e -> { - Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() + Logging.severe("Could not save data for player: " + playerProfile.getPlayerName() + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); e.printStackTrace(); }); @@ -244,8 +250,9 @@ public CompletableFuture getPlayerData(ProfileKey profileKey) { return playerProfileCache.get(profileKey, (key, executor) -> { File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); if (!playerFile.exists()) { + Logging.fine("Not found on disk: %s", playerFile); return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), - key.getProfileType(), Bukkit.getOfflinePlayer(key.getPlayerUUID()))); + key.getProfileType(), key.getPlayerUUID(), key.getPlayerName())); } Logging.finer("%s not cached. loading from disk...", profileKey); return profileFileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); @@ -308,7 +315,7 @@ private Map convertSection(ConfigurationSection section) { private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); + pKey.getProfileType(), pKey.getPlayerUUID(), pKey.getPlayerName()); for (Object keyObj : playerData.keySet()) { String key = keyObj.toString(); final Object value = playerData.get(key); @@ -354,7 +361,7 @@ private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); } else { Logging.warning("Could not parse stat: '" + key + "' for player '" - + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" + + profile.getPlayerName() + "' for " + profile.getContainerType() + " '" + profile.getContainerName() + "'"); } } @@ -368,11 +375,11 @@ private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile try { jsonStats = (JSONObject) new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE).parse(stats); } catch (ParseException | ClassCastException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); } if (jsonStats == null) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + + Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + profile.getContainerType() + " '" + profile.getContainerName() + "'"); return; } @@ -504,7 +511,7 @@ public CompletableFuture getGlobalProfile(UUID playerUUID, String try { File globalFile = getGlobalFile(playerUUID.toString()); return globalProfileCache.get(playerUUID, (key, executor) -> { - Logging.finer("Global profile for player %s not in cached. Loading...", playerName); + Logging.finer("Global profile for player %s (%s) not in cached. Loading...", playerUUID, playerName); // Migrate from player name to uuid profile file File legacyFile = getGlobalFile(playerName); if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { @@ -620,4 +627,45 @@ public Map getCacheStats() { stats.put("profileCache", playerProfileCache.synchronous().stats()); return stats; } + + @Override + public Collection getGlobalPlayersList() { + try { + return Files.list(playerFolder.toPath()) + .filter(Files::isRegularFile) + .map(path -> UUID.fromString(com.google.common.io.Files.getNameWithoutExtension(path.toFile().getName()))) + .toList(); + } catch (IOException e) { + Logging.warning("Could not list global players: " + e.getMessage()); + return Collections.emptyList(); + } + } + + @Override + public Collection getContainerPlayersList(ContainerType containerType, String containerName) { + try (Stream filesList = Files.list(getProfileContainerFolder(containerType, containerName).toPath())) { + return filesList.filter(Files::isRegularFile) + .map(path -> com.google.common.io.Files.getNameWithoutExtension(path.toFile().getName())) + .toList(); + } catch (IOException e) { + Logging.warning("Could not list players for " + containerType + " '" + containerName + "': " + e.getMessage()); + return Collections.emptyList(); + } + } + + @Override + public Collection getContainerNames(ContainerType containerType) { + File folder = switch (containerType) { + case GROUP -> this.groupFolder; + case WORLD -> this.worldFolder; + }; + try (Stream folderList = Files.list(folder.toPath())) { + return folderList.filter(Files::isDirectory) + .map(path -> path.toFile().getName()) + .toList(); + } catch (IOException e) { + Logging.warning("Could not list " + containerType + " folders: " + e.getMessage()); + return Collections.emptyList(); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 25f2cac3..79367340 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -1,12 +1,8 @@ package org.mvplugins.multiverse.inventories.profile; -import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import org.bukkit.OfflinePlayer; -import org.mvplugins.multiverse.inventories.share.Sharables; -import java.util.HashMap; -import java.util.Map; +import java.util.UUID; /** * Contains all the world/group specific data for a player. @@ -14,21 +10,23 @@ public final class PlayerProfile extends ProfileDataSnapshot { static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, - ProfileType profileType, OfflinePlayer player) { - return new PlayerProfile(containerType, containerName, profileType, player); + ProfileType profileType, UUID playerUUID, String playerName) { + return new PlayerProfile(containerType, containerName, profileType, playerUUID, playerName); } - private final OfflinePlayer player; private final ContainerType containerType; private final String containerName; private final ProfileType profileType; + private final UUID playerUUID; + private final String playerName; - private PlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, OfflinePlayer player) { + private PlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, UUID playerUUID, String playerName) { super(); this.containerType = containerType; this.profileType = profileType; this.containerName = containerName; - this.player = player; + this.playerUUID = playerUUID; + this.playerName = playerName; } /** @@ -46,10 +44,17 @@ public String getContainerName() { } /** - * @return the Player associated with this profile. + * @return the Player uuid associated with this profile. */ - public OfflinePlayer getPlayer() { - return this.player; + public UUID getPlayerUUID() { + return this.playerUUID; + } + + /** + * @return the Player name associated with this profile. + */ + public String getPlayerName() { + return this.playerName; } /** @@ -66,7 +71,7 @@ public PlayerProfile clone() { @Override public String toString() { return "PlayerProfile{" + - "player=" + player.getName() + + "player=" + playerName + ", containerType=" + containerType + ", containerName='" + containerName + '\'' + ", profileType=" + profileType + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index e569922f..e422a881 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -5,8 +5,10 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import java.io.IOException; +import java.util.Collection; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -138,5 +140,11 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @return The cache stats. */ Map getCacheStats(); + + Collection getGlobalPlayersList(); + + Collection getContainerPlayersList(ContainerType containerType, String containerName); + + Collection getContainerNames(ContainerType containerType); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java index 3e2c51f1..3ffbe31c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java @@ -38,7 +38,7 @@ public static ProfileKey create( public static ProfileKey fromPlayerProfile(PlayerProfile profile) { return new ProfileKey(profile.getContainerType(), profile.getContainerName(), profile.getProfileType(), - profile.getPlayer().getUniqueId(), profile.getPlayer().getName()); + profile.getPlayerUUID(), profile.getPlayerName()); } private final ContainerType containerType; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 420d4a47..2cda6516 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -10,6 +10,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import java.util.Collection; import java.util.concurrent.CompletableFuture; /** @@ -99,6 +100,10 @@ public CompletableFuture removePlayerData(ProfileType profileType, Offline player.getUniqueId())); } + public Collection getPlayers() { + return profileDataSource.getContainerPlayersList(getContainerType(), getContainerName()); + } + /** * Returns the name of this profile container which is primarily used for persistence purposes. *

The name reflects the world name if this is a world profile container, or the arbitrary group name if diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 1b5f9b22..233a516e 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(18, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(19, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From 2ad41bfcf3f1fa8b7802b9e69309cc41b2a5d2f0 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 2 Apr 2025 22:53:08 +0800 Subject: [PATCH 137/180] Refactor out some FlatFileProfileDataSource functions into smaller individual classes --- .../inventories/MVEventsListener.java | 9 +- .../inventories/MultiverseInventories.java | 6 +- .../inventories/commands/BulkEditCommand.java | 6 +- .../inventories/commands/CacheCommand.java | 14 +- .../inventories/commands/GiveCommand.java | 4 +- .../inventories/commands/InfoCommand.java | 3 +- .../multiinv/MultiInvImportHelper.java | 4 +- .../perworldinventory/PwiImportHelper.java | 8 +- .../WorldInventoriesImportHelper.java | 4 +- .../LastLocationDestinationInstance.java | 2 +- .../handleshare/GameModeShareHandler.java | 4 +- .../handleshare/PersistingProfile.java | 1 - .../handleshare/ShareHandleListener.java | 6 +- .../inventories/handleshare/ShareHandler.java | 2 +- .../handleshare/ShareHandlingUpdater.java | 1 - .../handleshare/SingleShareReader.java | 4 +- .../handleshare/SingleShareWriter.java | 6 +- .../handleshare/WriteOnlyShareHandler.java | 4 +- .../{ProfileFileIO.java => AsyncFileIO.java} | 10 +- .../profile/FlatFileProfileDataSource.java | 398 +++--------------- .../inventories/profile/PlayerProfile.java | 3 +- .../profile/PlayerProfileJsonSerializer.java | 124 ++++++ .../profile/ProfileCacheManager.java | 108 +++++ .../profile/ProfileDataSource.java | 28 +- .../profile/ProfileFilesLocator.java | 143 +++++++ .../profile/container/ProfileContainer.java | 14 +- .../container/ProfileContainerStore.java | 1 + .../ProfileContainerStoreProvider.java | 1 + .../inventories/profile/group/WorldGroup.java | 3 +- .../{container => key}/ContainerType.java | 4 +- .../profile/{ => key}/ProfileKey.java | 4 +- .../profile/{ => key}/ProfileType.java | 2 +- .../profile/{ => key}/ProfileTypes.java | 2 +- .../handleshare/ShareHandlingUpdaterTest.kt | 6 +- .../profile/FilePerformanceTest.kt | 13 +- .../profile/ProfileDataSourceTest.kt | 4 +- 36 files changed, 527 insertions(+), 429 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ProfileFileIO.java => AsyncFileIO.java} (94%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java rename src/main/java/org/mvplugins/multiverse/inventories/profile/{container => key}/ContainerType.java (66%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ => key}/ProfileKey.java (96%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ => key}/ProfileType.java (93%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ => key}/ProfileTypes.java (97%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java index 6ed513da..a6145e40 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java @@ -10,6 +10,7 @@ import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; @@ -22,18 +23,18 @@ final class MVEventsListener implements Listener { private final MultiverseInventories inventories; private final InventoriesConfig config; private final WorldGroupManager worldGroupManager; - private final ProfileDataSource profileDataSource; + private final ProfileCacheManager profileCacheManager; @Inject MVEventsListener( @NotNull MultiverseInventories inventories, @NotNull InventoriesConfig config, @NotNull WorldGroupManager worldGroupManager, - @NotNull ProfileDataSource profileDataSource) { + @NotNull ProfileCacheManager profileCacheManager) { this.inventories = inventories; this.config = config; this.worldGroupManager = worldGroupManager; - this.profileDataSource = profileDataSource; + this.profileCacheManager = profileCacheManager; } /** @@ -53,7 +54,7 @@ void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { private String generateCacheStatsContent() { var builder = new StringBuilder(); - profileDataSource.getCacheStats().forEach((cacheName, stats) -> { + profileCacheManager.getCacheStats().forEach((cacheName, stats) -> { builder.append("# ").append(cacheName).append("\n") .append("- hits count: ").append(stats.hitCount()).append("\n") .append("- misses count: ").append(stats.missCount()).append("\n") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 0d8b7955..44d34c19 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -19,8 +19,9 @@ import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -60,6 +61,7 @@ public class MultiverseInventories extends MultiversePlugin { private Provider worldGroupManager; @Inject private Provider profileDataSource; + @Inject Provider profileCacheManager; @Inject private Provider profileContainerStoreProvider; @Inject @@ -235,7 +237,7 @@ public void reloadConfig() { profileContainerStoreProvider.get().clearCache(); if (profileDataSource.get() != null) { - profileDataSource.get().clearAllCache(); + profileCacheManager.get().clearAllCache(); } Logging.fine("Reloaded all config and groups!"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java index 7c243c95..1c10d65f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java @@ -10,9 +10,9 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileKey; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import java.util.Collection; import java.util.UUID; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index 3e67b8d2..5c207182 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -13,6 +13,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import java.util.Map; @@ -21,18 +22,18 @@ @CommandAlias("mvinv") final class CacheCommand extends InventoriesCommand { - private final ProfileDataSource profileDataSource; + private final ProfileCacheManager ProfileCacheManager; @Inject - CacheCommand(@NotNull MVCommandManager commandManager, @NotNull ProfileDataSource profileDataSource) { + CacheCommand(@NotNull MVCommandManager commandManager, @NotNull ProfileCacheManager ProfileCacheManager) { super(commandManager); - this.profileDataSource = profileDataSource; + this.ProfileCacheManager = ProfileCacheManager; } @Subcommand("cache stats") @CommandPermission("multiverse.inventories.cache.stats") void onCacheStatsCommand(MVCommandIssuer issuer) { - Map stats = this.profileDataSource.getCacheStats(); + Map stats = this.ProfileCacheManager.getCacheStats(); for (Map.Entry entry : stats.entrySet()) { issuer.sendMessage("Cache: " + entry.getKey()); issuer.sendMessage(" hits count: " + entry.getValue().hitCount()); @@ -49,7 +50,7 @@ void onCacheStatsCommand(MVCommandIssuer issuer) { @Subcommand("cache invalidate all") @CommandPermission("multiverse.inventories.cache.invalidate") void onCacheClearAllCommand(MVCommandIssuer issuer) { - this.profileDataSource.clearAllCache(); + this.ProfileCacheManager.clearAllCache(); } @Subcommand("cache invalidate player") @@ -61,6 +62,7 @@ void onCacheClearProfileCommand( @Flags("resolve=issuerAware") Player player) { - this.profileDataSource.clearProfileCache(key -> key.getPlayerUUID().equals(player.getUniqueId())); + this.ProfileCacheManager.clearPlayerProfileCache(key -> + key.getPlayerUUID().equals(player.getUniqueId())); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index 45d92cfe..681c1cad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -26,8 +26,8 @@ import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.share.Sharables; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index bb69ea9b..3adb504f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -1,8 +1,7 @@ package org.mvplugins.multiverse.inventories.commands; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.bukkit.Bukkit; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java index cbc1dd97..8fbc3c18 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java @@ -10,8 +10,8 @@ import org.mvplugins.multiverse.inventories.dataimport.DataImportException; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index b2003497..e0e2eaa5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -27,8 +27,8 @@ import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -205,10 +205,10 @@ private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throw private List getMVPlayerData( @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { List profiles = new ArrayList<>(); - profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.ProfileKey + profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.key.ProfileKey .create(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); for (var worldName : group.getWorlds()) { - profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.ProfileKey + profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.key.ProfileKey .create(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); } return profiles; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java index 1c74dfc7..88918308 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java @@ -13,8 +13,8 @@ import org.mvplugins.multiverse.inventories.dataimport.DataImportException; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java index 9d254389..c2d72049 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java @@ -8,7 +8,7 @@ import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.vavr.control.Option; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 95ba52b3..15118522 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -5,8 +5,8 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.event.GameModeChangeShareHandlingEvent; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java index 575f367d..c9ae3ad0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java @@ -3,7 +3,6 @@ import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.share.Shares; -import java.util.Objects; import java.util.concurrent.CompletableFuture; /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 63faf4de..e9fcb43a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -7,9 +7,9 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileKey; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index de90d136..b2485f86 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -6,7 +6,7 @@ import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.profile.ProfileDataSnapshot; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 6e622e99..7a88cd28 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -3,7 +3,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.share.Sharable; import org.bukkit.entity.Player; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java index 24a32724..394051a5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java @@ -2,8 +2,8 @@ import org.bukkit.OfflinePlayer; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 73de6ab2..7f30f394 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -7,9 +7,9 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharable; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java index 383d34b6..1906ab93 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java @@ -4,8 +4,8 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; import org.mvplugins.multiverse.inventories.event.WriteOnlyShareHandlingEvent; -import org.mvplugins.multiverse.inventories.profile.ProfileType; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java similarity index 94% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java index 2ecad391..5a465740 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java @@ -1,7 +1,8 @@ package org.mvplugins.multiverse.inventories.profile; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.external.vavr.CheckedRunnable; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import java.io.File; @@ -12,17 +13,18 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; -final class ProfileFileIO { +@Service +final class AsyncFileIO { private final ExecutorService fileIOExecutorService = Executors.newWorkStealingPool(); private final Map fileLocks = new ConcurrentHashMap<>(); - ProfileFileIO() { + @Inject + AsyncFileIO() { } CompletableFuture queueAction(File file, Runnable action) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index dea9d75a..95da5fb2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -2,25 +2,15 @@ import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; import com.dumptruckman.minecraft.util.Logging; -import com.github.benmanes.caffeine.cache.AsyncCache; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.stats.CacheStats; -import com.google.common.collect.Sets; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.external.vavr.control.Try; -import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.share.ProfileEntry; -import org.mvplugins.multiverse.inventories.share.Sharable; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; -import net.minidev.json.JSONObject; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; @@ -28,13 +18,8 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -44,109 +29,25 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.stream.Stream; @Service final class FlatFileProfileDataSource implements ProfileDataSource { private static final String JSON = ".json"; - private final ProfileFileIO profileFileIO; - - private final Cache playerFileCache; - private final AsyncCache playerProfileCache; - private final AsyncCache globalProfileCache; - - private final File worldFolder; - private final File groupFolder; - private final File playerFolder; + private final AsyncFileIO asyncFileIO; + private final ProfileFilesLocator profileFilesLocator; + private final ProfileCacheManager profileCacheManager; @Inject - FlatFileProfileDataSource(@NotNull MultiverseInventories plugin, @NotNull InventoriesConfig inventoriesConfig) throws IOException { - this.profileFileIO = new ProfileFileIO(); - - this.playerFileCache = Caffeine.newBuilder() - .expireAfterAccess(inventoriesConfig.getPlayerFileCacheExpiry(), TimeUnit.MINUTES) - .maximumSize(inventoriesConfig.getPlayerFileCacheSize()) - .recordStats() - .build(); - - this.playerProfileCache = Caffeine.newBuilder() - .expireAfterAccess(inventoriesConfig.getPlayerProfileCacheExpiry(), TimeUnit.MINUTES) - .maximumSize(inventoriesConfig.getPlayerProfileCacheSize()) - .executor(profileFileIO.getExecutor()) - .recordStats() - .buildAsync(); - - this.globalProfileCache = Caffeine.newBuilder() - .expireAfterAccess(inventoriesConfig.getGlobalProfileCacheExpiry(), TimeUnit.MINUTES) - .maximumSize(inventoriesConfig.getGlobalProfileCacheSize()) - .executor(profileFileIO.getExecutor()) - .recordStats() - .buildAsync(); - - // Make the data folders - plugin.getDataFolder().mkdirs(); - - // Check if the data file exists. If not, create it. - this.worldFolder = new File(plugin.getDataFolder(), "worlds"); - if (!this.worldFolder.exists()) { - if (!this.worldFolder.mkdirs()) { - throw new IOException("Could not create world folder!"); - } - } - this.groupFolder = new File(plugin.getDataFolder(), "groups"); - if (!this.groupFolder.exists()) { - if (!this.groupFolder.mkdirs()) { - throw new IOException("Could not create group folder!"); - } - } - this.playerFolder = new File(plugin.getDataFolder(), "players"); - if (!this.playerFolder.exists()) { - if (!this.playerFolder.mkdirs()) { - throw new IOException("Could not create player folder!"); - } - } - } - - /** - * Retrieves the data file for a player based on a given world/group name. - * - * @param profileKey The profile target to get the file - * @return The data file for a player. - */ - private File getPlayerFile(ProfileKey profileKey) { - return getPlayerFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); - } - - /** - * Retrieves the data file for a player based on a given world/group name. - * - * @param type Indicates whether data is for group or world. - * @param dataName The name of the group or world. - * @param playerName The name of the player. - * @return The data file for a player. - */ - private File getPlayerFile(ContainerType type, String dataName, String playerName) { - File jsonPlayerFile = new File(getProfileContainerFolder(type, dataName), playerName + JSON); - Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", - jsonPlayerFile.getPath(), type, dataName, playerName); - return jsonPlayerFile; - } - - private File getProfileContainerFolder(ContainerType type, String folderName) { - File folder = switch (type) { - case GROUP -> new File(this.groupFolder, folderName); - case WORLD -> new File(this.worldFolder, folderName); - default -> new File(this.worldFolder, folderName); - }; - - if (!folder.exists() && !folder.mkdirs()) { - Logging.severe("Could not create profile container folder!"); - } - return folder; + FlatFileProfileDataSource( + @NotNull AsyncFileIO asyncFileIO, + @NotNull ProfileFilesLocator profileFilesLocator, + @NotNull ProfileCacheManager profileCacheManager + ) { + this.asyncFileIO = asyncFileIO; + this.profileFilesLocator = profileFilesLocator; + this.profileCacheManager = profileCacheManager; } private FileConfiguration parseToConfiguration(File file) { @@ -163,7 +64,7 @@ private FileConfiguration parseToConfiguration(File file) { private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playerFile) { ProfileKey fileProfileKey = profileKey.forProfileType(null); return Try.of(() -> - playerFileCache.get(fileProfileKey, (key) -> playerFile.exists() + profileCacheManager.getOrLoadPlayerFile(fileProfileKey, (key) -> playerFile.exists() ? parseToConfiguration(playerFile) : new JsonConfiguration()) ).getOrElseThrow(e -> { @@ -178,13 +79,13 @@ private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playe @Override public CompletableFuture updatePlayerData(PlayerProfile playerProfile) { ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); - File playerFile = getPlayerFile(profileKey); - return profileFileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + return asyncFileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); } private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, PlayerProfile playerProfile) { FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); - Map serializedData = serializePlayerProfile(playerProfile); + Map serializedData = PlayerProfileJsonSerializer.serialize(playerProfile); if (serializedData.isEmpty()) { return; } @@ -196,39 +97,6 @@ private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, Pla }); } - private Map serializePlayerProfile(PlayerProfile playerProfile) { - Map playerData = new LinkedHashMap<>(); - JSONObject jsonStats = new JSONObject(); - - for (var entry : playerProfile.getData().entrySet()) { - Sharable sharable = entry.getKey(); - Object sharableValue = entry.getValue(); - if (sharableValue == null) { - continue; - } - - var serializer = sharable.getSerializer(); - var profileEntry = sharable.getProfileEntry(); - if (serializer == null || profileEntry == null) { - continue; - } - - String fileTag = profileEntry.fileTag(); - Object serializedValue = serializer.serialize(sharableValue); - if (profileEntry.isStat()) { - jsonStats.put(fileTag, serializedValue); - } else { - playerData.put(fileTag, serializedValue); - } - } - - if (!jsonStats.isEmpty()) { - playerData.put(DataStrings.PLAYER_STATS, jsonStats); - } - - return playerData; - } - /** * {@inheritDoc} */ @@ -247,15 +115,15 @@ public PlayerProfile getPlayerDataNow(ProfileKey profileKey) { @Override public CompletableFuture getPlayerData(ProfileKey profileKey) { try { - return playerProfileCache.get(profileKey, (key, executor) -> { - File playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); + return profileCacheManager.getOrLoadPlayerProfile(profileKey, (key, executor) -> { + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); if (!playerFile.exists()) { Logging.fine("Not found on disk: %s", playerFile); return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), key.getPlayerUUID(), key.getPlayerName())); } Logging.finer("%s not cached. loading from disk...", profileKey); - return profileFileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); + return asyncFileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); }); } catch (Exception e) { Logging.severe("Could not get data for player: " + profileKey.getPlayerName() @@ -282,7 +150,7 @@ private PlayerProfile getPlayerDataFromDisk(ProfileKey key, File playerFile) { if (section == null) { section = playerData.createSection(key.getProfileType().getName()); } - return deserializePlayerProfile(key, convertSection(section)); + return PlayerProfileJsonSerializer.deserialize(key, convertSection(section)); } @Deprecated @@ -313,88 +181,15 @@ private Map convertSection(ConfigurationSection section) { return resultMap; } - private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), pKey.getPlayerUUID(), pKey.getPlayerName()); - for (Object keyObj : playerData.keySet()) { - String key = keyObj.toString(); - final Object value = playerData.get(key); - if (value == null) { - Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); - continue; - } - - if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { - if (value instanceof String) { - parseJsonPlayerStatsIntoProfile((String) value, profile); - continue; - } - if (value instanceof Map) { - parsePlayerStatsIntoProfile((Map) value, profile); - } else { - Logging.warning("Could not parse stats for " + pKey.getPlayerName()); - } - continue; - } - - try { - Sharable sharable = ProfileEntry.lookup(false, key); - if (sharable == null) { - Logging.fine("Player fileTag '" + key + "' is unrecognized!"); - continue; - } - profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); - } catch (Exception e) { - Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); - Logging.getLogger().log(Level.FINE, "Exception: ", e); - e.printStackTrace(); - } - } - Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); - return profile; - } - - private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { - for (Object key : stats.keySet()) { - Sharable sharable = ProfileEntry.lookup(true, key.toString()); - if (sharable != null) { - profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); - } else { - Logging.warning("Could not parse stat: '" + key + "' for player '" - + profile.getPlayerName() + "' for " + profile.getContainerType() + " '" - + profile.getContainerName() + "'"); - } - } - } - - private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { - if (stats.isEmpty()) { - return; - } - JSONObject jsonStats = null; - try { - jsonStats = (JSONObject) new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE).parse(stats); - } catch (ParseException | ClassCastException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } - if (jsonStats == null) { - Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "'"); - return; - } - parsePlayerStatsIntoProfile(jsonStats, profile); - } - /** * {@inheritDoc} */ @Override public CompletableFuture removePlayerData(ProfileKey profileKey) { - File playerFile = getPlayerFile(profileKey); + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); if (profileKey.getProfileType() == null) { for (var type : ProfileTypes.getTypes()) { - Option.of(playerProfileCache.synchronous().getIfPresent(profileKey.forProfileType(type))) + profileCacheManager.getCachedPlayerProfile(profileKey.forProfileType(type)) .peek(profile -> profile.getData().clear()); } if (!playerFile.exists()) { @@ -402,10 +197,10 @@ public CompletableFuture removePlayerData(ProfileKey profileKey) { + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); return CompletableFuture.completedFuture(null); } - return profileFileIO.queueAction(playerFile, playerFile::delete); + return asyncFileIO.queueAction(playerFile, playerFile::delete); } - Option.of(playerProfileCache.synchronous().getIfPresent(profileKey)).peek(profile -> profile.getData().clear()); - return profileFileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); + profileCacheManager.getCachedPlayerProfile(profileKey).peek(profile -> profile.getData().clear()); + return asyncFileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); } private void processRemovePlayerData(ProfileKey profileKey, File playerFile) { @@ -424,26 +219,20 @@ private void processRemovePlayerData(ProfileKey profileKey, File playerFile) { * {@inheritDoc} */ @Override - public void migratePlayerData(String oldName, String newName, UUID uuid) throws IOException { - clearPlayerCache(uuid); + public void migratePlayerData(String oldName, String newName, UUID uuid) { + profileCacheManager.clearPlayerCache(uuid); - File[] worldFolders = worldFolder.listFiles(File::isDirectory); - if (worldFolders == null) { - throw new IOException("Could not enumerate world folders"); - } - File[] groupFolders = groupFolder.listFiles(File::isDirectory); - if (groupFolders == null) { - throw new IOException("Could not enumerate group folders"); - } + List worldFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.WORLD); + List groupFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.GROUP); migrateForContainerType(worldFolders, ContainerType.WORLD, oldName, newName); migrateForContainerType(groupFolders, ContainerType.GROUP, oldName, newName); } - private void migrateForContainerType(File[] folders, ContainerType containerType, String oldName, String newName) { + private void migrateForContainerType(List folders, ContainerType containerType, String oldName, String newName) { for (File folder : folders) { - File oldNameFile = getPlayerFile(containerType, folder.getName(), oldName); - File newNameFile = getPlayerFile(containerType, folder.getName(), newName); + File oldNameFile = profileFilesLocator.getPlayerProfileFile(containerType, folder.getName(), oldName); + File newNameFile = profileFilesLocator.getPlayerProfileFile(containerType, folder.getName(), newName); if (!oldNameFile.exists()) { Logging.fine("No old data for player %s in %s %s to migrate.", oldName, containerType.name(), folder.getName()); @@ -509,20 +298,20 @@ public CompletableFuture getGlobalProfile(OfflinePlayer player) { @Override public CompletableFuture getGlobalProfile(UUID playerUUID, String playerName) { try { - File globalFile = getGlobalFile(playerUUID.toString()); - return globalProfileCache.get(playerUUID, (key, executor) -> { - Logging.finer("Global profile for player %s (%s) not in cached. Loading...", playerUUID, playerName); - // Migrate from player name to uuid profile file - File legacyFile = getGlobalFile(playerName); - if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - - // Load from existing profile file - if (!globalFile.exists()) { - return CompletableFuture.completedFuture(GlobalProfile.createGlobalProfile(playerUUID, playerName)); - } - return profileFileIO.queueCallable(globalFile, () -> getGlobalProfileFromDisk(playerUUID, playerName, globalFile)); + File globalFile = profileFilesLocator.getGlobalFile(playerUUID.toString()); + return profileCacheManager.getOrLoadGlobalProfile(playerUUID, (key, executor) -> { + Logging.finer("Global profile for player %s (%s) not in cached. Loading...", playerUUID, playerName); + // Migrate from player name to uuid profile file + File legacyFile = profileFilesLocator.getGlobalFile(playerName); + if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } + + // Load from existing profile file + if (!globalFile.exists()) { + return CompletableFuture.completedFuture(GlobalProfile.createGlobalProfile(playerUUID, playerName)); + } + return asyncFileIO.queueCallable(globalFile, () -> getGlobalProfileFromDisk(playerUUID, playerName, globalFile)); }); } catch (Exception e) { Logging.severe("Unable to get global profile for player: " + playerName); @@ -533,7 +322,7 @@ public CompletableFuture getGlobalProfile(UUID playerUUID, String @NotNull @Override public CompletableFuture> getExistingGlobalProfile(UUID playerUUID, String playerName) { - File uuidFile = getGlobalFile(playerUUID.toString()); + File uuidFile = profileFilesLocator.getGlobalFile(playerUUID.toString()); if (!uuidFile.exists()) { return CompletableFuture.completedFuture(Option.none()); } @@ -541,7 +330,7 @@ public CompletableFuture> getExistingGlobalProfile(UUID pl } private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { - return legacyFile.renameTo(getGlobalFile(playerUUID.toString())); + return legacyFile.renameTo(profileFilesLocator.getGlobalFile(playerUUID.toString())); } private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { @@ -571,8 +360,8 @@ private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, */ @Override public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { - File globalFile = getGlobalFile(globalProfile.getPlayerUUID().toString()); - return profileFileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); + File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); + return asyncFileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); } private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalFile) { @@ -586,86 +375,27 @@ private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalF } } - /** - * Retrieves the data file for a player for their global data. - * - * @param fileName The name of the file (player name or UUID) without extension. - * @return The data file for a player. - */ - private File getGlobalFile(String fileName) { - return new File(playerFolder, fileName + JSON); - } - - void clearPlayerCache(UUID playerUUID) { - clearProfileCache(key -> key.getPlayerUUID().equals(playerUUID)); - } - - @Override - public void clearProfileCache(ProfileKey key) { - playerFileCache.invalidate(key); - playerProfileCache.synchronous().invalidate(key); - } - - @Override - public void clearProfileCache(Predicate predicate) { - playerFileCache.invalidateAll(Sets.filter(playerFileCache.asMap().keySet(), predicate::test)); - playerProfileCache.synchronous().invalidateAll(Sets.filter(playerProfileCache.asMap().keySet(), predicate::test)); - } - - @Override - public void clearAllCache() { - playerFileCache.invalidateAll(); - globalProfileCache.synchronous().invalidateAll(); - playerProfileCache.synchronous().invalidateAll(); - } - - @Override - public Map getCacheStats() { - Map stats = new HashMap<>(); - stats.put("playerFileCache", playerFileCache.stats()); - stats.put("globalProfileCache", globalProfileCache.synchronous().stats()); - stats.put("profileCache", playerProfileCache.synchronous().stats()); - return stats; - } - @Override public Collection getGlobalPlayersList() { - try { - return Files.list(playerFolder.toPath()) - .filter(Files::isRegularFile) - .map(path -> UUID.fromString(com.google.common.io.Files.getNameWithoutExtension(path.toFile().getName()))) - .toList(); - } catch (IOException e) { - Logging.warning("Could not list global players: " + e.getMessage()); - return Collections.emptyList(); - } + return profileFilesLocator.listGlobalFiles() + .stream() + .map(file -> UUID.fromString(com.google.common.io.Files.getNameWithoutExtension(file.getName()))) + .toList(); } @Override public Collection getContainerPlayersList(ContainerType containerType, String containerName) { - try (Stream filesList = Files.list(getProfileContainerFolder(containerType, containerName).toPath())) { - return filesList.filter(Files::isRegularFile) - .map(path -> com.google.common.io.Files.getNameWithoutExtension(path.toFile().getName())) - .toList(); - } catch (IOException e) { - Logging.warning("Could not list players for " + containerType + " '" + containerName + "': " + e.getMessage()); - return Collections.emptyList(); - } + return profileFilesLocator.listPlayerProfileFiles(containerType, containerName) + .stream() + .map(file -> com.google.common.io.Files.getNameWithoutExtension(file.getName())) + .toList(); } @Override public Collection getContainerNames(ContainerType containerType) { - File folder = switch (containerType) { - case GROUP -> this.groupFolder; - case WORLD -> this.worldFolder; - }; - try (Stream folderList = Files.list(folder.toPath())) { - return folderList.filter(Files::isDirectory) - .map(path -> path.toFile().getName()) - .toList(); - } catch (IOException e) { - Logging.warning("Could not list " + containerType + " folders: " + e.getMessage()); - return Collections.emptyList(); - } + return profileFilesLocator.listProfileContainerFolders(containerType) + .stream() + .map(File::getName) + .toList(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index 79367340..a64dd61a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.profile; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import java.util.UUID; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java new file mode 100644 index 00000000..44c93472 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java @@ -0,0 +1,124 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.share.ProfileEntry; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.util.DataStrings; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +final class PlayerProfileJsonSerializer { + + static Map serialize(PlayerProfile playerProfile) { + Map playerData = new LinkedHashMap<>(); + JSONObject jsonStats = new JSONObject(); + + for (var entry : playerProfile.getData().entrySet()) { + Sharable sharable = entry.getKey(); + Object sharableValue = entry.getValue(); + if (sharableValue == null) { + continue; + } + + var serializer = sharable.getSerializer(); + var profileEntry = sharable.getProfileEntry(); + if (serializer == null || profileEntry == null) { + continue; + } + + String fileTag = profileEntry.fileTag(); + Object serializedValue = serializer.serialize(sharableValue); + if (profileEntry.isStat()) { + jsonStats.put(fileTag, serializedValue); + } else { + playerData.put(fileTag, serializedValue); + } + } + + if (!jsonStats.isEmpty()) { + playerData.put(DataStrings.PLAYER_STATS, jsonStats); + } + + return playerData; + } + + static PlayerProfile deserialize(ProfileKey pKey, Map playerData) { + PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), + pKey.getProfileType(), pKey.getPlayerUUID(), pKey.getPlayerName()); + for (Object keyObj : playerData.keySet()) { + String key = keyObj.toString(); + final Object value = playerData.get(key); + if (value == null) { + Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + + if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { + if (value instanceof String) { + parseJsonPlayerStatsIntoProfile((String) value, profile); + continue; + } + if (value instanceof Map) { + parsePlayerStatsIntoProfile((Map) value, profile); + } else { + Logging.warning("Could not parse stats for " + pKey.getPlayerName()); + } + continue; + } + + try { + Sharable sharable = ProfileEntry.lookup(false, key); + if (sharable == null) { + Logging.fine("Player fileTag '" + key + "' is unrecognized!"); + continue; + } + profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); + } catch (Exception e) { + Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); + Logging.getLogger().log(Level.FINE, "Exception: ", e); + e.printStackTrace(); + } + } + Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); + return profile; + } + + private static void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { + for (Object key : stats.keySet()) { + Sharable sharable = ProfileEntry.lookup(true, key.toString()); + if (sharable != null) { + profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); + } else { + Logging.warning("Could not parse stat: '" + key + "' for player '" + + profile.getPlayerName() + "' for " + profile.getContainerType() + " '" + + profile.getContainerName() + "'"); + } + } + } + + private static void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { + if (stats.isEmpty()) { + return; + } + JSONObject jsonStats = null; + try { + jsonStats = (JSONObject) new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE).parse(stats); + } catch (ParseException | ClassCastException e) { + Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); + } + if (jsonStats == null) { + Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "'"); + return; + } + parsePlayerStatsIntoProfile(jsonStats, profile); + } + +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java new file mode 100644 index 00000000..dc989301 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java @@ -0,0 +1,108 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.github.benmanes.caffeine.cache.AsyncCache; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.stats.CacheStats; +import com.google.common.collect.Sets; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +@Service +public final class ProfileCacheManager { + + private final Cache playerFileCache; + private final AsyncCache playerProfileCache; + private final AsyncCache globalProfileCache; + + @Inject + ProfileCacheManager(@NotNull InventoriesConfig inventoriesConfig, @NotNull AsyncFileIO asyncFileIO) { + this.playerFileCache = Caffeine.newBuilder() + .expireAfterAccess(inventoriesConfig.getPlayerFileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getPlayerFileCacheSize()) + .recordStats() + .build(); + + this.playerProfileCache = Caffeine.newBuilder() + .expireAfterAccess(inventoriesConfig.getPlayerProfileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getPlayerProfileCacheSize()) + .executor(asyncFileIO.getExecutor()) + .recordStats() + .buildAsync(); + + this.globalProfileCache = Caffeine.newBuilder() + .expireAfterAccess(inventoriesConfig.getGlobalProfileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getGlobalProfileCacheSize()) + .executor(asyncFileIO.getExecutor()) + .recordStats() + .buildAsync(); + } + + FileConfiguration getOrLoadPlayerFile(ProfileKey key, Function mappingFunction) { + return playerFileCache.get(key, mappingFunction); + } + + CompletableFuture getOrLoadPlayerProfile(ProfileKey key, BiFunction> mappingFunction) { + return playerProfileCache.get(key, mappingFunction); + } + + CompletableFuture getOrLoadGlobalProfile(UUID uuid, BiFunction> mappingFunction) { + return globalProfileCache.get(uuid, mappingFunction); + } + + Option getCachedPlayerProfile(ProfileKey key) { + return Option.of(playerProfileCache.synchronous().getIfPresent(key)); + } + + public void clearPlayerCache(UUID playerUUID) { + clearPlayerProfileCache(key -> key.getPlayerUUID().equals(playerUUID)); + clearGlobalProfileCache(key -> key.equals(playerUUID)); + } + + public void clearPlayerProfileCache(Predicate predicate) { + playerFileCache.invalidateAll(Sets.filter(playerFileCache.asMap().keySet(), predicate::test)); + playerProfileCache.synchronous().invalidateAll(Sets.filter(playerProfileCache.asMap().keySet(), predicate::test)); + } + + public void clearAllPlayerProfileCaches() { + playerFileCache.invalidateAll(); + playerProfileCache.synchronous().invalidateAll(); + } + + public void clearGlobalProfileCache(Predicate predicate) { + globalProfileCache.synchronous().invalidateAll(Sets.filter(globalProfileCache.asMap().keySet(), predicate::test)); + } + + public void clearAllGlobalProfileCaches() { + globalProfileCache.synchronous().invalidateAll(); + } + + public void clearAllCache() { + playerFileCache.invalidateAll(); + globalProfileCache.synchronous().invalidateAll(); + playerProfileCache.synchronous().invalidateAll(); + } + + public Map getCacheStats() { + Map stats = new HashMap<>(); + stats.put("playerFileCache", playerFileCache.stats()); + stats.put("globalProfileCache", globalProfileCache.synchronous().stats()); + stats.put("profileCache", playerProfileCache.synchronous().stats()); + return stats; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index e422a881..910947e9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,19 +1,17 @@ package org.mvplugins.multiverse.inventories.profile; -import com.github.benmanes.caffeine.cache.stats.CacheStats; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Option; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import java.io.IOException; import java.util.Collection; -import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import java.util.function.Predicate; /** * A source for updating and retrieving player profiles via persistence. @@ -119,28 +117,6 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { */ CompletableFuture updateGlobalProfile(GlobalProfile globalProfile); - /** - * Clears a single profile in cache. - */ - void clearProfileCache(ProfileKey key); - - /** - * Clears all profiles in cache that match the predicate. - */ - void clearProfileCache(Predicate predicate); - - /** - * Clears all profiles in cache. - */ - void clearAllCache(); - - /** - * Gets the cache stats for the profile data source. - * - * @return The cache stats. - */ - Map getCacheStats(); - Collection getGlobalPlayersList(); Collection getContainerPlayersList(ContainerType containerType, String containerName); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java new file mode 100644 index 00000000..20f2f9d4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java @@ -0,0 +1,143 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Service +final class ProfileFilesLocator { + + private static final String JSON = ".json"; + + private final File worldFolder; + private final File groupFolder; + private final File globalFolder; + + @Inject + ProfileFilesLocator(@NotNull MultiverseInventories plugin) throws IOException { + // Make the data folders + plugin.getDataFolder().mkdirs(); + + // Check if the data file exists. If not, create it. + this.worldFolder = new File(plugin.getDataFolder(), "worlds"); + if (!this.worldFolder.exists()) { + if (!this.worldFolder.mkdirs()) { + throw new IOException("Could not create world folder!"); + } + } + this.groupFolder = new File(plugin.getDataFolder(), "groups"); + if (!this.groupFolder.exists()) { + if (!this.groupFolder.mkdirs()) { + throw new IOException("Could not create group folder!"); + } + } + this.globalFolder = new File(plugin.getDataFolder(), "players"); + if (!this.globalFolder.exists()) { + if (!this.globalFolder.mkdirs()) { + throw new IOException("Could not create player folder!"); + } + } + } + + File getWorldFolder() { + return worldFolder; + } + + File getGroupFolder() { + return groupFolder; + } + + File getContainerFolder(ContainerType type) { + return switch (type) { + case GROUP -> this.groupFolder; + case WORLD -> this.worldFolder; + }; + } + + List listProfileContainerFolders(ContainerType type) { + return Option.of(getContainerFolder(type).listFiles()) + .map(filesList -> Arrays.stream(filesList) + .filter(File::isDirectory) + .toList()) + .getOrElse(Collections::emptyList); + } + + File getProfileContainerFolder(ContainerType type, String folderName) { + File folder = new File(getContainerFolder(type), folderName); + if (!folder.exists() && !folder.mkdirs()) { + Logging.severe("Could not create profile container folder!"); + } + return folder; + } + + List listPlayerProfileFiles(ContainerType type, String dataName) { + return Option.of(getProfileContainerFolder(type, dataName).listFiles()) + .map(filesList -> Arrays.stream(filesList) + .filter(File::isFile) + .toList()) + .getOrElse(Collections::emptyList); + } + + /** + * Retrieves the data file for a player based on a given world/group name. + * + * @param profileKey The profile target to get the file + * @return The data file for a player. + */ + File getPlayerProfileFile(ProfileKey profileKey) { + return getPlayerProfileFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); + } + + /** + * Retrieves the data file for a player based on a given world/group name. + * + * @param type Indicates whether data is for group or world. + * @param dataName The name of the group or world. + * @param playerName The name of the player. + * @return The data file for a player. + */ + File getPlayerProfileFile(ContainerType type, String dataName, String playerName) { + File jsonPlayerFile = new File(getProfileContainerFolder(type, dataName), playerName + JSON); + Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", + jsonPlayerFile.getPath(), type, dataName, playerName); + return jsonPlayerFile; + } + + File getGlobalFolder() { + return this.globalFolder; + } + + List listGlobalFiles() { + return Option.of(this.globalFolder.listFiles()) + .map(filesList -> Arrays.stream(filesList) + .filter(File::isFile) + .toList()) + .getOrElse(Collections::emptyList); + } + + File getGlobalFile(UUID playerUUID) { + return getGlobalFile(playerUUID.toString()); + } + + /** + * Retrieves the data file for a player for their global data. + * + * @param playerIdentifier The name of the file (player name or UUID) without extension. + * @return The data file for a player. + */ + File getGlobalFile(String playerIdentifier) { + return new File(globalFolder, playerIdentifier + JSON); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 2cda6516..dc115af9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -2,11 +2,13 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.ProfileKey; -import org.mvplugins.multiverse.inventories.profile.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; -import org.mvplugins.multiverse.inventories.profile.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; @@ -23,13 +25,13 @@ public final class ProfileContainer { private final String name; private final ContainerType type; private final ProfileDataSource profileDataSource; - private final InventoriesConfig config; + private final ProfileCacheManager profileCacheManager; ProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { this.name = name; this.type = type; this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); - this.config = inventories.getServiceLocator().getService(InventoriesConfig.class); + this.profileCacheManager = inventories.getServiceLocator().getService(ProfileCacheManager.class); } public CompletableFuture getPlayerData(Player player) { @@ -128,7 +130,7 @@ public ContainerType getContainerType() { * Clears all cached data in the container. */ public void clearContainerCache() { - profileDataSource.clearProfileCache(key -> + profileCacheManager.clearPlayerProfileCache(key -> key.getContainerType().equals(type) && key.getDataName().equals(name)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java index 14f83748..62590a94 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.profile.container; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import java.util.Map; import java.util.WeakHashMap; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java index f3f8bd1a..630db106 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java @@ -4,6 +4,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import java.util.EnumMap; import java.util.Map; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index 2c2e27de..727f0697 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -1,9 +1,8 @@ package org.mvplugins.multiverse.inventories.profile.group; import com.dumptruckman.minecraft.util.Logging; -import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.share.Sharable; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ContainerType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerType.java similarity index 66% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/container/ContainerType.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerType.java index 8659e22a..93044852 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ContainerType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerType.java @@ -1,4 +1,6 @@ -package org.mvplugins.multiverse.inventories.profile.container; +package org.mvplugins.multiverse.inventories.profile.key; + +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; /** * Used to describe whether a {@link ProfileContainer} represents a single world or a group of worlds. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java similarity index 96% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java index 3ffbe31c..82b0ab86 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java @@ -1,11 +1,11 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.profile.key; import com.google.common.base.Objects; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.mvplugins.multiverse.inventories.profile.container.ContainerType; import org.bukkit.Bukkit; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import java.util.UUID; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java similarity index 93% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java index 47134579..4b129810 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.profile.key; /** * Used to differentiate between profiles in the same world or world group, primarily for game modes. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java similarity index 97% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java index cd3ba8ff..a071ef90 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.profile.key; import org.bukkit.GameMode; import org.bukkit.entity.Player; diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index 06b3e45d..d1a2f0f6 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -3,9 +3,9 @@ package org.mvplugins.multiverse.inventories.handleshare import org.mockbukkit.mockbukkit.entity.PlayerMock import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.ProfileDataSource -import org.mvplugins.multiverse.inventories.profile.ProfileKey -import org.mvplugins.multiverse.inventories.profile.ProfileTypes -import org.mvplugins.multiverse.inventories.profile.container.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes +import org.mvplugins.multiverse.inventories.profile.key.ContainerType import org.mvplugins.multiverse.inventories.share.Sharables import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index c5a52b6b..2bb352f0 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -12,7 +12,9 @@ import org.mvplugins.multiverse.core.utils.CoreLogging import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit -import org.mvplugins.multiverse.inventories.profile.container.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes import org.mvplugins.multiverse.inventories.share.Sharables import java.util.* import java.util.concurrent.CompletableFuture @@ -27,6 +29,7 @@ class FilePerformanceTest : TestWithMockBukkit() { private lateinit var worldManager: WorldManager private lateinit var profileDataSource: ProfileDataSource + private lateinit var profileCacheManager: ProfileCacheManager @BeforeTest fun setUp() { @@ -34,6 +37,8 @@ class FilePerformanceTest : TestWithMockBukkit() { throw IllegalStateException("WorldManager is not available as a service") } profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { throw IllegalStateException("ProfileDataSource is not available as a service") } + profileCacheManager = serviceLocator.getService(ProfileCacheManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileCacheManager is not available as a service") } CoreLogging.setDebugLevel(0); Logging.setDebugLevel(0) assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) @@ -86,7 +91,7 @@ class FilePerformanceTest : TestWithMockBukkit() { future.get() } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - profileDataSource.clearAllCache() + profileCacheManager.clearAllCache() val startTime2 = System.nanoTime() val futures2 = ArrayList>(1000) @@ -122,7 +127,7 @@ class FilePerformanceTest : TestWithMockBukkit() { } Logging.info("Time taken: " + (System.nanoTime() - startTime3) / 1000000 + "ms") - val cacheStats = profileDataSource.getCacheStats() + val cacheStats = profileCacheManager.getCacheStats() Logging.info(cacheStats.values.toString()) for (cacheStat in cacheStats) { Logging.info(cacheStat.key + ": " + cacheStat.value.averageLoadPenalty() / 1000000 + "ms") @@ -153,7 +158,7 @@ class FilePerformanceTest : TestWithMockBukkit() { server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } } Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") - val cacheStats = profileDataSource.getCacheStats() + val cacheStats = profileCacheManager.getCacheStats() for (cacheStat in cacheStats) { Logging.info(cacheStat.key + ": " + cacheStat.value.averageLoadPenalty() / 1000000 + "ms") } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt index 22ba2a19..3c4228d3 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt @@ -3,7 +3,9 @@ package org.mvplugins.multiverse.inventories.profile import com.dumptruckman.minecraft.util.Logging import org.junit.jupiter.api.Test import org.mvplugins.multiverse.inventories.TestWithMockBukkit -import org.mvplugins.multiverse.inventories.profile.container.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes import kotlin.test.BeforeTest class ProfileDataSourceTest : TestWithMockBukkit() { From 4c4118797bc4f830de64647206f8cb7a071adfd8 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 3 Apr 2025 20:13:59 +0800 Subject: [PATCH 138/180] Further refactor ProfileDataSource --- .../inventories/MultiverseInventories.java | 4 +- .../inventories/commands/BulkEditCommand.java | 17 +- .../inventories/commands/GiveCommand.java | 3 +- .../multiinv/MultiInvImportHelper.java | 6 +- .../perworldinventory/PwiImportHelper.java | 14 +- .../WorldInventoriesImportHelper.java | 4 +- .../LastLocationDestinationInstance.java | 4 +- .../handleshare/ShareHandleListener.java | 25 +- .../inventories/handleshare/ShareHandler.java | 2 +- .../handleshare/ShareHandlingUpdater.java | 2 +- .../handleshare/SingleShareWriter.java | 2 +- .../inventories/profile/AsyncFileIO.java | 32 +- .../profile/FlatFileProfileDataSource.java | 317 +++++++++--------- .../inventories/profile/GlobalProfile.java | 7 +- .../inventories/profile/PlayerProfile.java | 15 +- .../profile/PlayerProfileJsonSerializer.java | 3 +- .../profile/ProfileDataSource.java | 105 +++--- .../profile/ProfileFilesLocator.java | 3 +- .../profile/container/ProfileContainer.java | 39 +-- .../container/ProfileContainerStore.java | 24 +- .../profile/key/GlobalProfileKey.java | 59 ++++ .../profile/key/ProfileFileKey.java | 120 +++++++ .../inventories/profile/key/ProfileKey.java | 58 +--- .../inventories/util/FutureNow.java | 22 ++ .../handleshare/ShareHandlingUpdaterTest.kt | 7 +- .../profile/FilePerformanceTest.kt | 20 +- .../profile/PlayerNameChangeTest.kt | 4 +- .../profile/ProfileDataSourceTest.kt | 6 +- 28 files changed, 563 insertions(+), 361 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 44d34c19..2a83875b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -21,6 +21,7 @@ import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; @@ -156,7 +157,8 @@ public void onDisable() { if (inventoriesConfig.get().getSavePlayerdataOnQuit()) { new WriteOnlyShareHandler(this, player).handleSharing(); if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { - profileDataSource.get().modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true)); + profileDataSource.get().modifyGlobalProfile( + GlobalProfileKey.create(player), profile -> profile.setLoadOnLogin(true)); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java index 1c10d65f..3ddbc269 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java @@ -10,6 +10,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; @@ -38,13 +39,13 @@ void onBulkEditCommand(MVCommandIssuer issuer) { inventoriesConfig.setUseByteSerializationForInventoryData(true); inventoriesConfig.save(); - Collection globalPlayersList = profileDataSource.getGlobalPlayersList(); - Collection worldContainerNames = profileDataSource.getContainerNames(ContainerType.WORLD); - Collection groupContainerNames = profileDataSource.getContainerNames(ContainerType.GROUP); + Collection globalPlayersList = profileDataSource.listGlobalProfileUUIDs(); + Collection worldContainerNames = profileDataSource.listContainerDataNames(ContainerType.WORLD); + Collection groupContainerNames = profileDataSource.listContainerDataNames(ContainerType.GROUP); Logging.fine(String.join(", ", worldContainerNames)); CompletableFuture.allOf(globalPlayersList .stream() - .map(playerUUID -> profileDataSource.getGlobalProfile(playerUUID, "") + .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID)) .thenApply(profile -> { profile.setLoadOnLogin(true); profileDataSource.updateGlobalProfile(profile); @@ -53,16 +54,16 @@ void onBulkEditCommand(MVCommandIssuer issuer) { .thenCompose(profile -> { return CompletableFuture.allOf(worldContainerNames.stream().flatMap(worldName -> { return ProfileTypes.getTypes().stream().map(profileType -> { - return profileDataSource.getPlayerData( + return profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.WORLD, worldName, profileType, profile.getPlayerUUID(), profile.getLastKnownName()) - ).thenCompose(profileDataSource::updatePlayerData); + ).thenCompose(profileDataSource::updatePlayerProfile); }); }).toArray(CompletableFuture[]::new)).thenCompose(ignore -> { return CompletableFuture.allOf(groupContainerNames.stream().flatMap(worldName -> { return ProfileTypes.getTypes().stream().map(profileType -> { - return profileDataSource.getPlayerData( + return profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.GROUP, worldName, profileType, profile.getPlayerUUID(), profile.getLastKnownName()) - ).thenCompose(profileDataSource::updatePlayerData); + ).thenCompose(profileDataSource::updatePlayerProfile); }); }).toArray(CompletableFuture[]::new)); }); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index 681c1cad..fd41b739 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -26,6 +26,7 @@ import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -155,7 +156,7 @@ private CompletableFuture updatePlayerInventory( .write(inventory, true) .thenCompose(ignore -> player.isOnline() ? CompletableFuture.completedFuture(null) - : profileDataSource.modifyGlobalProfile(player, profile -> profile.setLoadOnLogin(true))) + : profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(player), profile -> profile.setLoadOnLogin(true))) .thenRun(() -> issuer.sendInfo("Gave player %s %s %s in world %s." .formatted(player.getName(), itemStack.getAmount(), itemStack.getI18NDisplayName(), world.getName()))); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java index 8fbc3c18..9ea2b492 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java @@ -106,10 +106,10 @@ private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader Logging.warning("Could not import player data for group: " + dataName); return; } - playerProfile = group.getGroupProfileContainer().getPlayerDataNow(ProfileTypes.SURVIVAL, player); + playerProfile = group.getGroupProfileContainer().getPlayerProfileNow(ProfileTypes.SURVIVAL, player); } else { playerProfile = profileContainerStoreProvider.getStore(type) - .getContainer(dataName).getPlayerDataNow(ProfileTypes.SURVIVAL, player); + .getContainer(dataName).getPlayerProfileNow(ProfileTypes.SURVIVAL, player); } MIInventoryInterface inventoryInterface = playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); @@ -121,7 +121,7 @@ private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader playerProfile.set(Sharables.TOTAL_EXPERIENCE, playerFileLoader.getTotalExperience()); playerProfile.set(Sharables.LEVEL, playerFileLoader.getLevel()); playerProfile.set(Sharables.FOOD_LEVEL, playerFileLoader.getHunger()); - profileDataSource.updatePlayerData(playerProfile); + profileDataSource.updatePlayerProfile(playerProfile); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index e0e2eaa5..55423e21 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -27,11 +27,13 @@ import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.FutureNow; import org.mvplugins.multiverse.inventories.util.PlayerStats; import java.io.File; @@ -180,7 +182,7 @@ private void saveMVDataForGroup(Group group) throws DataImportException { } private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throws DataImportException { - GlobalProfile globalProfile = profileDataSource.getGlobalProfileNow(offlinePlayer); + GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(offlinePlayer))); globalProfile.setLoadOnLogin(pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); profileDataSource.updateGlobalProfile(globalProfile); for (GameMode gameMode : GameMode.values()) { @@ -205,11 +207,11 @@ private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throw private List getMVPlayerData( @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { List profiles = new ArrayList<>(); - profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.key.ProfileKey - .create(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); + profiles.add(FutureNow.get(profileDataSource.getPlayerProfile(org.mvplugins.multiverse.inventories.profile.key.ProfileKey + .create(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); for (var worldName : group.getWorlds()) { - profiles.add(profileDataSource.getPlayerDataNow(org.mvplugins.multiverse.inventories.profile.key.ProfileKey - .create(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId()))); + profiles.add(FutureNow.get(profileDataSource.getPlayerProfile(org.mvplugins.multiverse.inventories.profile.key.ProfileKey + .create(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); } return profiles; } @@ -344,6 +346,6 @@ private void transferToMVPlayerData( // mvPlayerProfile.set(Sharables.OFF_HAND, pwiPlayerProfile); // mvPlayerProfile.set(Sharables.TOTAL_EXPERIENCE, pwiPlayerProfile); - profileDataSource.updatePlayerData(mvPlayerProfile); + profileDataSource.updatePlayerProfile(mvPlayerProfile); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java index 88918308..fcaa1d8f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java @@ -144,7 +144,7 @@ private Set getWorldsWithoutGroups() { } private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer profileContainer) { - PlayerProfile playerProfile = profileContainer.getPlayerDataNow(ProfileTypes.SURVIVAL, player); + PlayerProfile playerProfile = profileContainer.getPlayerProfileNow(ProfileTypes.SURVIVAL, player); WIPlayerInventory wiInventory = this.loadPlayerInventory(player, wiGroup); WIPlayerStats wiStats = this.loadPlayerStats(player, wiGroup); if (wiInventory != null) { @@ -159,7 +159,7 @@ private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer playerProfile.set(Sharables.EXHAUSTION, wiStats.getExhaustion()); playerProfile.set(Sharables.FOOD_LEVEL, wiStats.getFoodLevel()); } - profileDataSource.updatePlayerData(playerProfile); + profileDataSource.updatePlayerProfile(playerProfile); Logging.finest("Player's data imported successfully for group: " + profileContainer.getContainerName()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java index c2d72049..6c9748d1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java @@ -47,7 +47,7 @@ public final class LastLocationDestinationInstance extends DestinationInstance> profileFutures = new ArrayList<>(); - config.getPreloadDataOnJoinWorlds().forEach(worldName -> profileFutures.add(profileDataSource.getPlayerData( + config.getPreloadDataOnJoinWorlds().forEach(worldName -> profileFutures.add(profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.WORLD, worldName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); - config.getPreloadDataOnJoinGroups().forEach(groupName -> profileFutures.add(profileDataSource.getPlayerData( + config.getPreloadDataOnJoinGroups().forEach(groupName -> profileFutures.add(profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.GROUP, groupName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); Try.run(() -> CompletableFuture.allOf(profileFutures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS)) .onSuccess(ignore -> Logging.finer("Preloaded data for Player{name:'%s', uuid:'%s'}. Time taken: %4.4f ms", @@ -108,7 +110,7 @@ void playerJoin(final PlayerJoinEvent event) { // Just in case AsyncPlayerPreLoginEvent was still the old name verifyCorrectPlayerName(player.getUniqueId(), player.getName()); - final GlobalProfile globalProfile = profileDataSource.getGlobalProfileNow(player); + final GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player))); if (config.getApplyPlayerdataOnJoin() && globalProfile.shouldLoadOnLogin()) { new ReadOnlyShareHandler(inventories, player).handleSharing(); } @@ -118,7 +120,7 @@ void playerJoin(final PlayerJoinEvent event) { } private void verifyCorrectPlayerName(UUID uuid, String name) { - profileDataSource.getExistingGlobalProfileNow(uuid, name).peek(globalProfile -> { + FutureNow.get(profileDataSource.getExistingGlobalProfile(GlobalProfileKey.create(uuid, name))).peek(globalProfile -> { if (globalProfile.getLastKnownName().equals(name)) { return; } @@ -127,7 +129,7 @@ private void verifyCorrectPlayerName(UUID uuid, String name) { Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", uuid, globalProfile.getLastKnownName(), name); try { - profileDataSource.migratePlayerData(globalProfile.getLastKnownName(), name, uuid); + profileDataSource.migratePlayerProfileName(globalProfile.getLastKnownName(), name, uuid); } catch (IOException e) { Logging.severe("An error occurred while trying to migrate playerdata."); e.printStackTrace(); @@ -148,7 +150,7 @@ void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); - CompletableFuture globalProfile = profileDataSource.getGlobalProfile(player); + CompletableFuture globalProfile = profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)); globalProfile.thenAccept(p -> p.setLastWorld(world)); // Write last location as its possible for players to join at a different world @@ -215,7 +217,8 @@ void playerChangedWorld(PlayerChangedWorldEvent event) { } new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); - profileDataSource.modifyGlobalProfile(player, profile -> profile.setLastWorld(toWorld.getName())); + profileDataSource.modifyGlobalProfile( + GlobalProfileKey.create(player), profile -> profile.setLastWorld(toWorld.getName())); } /** @@ -248,10 +251,10 @@ void playerDeath(PlayerDeathEvent event) { Logging.finer("=== Handling PlayerDeathEvent for: " + event.getEntity().getName() + " ==="); String deathWorld = event.getEntity().getWorld().getName(); ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(deathWorld); - PlayerProfile profile = worldProfileContainer.getPlayerDataNow(event.getEntity()); + PlayerProfile profile = worldProfileContainer.getPlayerProfileNow(event.getEntity()); resetStatsOnDeath(event, profile); for (WorldGroup worldGroup : worldGroupManager.getGroupsForWorld(deathWorld)) { - profile = worldGroup.getGroupProfileContainer().getPlayerDataNow(event.getEntity()); + profile = worldGroup.getGroupProfileContainer().getPlayerProfileNow(event.getEntity()); resetStatsOnDeath(event, profile); } Logging.finer("=== Finished handling PlayerDeathEvent for: " + event.getEntity().getName() + "! ==="); @@ -264,7 +267,7 @@ private void resetStatsOnDeath(PlayerDeathEvent event, PlayerProfile profile) { if (config.getResetLastLocationOnDeath()) { profile.set(Sharables.LAST_LOCATION, null); } - profileDataSource.updatePlayerData(profile); + profileDataSource.updatePlayerProfile(profile); } @EventHandler(priority = EventPriority.MONITOR) @@ -280,7 +283,7 @@ void playerRespawn(PlayerRespawnEvent event) { () -> verifyCorrectWorld( player, player.getWorld().getName(), - profileDataSource.getGlobalProfileNow(player)), + FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)))), 2L); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index b2485f86..b64ca64b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -110,7 +110,7 @@ private void updatePersistingProfile(PersistingProfile persistingProfile, Profil + " (" + playerProfile.getProfileType() + ")" + " for player " + playerProfile.getPlayerName()); playerProfile.update(snapshot, persistingProfile.getShares()); - profileDataStore.updatePlayerData(playerProfile); + profileDataStore.updatePlayerProfile(playerProfile); }); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 7a88cd28..8055aa68 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -48,7 +48,7 @@ private void updateProfile() { + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")" + " for player " + playerProfile.getPlayerName()); - inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerData(playerProfile); + inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerProfile(playerProfile); }) .onFailure(e -> Logging.severe("Error getting playerdata: " + e.getMessage())); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 7f30f394..23866b55 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -85,7 +85,7 @@ private CompletableFuture writeNewValueToProfile(PlayerProfile profile, T Logging.finest("Writing %s value: %s for profile %s", sharable, value, profile); profile.set(sharable, value); if (save) { - return profileDataSource.updatePlayerData(profile); + return profileDataSource.updatePlayerProfile(profile); } return CompletableFuture.completedFuture(null); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java index 5a465740..267aab66 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java @@ -23,11 +23,27 @@ final class AsyncFileIO { private final ExecutorService fileIOExecutorService = Executors.newWorkStealingPool(); private final Map fileLocks = new ConcurrentHashMap<>(); - @Inject - AsyncFileIO() { + CompletableFuture queueAction(Runnable action) { + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { + Try.runRunnable(action) + .onFailure(future::completeExceptionally) + .onSuccess(ignore -> future.complete(null)); + }); + return future; } - CompletableFuture queueAction(File file, Runnable action) { + CompletableFuture queueCallable(Supplier supplier) { + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { + Try.ofSupplier(supplier) + .onFailure(future::completeExceptionally) + .onSuccess(future::complete); + }); + return future; + } + + CompletableFuture queueFileAction(File file, Runnable action) { CountDownLatch thisLatch = new CountDownLatch(1); CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); CompletableFuture future = new CompletableFuture<>(); @@ -41,7 +57,7 @@ CompletableFuture queueAction(File file, Runnable action) { return future; } - CompletableFuture queueCallable(File file, Supplier supplier) { + CompletableFuture queueFileCallable(File file, Supplier supplier) { CountDownLatch thisLatch = new CountDownLatch(1); CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); CompletableFuture future = new CompletableFuture<>(); @@ -67,14 +83,6 @@ private void waitForLock(File file, CountDownLatch toWaitLatch) { } } - T waitForData(File file, Supplier callable) { - try { - return queueCallable(file, callable).get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); - } - } - ExecutorService getExecutor() { return fileIOExecutorService; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 95da5fb2..0279acad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -2,39 +2,35 @@ import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; import com.dumptruckman.minecraft.util.Logging; -import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.exceptions.MultiverseException; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; -import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.mvplugins.multiverse.inventories.util.DataStrings; import java.io.File; import java.io.IOException; -import java.util.Collection; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @Service final class FlatFileProfileDataSource implements ProfileDataSource { - private static final String JSON = ".json"; - private final AsyncFileIO asyncFileIO; private final ProfileFilesLocator profileFilesLocator; private final ProfileCacheManager profileCacheManager; @@ -50,7 +46,7 @@ final class FlatFileProfileDataSource implements ProfileDataSource { this.profileCacheManager = profileCacheManager; } - private FileConfiguration parseToConfiguration(File file) { + private FileConfiguration loadFileToJsonConfiguration(File file) { JsonConfiguration jsonConfiguration = new JsonConfiguration(); jsonConfiguration.options().continueOnSerializationError(false); Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { @@ -61,11 +57,11 @@ private FileConfiguration parseToConfiguration(File file) { return jsonConfiguration; } - private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playerFile) { + private FileConfiguration getOrLoadPlayerProfileFile(ProfileKey profileKey, File playerFile) { ProfileKey fileProfileKey = profileKey.forProfileType(null); return Try.of(() -> profileCacheManager.getOrLoadPlayerFile(fileProfileKey, (key) -> playerFile.exists() - ? parseToConfiguration(playerFile) + ? loadFileToJsonConfiguration(playerFile) : new JsonConfiguration()) ).getOrElseThrow(e -> { Logging.severe("Could not load profile data for player: " + fileProfileKey); @@ -77,53 +73,16 @@ private FileConfiguration getOrLoadProfileFile(ProfileKey profileKey, File playe * {@inheritDoc} */ @Override - public CompletableFuture updatePlayerData(PlayerProfile playerProfile) { - ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); - File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); - return asyncFileIO.queueAction(playerFile, () -> processUpdatePlayerData(profileKey, playerFile, playerProfile.clone())); - } - - private void processUpdatePlayerData(ProfileKey profileKey, File playerFile, PlayerProfile playerProfile) { - FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); - Map serializedData = PlayerProfileJsonSerializer.serialize(playerProfile); - if (serializedData.isEmpty()) { - return; - } - playerData.createSection(playerProfile.getProfileType().getName(), serializedData); - Try.run(() -> playerData.save(playerFile)).onFailure(e -> { - Logging.severe("Could not save data for player: " + playerProfile.getPlayerName() - + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); - e.printStackTrace(); - }); - } - - /** - * {@inheritDoc} - */ - @Override - public PlayerProfile getPlayerDataNow(ProfileKey profileKey) { - try { - return getPlayerData(profileKey).get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public CompletableFuture getPlayerData(ProfileKey profileKey) { + public CompletableFuture getPlayerProfile(ProfileKey profileKey) { try { return profileCacheManager.getOrLoadPlayerProfile(profileKey, (key, executor) -> { File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); if (!playerFile.exists()) { Logging.fine("Not found on disk: %s", playerFile); - return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), - key.getProfileType(), key.getPlayerUUID(), key.getPlayerName())); + return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key)); } Logging.finer("%s not cached. loading from disk...", profileKey); - return asyncFileIO.queueCallable(playerFile, () -> getPlayerDataFromDisk(key, playerFile)); + return asyncFileIO.queueFileCallable(playerFile, () -> getPlayerProfileFromDisk(key, playerFile)); }); } catch (Exception e) { Logging.severe("Could not get data for player: " + profileKey.getPlayerName() @@ -132,8 +91,8 @@ public CompletableFuture getPlayerData(ProfileKey profileKey) { } } - private PlayerProfile getPlayerDataFromDisk(ProfileKey key, File playerFile) { - FileConfiguration playerData = getOrLoadProfileFile(key, playerFile); + private PlayerProfile getPlayerProfileFromDisk(ProfileKey key, File playerFile) { + FileConfiguration playerData = getOrLoadPlayerProfileFile(key, playerFile); // Migrate from none profile-type data if (migrateToProfileType(playerData)) { @@ -153,7 +112,6 @@ private PlayerProfile getPlayerDataFromDisk(ProfileKey key, File playerFile) { return PlayerProfileJsonSerializer.deserialize(key, convertSection(section)); } - @Deprecated private boolean migrateToProfileType(FileConfiguration config) { ConfigurationSection section = config.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { @@ -185,27 +143,42 @@ private Map convertSection(ConfigurationSection section) { * {@inheritDoc} */ @Override - public CompletableFuture removePlayerData(ProfileKey profileKey) { + public CompletableFuture updatePlayerProfile(PlayerProfile playerProfile) { + ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); - if (profileKey.getProfileType() == null) { - for (var type : ProfileTypes.getTypes()) { - profileCacheManager.getCachedPlayerProfile(profileKey.forProfileType(type)) - .peek(profile -> profile.getData().clear()); - } - if (!playerFile.exists()) { - Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() - + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); - return CompletableFuture.completedFuture(null); - } - return asyncFileIO.queueAction(playerFile, playerFile::delete); + return asyncFileIO.queueFileAction( + playerFile, + () -> savePlayerProfileToDisk(profileKey, playerFile, playerProfile.clone()) + ); + } + + private void savePlayerProfileToDisk(ProfileKey profileKey, File playerFile, PlayerProfile playerProfile) { + FileConfiguration playerData = getOrLoadPlayerProfileFile(profileKey, playerFile); + Map serializedData = PlayerProfileJsonSerializer.serialize(playerProfile); + if (serializedData.isEmpty()) { + return; } + playerData.createSection(playerProfile.getProfileType().getName(), serializedData); + Try.run(() -> playerData.save(playerFile)).onFailure(e -> { + Logging.severe("Could not save data for player: " + playerProfile.getPlayerName() + + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); + e.printStackTrace(); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture deletePlayerProfile(ProfileKey profileKey) { + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); profileCacheManager.getCachedPlayerProfile(profileKey).peek(profile -> profile.getData().clear()); - return asyncFileIO.queueAction(playerFile, () -> processRemovePlayerData(profileKey, playerFile)); + return asyncFileIO.queueFileAction(playerFile, () -> deletePlayerProfileFromDisk(profileKey, playerFile)); } - private void processRemovePlayerData(ProfileKey profileKey, File playerFile) { + private void deletePlayerProfileFromDisk(ProfileKey profileKey, File playerFile) { try { - FileConfiguration playerData = getOrLoadProfileFile(profileKey, playerFile); + FileConfiguration playerData = getOrLoadPlayerProfileFile(profileKey, playerFile); playerData.set(profileKey.getProfileType().getName(), null); playerData.save(playerFile); } catch (IOException e) { @@ -219,7 +192,30 @@ private void processRemovePlayerData(ProfileKey profileKey, File playerFile) { * {@inheritDoc} */ @Override - public void migratePlayerData(String oldName, String newName, UUID uuid) { + public CompletableFuture deletePlayerFile(ProfileFileKey profileKey) { + for (var type : ProfileTypes.getTypes()) { + profileCacheManager.getCachedPlayerProfile(profileKey.forProfileType(type)) + .peek(profile -> profile.getData().clear()); + } + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + if (!playerFile.exists()) { + Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); + return CompletableFuture.completedFuture(null); + } + return asyncFileIO.queueFileAction(playerFile, () -> { + if (!playerFile.delete()) { + Logging.warning("Could not delete file for player " + profileKey.getPlayerName() + + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void migratePlayerProfileName(String oldName, String newName, UUID uuid) { profileCacheManager.clearPlayerCache(uuid); List worldFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.WORLD); @@ -253,88 +249,46 @@ private void migrateForContainerType(List folders, ContainerType container } } - @NotNull - @Override - public GlobalProfile getGlobalProfileNow(UUID playerUUID) { - return getGlobalProfileNow(Bukkit.getOfflinePlayer(playerUUID)); - } - - @NotNull - @Override - public GlobalProfile getGlobalProfileNow(OfflinePlayer player) { - return getGlobalProfileNow(player.getUniqueId(), player.getName()); - } - - @NotNull - @Override - public GlobalProfile getGlobalProfileNow(UUID playerUUID, String playerName) { - try { - return getGlobalProfile(playerUUID, playerName).get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); - } - } - - @Override - public @NotNull Option getExistingGlobalProfileNow(UUID playerUUID, String playerName) { - try { - return getExistingGlobalProfile(playerUUID, playerName).get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); - } - } - - @Override - public CompletableFuture getGlobalProfile(UUID playerUUID) { - return getGlobalProfile(Bukkit.getOfflinePlayer(playerUUID)); - } - - @Override - public CompletableFuture getGlobalProfile(OfflinePlayer player) { - return getGlobalProfile(player.getUniqueId(), player.getName()); - } - - @NotNull + /** + * {@inheritDoc} + */ @Override - public CompletableFuture getGlobalProfile(UUID playerUUID, String playerName) { - try { - File globalFile = profileFilesLocator.getGlobalFile(playerUUID.toString()); - return profileCacheManager.getOrLoadGlobalProfile(playerUUID, (key, executor) -> { - Logging.finer("Global profile for player %s (%s) not in cached. Loading...", playerUUID, playerName); - // Migrate from player name to uuid profile file - File legacyFile = profileFilesLocator.getGlobalFile(playerName); - if (legacyFile.exists() && !migrateGlobalProfileToUUID(legacyFile, playerUUID)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - - // Load from existing profile file - if (!globalFile.exists()) { - return CompletableFuture.completedFuture(GlobalProfile.createGlobalProfile(playerUUID, playerName)); - } - return asyncFileIO.queueCallable(globalFile, () -> getGlobalProfileFromDisk(playerUUID, playerName, globalFile)); - }); - } catch (Exception e) { - Logging.severe("Unable to get global profile for player: " + playerName); - throw new RuntimeException(e); - } + public CompletableFuture getGlobalProfile(GlobalProfileKey key) { + File globalFile = profileFilesLocator.getGlobalFile(key.getPlayerUUID()); + return profileCacheManager.getOrLoadGlobalProfile(key.getPlayerUUID(), (uuid, executor) -> { + Logging.finer("Global profile for player %s (%s) not in cached. Loading...", uuid, key.getPlayerName()); + migrateGlobalProfileToUUID(uuid, key.getPlayerName()); + if (!globalFile.exists()) { + return CompletableFuture.completedFuture(GlobalProfile.createGlobalProfile(key)); + } + return asyncFileIO.queueFileCallable(globalFile, () -> getGlobalProfileFromDisk(key.getPlayerUUID(), key.getPlayerName(), globalFile)); + }); } - @NotNull + /** + * {@inheritDoc} + */ @Override - public CompletableFuture> getExistingGlobalProfile(UUID playerUUID, String playerName) { - File uuidFile = profileFilesLocator.getGlobalFile(playerUUID.toString()); + public CompletableFuture> getExistingGlobalProfile(GlobalProfileKey key) { + File uuidFile = profileFilesLocator.getGlobalFile(key.getPlayerUUID()); if (!uuidFile.exists()) { return CompletableFuture.completedFuture(Option.none()); } - return getGlobalProfile(playerUUID, playerName).thenApply(Option::of); + return getGlobalProfile(key).thenApply(Option::of); } - private boolean migrateGlobalProfileToUUID(File legacyFile, UUID playerUUID) { - return legacyFile.renameTo(profileFilesLocator.getGlobalFile(playerUUID.toString())); + private void migrateGlobalProfileToUUID(UUID playerUUID, String playerName) { + File legacyFile = profileFilesLocator.getGlobalFile(playerName); + if (!legacyFile.exists()) { + return; + } + if (!legacyFile.renameTo(profileFilesLocator.getGlobalFile(playerUUID.toString()))) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } } private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { - FileConfiguration playerData = parseToConfiguration(globalFile); + FileConfiguration playerData = loadFileToJsonConfiguration(globalFile); ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); if (section == null) { return GlobalProfile.createGlobalProfile(playerUUID, playerName); @@ -342,12 +296,12 @@ private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerNam return GlobalProfile.deserialize(playerName, playerUUID, section); } - public CompletableFuture modifyGlobalProfile(UUID playerUUID, Consumer consumer) { - return getGlobalProfile(playerUUID).thenCompose(globalProfile -> modifyGlobalProfile(globalProfile, consumer)); - } - - public CompletableFuture modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer) { - return getGlobalProfile(offlinePlayer).thenCompose(globalProfile -> modifyGlobalProfile(globalProfile, consumer)); + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture modifyGlobalProfile(GlobalProfileKey key, Consumer consumer) { + return getGlobalProfile(key).thenCompose(globalProfile -> modifyGlobalProfile(globalProfile, consumer)); } private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { @@ -361,7 +315,7 @@ private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, @Override public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); - return asyncFileIO.queueAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); + return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); } private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalFile) { @@ -375,27 +329,82 @@ private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalF } } + /** + * {@inheritDoc} + */ @Override - public Collection getGlobalPlayersList() { - return profileFilesLocator.listGlobalFiles() + public CompletableFuture deleteGlobalProfile(GlobalProfileKey key) { + return deleteGlobalProfile(key, true); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture deleteGlobalProfile(GlobalProfileKey key, boolean clearPlayerFiles) { + return getExistingGlobalProfile(key) + .thenCompose(globalProfile -> { + if (globalProfile.isEmpty()) { + return CompletableFuture.failedFuture(new MultiverseException("Invalid global profile for player: " + key)); + } + return deleteGlobalProfileFromDisk(globalProfile.get()); + }) + .thenCompose(ignore -> clearPlayerFiles + ? clearAllPlayerProfileFiles(key.getPlayerUUID(), key.getPlayerName()) + : CompletableFuture.completedFuture(null)); + } + + private CompletableFuture deleteGlobalProfileFromDisk(GlobalProfile globalProfile) { + File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); + return asyncFileIO.queueFileAction(globalFile, () -> { + if (!globalFile.delete()) { + throw new RuntimeException("Could not delete global profile file: " + globalFile); + } + }); + } + + private CompletableFuture clearAllPlayerProfileFiles(UUID playerUUID, String playerName) { + return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) + .flatMap(containerType -> listContainerDataNames(containerType) + .stream() + .map(containerName -> deletePlayerFile(ProfileFileKey.create( + containerType, + containerName, + playerUUID, + playerName)))) + .toArray(CompletableFuture[]::new)); + } + + /** + * {@inheritDoc} + */ + @Override + public List listContainerDataNames(ContainerType containerType) { + return profileFilesLocator.listProfileContainerFolders(containerType) .stream() - .map(file -> UUID.fromString(com.google.common.io.Files.getNameWithoutExtension(file.getName()))) + .map(File::getName) .toList(); } + /** + * {@inheritDoc} + */ @Override - public Collection getContainerPlayersList(ContainerType containerType, String containerName) { + public List listPlayerProfileNames(ContainerType containerType, String containerName) { return profileFilesLocator.listPlayerProfileFiles(containerType, containerName) .stream() .map(file -> com.google.common.io.Files.getNameWithoutExtension(file.getName())) .toList(); } + /** + * {@inheritDoc} + */ @Override - public Collection getContainerNames(ContainerType containerType) { - return profileFilesLocator.listProfileContainerFolders(containerType) + public List listGlobalProfileUUIDs() { + return profileFilesLocator.listGlobalFiles() .stream() - .map(File::getName) + .map(file -> UUID.fromString(com.google.common.io.Files.getNameWithoutExtension(file.getName()))) .toList(); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index 445b2d2a..c6ac7dde 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -2,6 +2,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.util.DataStrings; import java.util.HashMap; @@ -16,11 +17,11 @@ public final class GlobalProfile { /** * Creates a global profile object for the given player with default values. * - * @param player the player to create the profile object for. + * @param key the player to create the profile object for. * @return a new GlobalProfile for the given player. */ - static GlobalProfile createGlobalProfile(OfflinePlayer player) { - return new GlobalProfile(player.getName(), player.getUniqueId()); + static GlobalProfile createGlobalProfile(GlobalProfileKey key) { + return new GlobalProfile(key.getPlayerName(), key.getPlayerUUID()); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java index a64dd61a..c452aec5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.profile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; @@ -10,9 +11,14 @@ */ public final class PlayerProfile extends ProfileDataSnapshot { - static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, - ProfileType profileType, UUID playerUUID, String playerName) { - return new PlayerProfile(containerType, containerName, profileType, playerUUID, playerName); + static PlayerProfile createPlayerProfile(ProfileKey profileKey) { + return new PlayerProfile( + profileKey.getContainerType(), + profileKey.getDataName(), + profileKey.getProfileType(), + profileKey.getPlayerUUID(), + profileKey.getPlayerName() + ); } private final ContainerType containerType; @@ -72,7 +78,8 @@ public PlayerProfile clone() { @Override public String toString() { return "PlayerProfile{" + - "player=" + playerName + + "playerUUID=" + playerUUID + + ", playerName='" + playerName + '\'' + ", containerType=" + containerType + ", containerName='" + containerName + '\'' + ", profileType=" + profileType + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java index 44c93472..53d8543e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java @@ -49,8 +49,7 @@ static Map serialize(PlayerProfile playerProfile) { } static PlayerProfile deserialize(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), pKey.getPlayerUUID(), pKey.getPlayerName()); + PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey); for (Object keyObj : playerData.keySet()) { String key = keyObj.toString(); final Object value = playerData.get(key); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 910947e9..be7e3749 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,14 +1,16 @@ package org.mvplugins.multiverse.inventories.profile; import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import java.io.IOException; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -18,15 +20,6 @@ */ @Contract public sealed interface ProfileDataSource permits FlatFileProfileDataSource { - - /** - * Updates the persisted data for a player for a specific profile. - * - * - * @param playerProfile The profile for the player that is being updated. - */ - CompletableFuture updatePlayerData(PlayerProfile playerProfile); - /** * Retrieves a PlayerProfile from the data source. * @@ -34,24 +27,18 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @return The player as returned from data. If no data was found, a new PlayerProfile will be * created. */ - PlayerProfile getPlayerDataNow(ProfileKey profileKey); + CompletableFuture getPlayerProfile(ProfileKey profileKey); /** - * Retrieves a PlayerProfile from the data source. + * Updates the persisted data for a player for a specific profile to disk. * - * @param profileKey The key of the profile to retrieve. - * @return The player as returned from data. If no data was found, a new PlayerProfile will be - * created. + * @param playerProfile The profile for the player that is being updated. */ - CompletableFuture getPlayerData(ProfileKey profileKey); + CompletableFuture updatePlayerProfile(PlayerProfile playerProfile); - /** - * Removes the persisted data for a player for a specific profile. - * - * @param profileKey The key of the profile to remove. - * @return True if successfully removed. - */ - CompletableFuture removePlayerData(ProfileKey profileKey); + CompletableFuture deletePlayerProfile(ProfileKey profileKey); + + CompletableFuture deletePlayerFile(ProfileFileKey profileKey); /** * Copies all the data belonging to oldName to newName and removes the old data. @@ -61,66 +48,64 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * @param playerUUID the UUID of the player. * @throws IOException Thrown if something goes wrong while migrating the files. */ - void migratePlayerData(String oldName, String newName, UUID playerUUID) throws IOException; + void migratePlayerProfileName(String oldName, String newName, UUID playerUUID) throws IOException; /** * Retrieves the global profile for a player which contains meta-data for the player. * - * @param playerUUID The UUID of the player. - * @return The global profile for the specified player. + * @param key The key of the player. + * @return The global profile for the specified player asynchronously. */ - @NotNull GlobalProfile getGlobalProfileNow(UUID playerUUID); + CompletableFuture getGlobalProfile(GlobalProfileKey key); /** - * Retrieves the global profile for a player which contains meta-data for the player. + * Retrieves the global profile for a player which contains meta-data for the player if it exists. * - * @param player The player. - * @return The global profile for the specified player. + * @param key The key of the player. + * @return The global profile for the specified player or {@link Option#none} if it does not exist asynchronously. */ - @NotNull GlobalProfile getGlobalProfileNow(OfflinePlayer player); + CompletableFuture> getExistingGlobalProfile(GlobalProfileKey key); /** - * Retrieves the global profile for a player which contains meta-data for the player. - * Creates the profile if it doesn't exist. + * Modifies the global profile for a player and automatically saves it. * - * @param playerUUID The UUID of the player. - * @param playerName The name of the player. - * @return The global profile for the specified player. + * @param key The key of the player. + * @return A CompletableFuture that completes when the global profile has been saved. */ - @NotNull GlobalProfile getGlobalProfileNow(UUID playerUUID, String playerName); + CompletableFuture modifyGlobalProfile(GlobalProfileKey key, Consumer consumer); /** - * Retrieves the global profile for a player which contains meta-data for the player if it exists. + * Update the file for a player's global profile to disk. * - * @param playerUUID The UUID of the player. - * @param playerName The name of the player. - * @return The global profile for the specified player or empty if it doesn't exist. + * @param globalProfile The GlobalProfile object to update the file for. */ - @NotNull Option getExistingGlobalProfileNow(UUID playerUUID, String playerName); - - CompletableFuture getGlobalProfile(UUID playerUUID); - - CompletableFuture getGlobalProfile(OfflinePlayer player); - - @NotNull CompletableFuture getGlobalProfile(UUID playerUUID, String playerName); - - @NotNull CompletableFuture> getExistingGlobalProfile(UUID playerUUID, String playerName); + CompletableFuture updateGlobalProfile(GlobalProfile globalProfile); - CompletableFuture modifyGlobalProfile(UUID playerUUID, Consumer consumer); + CompletableFuture deleteGlobalProfile(GlobalProfileKey key); - CompletableFuture modifyGlobalProfile(OfflinePlayer offlinePlayer, Consumer consumer); + CompletableFuture deleteGlobalProfile(GlobalProfileKey key, boolean clearPlayerFiles); /** - * Update the file for a player's global profile. + * Lists the names of all available data containers of the specified type. * - * @param globalProfile The GlobalProfile object to update the file for. + * @param containerType The type of the container (e.g., WORLD, GROUP) whose data names are to be listed. + * @return A collection of strings representing the names of the data containers. */ - CompletableFuture updateGlobalProfile(GlobalProfile globalProfile); - - Collection getGlobalPlayersList(); + List listContainerDataNames(ContainerType containerType); - Collection getContainerPlayersList(ContainerType containerType, String containerName); + /** + * Lists the names of all available player profiles within the given container type and container name. + * + * @param containerType The type of the container (e.g., WORLD, GROUP) whose player profiles are to be listed. + * @param containerName The name of the container whose player profiles are to be listed. + * @return A collection of strings representing the names of all available player profiles. + */ + List listPlayerProfileNames(ContainerType containerType, String containerName); - Collection getContainerNames(ContainerType containerType); + /** + * Retrieves a collection of UUIDs of all players who have a global profile. + * + * @return A collection of UUIDs of all players who have a global profile. + */ + List listGlobalProfileUUIDs(); } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java index 20f2f9d4..8cbfc874 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java @@ -7,6 +7,7 @@ import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import java.io.File; @@ -96,7 +97,7 @@ List listPlayerProfileFiles(ContainerType type, String dataName) { * @param profileKey The profile target to get the file * @return The data file for a player. */ - File getPlayerProfileFile(ProfileKey profileKey) { + File getPlayerProfileFile(ProfileFileKey profileKey) { return getPlayerProfileFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index dc115af9..ccb4fb42 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -1,16 +1,17 @@ package org.mvplugins.multiverse.inventories.profile.container; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.util.FutureNow; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -34,12 +35,16 @@ public final class ProfileContainer { this.profileCacheManager = inventories.getServiceLocator().getService(ProfileCacheManager.class); } + public Collection listPlayerProfileNames() { + return profileDataSource.listPlayerProfileNames(getContainerType(), getContainerName()); + } + public CompletableFuture getPlayerData(Player player) { return getPlayerData(ProfileTypes.forPlayer(player), player); } public CompletableFuture getPlayerData(ProfileType profileType, OfflinePlayer player) { - return profileDataSource.getPlayerData(ProfileKey.create( + return profileDataSource.getPlayerProfile(ProfileKey.create( getContainerType(), getContainerName(), profileType, @@ -54,8 +59,8 @@ public CompletableFuture getPlayerData(ProfileType profileType, O * @param player Player to get profile for. * @return The profile for the given player. */ - public PlayerProfile getPlayerDataNow(Player player) { - return getPlayerDataNow(ProfileTypes.forPlayer(player), player); + public PlayerProfile getPlayerProfileNow(Player player) { + return getPlayerProfileNow(ProfileTypes.forPlayer(player), player); } /** @@ -65,12 +70,12 @@ public PlayerProfile getPlayerDataNow(Player player) { * @param player Player to get profile for. * @return The profile of the given type for the given player. */ - public PlayerProfile getPlayerDataNow(ProfileType profileType, OfflinePlayer player) { - return profileDataSource.getPlayerDataNow(ProfileKey.create( + public PlayerProfile getPlayerProfileNow(ProfileType profileType, OfflinePlayer player) { + return FutureNow.get(profileDataSource.getPlayerProfile(ProfileKey.create( getContainerType(), getContainerName(), profileType, - player)); + player))); } /** @@ -79,12 +84,8 @@ public PlayerProfile getPlayerDataNow(ProfileType profileType, OfflinePlayer pla * @param player Player to remove data for. * @return */ - public CompletableFuture removeAllPlayerData(OfflinePlayer player) { - return profileDataSource.removePlayerData(ProfileKey.create( - getContainerType(), - getContainerName(), - null, - player.getUniqueId())); + public CompletableFuture deletePlayerFile(OfflinePlayer player) { + return profileDataSource.deletePlayerFile(ProfileFileKey.create(type, name, player)); } /** @@ -94,16 +95,8 @@ public CompletableFuture removeAllPlayerData(OfflinePlayer player) { * @param player Player to remove data for. * @return */ - public CompletableFuture removePlayerData(ProfileType profileType, OfflinePlayer player) { - return profileDataSource.removePlayerData(ProfileKey.create( - getContainerType(), - getContainerName(), - profileType, - player.getUniqueId())); - } - - public Collection getPlayers() { - return profileDataSource.getContainerPlayersList(getContainerType(), getContainerName()); + public CompletableFuture deletePlayerProfile(ProfileType profileType, OfflinePlayer player) { + return profileDataSource.deletePlayerProfile(ProfileKey.create(type, name, profileType, player)); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java index 62590a94..b99ae70b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java @@ -1,8 +1,10 @@ package org.mvplugins.multiverse.inventories.profile.container; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import java.util.List; import java.util.Map; import java.util.WeakHashMap; @@ -15,19 +17,16 @@ public final class ProfileContainerStore { private final MultiverseInventories inventories; private final ContainerType containerType; + private final ProfileDataSource profileDataSource; ProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { this.inventories = inventories; this.containerType = containerType; - } + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + } - /** - * Adds a profile container to the store. - * - * @param container profile container to add. - */ - public void addContainer(ProfileContainer container) { - this.containers.put(container.getContainerName().toLowerCase(), container); + public List listContainerDataNames() { + return profileDataSource.listContainerDataNames(containerType); } /** @@ -44,5 +43,14 @@ public ProfileContainer getContainer(String containerName) { } return container; } + + /** + * Adds a profile container to the store. + * + * @param container profile container to add. + */ + private void addContainer(ProfileContainer container) { + this.containers.put(container.getContainerName().toLowerCase(), container); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java new file mode 100644 index 00000000..976b1897 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java @@ -0,0 +1,59 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; + +import java.util.Objects; +import java.util.UUID; + +public class GlobalProfileKey { + + public static GlobalProfileKey create(UUID playerUUID) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerUUID); + return create(offlinePlayer); + } + + public static GlobalProfileKey create(OfflinePlayer offlinePlayer) { + return create(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + + public static GlobalProfileKey create(UUID playerUUID, String playerName) { + return new GlobalProfileKey(playerUUID, playerName); + } + + private final UUID playerUUID; + private final String playerName; + + private GlobalProfileKey(UUID playerUUID, String playerName) { + this.playerUUID = playerUUID; + this.playerName = playerName; + } + + public UUID getPlayerUUID() { + return playerUUID; + } + + public String getPlayerName() { + return playerName; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + GlobalProfileKey that = (GlobalProfileKey) o; + return Objects.equals(playerUUID, that.playerUUID); + } + + @Override + public int hashCode() { + return playerUUID.hashCode(); + } + + @Override + public String toString() { + return "GlobalProfileKey{" + + "playerUUID=" + playerUUID + + ", playerName='" + playerName + '\'' + + '}'; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java new file mode 100644 index 00000000..254d99f7 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java @@ -0,0 +1,120 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import com.google.common.base.Objects; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; + +import java.util.UUID; + +public class ProfileFileKey { + + public static ProfileFileKey create( + ContainerType containerType, + String dataName, + UUID playerUUID, + String playerName) { + return new ProfileFileKey(containerType, dataName, playerUUID, playerName); + } + + public static ProfileFileKey create( + ContainerType containerType, + String dataName, + OfflinePlayer offlinePlayer) { + return new ProfileFileKey(containerType, dataName, offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + + public static ProfileFileKey create( + ContainerType containerType, + String dataName, + UUID playerUUID) { + return new ProfileFileKey(containerType, dataName, playerUUID, Bukkit.getOfflinePlayer(playerUUID).getName()); + } + + public static ProfileFileKey fromPlayerProfile(PlayerProfile profile) { + return new ProfileFileKey( + profile.getContainerType(), + profile.getContainerName(), + profile.getPlayerUUID(), + profile.getPlayerName() + ); + } + + protected final ContainerType containerType; + protected final String dataName; + protected final String playerName; + protected final UUID playerUUID; + protected final int hashCode; + + private ProfileFileKey(ContainerType containerType, String dataName, UUID playerUUID, String playerName) { + this(containerType, + dataName, + playerUUID, + playerName, + Objects.hashCode(containerType, dataName, playerUUID, playerName)); + } + + protected ProfileFileKey(ContainerType containerType, String dataName, UUID playerUUID, String playerName, int hashCode) { + this.containerType = containerType; + this.dataName = dataName; + this.playerUUID = playerUUID; + this.playerName = playerName; + this.hashCode = hashCode; + } + + public ProfileKey forProfileType(@Nullable ProfileType profileType) { + return ProfileKey.create(containerType, dataName, profileType, playerUUID, playerName); + } + + public ProfileFileKey forContainerType(@NotNull ContainerType containerType) { + return new ProfileFileKey(containerType, dataName, playerUUID, playerName); + } + + public ContainerType getContainerType() { + return containerType; + } + + public String getDataName() { + return dataName; + } + + public String getPlayerName() { + return playerName; + } + + public UUID getPlayerUUID() { + return playerUUID; + } + + public boolean isSameFile(ProfileFileKey other) { + return Objects.equal(getContainerType(), other.getContainerType()) && + Objects.equal(getDataName(), other.getDataName()) && + Objects.equal(getPlayerUUID(), other.getPlayerUUID()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ProfileKey that)) return false; + return getContainerType() == that.getContainerType() && + Objects.equal(getDataName(), that.getDataName()) && + Objects.equal(getPlayerUUID(), that.getPlayerUUID()); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return "ProfileFileKey{" + + "containerType=" + containerType + + ", dataName='" + dataName + '\'' + + ", playerName='" + playerName + '\'' + + ", playerUUID=" + playerUUID + + '}'; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java index 82b0ab86..b9cb1feb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java @@ -9,7 +9,7 @@ import java.util.UUID; -public final class ProfileKey { +public final class ProfileKey extends ProfileFileKey { public static ProfileKey create( ContainerType containerType, @@ -37,55 +37,37 @@ public static ProfileKey create( } public static ProfileKey fromPlayerProfile(PlayerProfile profile) { - return new ProfileKey(profile.getContainerType(), profile.getContainerName(), profile.getProfileType(), - profile.getPlayerUUID(), profile.getPlayerName()); + return new ProfileKey( + profile.getContainerType(), + profile.getContainerName(), + profile.getProfileType(), + profile.getPlayerUUID(), + profile.getPlayerName() + ); } - private final ContainerType containerType; - private final String dataName; private final ProfileType profileType; - private final String playerName; - private final UUID playerUUID; - private final int hashCode; - private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, - UUID playerUUID, String playerName) { - this.containerType = containerType; - this.dataName = dataName; + private ProfileKey( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID, + String playerName + ) { + super(containerType, dataName, playerUUID, playerName, Objects.hashCode(containerType, dataName, profileType, playerUUID)); this.profileType = profileType; - this.playerUUID = playerUUID; - this.playerName = playerName; - this.hashCode = Objects.hashCode(playerUUID, containerType, dataName, profileType); - } - - public ProfileKey forProfileType(@Nullable ProfileType profileType) { - return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); } + @Override public ProfileKey forContainerType(@NotNull ContainerType containerType) { return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); } - public ContainerType getContainerType() { - return containerType; - } - - public String getDataName() { - return dataName; - } - public ProfileType getProfileType() { return profileType; } - public String getPlayerName() { - return playerName; - } - - public UUID getPlayerUUID() { - return playerUUID; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -93,15 +75,9 @@ public boolean equals(Object o) { return getContainerType() == that.getContainerType() && Objects.equal(getDataName(), that.getDataName()) && Objects.equal(getProfileType(), that.getProfileType()) && - Objects.equal(getPlayerName(), that.getPlayerName()) && Objects.equal(getPlayerUUID(), that.getPlayerUUID()); } - @Override - public int hashCode() { - return hashCode; - } - @Override public String toString() { return "ProfileKey{" + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java b/src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java new file mode 100644 index 00000000..1ba2fe88 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java @@ -0,0 +1,22 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public final class FutureNow { + + private static final long TIMEOUT = TimeUnit.SECONDS.toNanos(10); + + public static T get(CompletableFuture future) { + try { + return future.get(TIMEOUT, TimeUnit.NANOSECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Logging.severe("Could not get future as it timed out: " + future); + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index d1a2f0f6..60999d2c 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -7,6 +7,7 @@ import org.mvplugins.multiverse.inventories.profile.key.ProfileKey import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes import org.mvplugins.multiverse.inventories.profile.key.ContainerType import org.mvplugins.multiverse.inventories.share.Sharables +import org.mvplugins.multiverse.inventories.util.FutureNow import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -29,7 +30,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { player.health = 4.4 player.maxHealth = 15.1 - val playerProfileFuture = profileDataSource.getPlayerData( + val playerProfileFuture = profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfileFuture)) val playerProfile = playerProfileFuture.get() @@ -39,8 +40,8 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { @Test fun `Test updating player`() { - val playerProfile = profileDataSource.getPlayerDataNow( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId))) playerProfile.set(Sharables.HEALTH, 4.4) playerProfile.set(Sharables.MAX_HEALTH, 15.1) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 2bb352f0..e4e10b65 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -13,9 +13,11 @@ import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.key.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey import org.mvplugins.multiverse.inventories.profile.key.ProfileKey import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes import org.mvplugins.multiverse.inventories.share.Sharables +import org.mvplugins.multiverse.inventories.util.FutureNow import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Future @@ -50,7 +52,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val startTime = System.nanoTime() val futures = ArrayList>(10000) for (i in 0..9999) { - futures.add(profileDataSource.modifyGlobalProfile(UUID.randomUUID(), { globalProfile -> + futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(UUID.randomUUID()), { globalProfile -> globalProfile.setLoadOnLogin(true) })) } @@ -68,8 +70,8 @@ class FilePerformanceTest : TestWithMockBukkit() { for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - val playerProfile = profileDataSource.getPlayerDataNow( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) playerProfile.set(Sharables.HEALTH, 5.0) playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) playerProfile.set(Sharables.INVENTORY, arrayOf( @@ -84,7 +86,7 @@ class FilePerformanceTest : TestWithMockBukkit() { PotionEffect(PotionEffectType.POISON, 100, 1), PotionEffect(PotionEffectType.SPEED, 50, 1), )) - futures.add(profileDataSource.updatePlayerData(playerProfile)) + futures.add(profileDataSource.updatePlayerProfile(playerProfile)) } } for (future in futures) { @@ -98,7 +100,7 @@ class FilePerformanceTest : TestWithMockBukkit() { for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - futures2.add(profileDataSource.getPlayerData( + futures2.add(profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) } } @@ -114,10 +116,10 @@ class FilePerformanceTest : TestWithMockBukkit() { for (i in 0..999) { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { - futures3.add(profileDataSource.removePlayerData( + futures3.add(profileDataSource.deletePlayerProfile( + ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) - val playerProfile = profileDataSource.getPlayerDataNow( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId)) assertNull(playerProfile.get(Sharables.HEALTH)) assertNull(playerProfile.get(Sharables.OFF_HAND)) } @@ -134,7 +136,7 @@ class FilePerformanceTest : TestWithMockBukkit() { } } - fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { + private fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { val itemStack = ItemStack(material, amount) modify.accept(itemStack) return itemStack diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt index 100b6ff2..4afaa294 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt @@ -8,6 +8,8 @@ import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey +import org.mvplugins.multiverse.inventories.util.FutureNow import java.nio.file.Path import kotlin.test.BeforeTest import kotlin.test.Test @@ -78,6 +80,6 @@ class PlayerNameChangeTest : TestWithMockBukkit() { assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "Benji_0224.json").toFile().exists()) // check player profile - assertEquals("benthecat10", profileDataSource.getGlobalProfileNow(player)?.lastKnownName) + assertEquals("benthecat10", FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)))?.lastKnownName) } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt index 3c4228d3..f31ae5eb 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt @@ -23,9 +23,9 @@ class ProfileDataSourceTest : TestWithMockBukkit() { server.setPlayers(1) writeResourceToConfigFile("/playerdata.json", "worlds/world/Player0.json") val key = ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, server.getPlayer("Player0")) - profileDataSource.getPlayerData(key).thenAccept { profile -> Logging.info(profile.toString()) } - profileDataSource.getPlayerData(key).thenAccept { profile -> Logging.info(profile.toString()) } - profileDataSource.getPlayerData(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } Logging.info("Getting player data...") } } From c3c65d1230ccf9c1851422ded36a38f3abedd8c3 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:26:35 +0800 Subject: [PATCH 139/180] Improve migrate inventory-serialization logic --- .../inventories/commands/BulkEditCommand.java | 81 ----------------- .../MigrateInventorySerializationCommand.java | 90 +++++++++++++++++++ 2 files changed, 90 insertions(+), 81 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java deleted file mode 100644 index 3ddbc269..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/BulkEditCommand.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.mvplugins.multiverse.inventories.commands; - -import com.dumptruckman.minecraft.util.Logging; -import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; -import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; -import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; -import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; -import org.mvplugins.multiverse.external.jakarta.inject.Inject; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; -import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; -import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.key.ContainerType; - -import java.util.Collection; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -@Service -@CommandAlias("mvinv") -final class BulkEditCommand extends InventoriesCommand { - - private final ProfileDataSource profileDataSource; - private final InventoriesConfig inventoriesConfig; - - @Inject - BulkEditCommand(MVCommandManager commandManager, ProfileDataSource profileDataSource, InventoriesConfig inventoriesConfig) { - super(commandManager); - this.profileDataSource = profileDataSource; - this.inventoriesConfig = inventoriesConfig; - } - - @Subcommand("bulkedit migrate inventory-serialization nbt") - @CommandPermission("multiverse.inventories.bulkedit") - void onBulkEditCommand(MVCommandIssuer issuer) { - inventoriesConfig.setUseByteSerializationForInventoryData(true); - inventoriesConfig.save(); - - Collection globalPlayersList = profileDataSource.listGlobalProfileUUIDs(); - Collection worldContainerNames = profileDataSource.listContainerDataNames(ContainerType.WORLD); - Collection groupContainerNames = profileDataSource.listContainerDataNames(ContainerType.GROUP); - Logging.fine(String.join(", ", worldContainerNames)); - CompletableFuture.allOf(globalPlayersList - .stream() - .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID)) - .thenApply(profile -> { - profile.setLoadOnLogin(true); - profileDataSource.updateGlobalProfile(profile); - return profile; - }) - .thenCompose(profile -> { - return CompletableFuture.allOf(worldContainerNames.stream().flatMap(worldName -> { - return ProfileTypes.getTypes().stream().map(profileType -> { - return profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, worldName, profileType, profile.getPlayerUUID(), profile.getLastKnownName()) - ).thenCompose(profileDataSource::updatePlayerProfile); - }); - }).toArray(CompletableFuture[]::new)).thenCompose(ignore -> { - return CompletableFuture.allOf(groupContainerNames.stream().flatMap(worldName -> { - return ProfileTypes.getTypes().stream().map(profileType -> { - return profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.GROUP, worldName, profileType, profile.getPlayerUUID(), profile.getLastKnownName()) - ).thenCompose(profileDataSource::updatePlayerProfile); - }); - }).toArray(CompletableFuture[]::new)); - }); - }) - .exceptionally(throwable -> { - issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); - return null; - })) - .toArray(CompletableFuture[]::new)) - .thenRun(() -> { - issuer.sendMessage("Bulk edit complete."); - issuer.sendMessage("Please restart your server to complete the migration."); - }); - } -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java new file mode 100644 index 00000000..186907b2 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -0,0 +1,90 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; + +import java.util.Arrays; +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@Service +@CommandAlias("mvinv") +final class MigrateInventorySerializationCommand extends InventoriesCommand { + + private final ProfileDataSource profileDataSource; + private final InventoriesConfig inventoriesConfig; + + @Inject + MigrateInventorySerializationCommand( + MVCommandManager commandManager, + ProfileDataSource profileDataSource, + InventoriesConfig inventoriesConfig + ) { + super(commandManager); + this.profileDataSource = profileDataSource; + this.inventoriesConfig = inventoriesConfig; + } + + @Subcommand("bulkedit migrate inventory-serialization nbt") + @CommandPermission("multiverse.inventories.bulkedit") + void onNbtCommand(MVCommandIssuer issuer) { + doMigration(issuer, true); + } + + @Subcommand("bulkedit migrate inventory-serialization bukkit") + @CommandPermission("multiverse.inventories.bulkedit") + void onBukkitCommand(MVCommandIssuer issuer) { + doMigration(issuer, false); + } + + private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { + inventoriesConfig.setUseByteSerializationForInventoryData(useByteSerialization); + inventoriesConfig.save(); + + long startTime = System.nanoTime(); + CompletableFuture.allOf(profileDataSource.listGlobalProfileUUIDs() + .stream() + .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID)) + .thenCompose(profile -> run(profile)) + .exceptionally(throwable -> { + issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); + return null; + })) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> { + long timeDuration = (System.nanoTime() - startTime) / 1000000; + issuer.sendMessage("Bulk edit completed in " + timeDuration + " ms."); + issuer.sendMessage("Please restart your server to complete the migration."); + }); + } + + private CompletableFuture run(GlobalProfile profile) { + return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) + .flatMap(containerType -> profileDataSource.listContainerDataNames(containerType).stream() + .flatMap(dataName -> ProfileTypes.getTypes().stream() + .map(profileType -> profileDataSource.getPlayerProfile(ProfileKey.create( + containerType, + dataName, + profileType, + profile.getPlayerUUID(), + profile.getLastKnownName() + )).thenCompose(profileDataSource::updatePlayerProfile)))) + .toArray(CompletableFuture[]::new)); + } +} From 25e10ff26fd89e0e949121e34bc671f6e13c35be Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:26:54 +0800 Subject: [PATCH 140/180] Scaffold bulkedit commands --- .../commands/bulkedit/globalprofile/DeleteCommand.java | 4 ++++ .../commands/bulkedit/globalprofile/ModifyCommand.java | 4 ++++ .../commands/bulkedit/playerprofile/DeleteCommand.java | 4 ++++ .../bulkedit/playerprofile/MigratePlayerNameCommand.java | 4 ++++ .../commands/bulkedit/playerprofile/ResetCommand.java | 4 ++++ 5 files changed, 20 insertions(+) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java new file mode 100644 index 00000000..17b7f25d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java @@ -0,0 +1,4 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; + +public class DeleteCommand { +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java new file mode 100644 index 00000000..b107b98f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java @@ -0,0 +1,4 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; + +public class ModifyCommand { +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java new file mode 100644 index 00000000..353c6215 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -0,0 +1,4 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +public class DeleteCommand { +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java new file mode 100644 index 00000000..613b0ee2 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java @@ -0,0 +1,4 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +public class MigratePlayerNameCommand { +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java new file mode 100644 index 00000000..7df477cc --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java @@ -0,0 +1,4 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +public class ResetCommand { +} From 13d4f08bb2b976a9661c471f335db4a15df3304f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:40:31 +0800 Subject: [PATCH 141/180] Add counter and duration to migration output --- .../MigrateInventorySerializationCommand.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java index 186907b2..cec4769d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; @Service @CommandAlias("mvinv") @@ -58,10 +59,11 @@ private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { inventoriesConfig.save(); long startTime = System.nanoTime(); + AtomicLong profileCounter = new AtomicLong(0); CompletableFuture.allOf(profileDataSource.listGlobalProfileUUIDs() .stream() .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID)) - .thenCompose(profile -> run(profile)) + .thenCompose(profile -> run(profile, profileCounter)) .exceptionally(throwable -> { issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); return null; @@ -69,12 +71,12 @@ private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { .toArray(CompletableFuture[]::new)) .thenRun(() -> { long timeDuration = (System.nanoTime() - startTime) / 1000000; + issuer.sendMessage("Updated " + profileCounter.get() + " player profiles."); issuer.sendMessage("Bulk edit completed in " + timeDuration + " ms."); - issuer.sendMessage("Please restart your server to complete the migration."); }); } - private CompletableFuture run(GlobalProfile profile) { + private CompletableFuture run(GlobalProfile profile, AtomicLong profileCounter) { return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) .flatMap(containerType -> profileDataSource.listContainerDataNames(containerType).stream() .flatMap(dataName -> ProfileTypes.getTypes().stream() @@ -84,7 +86,13 @@ private CompletableFuture run(GlobalProfile profile) { profileType, profile.getPlayerUUID(), profile.getLastKnownName() - )).thenCompose(profileDataSource::updatePlayerProfile)))) + )).thenCompose(playerProfile -> { + if (playerProfile.getData().isEmpty()) { + return CompletableFuture.completedFuture(null); + } + profileCounter.incrementAndGet(); + return profileDataSource.updatePlayerProfile(playerProfile); + })))) .toArray(CompletableFuture[]::new)); } } From ffb176b29967d37722c7a29386138595826c4cd6 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 4 Apr 2025 22:40:01 +0800 Subject: [PATCH 142/180] Fix profileCacheManager provider to be private --- .../multiverse/inventories/MultiverseInventories.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 2a83875b..57d62107 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -62,7 +62,8 @@ public class MultiverseInventories extends MultiversePlugin { private Provider worldGroupManager; @Inject private Provider profileDataSource; - @Inject Provider profileCacheManager; + @Inject + private Provider profileCacheManager; @Inject private Provider profileContainerStoreProvider; @Inject From bfe239575f5762ab95454362d3a0b2ff7469ca48 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 4 Apr 2025 22:42:33 +0800 Subject: [PATCH 143/180] Fix command conditions not registering --- .../multiverse/inventories/MultiverseInventories.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 57d62107..b23e4cf3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -9,6 +9,7 @@ import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.inventories.command.MVInvCommandConditions; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.command.MVInvCommandCompletion; import org.mvplugins.multiverse.inventories.command.MVInvCommandContexts; @@ -72,6 +73,8 @@ public class MultiverseInventories extends MultiversePlugin { private Provider mvInvCommandCompletion; @Inject private Provider mvInvCommandContexts; + @Inject + private Provider mvInvCommandConditions; private PluginServiceLocator serviceLocator; private InventoriesDupingPatch dupingPatch; @@ -177,6 +180,7 @@ private void registerCommands() { mvInvCommandCompletion.get(); mvInvCommandContexts.get(); + mvInvCommandConditions.get(); serviceLocator.getAllServices(InventoriesCommand.class).forEach(commandManager::registerCommand); }) From 183089cb2727d2915b207bcefe58d532d431d646 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 6 Apr 2025 18:17:05 +0800 Subject: [PATCH 144/180] Update for core's api changes --- .../multiverse/inventories/commands/ConfigCommand.java | 2 +- .../multiverse/inventories/config/InventoriesConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java index a6600b28..6f63f591 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -65,6 +65,6 @@ private void updateConfigValue(MVCommandIssuer issuer, String name, String value issuer.sendMessage("Successfully set " + name + " to " + value); }) .onFailure(ignore -> issuer.sendMessage("Unable to set " + name + " to " + value + ".")) - .onFailure(MultiverseException.class, e -> Option.of(e.getMVMessage()).peek(issuer::sendError)); + .onFailure(MultiverseException.class, e -> Option.of(e.getLocalizableMessage()).peek(issuer::sendError)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 9b3d7019..7e430e48 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -5,7 +5,7 @@ import org.mvplugins.multiverse.core.config.handle.CommentedConfigurationHandle; import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; import org.mvplugins.multiverse.core.config.migration.ConfigMigrator; -import org.mvplugins.multiverse.core.config.migration.MoveMigratorAction; +import org.mvplugins.multiverse.core.config.migration.action.MoveMigratorAction; import org.mvplugins.multiverse.core.config.migration.VersionMigrator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; From 2b99c3cb7a24344701ee10f99fb9fcf3ff9bba79 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:47:38 +0800 Subject: [PATCH 145/180] Remove save-playerdata-on-quit and make it a standard behaviour --- .../inventories/MVEventsListener.java | 2 +- .../inventories/MultiverseInventories.java | 12 +++++------ .../inventories/commands/GiveCommand.java | 5 ----- .../inventories/config/InventoriesConfig.java | 20 +------------------ .../config/InventoriesConfigNodes.java | 7 ------- .../perworldinventory/PwiImportHelper.java | 2 +- .../handleshare/ShareHandleListener.java | 12 ++++------- src/test/resources/config/fresh_config.yml | 1 - src/test/resources/config/migrated_config.yml | 3 +-- 9 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java index a6145e40..0f0d136d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java @@ -80,7 +80,7 @@ private String getDebugInfo() { + "[Multiverse-Inventories] First Run: " + config.getFirstRun() + '\n' + "[Multiverse-Inventories] Using Bypass: " + config.getEnableBypassPermissions() + '\n' + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.getDefaultUngroupedWorlds() + '\n' - + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.getSavePlayerdataOnQuit() + '\n' + + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.getApplyPlayerdataOnJoin() + '\n' + "[Multiverse-Inventories] Using GameMode Profiles: " + config.getEnableGamemodeShareHandling() + '\n' + "[Multiverse-Inventories] === Shares ===" + '\n' + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.getUseOptionalsForUngroupedWorlds() + '\n' diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index b23e4cf3..13a9fc73 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -18,6 +18,7 @@ import org.mvplugins.multiverse.inventories.dataimport.DataImporter; import org.mvplugins.multiverse.inventories.destination.LastLocationDestination; import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; @@ -158,12 +159,11 @@ public void onDisable() { super.onDisable(); for (final Player player : getServer().getOnlinePlayers()) { - if (inventoriesConfig.get().getSavePlayerdataOnQuit()) { - new WriteOnlyShareHandler(this, player).handleSharing(); - if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { - profileDataSource.get().modifyGlobalProfile( - GlobalProfileKey.create(player), profile -> profile.setLoadOnLogin(true)); - } + SingleShareWriter.of(this, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); + new WriteOnlyShareHandler(this, player).handleSharing(); + if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { + profileDataSource.get().modifyGlobalProfile( + GlobalProfileKey.create(player), profile -> profile.setLoadOnLogin(true)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index fd41b739..ed0601fb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -68,11 +68,6 @@ void onGiveCommand( GameMode gameMode, String item ) { - if (!player.isOnline() && !inventoriesConfig.getSavePlayerdataOnQuit()) { - issuer.sendError("You need to enable save-playerdata-on-quit in your config to give an offline player inventory items."); - return; - } - ItemStack itemStack = parseItemFromString(issuer, item); if (itemStack == null) { return; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 7e430e48..7694c636 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -41,7 +41,7 @@ public final class InventoriesConfig { .addAction(MoveMigratorAction.of("settings.first_run", "first-run")) .addAction(MoveMigratorAction.of("settings.use_bypass", "share-handling.enable-bypass-permissions")) .addAction(MoveMigratorAction.of("settings.default_ungrouped_worlds", "share-handling.default-ungrouped-worlds")) - .addAction(MoveMigratorAction.of("settings.save_load_on_log_in_out", "performance.save-playerdata-on-quit")) + .addAction(MoveMigratorAction.of("settings.save_load_on_log_in_out", "performance.apply-playerdata-on-join")) .addAction(MoveMigratorAction.of("settings.use_game_mode_profiles", "share-handling.enable-gamemode-share-handling")) .addAction(MoveMigratorAction.of("shares.optionals_for_ungrouped_worlds", "share-handling.use-optionals-for-ungrouped-worlds")) .addAction(MoveMigratorAction.of("shares.use_optionals", "share-handling.active-optional-shares")) @@ -175,24 +175,6 @@ public Try setUseByteSerializationForInventoryData(boolean useByteSerializ return this.configHandle.set(configNodes.useByteSerializationForInventoryData, useByteSerializationForInventoryData); } - /** - * Tells whether Multiverse-Inventories should save on player logout. - * - * @return True if should save on player log out. - */ - public boolean getSavePlayerdataOnQuit() { - return this.configHandle.get(configNodes.savePlayerdataOnQuit); - } - - /** - * Sets whether Multiverse-Inventories should save on player logout. - * - * @param useLoggingSaveLoad true if should save on player log out. - */ - public Try setSavePlayerdataOnQuit(boolean useLoggingSaveLoad) { - return this.configHandle.set(configNodes.savePlayerdataOnQuit, useLoggingSaveLoad); - } - public boolean getApplyPlayerdataOnJoin() { return this.configHandle.get(configNodes.applyPlayerdataOnJoin); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index 95e3bd90..bbf6af36 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -155,13 +155,6 @@ public Object serialize(Shares sharables, Class aClass) { .comment("") .build()); - final ConfigNode savePlayerdataOnQuit = node(ConfigNode.builder("performance.save-playerdata-on-quit", Boolean.class) - .comment("This option may be useful if you want an up-to-date offline copy of the playerdata within mvinv.") - .comment("However, this will result in minor performance overhead on every player quit.") - .defaultValue(true) - .name("save-playerdata-on-quit") - .build()); - final ConfigNode applyPlayerdataOnJoin = node(ConfigNode.builder("performance.apply-playerdata-on-join", Boolean.class) .comment("") .comment("This will only work if save-playerdata-on-quit is set to true.") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 55423e21..71b4a4c5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -112,7 +112,7 @@ private void pwiSetUp() { */ private void transferConfigOptions() { inventoriesConfig.setEnableGamemodeShareHandling(this.pwiSettings.getProperty(PluginSettings.SEPARATE_GM_INVENTORIES)); - inventoriesConfig.setSavePlayerdataOnQuit(this.pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); + inventoriesConfig.setApplyPlayerdataOnJoin(this.pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); inventoriesConfig.setDefaultUngroupedWorlds(this.pwiSettings.getProperty(PluginSettings.SHARE_IF_UNCONFIGURED)); inventoriesConfig.getActiveOptionalShares().setSharing(Sharables.ECONOMY, this.pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)); inventoriesConfig.save(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 7ceaf4bb..0a084cd5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -154,14 +154,10 @@ void playerQuit(final PlayerQuitEvent event) { globalProfile.thenAccept(p -> p.setLastWorld(world)); // Write last location as its possible for players to join at a different world - SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION) - .write(player.getLocation().clone(), !config.getSavePlayerdataOnQuit()); - - if (config.getSavePlayerdataOnQuit()) { - new WriteOnlyShareHandler(inventories, player).handleSharing(); - if (config.getApplyPlayerdataOnJoin()) { - globalProfile.thenAccept(p -> p.setLoadOnLogin(true)); - } + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); + new WriteOnlyShareHandler(inventories, player).handleSharing(); + if (config.getApplyPlayerdataOnJoin()) { + globalProfile.thenAccept(p -> p.setLoadOnLogin(true)); } globalProfile.thenAccept(profileDataSource::updateGlobalProfile); } diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml index 3d5f7475..6335cd6c 100644 --- a/src/test/resources/config/fresh_config.yml +++ b/src/test/resources/config/fresh_config.yml @@ -12,7 +12,6 @@ sharables: use-byte-serialization-for-inventory-data: false performance: - save-playerdata-on-quit: true apply-playerdata-on-join: false always-write-world-profile: true preload-data-on-join: diff --git a/src/test/resources/config/migrated_config.yml b/src/test/resources/config/migrated_config.yml index 167bb13b..20f7f48e 100644 --- a/src/test/resources/config/migrated_config.yml +++ b/src/test/resources/config/migrated_config.yml @@ -13,8 +13,7 @@ sharables: use-byte-serialization-for-inventory-data: false performance: - save-playerdata-on-quit: true - apply-playerdata-on-join: false + apply-playerdata-on-join: true always-write-world-profile: true preload-data-on-join: worlds: [] From 4214f981b3f862c074097aba3be0086772fc9d78 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:09:39 +0800 Subject: [PATCH 146/180] Update to latest core api --- .../inventories/MultiverseInventories.java | 2 +- .../commands/AddDisabledSharesCommand.java | 3 +- .../commands/AddSharesCommand.java | 3 +- .../commands/AddWorldsCommand.java | 3 +- .../inventories/commands/CacheCommand.java | 3 +- .../inventories/commands/ConfigCommand.java | 3 +- .../commands/CreateGroupCommand.java | 3 +- .../commands/DeleteGroupCommand.java | 8 ++-- .../inventories/commands/GiveCommand.java | 2 - .../inventories/commands/GroupCommand.java | 3 +- .../{UsageCommand.java => HelpCommand.java} | 11 ++--- .../inventories/commands/ImportCommand.java | 5 +-- .../inventories/commands/InfoCommand.java | 5 +-- .../commands/InventoriesCommand.java | 3 -- .../inventories/commands/ListCommand.java | 5 +-- .../inventories/commands/ReloadCommand.java | 3 +- .../commands/RemoveDisabledSharesCommand.java | 3 +- .../commands/RemoveSharesCommand.java | 3 +- .../commands/RemoveWorldsCommand.java | 3 +- .../inventories/commands/ToggleCommand.java | 5 +-- .../MigrateInventorySerializationCommand.java | 7 ++- .../destination/LastLocationDestination.java | 43 +++++++++++++------ .../inventories/share/Sharables.java | 5 +++ 23 files changed, 66 insertions(+), 68 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/commands/{UsageCommand.java => HelpCommand.java} (81%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 13a9fc73..581b71ea 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -106,7 +106,7 @@ public final void onEnable() { Sharables.init(this); Perm.register(this); ItemStackConverter.init(this); - Logging.warning("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); + Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); this.reloadConfig(); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java index 4c510b98..cb87ab2c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java @@ -25,8 +25,7 @@ final class AddDisabledSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - AddDisabledSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + AddDisabledSharesCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java index f87f55d8..1664c20f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java @@ -27,8 +27,7 @@ final class AddSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - AddSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + AddSharesCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java index 9ec6dce9..3195e87f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java @@ -29,8 +29,7 @@ final class AddWorldsCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - AddWorldsCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + AddWorldsCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index 5c207182..8a14430d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -25,8 +25,7 @@ final class CacheCommand extends InventoriesCommand { private final ProfileCacheManager ProfileCacheManager; @Inject - CacheCommand(@NotNull MVCommandManager commandManager, @NotNull ProfileCacheManager ProfileCacheManager) { - super(commandManager); + CacheCommand(@NotNull ProfileCacheManager ProfileCacheManager) { this.ProfileCacheManager = ProfileCacheManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java index 6f63f591..763f7603 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -23,8 +23,7 @@ final class ConfigCommand extends InventoriesCommand { private final InventoriesConfig config; @Inject - ConfigCommand(@NotNull MVCommandManager commandManager, @NotNull InventoriesConfig config) { - super(commandManager); + ConfigCommand(@NotNull InventoriesConfig config) { this.config = config; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java index bcc30b26..052d4634 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -29,8 +29,7 @@ final class CreateGroupCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - CreateGroupCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + CreateGroupCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java index 975241a4..699df977 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java @@ -27,10 +27,10 @@ final class DeleteGroupCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - DeleteGroupCommand(@NotNull MVCommandManager commandManager, - @NotNull CommandQueueManager commandQueueManager, - @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + DeleteGroupCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull WorldGroupManager worldGroupManager + ) { this.commandQueueManager = commandQueueManager; this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index ed0601fb..d06628c8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -44,12 +44,10 @@ final class GiveCommand extends InventoriesCommand { @Inject GiveCommand( - @NotNull MVCommandManager commandManager, @NotNull MultiverseInventories inventories, @NotNull ProfileDataSource profileDataSource, @NotNull InventoriesConfig inventoriesConfig ) { - super(commandManager); this.inventories = inventories; this.profileDataSource = profileDataSource; this.inventoriesConfig = inventoriesConfig; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index 3167e856..6eb66677 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -24,8 +24,7 @@ final class GroupCommand extends InventoriesCommand { private final MultiverseInventories plugin; @Inject - GroupCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { - super(commandManager); + GroupCommand(@NotNull MultiverseInventories plugin) { this.plugin = plugin; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java similarity index 81% rename from src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java index a7e90118..dcf9e37c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/UsageCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java @@ -8,21 +8,22 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; -import org.mvplugins.multiverse.external.acf.commands.annotation.HelpCommand; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @Service @CommandAlias("mvinv") -final class UsageCommand extends InventoriesCommand { +final class HelpCommand extends InventoriesCommand { + + private final MVCommandManager commandManager; @Inject - UsageCommand(@NotNull MVCommandManager commandManager) { - super(commandManager); + HelpCommand(@NotNull MVCommandManager commandManager) { + this.commandManager = commandManager; } - @HelpCommand + @org.mvplugins.multiverse.external.acf.commands.annotation.HelpCommand @Subcommand("help") @CommandPermission("multiverse.inventories.help") @CommandCompletion("@commands:mvinv") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index e09248b1..f8781db0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -30,10 +30,9 @@ final class ImportCommand extends InventoriesCommand { @Inject ImportCommand( - @NotNull MVCommandManager commandManager, @NotNull DataImportManager dataImportManager, - @NotNull CommandQueueManager commandQueueManager) { - super(commandManager); + @NotNull CommandQueueManager commandQueueManager + ) { this.dataImportManager = dataImportManager; this.commandQueueManager = commandQueueManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index 3adb504f..f4ab062c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -35,10 +35,9 @@ final class InfoCommand extends InventoriesCommand { @Inject InfoCommand( - @NotNull MVCommandManager commandManager, @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, - @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + @NotNull WorldGroupManager worldGroupManager + ) { this.profileContainerStoreProvider = profileContainerStoreProvider; this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java index 83223463..ee295102 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -10,7 +10,4 @@ */ @Contract public abstract class InventoriesCommand extends MultiverseCommand { - protected InventoriesCommand(@NotNull MVCommandManager commandManager) { - super(commandManager, "mvinv"); - } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 92b1d8dc..40535be4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -25,10 +25,7 @@ final class ListCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - ListCommand( - @NotNull MVCommandManager commandManager, - @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + ListCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index 8a4f1e64..61983d5f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -19,8 +19,7 @@ final class ReloadCommand extends InventoriesCommand { private final MultiverseInventories plugin; @Inject - ReloadCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseInventories plugin) { - super(commandManager); + ReloadCommand(@NotNull MultiverseInventories plugin) { this.plugin = plugin; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java index dcab48be..ff6e8e83 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java @@ -25,8 +25,7 @@ final class RemoveDisabledSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - RemoveDisabledSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + RemoveDisabledSharesCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java index 2e3e8515..e58f0d4c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java @@ -25,8 +25,7 @@ final class RemoveSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - RemoveSharesCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + RemoveSharesCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java index 539654f7..7e718f07 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java @@ -31,8 +31,7 @@ final class RemoveWorldsCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @Inject - RemoveWorldsCommand(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { - super(commandManager); + RemoveWorldsCommand(@NotNull WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 0bc65f34..983833b3 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -26,10 +26,7 @@ final class ToggleCommand extends InventoriesCommand { private final InventoriesConfig inventoriesConfig; @Inject - ToggleCommand( - @NotNull MVCommandManager commandManager, - @NotNull InventoriesConfig inventoriesConfig) { - super(commandManager); + ToggleCommand(@NotNull InventoriesConfig inventoriesConfig) { this.inventoriesConfig = inventoriesConfig; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java index cec4769d..35709929 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -8,6 +8,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; @@ -33,11 +34,9 @@ final class MigrateInventorySerializationCommand extends InventoriesCommand { @Inject MigrateInventorySerializationCommand( - MVCommandManager commandManager, - ProfileDataSource profileDataSource, - InventoriesConfig inventoriesConfig + @NotNull ProfileDataSource profileDataSource, + @NotNull InventoriesConfig inventoriesConfig ) { - super(commandManager); this.profileDataSource = profileDataSource; this.inventoriesConfig = inventoriesConfig; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java index f1ad5d63..49d8c003 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java @@ -2,10 +2,16 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.command.CommandSender; +import org.checkerframework.checker.units.qual.N; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.destination.Destination; import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket; +import org.mvplugins.multiverse.core.locale.MVCorei18n; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.core.utils.result.FailureReason; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.acf.locales.MessageKey; +import org.mvplugins.multiverse.external.acf.locales.MessageKeyProvider; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; @@ -15,7 +21,7 @@ import java.util.Collection; @Service -public final class LastLocationDestination implements Destination { +public final class LastLocationDestination implements Destination { private final WorldManager worldManager; private final WorldGroupManager worldGroupManager; @@ -37,19 +43,11 @@ public final class LastLocationDestination implements Destination getDestinationInstance(@NotNull String destinationParams) { + if (!worldManager.isLoadedWorld(destinationParams)) { + return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND); } + return Attempt.success(new LastLocationDestinationInstance(this, worldGroupManager, profileContainerStoreProvider, destinationParams)); } @Override @@ -58,4 +56,23 @@ public LastLocationDestinationInstance getDestinationInstance(@Nullable String d .map(world -> new DestinationSuggestionPacket(this, world.getName(), world.getName())) .toList(); } + + public enum InstanceFailureReason implements FailureReason { + WORLD_NOT_FOUND(MVCorei18n.DESTINATION_SHARED_FAILUREREASON_WORLDNOTFOUND), + ; + + private final MessageKeyProvider messageKey; + + InstanceFailureReason(MessageKeyProvider message) { + this.messageKey = message; + } + + /** + * {@inheritDoc} + */ + @Override + public MessageKey getMessageKey() { + return messageKey.getMessageKey(); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 57f6ca14..7869d888 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -89,7 +89,12 @@ public static void init(MultiverseInventories inventories) { if (Sharables.worldGroupManager == null) { Sharables.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); } + Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); + if (Sharables.maxHealthAttr == null) { + // Old key for older minecraft version (<1.21) + Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("generic.max_health")); + } if (Sharables.maxHealthAttr == null) { Logging.warning("Could not find max_health attribute. Health related sharables may not work as expected."); } From 265142675e36d8376bc4ad3252401e34c215a269 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:13:53 +0800 Subject: [PATCH 147/180] Simplify profile type handling for give command --- .../multiverse/inventories/commands/GiveCommand.java | 12 +++++------- .../inventories/profile/key/ProfileTypes.java | 6 +++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index d06628c8..328ae73b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -40,30 +40,28 @@ final class GiveCommand extends InventoriesCommand { private final MultiverseInventories inventories; private final ProfileDataSource profileDataSource; - private final InventoriesConfig inventoriesConfig; @Inject GiveCommand( @NotNull MultiverseInventories inventories, - @NotNull ProfileDataSource profileDataSource, - @NotNull InventoriesConfig inventoriesConfig + @NotNull ProfileDataSource profileDataSource ) { this.inventories = inventories; this.profileDataSource = profileDataSource; - this.inventoriesConfig = inventoriesConfig; } + // TODO Support custom gamemode when gamemode profile is enabled + // TODO Better offline player parsing @CommandAlias("mvinvgive") @Subcommand("give") @CommandPermission("multiverse.inventories.give") - @CommandCompletion("@players @mvworlds @gamemodes @materials @range:64") + @CommandCompletion("@players @mvworlds @materials @range:64") @Syntax("") @Description("World and Group Information") void onGiveCommand( MVCommandIssuer issuer, OfflinePlayer player, MultiverseWorld world, - GameMode gameMode, String item ) { ItemStack itemStack = parseItemFromString(issuer, item); @@ -82,7 +80,7 @@ void onGiveCommand( return; } - ProfileType profileType = ProfileTypes.forGameMode(gameMode); + ProfileType profileType = ProfileTypes.getDefault(); SingleShareReader.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) .read() .thenCompose(inventory -> updatePlayerInventory(issuer, player, world, profileType, inventory, itemStack)) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java index a071ef90..d1bb7b08 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java @@ -50,11 +50,15 @@ public static List getTypes() { */ public static final ProfileType SPECTATOR = createProfileType("SPECTATOR"); + public static ProfileType getDefault() { + return SURVIVAL; + } + public static ProfileType forPlayer(Player player) { if (config != null && config.getEnableGamemodeShareHandling()) { return forGameMode(player.getGameMode()); } - return SURVIVAL; + return getDefault(); } /** From 0ee0bf977bd6750fd59d73d864d37df7fd15e977 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:17:34 +0800 Subject: [PATCH 148/180] Add confirmation for migrate inventory serialization command --- .../MigrateInventorySerializationCommand.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java index 35709929..0308b48b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -1,9 +1,13 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; import com.dumptruckman.minecraft.util.Logging; +import org.checkerframework.checker.units.qual.N; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; @@ -29,14 +33,17 @@ @CommandAlias("mvinv") final class MigrateInventorySerializationCommand extends InventoriesCommand { + private final CommandQueueManager commandQueueManager; private final ProfileDataSource profileDataSource; private final InventoriesConfig inventoriesConfig; @Inject MigrateInventorySerializationCommand( + @NotNull CommandQueueManager commandQueueManager, @NotNull ProfileDataSource profileDataSource, @NotNull InventoriesConfig inventoriesConfig ) { + this.commandQueueManager = commandQueueManager; this.profileDataSource = profileDataSource; this.inventoriesConfig = inventoriesConfig; } @@ -44,13 +51,17 @@ final class MigrateInventorySerializationCommand extends InventoriesCommand { @Subcommand("bulkedit migrate inventory-serialization nbt") @CommandPermission("multiverse.inventories.bulkedit") void onNbtCommand(MVCommandIssuer issuer) { - doMigration(issuer, true); + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to migrate all player data to NBT?")) + .action(() -> doMigration(issuer, true))); } @Subcommand("bulkedit migrate inventory-serialization bukkit") @CommandPermission("multiverse.inventories.bulkedit") void onBukkitCommand(MVCommandIssuer issuer) { - doMigration(issuer, false); + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to migrate all player data to old Bukkit serialization?")) + .action(() -> doMigration(issuer, false))); } private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { From 6f9ad7f4d3f6646f3c4083f9daa409c9f76472b9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:55:24 +0800 Subject: [PATCH 149/180] Implement legacy command alias --- .../inventories/commands/GiveCommand.java | 1 - .../inventories/commands/GroupCommand.java | 18 ++++++++++++++++-- .../inventories/commands/InfoCommand.java | 18 ++++++++++++++++-- .../inventories/commands/ListCommand.java | 19 +++++++++++++++++-- .../inventories/commands/ReloadCommand.java | 18 ++++++++++++++++-- .../inventories/commands/ToggleCommand.java | 18 ++++++++++++++++-- 6 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index 328ae73b..820a7b4e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -52,7 +52,6 @@ final class GiveCommand extends InventoriesCommand { // TODO Support custom gamemode when gamemode profile is enabled // TODO Better offline player parsing - @CommandAlias("mvinvgive") @Subcommand("give") @CommandPermission("multiverse.inventories.give") @CommandCompletion("@players @mvworlds @materials @range:64") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index 6eb66677..6d77a268 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.commands.prompts.GroupControlPrompt; @@ -19,7 +20,7 @@ @Service @CommandAlias("mvinv") -final class GroupCommand extends InventoriesCommand { +class GroupCommand extends InventoriesCommand { private final MultiverseInventories plugin; @@ -28,7 +29,6 @@ final class GroupCommand extends InventoriesCommand { this.plugin = plugin; } - @CommandAlias("mvinvgroup|mvinvg") @Subcommand("group") @CommandPermission("multiverse.inventories.group") @Description("Manage a world group with prompts!") @@ -43,4 +43,18 @@ void onGroupCommand(@NotNull MVCommandIssuer issuer) { .withModality(false).buildConversation(conversable); conversation.begin(); } + + @Service + private final static class LegacyAlias extends GroupCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(MultiverseInventories plugin) { + super(plugin); + } + + @Override + @CommandAlias("mvinvgroup|mvinvg") + void onGroupCommand(MVCommandIssuer issuer) { + super.onGroupCommand(issuer); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index f4ab062c..595fb0f1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; @@ -28,7 +29,7 @@ @Service @CommandAlias("mvinv") -final class InfoCommand extends InventoriesCommand { +class InfoCommand extends InventoriesCommand { private final ProfileContainerStoreProvider profileContainerStoreProvider; private final WorldGroupManager worldGroupManager; @@ -42,7 +43,6 @@ final class InfoCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @CommandAlias("mvinvinfo|mvinvi") @Subcommand("info") @CommandPermission("multiverse.inventories.info") @CommandCompletion("@mvworlds") @@ -114,4 +114,18 @@ private void worldInfo(MVCommandIssuer issuer, ProfileContainer worldProfileCont } issuer.sendInfo(MVInvi18n.INFO_WORLD_INFO, replace("{groups}").with(groupsString)); } + + @Service + private final static class LegacyAlias extends InfoCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(ProfileContainerStoreProvider profileContainerStoreProvider, WorldGroupManager worldGroupManager) { + super(profileContainerStoreProvider, worldGroupManager); + } + + @Override + @CommandAlias("mvinvinfo|mvinvi") + void onInfoCommand(MVCommandIssuer issuer, String name) { + super.onInfoCommand(issuer, name); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 40535be4..58e6c789 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; @@ -15,12 +16,13 @@ import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.Collection; +import java.util.List; import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service @CommandAlias("mvinv") -final class ListCommand extends InventoriesCommand { +class ListCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -29,7 +31,6 @@ final class ListCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @CommandAlias("mvinvlist|mvinvl") @Subcommand("list") @CommandPermission("multiverse.inventories.list") @Description("World and Group Information") @@ -49,4 +50,18 @@ void onListCommand(@NotNull MVCommandIssuer issuer) { issuer.sendInfo(MVInvi18n.LIST_GROUPS); issuer.sendInfo(MVInvi18n.LIST_GROUPS_INFO, replace("{groups}").with(groupsString)); } + + @Service + private final static class LegacyAlias extends ListCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(WorldGroupManager worldGroupManager) { + super(worldGroupManager); + } + + @Override + @CommandAlias("mvinvlist|mvinvl") + void onListCommand(MVCommandIssuer issuer) { + super.onListCommand(issuer); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index 61983d5f..45be6e79 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.core.command.MVCommandManager; @@ -14,7 +15,7 @@ @Service @CommandAlias("mvinv") -final class ReloadCommand extends InventoriesCommand { +class ReloadCommand extends InventoriesCommand { private final MultiverseInventories plugin; @@ -23,7 +24,6 @@ final class ReloadCommand extends InventoriesCommand { this.plugin = plugin; } - @CommandAlias("mvinvreload") @Subcommand("reload") @CommandPermission("multiverse.inventories.reload") @Description("Reloads config file") @@ -31,4 +31,18 @@ void onReloadCommand(@NotNull MVCommandIssuer issuer) { this.plugin.reloadConfig(); issuer.sendInfo(MVInvi18n.RELOAD_COMPLETE); } + + @Service + private final static class LegacyAlias extends ReloadCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(MultiverseInventories plugin) { + super(plugin); + } + + @Override + @CommandAlias("mvinvreload") + void onReloadCommand(MVCommandIssuer issuer) { + super.onReloadCommand(issuer); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 983833b3..07653911 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.commands; +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.share.Sharable; @@ -21,7 +22,7 @@ @Service @CommandAlias("mvinv") -final class ToggleCommand extends InventoriesCommand { +class ToggleCommand extends InventoriesCommand { private final InventoriesConfig inventoriesConfig; @@ -30,7 +31,6 @@ final class ToggleCommand extends InventoriesCommand { this.inventoriesConfig = inventoriesConfig; } - @CommandAlias("mvinvtoggle") @Subcommand("toggle") @CommandPermission("multiverse.inventories.addshares") @CommandCompletion("@sharables:scope=optional") @@ -59,4 +59,18 @@ void onToggleCommand( inventoriesConfig.setActiveOptionalShares(optionalShares); inventoriesConfig.save(); } + + @Service + private final static class LegacyAlias extends ToggleCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(InventoriesConfig inventoriesConfig) { + super(inventoriesConfig); + } + + @Override + @CommandAlias("mvinvtoggle") + void onToggleCommand(MVCommandIssuer issuer, Sharable sharable) { + super.onToggleCommand(issuer, sharable); + } + } } From 66723f36ef07050bf81ada39e35ed4ef10c813c2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:56:09 +0800 Subject: [PATCH 150/180] Cleanup use of @CommandAlias("mvinv") --- .../inventories/commands/AddDisabledSharesCommand.java | 2 +- .../multiverse/inventories/commands/AddSharesCommand.java | 2 +- .../multiverse/inventories/commands/AddWorldsCommand.java | 2 +- .../mvplugins/multiverse/inventories/commands/CacheCommand.java | 2 +- .../multiverse/inventories/commands/ConfigCommand.java | 2 +- .../multiverse/inventories/commands/CreateGroupCommand.java | 2 +- .../multiverse/inventories/commands/DeleteGroupCommand.java | 2 +- .../mvplugins/multiverse/inventories/commands/GiveCommand.java | 2 +- .../mvplugins/multiverse/inventories/commands/GroupCommand.java | 2 +- .../mvplugins/multiverse/inventories/commands/HelpCommand.java | 2 +- .../multiverse/inventories/commands/ImportCommand.java | 2 +- .../mvplugins/multiverse/inventories/commands/InfoCommand.java | 2 +- .../multiverse/inventories/commands/InventoriesCommand.java | 2 ++ .../mvplugins/multiverse/inventories/commands/ListCommand.java | 2 +- .../multiverse/inventories/commands/ReloadCommand.java | 2 +- .../inventories/commands/RemoveDisabledSharesCommand.java | 2 +- .../multiverse/inventories/commands/RemoveSharesCommand.java | 2 +- .../multiverse/inventories/commands/RemoveWorldsCommand.java | 2 +- .../multiverse/inventories/commands/ToggleCommand.java | 1 - 19 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java index cb87ab2c..bece508d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java @@ -19,7 +19,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class AddDisabledSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java index 1664c20f..95871427 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java @@ -21,7 +21,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class AddSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java index 3195e87f..8a1abec0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java @@ -23,7 +23,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class AddWorldsCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index 8a14430d..e997aa4d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -19,7 +19,7 @@ import java.util.Map; @Service -@CommandAlias("mvinv") + final class CacheCommand extends InventoriesCommand { private final ProfileCacheManager ProfileCacheManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java index 763f7603..bb420c6a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -17,7 +17,7 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; @Service -@CommandAlias("mvinv") + final class ConfigCommand extends InventoriesCommand { private final InventoriesConfig config; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java index 052d4634..a629dd11 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -24,7 +24,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class CreateGroupCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java index 699df977..5ce4415f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java @@ -21,7 +21,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class DeleteGroupCommand extends InventoriesCommand { private final CommandQueueManager commandQueueManager; private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index 820a7b4e..a0c82648 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @Service -@CommandAlias("mvinv") + final class GiveCommand extends InventoriesCommand { private final MultiverseInventories inventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index 6d77a268..2dd462c7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -19,7 +19,7 @@ import org.mvplugins.multiverse.inventories.util.MVInvi18n; @Service -@CommandAlias("mvinv") + class GroupCommand extends InventoriesCommand { private final MultiverseInventories plugin; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java index dcf9e37c..200aa0be 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java @@ -13,7 +13,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; @Service -@CommandAlias("mvinv") + final class HelpCommand extends InventoriesCommand { private final MVCommandManager commandManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index f8781db0..f4da3306 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -22,7 +22,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class ImportCommand extends InventoriesCommand { private final DataImportManager dataImportManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index 595fb0f1..0973b246 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -28,7 +28,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + class InfoCommand extends InventoriesCommand { private final ProfileContainerStoreProvider profileContainerStoreProvider; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java index ee295102..34c50c2c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -3,11 +3,13 @@ import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.command.MultiverseCommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; /** * Base class for all multiverse inventories commands. */ @Contract +@CommandAlias("mvinv") public abstract class InventoriesCommand extends MultiverseCommand { } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 58e6c789..5538fd22 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -21,7 +21,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + class ListCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index 45be6e79..3184c822 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -14,7 +14,7 @@ import org.mvplugins.multiverse.inventories.util.MVInvi18n; @Service -@CommandAlias("mvinv") + class ReloadCommand extends InventoriesCommand { private final MultiverseInventories plugin; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java index ff6e8e83..921a1416 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java @@ -19,7 +19,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class RemoveDisabledSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java index e58f0d4c..3534d310 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java @@ -20,7 +20,7 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") + final class RemoveSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java index 7e718f07..1a2e0f95 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java @@ -25,7 +25,7 @@ @Service -@CommandAlias("mvinv") + final class RemoveWorldsCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java index 07653911..a6e93e9e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -21,7 +21,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -@CommandAlias("mvinv") class ToggleCommand extends InventoriesCommand { private final InventoriesConfig inventoriesConfig; From 7c4ea39a9d0726fcbadacf65cb28374f07acae2e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:56:34 +0800 Subject: [PATCH 151/180] Fix command injection test --- .../java/org/mvplugins/multiverse/inventories/InjectionTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 233a516e..699b0ef2 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(19, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(24, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From 2b2c75340a544fba5582858e5ae88d011ecfb778 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:45:29 +0800 Subject: [PATCH 152/180] Fix give command syntax --- .../multiverse/inventories/commands/GiveCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index a0c82648..846d5423 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -54,8 +54,8 @@ final class GiveCommand extends InventoriesCommand { // TODO Better offline player parsing @Subcommand("give") @CommandPermission("multiverse.inventories.give") - @CommandCompletion("@players @mvworlds @materials @range:64") - @Syntax("") + @CommandCompletion("@players @mvworlds:scope=both @materials @range:64") + @Syntax(" [amount]") @Description("World and Group Information") void onGiveCommand( MVCommandIssuer issuer, From 1d87788b5b8ebe7e332b5f6116c45a92840f698f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:06:48 +0800 Subject: [PATCH 153/180] Use shared methods from MultiverseModule --- .../inventories/MultiverseInventories.java | 91 +++++++------------ .../MultiverseInventoriesPluginBinder.java | 7 +- 2 files changed, 35 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 581b71ea..6075f874 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -4,10 +4,10 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.mvplugins.multiverse.core.MultiverseCoreApi; -import org.mvplugins.multiverse.core.MultiversePlugin; import org.mvplugins.multiverse.core.config.CoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; +import org.mvplugins.multiverse.core.module.MultiverseModule; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.command.MVInvCommandConditions; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; @@ -42,12 +42,10 @@ * Multiverse-Inventories plugin main class. */ @Service -public class MultiverseInventories extends MultiversePlugin { +public class MultiverseInventories extends MultiverseModule { private static final double TARGET_CORE_API_VERSION = 5.0; - @Inject - private Provider commandManager; @Inject private Provider coreConfig; @Inject @@ -77,7 +75,6 @@ public class MultiverseInventories extends MultiversePlugin { @Inject private Provider mvInvCommandConditions; - private PluginServiceLocator serviceLocator; private InventoriesDupingPatch dupingPatch; private boolean usingSpawnChangeEvent = false; @@ -101,7 +98,7 @@ public void onLoad() { public final void onEnable() { super.onEnable(); - initializeDependencyInjection(); + initializeDependencyInjection(new MultiverseInventoriesPluginBinder(this)); ProfileTypes.init(this); Sharables.init(this); Perm.register(this); @@ -110,25 +107,12 @@ public final void onEnable() { this.reloadConfig(); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); - // Register Events - PluginManager pluginManager = this.getServer().getPluginManager(); - pluginManager.registerEvents(shareHandleListener.get(), this); - pluginManager.registerEvents(respawnListener.get(), this); - pluginManager.registerEvents(mvEventsListener.get(), this); - if (inventoriesConfig.get().getUseImprovedRespawnLocationDetection()) { - try { - Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); - pluginManager.registerEvents(new SpawnChangeListener(this), this); - usingSpawnChangeEvent = true; - Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); - } catch (ClassNotFoundException e) { - Logging.fine("PlayerSpawnChangeEvent will not be used!"); - } - } - - // Register Commands + // Register Stuff + this.registerEvents(); + this.setUpLocales(); this.registerCommands(); this.registerDestinations(); + // Hook plugins that can be imported from this.hookImportables(); @@ -140,17 +124,6 @@ public final void onEnable() { this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(this.getDescription().getAuthors())); } - private void initializeDependencyInjection() { - serviceLocator = PluginServiceLocatorFactory.get() - .registerPlugin(new MultiverseInventoriesPluginBinder(this), MultiverseCoreApi.get().getServiceLocator()) - .flatMap(PluginServiceLocator::enable) - .getOrElseThrow(exception -> { - Logging.severe("Failed to initialize dependency injection!"); - getServer().getPluginManager().disablePlugin(this); - return new RuntimeException(exception); - }); - } - /** * {@inheritDoc} */ @@ -171,23 +144,32 @@ public void onDisable() { Logging.shutdown(); } - private void registerCommands() { - Try.of(() -> commandManager.get()) - .andThenTry(commandManager -> { - commandManager.getLocales().addFileResClassLoader(this); - commandManager.getLocales().addBundleClassLoader(this.getClassLoader()); - commandManager.getLocales().addMessageBundles("multiverse-inventories"); - - mvInvCommandCompletion.get(); - mvInvCommandContexts.get(); - mvInvCommandConditions.get(); + private void registerEvents() { + PluginManager pluginManager = this.getServer().getPluginManager(); + pluginManager.registerEvents(shareHandleListener.get(), this); + pluginManager.registerEvents(respawnListener.get(), this); + pluginManager.registerEvents(mvEventsListener.get(), this); + if (inventoriesConfig.get().getUseImprovedRespawnLocationDetection()) { + try { + Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); + pluginManager.registerEvents(new SpawnChangeListener(this), this); + usingSpawnChangeEvent = true; + Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); + } catch (ClassNotFoundException e) { + Logging.fine("PlayerSpawnChangeEvent will not be used!"); + } + } + } - serviceLocator.getAllServices(InventoriesCommand.class).forEach(commandManager::registerCommand); - }) - .onFailure(e -> { - Logging.severe("Failed to register commands"); - e.printStackTrace(); - }); + private void registerCommands() { + Try.run(() -> { + mvInvCommandCompletion.get(); + mvInvCommandContexts.get(); + mvInvCommandConditions.get(); + }).onFailure(e -> { + Logging.warning("Failed to register command completers: %s", e.getMessage()); + }); + registerCommands(InventoriesCommand.class); } private void registerDestinations() { @@ -217,14 +199,6 @@ public double getTargetCoreVersion() { return TARGET_CORE_API_VERSION; } - /** - * {@inheritDoc} - */ - @Override - public PluginServiceLocator getServiceLocator() { - return serviceLocator; - } - /** * Nulls the config object and reloads a new one, also resetting the world groups in memory. */ @@ -273,4 +247,3 @@ public boolean isUsingSpawnChangeEvent() { return usingSpawnChangeEvent; } } - diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java index 18f7000f..fa8bf235 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -1,11 +1,10 @@ package org.mvplugins.multiverse.inventories; -import org.mvplugins.multiverse.core.MultiversePlugin; -import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder; +import org.mvplugins.multiverse.core.module.MultiverseModuleBinder; import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -final class MultiverseInventoriesPluginBinder extends JavaPluginBinder { +final class MultiverseInventoriesPluginBinder extends MultiverseModuleBinder { MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugin) { super(plugin); @@ -14,6 +13,6 @@ final class MultiverseInventoriesPluginBinder extends JavaPluginBinder bindPluginClass (ScopedBindingBuilder bindingBuilder) { - return super.bindPluginClass(bindingBuilder).to(MultiversePlugin.class).to(MultiverseInventories.class); + return super.bindPluginClass(bindingBuilder).to(MultiverseInventories.class); } } From c5de7b3776eaf03f6dbafc0f60dc5d3997bb0ac9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:58:37 +0800 Subject: [PATCH 154/180] Properly shutdown dependency injection --- .../mvplugins/multiverse/inventories/MultiverseInventories.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 6075f874..e17226b4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -141,6 +141,7 @@ public void onDisable() { } this.dupingPatch.disable(); + this.shutdownDependencyInjection(); Logging.shutdown(); } From 3c45808b7ab16a99d7c9835d6576ca1e6f2d0d3f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 14:17:59 +0800 Subject: [PATCH 155/180] Make bulkdedit command classes non public --- .../commands/bulkedit/globalprofile/DeleteCommand.java | 2 +- .../commands/bulkedit/globalprofile/ModifyCommand.java | 2 +- .../commands/bulkedit/playerprofile/DeleteCommand.java | 2 +- .../bulkedit/playerprofile/MigratePlayerNameCommand.java | 2 +- .../commands/bulkedit/playerprofile/ResetCommand.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java index 17b7f25d..b8d895ad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java @@ -1,4 +1,4 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; -public class DeleteCommand { +final class DeleteCommand { } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java index b107b98f..287142f2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java @@ -1,4 +1,4 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; -public class ModifyCommand { +final class ModifyCommand { } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java index 353c6215..2f9dad01 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -1,4 +1,4 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; -public class DeleteCommand { +final class DeleteCommand { } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java index 613b0ee2..7c7ea8fe 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java @@ -1,4 +1,4 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; -public class MigratePlayerNameCommand { +final class MigratePlayerNameCommand { } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java index 7df477cc..523ac7db 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java @@ -1,4 +1,4 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; -public class ResetCommand { +final class ResetCommand { } From 0fe2ebbf1e393bc475c030a8d0e5292be63197c8 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:19:14 +0800 Subject: [PATCH 156/180] Use profilekeys for PersistingProfile instead of loading the profile data --- .../handleshare/AffectedProfiles.java | 13 +++---- .../handleshare/GameModeShareHandler.java | 12 +++---- .../handleshare/PersistingProfile.java | 13 +++---- .../handleshare/ReadOnlyShareHandler.java | 4 +-- .../inventories/handleshare/ShareHandler.java | 20 ++++++----- .../handleshare/ShareHandlingUpdater.java | 36 ++++++++++++------- .../handleshare/WorldChangeShareHandler.java | 10 +++--- .../handleshare/WriteOnlyShareHandler.java | 4 +-- .../profile/container/ProfileContainer.java | 14 +++++--- .../handleshare/ShareHandlingUpdaterTest.kt | 7 ++-- 10 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java index dd89aa7b..c609949a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.handleshare; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.share.Shares; import java.util.LinkedList; @@ -16,19 +17,19 @@ public final class AffectedProfiles { } /** - * @param profile The player profile that will need data saved to. + * @param profileKey The profile key that will need data saved to. * @param shares What from this group needs to be saved. */ - void addWriteProfile(CompletableFuture profile, Shares shares) { - writeProfiles.add(new PersistingProfile(shares, profile)); + void addWriteProfile(ProfileKey profileKey, Shares shares) { + writeProfiles.add(new PersistingProfile(shares, profileKey)); } /** - * @param profile The player profile that will need data loaded from. + * @param profileKey The profile key that will need data loaded from. * @param shares What from this group needs to be loaded. */ - void addReadProfile(CompletableFuture profile, Shares shares) { - readProfiles.add(new PersistingProfile(shares, profile)); + void addReadProfile(ProfileKey profileKey, Shares shares) { + readProfiles.add(new PersistingProfile(shares, profileKey)); } public List getWriteProfiles() { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 15118522..5b48d3fd 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -58,7 +58,7 @@ protected void prepareProfiles() { } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { // Write to world profile to ensure data is saved incase bypass is removed affectedProfiles.addWriteProfile( - worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), + worldProfileContainerStore.getContainer(world).getProfileKey(fromType, player), (worldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) ? Sharables.standard() : Sharables.enabled() @@ -84,23 +84,23 @@ private void addProfiles() { worldGroups.forEach(worldGroup -> addProfilesForWorldGroup(handledShares,worldGroup)); Shares unhandledShares = Sharables.enabledOf().setSharing(handledShares, false); if (!unhandledShares.isEmpty()) { - affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getPlayerData(toType, player), unhandledShares); + affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getProfileKey(toType, player), unhandledShares); } if (inventoriesConfig.getAlwaysWriteWorldProfile()) { - affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getProfileKey(fromType, player), inventoriesConfig.getUseOptionalsForUngroupedWorlds() ? Sharables.enabled() : Sharables.standard()); } else { if (!unhandledShares.isEmpty()) { - affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getPlayerData(fromType, player), unhandledShares); + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getProfileKey(fromType, player), unhandledShares); } } } private void addProfilesForWorldGroup(Shares handledShares, WorldGroup worldGroup) { ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(fromType, player), worldGroup.getApplicableShares()); - affectedProfiles.addReadProfile(container.getPlayerData(toType, player), worldGroup.getApplicableShares()); + affectedProfiles.addWriteProfile(container.getProfileKey(fromType, player), worldGroup.getApplicableShares()); + affectedProfiles.addReadProfile(container.getProfileKey(toType, player), worldGroup.getApplicableShares()); handledShares.addAll(worldGroup.getApplicableShares()); handledShares.addAll(worldGroup.getDisabledShares()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java index c9ae3ad0..71efd955 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.handleshare; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.share.Shares; import java.util.concurrent.CompletableFuture; @@ -11,15 +12,15 @@ */ public final class PersistingProfile { private final Shares shares; - private final CompletableFuture profile; + private final ProfileKey profileKey; public PersistingProfile(Shares shares, PlayerProfile profile) { - this(shares, CompletableFuture.completedFuture(profile)); + this(shares, ProfileKey.fromPlayerProfile(profile)); } - public PersistingProfile(Shares shares, CompletableFuture profile) { + public PersistingProfile(Shares shares, ProfileKey profile) { this.shares = shares; - this.profile = profile; + this.profileKey = profile; } /** @@ -37,8 +38,8 @@ public Shares getShares() { * * @return The player profile for the world/group that will be saved/loaded for. */ - public CompletableFuture getProfile() { - return this.profile; + public ProfileKey getProfileKey() { + return this.profileKey; } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java index 294285e3..3cdf04c1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java @@ -20,12 +20,12 @@ protected void prepareProfiles() { List worldGroups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()); Shares unhandledShares = Sharables.enabledOf(); for (WorldGroup worldGroup : worldGroups) { - affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getPlayerData(player), worldGroup.getApplicableShares()); + affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getProfileKey(player), worldGroup.getApplicableShares()); unhandledShares.removeAll(worldGroup.getApplicableShares()); } if (!unhandledShares.isEmpty()) { affectedProfiles.addReadProfile( - worldProfileContainerStore.getContainer(player.getWorld().getName()).getPlayerData(player), + worldProfileContainerStore.getContainer(player.getWorld().getName()).getProfileKey(player), unhandledShares ); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index b64ca64b..5ef2ee4b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -13,6 +13,9 @@ import org.mvplugins.multiverse.inventories.share.Sharables; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.util.FutureNow; + +import java.util.concurrent.CompletableFuture; /** * Abstract class for handling sharing of data between worlds and game modes. @@ -104,14 +107,15 @@ private void updatePersistingProfile(PersistingProfile persistingProfile, Profil Logging.finest("No shares to write - nothing more to do."); return; } - persistingProfile.getProfile().thenAcceptAsync(playerProfile -> { - Logging.finer("Persisted: " + persistingProfile.getShares() + " to " - + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() - + " (" + playerProfile.getProfileType() + ")" - + " for player " + playerProfile.getPlayerName()); - playerProfile.update(snapshot, persistingProfile.getShares()); - profileDataStore.updatePlayerProfile(playerProfile); - }); + profileDataStore.getPlayerProfile(persistingProfile.getProfileKey()) + .thenCompose(playerProfile -> { + Logging.finer("Persisted: " + persistingProfile.getShares() + " to " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")" + + " for player " + playerProfile.getPlayerName()); + playerProfile.update(snapshot, persistingProfile.getShares()); + return profileDataStore.updatePlayerProfile(playerProfile); + }); } private void logHandlingComplete(double timeTaken, ShareHandlingEvent event) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 8055aa68..292d5ea8 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -3,20 +3,23 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.share.Sharable; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.util.FutureNow; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; public final class ShareHandlingUpdater { - public static void updateProfile(final MultiverseInventories inventories, - final Player player, - final PersistingProfile profile) { - new ShareHandlingUpdater(inventories, player, profile).updateProfile(); + public static CompletableFuture updateProfile(final MultiverseInventories inventories, + final Player player, + final PersistingProfile profile) { + return new ShareHandlingUpdater(inventories, player, profile).updateProfile(); } public static void updatePlayer(final MultiverseInventories inventories, @@ -25,22 +28,22 @@ public static void updatePlayer(final MultiverseInventories inventories, new ShareHandlingUpdater(inventories, player, profile).updatePlayer(); } - private final MultiverseInventories inventories; private final Player player; private final PersistingProfile profile; + private final ProfileDataSource profileDataSource; private ShareHandlingUpdater(MultiverseInventories inventories, Player player, PersistingProfile profile) { - this.inventories = inventories; this.player = player; this.profile = profile; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); } - private void updateProfile() { + private CompletableFuture updateProfile() { if (profile.getShares().isEmpty()) { - return; + return CompletableFuture.completedFuture(null); } - Try.of(() -> profile.getProfile().get(10, TimeUnit.SECONDS)) - .peek(playerProfile -> { + return profileDataSource.getPlayerProfile(profile.getProfileKey()) + .thenCompose(playerProfile -> { for (Sharable sharable : profile.getShares()) { sharable.getHandler().updateProfile(playerProfile, player); } @@ -48,14 +51,21 @@ private void updateProfile() { + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + " (" + playerProfile.getProfileType() + ")" + " for player " + playerProfile.getPlayerName()); - inventories.getServiceLocator().getService(ProfileDataSource.class).updatePlayerProfile(playerProfile); + return profileDataSource.updatePlayerProfile(playerProfile); }) - .onFailure(e -> Logging.severe("Error getting playerdata: " + e.getMessage())); + .exceptionally(throwable -> { + Logging.severe("Could not persist profile for player: %s. %s", + player.getName(), throwable.getMessage()); + return null; + }); } private void updatePlayer() { + if (profile.getShares().isEmpty()) { + return; + } player.closeInventory(); - Try.of(() -> profile.getProfile().get(10, TimeUnit.SECONDS)) + Try.of(() -> FutureNow.get(profileDataSource.getPlayerProfile(profile.getProfileKey()))) .peek(playerProfile -> { List> loaded = new ArrayList<>(profile.getShares().size()); List> defaulted = new ArrayList<>(profile.getShares().size()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java index 96616274..c6bdd788 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -48,7 +48,7 @@ protected void prepareProfiles() { } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { // Write to world profile to ensure data is saved incase bypass is removed affectedProfiles.addWriteProfile( - worldProfileContainerStore.getContainer(fromWorld).getPlayerData(player), + worldProfileContainerStore.getContainer(fromWorld).getProfileKey(player), (fromWorldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) ? Sharables.standard() : Sharables.enabled() @@ -131,7 +131,7 @@ private void addReadProfileForWorldGroup(WorldGroup worldGroup) { Logging.finer("Removing lastLocation from applicableShares as it is not applied for all teleports"); applicableShares.remove(Sharables.LAST_LOCATION); } - affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getPlayerData(player), applicableShares); + affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getProfileKey(player), applicableShares); } private void useToWorldForMissingShares() { @@ -148,7 +148,7 @@ private void useToWorldForMissingShares() { } Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); affectedProfiles.addReadProfile( - worldProfileContainerStore.getContainer(toWorld).getPlayerData(player), + worldProfileContainerStore.getContainer(toWorld).getProfileKey(player), unhandledShares ); } @@ -165,7 +165,7 @@ private void conditionallyAddWriteProfiles() { : Sharables.enabledOf().setSharing(handledShares, false); if (!sharesToWrite.isEmpty()) { affectedProfiles.addWriteProfile( - worldProfileContainerStore.getContainer(fromWorld).getPlayerData(player), + worldProfileContainerStore.getContainer(fromWorld).getProfileKey(player), sharesToWrite); } } @@ -180,7 +180,7 @@ private void conditionallyAddWriteProfileForGroup(WorldGroup worldGroup) { void addWriteProfileForGroup(WorldGroup worldGroup) { ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(player), worldGroup.getApplicableShares()); + affectedProfiles.addWriteProfile(container.getProfileKey(player), worldGroup.getApplicableShares()); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java index 1906ab93..9eb66032 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java @@ -34,7 +34,7 @@ protected void prepareProfiles() { Shares unhandledShares = Sharables.enabledOf(); for (WorldGroup worldGroup : worldGroups) { affectedProfiles.addWriteProfile( - worldGroup.getGroupProfileContainer().getPlayerData(profileType, player), + worldGroup.getGroupProfileContainer().getProfileKey(profileType, player), worldGroup.getApplicableShares() ); unhandledShares.removeAll(worldGroup.getApplicableShares()); @@ -44,7 +44,7 @@ protected void prepareProfiles() { : unhandledShares; if (!sharesToWrite.isEmpty()) { affectedProfiles.addWriteProfile( - worldProfileContainerStore.getContainer(worldName).getPlayerData(profileType, player), + worldProfileContainerStore.getContainer(worldName).getProfileKey(profileType, player), sharesToWrite ); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index ccb4fb42..8ded96f2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -39,16 +39,20 @@ public Collection listPlayerProfileNames() { return profileDataSource.listPlayerProfileNames(getContainerType(), getContainerName()); } + public ProfileKey getProfileKey(Player player) { + return getProfileKey(ProfileTypes.forPlayer(player), player); + } + + public ProfileKey getProfileKey(ProfileType profileType, OfflinePlayer player) { + return ProfileKey.create(getContainerType(), getContainerName(), profileType, player); + } + public CompletableFuture getPlayerData(Player player) { return getPlayerData(ProfileTypes.forPlayer(player), player); } public CompletableFuture getPlayerData(ProfileType profileType, OfflinePlayer player) { - return profileDataSource.getPlayerProfile(ProfileKey.create( - getContainerType(), - getContainerName(), - profileType, - player)); + return profileDataSource.getPlayerProfile(getProfileKey(profileType, player)); } /** diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index 60999d2c..a3aa64a7 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -30,10 +30,9 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { player.health = 4.4 player.maxHealth = 15.1 - val playerProfileFuture = profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId)) - ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfileFuture)) - val playerProfile = playerProfileFuture.get() + val playerProfileKey = ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfileKey)) + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile(playerProfileKey)) assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) assertEquals(15.1, playerProfile.get(Sharables.MAX_HEALTH)) } From ed2e533f4f0616309a7a63a6fb8889bea1d1ff3e Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:31:48 +0800 Subject: [PATCH 157/180] use `-` for multi-word subcommands --- .../inventories/commands/AddDisabledSharesCommand.java | 3 +-- .../multiverse/inventories/commands/AddSharesCommand.java | 3 +-- .../multiverse/inventories/commands/AddWorldsCommand.java | 3 +-- .../multiverse/inventories/commands/CacheCommand.java | 4 ---- .../multiverse/inventories/commands/ConfigCommand.java | 1 - .../multiverse/inventories/commands/CreateGroupCommand.java | 3 +-- .../multiverse/inventories/commands/DeleteGroupCommand.java | 3 +-- .../multiverse/inventories/commands/GiveCommand.java | 1 - .../multiverse/inventories/commands/GroupCommand.java | 1 - .../multiverse/inventories/commands/HelpCommand.java | 1 - .../multiverse/inventories/commands/ImportCommand.java | 1 - .../multiverse/inventories/commands/InfoCommand.java | 1 - .../multiverse/inventories/commands/ListCommand.java | 1 - .../multiverse/inventories/commands/ReloadCommand.java | 1 - .../inventories/commands/RemoveDisabledSharesCommand.java | 3 +-- .../multiverse/inventories/commands/RemoveSharesCommand.java | 3 +-- .../multiverse/inventories/commands/RemoveWorldsCommand.java | 3 +-- 17 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java index bece508d..ab29340d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java @@ -19,7 +19,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class AddDisabledSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -29,7 +28,7 @@ final class AddDisabledSharesCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("adddisbledshares") + @Subcommand("add-disabled-shares") @CommandPermission("multiverse.inventories.adddisabledshares") @CommandCompletion("@worldGroups @shares") @Syntax(" ") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java index 95871427..080398cc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java @@ -21,7 +21,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class AddSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -31,7 +30,7 @@ final class AddSharesCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("addshares") + @Subcommand("add-shares") @CommandPermission("multiverse.inventories.addshares") @CommandCompletion("@worldGroups @shares") @Syntax(" ") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java index 8a1abec0..3cf97d1e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java @@ -23,7 +23,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class AddWorldsCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -33,7 +32,7 @@ final class AddWorldsCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("addworlds") + @Subcommand("add-worlds") @CommandPermission("multiverse.inventories.addworlds") @CommandCompletion("@worldGroups @mvworlds:multiple,scope=both") @Syntax(" ") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java index e997aa4d..f63bd0ed 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -4,8 +4,6 @@ import org.bukkit.entity.Player; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; -import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Flags; @@ -14,12 +12,10 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import java.util.Map; @Service - final class CacheCommand extends InventoriesCommand { private final ProfileCacheManager ProfileCacheManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java index bb420c6a..998df4b6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -17,7 +17,6 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; @Service - final class ConfigCommand extends InventoriesCommand { private final InventoriesConfig config; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java index a629dd11..8717975c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -24,7 +24,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class CreateGroupCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -33,7 +32,7 @@ final class CreateGroupCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("creategroup") + @Subcommand("create-group") @CommandPermission("multiverse.inventories.creategroup") @CommandCompletion("@empty @mvworlds:multiple,scope=both @shares") @Syntax(" [share[,extra]] [world[,extra]]") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java index 5ce4415f..42529934 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java @@ -21,7 +21,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class DeleteGroupCommand extends InventoriesCommand { private final CommandQueueManager commandQueueManager; private final WorldGroupManager worldGroupManager; @@ -35,7 +34,7 @@ final class DeleteGroupCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("deletegroup") + @Subcommand("delete-group") @CommandPermission("multiverse.inventories.deletegroup") @CommandCompletion("@worldGroups") @Syntax("") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index 846d5423..13073621 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean; @Service - final class GiveCommand extends InventoriesCommand { private final MultiverseInventories inventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java index 2dd462c7..ec821f38 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -19,7 +19,6 @@ import org.mvplugins.multiverse.inventories.util.MVInvi18n; @Service - class GroupCommand extends InventoriesCommand { private final MultiverseInventories plugin; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java index 200aa0be..54462e67 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java @@ -13,7 +13,6 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; @Service - final class HelpCommand extends InventoriesCommand { private final MVCommandManager commandManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java index f4da3306..0a62ff2b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java @@ -22,7 +22,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class ImportCommand extends InventoriesCommand { private final DataImportManager dataImportManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java index 0973b246..0ffc436c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -28,7 +28,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - class InfoCommand extends InventoriesCommand { private final ProfileContainerStoreProvider profileContainerStoreProvider; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java index 5538fd22..ae5f4444 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -21,7 +21,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - class ListCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java index 3184c822..cbf16d52 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -14,7 +14,6 @@ import org.mvplugins.multiverse.inventories.util.MVInvi18n; @Service - class ReloadCommand extends InventoriesCommand { private final MultiverseInventories plugin; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java index 921a1416..6ccf8c64 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java @@ -19,7 +19,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class RemoveDisabledSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -29,7 +28,7 @@ final class RemoveDisabledSharesCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("removedisabledshares") + @Subcommand("remove-disabled-shares") @CommandPermission("multiverse.inventories.removedisabledshares") @CommandCompletion("@worldGroups @shares") @Syntax(" ") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java index 3534d310..326471ce 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java @@ -20,7 +20,6 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service - final class RemoveSharesCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -29,7 +28,7 @@ final class RemoveSharesCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("removeshares") + @Subcommand("remove-shares") @CommandPermission("multiverse.inventories.removeshares") @CommandCompletion("@worldGroups @shares") @Syntax(" ") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java index 1a2e0f95..0840f9a1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java @@ -25,7 +25,6 @@ @Service - final class RemoveWorldsCommand extends InventoriesCommand { private final WorldGroupManager worldGroupManager; @@ -35,7 +34,7 @@ final class RemoveWorldsCommand extends InventoriesCommand { this.worldGroupManager = worldGroupManager; } - @Subcommand("removeworlds") + @Subcommand("remove-worlds") @CommandPermission("multiverse.inventories.removeworlds") @CommandCompletion("@worldGroups @worldGroupWorlds") @Syntax(", ") From 13a1500c0a138295ca1c8fa7074c3956a416f004 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:43:44 +0800 Subject: [PATCH 158/180] Add bulkedit migrate player-name --- .../MigrateInventorySerializationCommand.java | 1 - .../MigratePlayerNameCommand.java | 45 ++++++++++++++++++- .../handleshare/ShareHandleListener.java | 2 +- .../profile/FlatFileProfileDataSource.java | 4 +- .../profile/ProfileCacheManager.java | 4 ++ .../profile/ProfileDataSource.java | 3 +- 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java index 0308b48b..5b497e24 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -30,7 +30,6 @@ import java.util.concurrent.atomic.AtomicLong; @Service -@CommandAlias("mvinv") final class MigrateInventorySerializationCommand extends InventoriesCommand { private final CommandQueueManager commandQueueManager; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java index 7c7ea8fe..e4aec8ad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java @@ -1,4 +1,47 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; -final class MigratePlayerNameCommand { +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; + +final class MigratePlayerNameCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + + MigratePlayerNameCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull ProfileDataSource profileDataSource) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + } + + @Subcommand("bulkedit migrate player-name") + @CommandPermission("multiverse.inventories.bulkedit") + @Syntax(" ") + @Description("Only use this if automatic migration failed for some reason.") + void onCommand( + MVCommandIssuer issuer, + String oldName, + String newName + ) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to migrate all player data for %s to %s? This action cannot be undone." + .formatted(oldName, newName))) + .action(() -> doMigration(issuer, oldName, newName))); + } + + private void doMigration(MVCommandIssuer issuer, String oldName, String newName) { + Try.run(() -> profileDataSource.migratePlayerProfileName(oldName, newName)) + .onFailure(e -> issuer.sendMessage("Failed to migrate player data for " + oldName + ". " + e.getMessage())); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 0a084cd5..a7714f1c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -129,7 +129,7 @@ private void verifyCorrectPlayerName(UUID uuid, String name) { Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", uuid, globalProfile.getLastKnownName(), name); try { - profileDataSource.migratePlayerProfileName(globalProfile.getLastKnownName(), name, uuid); + profileDataSource.migratePlayerProfileName(globalProfile.getLastKnownName(), name); } catch (IOException e) { Logging.severe("An error occurred while trying to migrate playerdata."); e.printStackTrace(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 0279acad..5c05d54d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -215,8 +215,8 @@ public CompletableFuture deletePlayerFile(ProfileFileKey profileKey) { * {@inheritDoc} */ @Override - public void migratePlayerProfileName(String oldName, String newName, UUID uuid) { - profileCacheManager.clearPlayerCache(uuid); + public void migratePlayerProfileName(String oldName, String newName) { + profileCacheManager.clearPlayerCache(oldName); List worldFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.WORLD); List groupFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.GROUP); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java index dc989301..0a3d411b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java @@ -69,6 +69,10 @@ Option getCachedPlayerProfile(ProfileKey key) { return Option.of(playerProfileCache.synchronous().getIfPresent(key)); } + public void clearPlayerCache(String playerName) { + clearPlayerProfileCache(key -> key.getPlayerName().equals(playerName)); + } + public void clearPlayerCache(UUID playerUUID) { clearPlayerProfileCache(key -> key.getPlayerUUID().equals(playerUUID)); clearGlobalProfileCache(key -> key.equals(playerUUID)); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index be7e3749..2c188273 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -45,10 +45,9 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * * @param oldName the previous name of the player. * @param newName the new name of the player. - * @param playerUUID the UUID of the player. * @throws IOException Thrown if something goes wrong while migrating the files. */ - void migratePlayerProfileName(String oldName, String newName, UUID playerUUID) throws IOException; + void migratePlayerProfileName(String oldName, String newName) throws IOException; /** * Retrieves the global profile for a player which contains meta-data for the player. From 5291484baf5e24dcb15bd746e113a21d8cca37cb Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:42:48 +0800 Subject: [PATCH 159/180] Implement global profile modify and clear command --- .../command/MVInvCommandContexts.java | 21 +++ .../bulkedit/globalprofile/ClearCommand.java | 79 +++++++++ .../bulkedit/globalprofile/DeleteCommand.java | 4 - .../bulkedit/globalprofile/ModifyCommand.java | 65 +++++++- .../bulkedit/playerprofile/DeleteCommand.java | 4 - .../bulkedit/playerprofile/ResetCommand.java | 4 - .../handle/JsonConfigurationHandle.java | 73 +++++++++ .../handleshare/ShareHandleListener.java | 5 +- .../profile/FlatFileProfileDataSource.java | 27 ++-- .../inventories/profile/GlobalProfile.java | 151 +++++++----------- 10 files changed, 313 insertions(+), 120 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java index 5b0a0de0..2d050826 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.command; import com.google.common.base.Strings; +import org.bukkit.Bukkit; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; @@ -11,11 +12,15 @@ import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; import org.mvplugins.multiverse.inventories.util.MVInvi18n; +import java.util.Arrays; +import java.util.Objects; + @Service public final class MVInvCommandContexts { @@ -26,11 +31,27 @@ private MVInvCommandContexts(@NotNull MVCommandManager commandManager, @NotNull this.worldGroupManager = worldGroupManager; CommandContexts commandContexts = commandManager.getCommandContexts(); + commandContexts.registerContext(GlobalProfileKey[].class, this::parseGlobalProfileKeys); commandContexts.registerContext(Sharable.class, this::parseSharable); commandContexts.registerContext(Shares.class, this::parseShares); commandContexts.registerContext(WorldGroup.class, this::parseWorldGroup); } + private GlobalProfileKey[] parseGlobalProfileKeys(BukkitCommandExecutionContext context) { + String profileStrings = context.popFirstArg(); + if (profileStrings.equals("@all")) { + return Arrays.stream(Bukkit.getOfflinePlayers()) + .map(GlobalProfileKey::create) + .toArray(GlobalProfileKey[]::new); + } + + String[] profileNames = profileStrings.split(","); + return Arrays.stream(profileNames) + .map(Bukkit::getOfflinePlayer) + .map(GlobalProfileKey::create) + .toArray(GlobalProfileKey[]::new); + } + private Sharable parseSharable(BukkitCommandExecutionContext context) { String sharableName = context.popFirstArg(); Sharable targetSharable = Sharables.all().stream() diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java new file mode 100644 index 00000000..10649f76 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java @@ -0,0 +1,79 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.flag.CommandFlag; +import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; +import org.mvplugins.multiverse.core.command.flag.FlagBuilder; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +@Service +final class ClearCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + private final Flags flags; + + @Inject + ClearCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull ProfileDataSource profileDataSource, + @NotNull Flags flags + ) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + this.flags = flags; + } + + @Subcommand("bulkedit globalprofile clear") + @CommandPermission("multiverse.inventories.bulkedit") + @Syntax("") + void onCommand( + MVCommandIssuer issuer, + + @Syntax("") + GlobalProfileKey[] globalProfileKeys, + + String[] flagArray + ) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to clear %d profiles?".formatted(globalProfileKeys.length))) + .action(() -> doClear(issuer, globalProfileKeys, parsedFlags.hasFlag(flags.clearAllPlayerprofiles)))); + } + + private void doClear(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfile) { + CompletableFuture[] futures = Arrays.stream(globalProfileKeys) + .map(globalProfileKey -> profileDataSource.deleteGlobalProfile(globalProfileKey, clearPlayerProfile)) + .toArray(CompletableFuture[]::new); + + CompletableFuture.allOf(futures) + .thenRun(() -> issuer.sendMessage("Successfully cleared %d profiles.".formatted(globalProfileKeys.length))); + } + + @Service + private static final class Flags extends FlagBuilder { + @Inject + private Flags(@NotNull String name, @NotNull CommandFlagsManager flagsManager) { + super(name, flagsManager); + } + + private final CommandFlag clearAllPlayerprofiles = flag(CommandFlag.builder("--clear-all-playerprofiles") + .addAlias("-a") + .build()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java deleted file mode 100644 index b8d895ad..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/DeleteCommand.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; - -final class DeleteCommand { -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java index 287142f2..8ef3cc4e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java @@ -1,4 +1,67 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; -final class ModifyCommand { +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +final class ModifyCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + + @Inject + ModifyCommand(CommandQueueManager commandQueueManager, ProfileDataSource profileDataSource) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + } + + @Subcommand("bulkedit globalprofile modify") + @CommandPermission("multiverse.inventories.bulkedit") + @Syntax(" ") + void onCommand( + MVCommandIssuer issuer, + + @Syntax("") + String property, + + @Syntax("") + String value, + + @Syntax("") + GlobalProfileKey[] profileKeys + ) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to modify %s to %s for %d players?".formatted(property, value, profileKeys.length))) + .action(() -> doModify(issuer, property, value, profileKeys))); + } + + private void doModify(MVCommandIssuer issuer, String property, String value, GlobalProfileKey[] profileKeys) { + AtomicInteger counter = new AtomicInteger(0); + CompletableFuture[] futures = Arrays.stream(profileKeys) + .map(profileKey -> + profileDataSource.modifyGlobalProfile(profileKey, globalProfile -> + globalProfile.getStringPropertyHandle() + .modifyPropertyString(property, value, PropertyModifyAction.SET) + .onSuccess(ignore -> counter.incrementAndGet()) + .onFailure(throwable -> issuer.sendError("Failed to modify %s for %s. %s".formatted(property, profileKey, throwable.getMessage()))))) + .toArray(CompletableFuture[]::new); + + CompletableFuture.allOf(futures) + .thenRun(() -> issuer.sendMessage("Successfully modified %s to %s for %d players.".formatted(property, value, counter.get()))); + } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java deleted file mode 100644 index 2f9dad01..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; - -final class DeleteCommand { -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java deleted file mode 100644 index 523ac7db..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ResetCommand.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; - -final class ResetCommand { -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java b/src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java new file mode 100644 index 00000000..3b6133eb --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java @@ -0,0 +1,73 @@ +package org.mvplugins.multiverse.inventories.config.handle; + +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import org.bukkit.configuration.InvalidConfigurationException; +import org.mvplugins.multiverse.core.config.handle.FileConfigurationHandle; +import org.mvplugins.multiverse.core.config.migration.ConfigMigrator; +import org.mvplugins.multiverse.core.config.node.NodeGroup; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Try; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +public class JsonConfigurationHandle extends FileConfigurationHandle { + + /** + * Creates a new builder for {@link JsonConfigurationHandle}. + * + * @param configPath The path to the config file. + * @param nodes The nodes. + * @return The builder. + */ + public static @NotNull Builder builder(@NotNull Path configPath, @NotNull NodeGroup nodes) { + return new Builder<>(configPath, nodes); + } + + protected JsonConfigurationHandle( + @NotNull Path configPath, + @Nullable Logger logger, + @NotNull NodeGroup nodes, + @Nullable ConfigMigrator migrator + ) { + super(configPath, logger, nodes, migrator); + } + + @Override + protected void loadConfigObject() throws IOException, InvalidConfigurationException { + config = new JsonConfiguration(); + config.load(configFile); + } + + /** + * {@inheritDoc} + */ + @Override + public Try save() { + return Try.run(() -> config = new JsonConfiguration()) + .flatMap(ignore -> super.save()) + .andThenTry(ignore -> config.save(configFile)); + } + + /** + * Builder for {@link JsonConfigurationHandle}. + * + * @param The type of the builder. + */ + public static class Builder> extends FileConfigurationHandle.Builder { + + protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) { + super(configPath, nodes); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull JsonConfigurationHandle build() { + return new JsonConfigurationHandle(configPath, logger, nodes, migrator); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index a7714f1c..5a0e5385 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.inventories.handleshare; import com.dumptruckman.minecraft.util.Logging; +import com.google.common.base.Strings; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.vavr.control.Try; @@ -111,7 +112,7 @@ void playerJoin(final PlayerJoinEvent event) { verifyCorrectPlayerName(player.getUniqueId(), player.getName()); final GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player))); - if (config.getApplyPlayerdataOnJoin() && globalProfile.shouldLoadOnLogin()) { + if (globalProfile.shouldLoadOnLogin()) { new ReadOnlyShareHandler(inventories, player).handleSharing(); } globalProfile.setLoadOnLogin(false); @@ -163,7 +164,7 @@ void playerQuit(final PlayerQuitEvent event) { } private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { - if (globalProfile.getLastWorld() == null) { + if (Strings.isNullOrEmpty(globalProfile.getLastWorld())) { globalProfile.setLastWorld(world); } else { if (!world.equals(globalProfile.getLastWorld())) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 5c05d54d..0f508fad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -259,7 +259,9 @@ public CompletableFuture getGlobalProfile(GlobalProfileKey key) { Logging.finer("Global profile for player %s (%s) not in cached. Loading...", uuid, key.getPlayerName()); migrateGlobalProfileToUUID(uuid, key.getPlayerName()); if (!globalFile.exists()) { - return CompletableFuture.completedFuture(GlobalProfile.createGlobalProfile(key)); + GlobalProfile globalProfile = new GlobalProfile(key.getPlayerUUID(), globalFile.toPath()); + globalProfile.setLastKnownName(key.getPlayerName()); + return CompletableFuture.completedFuture(globalProfile); } return asyncFileIO.queueFileCallable(globalFile, () -> getGlobalProfileFromDisk(key.getPlayerUUID(), key.getPlayerName(), globalFile)); }); @@ -288,12 +290,9 @@ private void migrateGlobalProfileToUUID(UUID playerUUID, String playerName) { } private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { - FileConfiguration playerData = loadFileToJsonConfiguration(globalFile); - ConfigurationSection section = playerData.getConfigurationSection(DataStrings.PLAYER_DATA); - if (section == null) { - return GlobalProfile.createGlobalProfile(playerUUID, playerName); - } - return GlobalProfile.deserialize(playerName, playerUUID, section); + GlobalProfile globalProfile = new GlobalProfile(playerUUID, globalFile.toPath()); + globalProfile.setLastKnownName(playerName); + return globalProfile; } /** @@ -315,18 +314,14 @@ private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, @Override public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); - return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile, globalFile)); + return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile)); } - private void processGlobalProfileWrite(GlobalProfile globalProfile, File globalFile) { - FileConfiguration playerData = new JsonConfiguration(); - playerData.createSection(DataStrings.PLAYER_DATA, globalProfile.serialize(globalProfile)); - try { - playerData.save(globalFile); - } catch (IOException e) { + private void processGlobalProfileWrite(GlobalProfile globalProfile) { + globalProfile.save().onFailure(throwable -> { Logging.severe("Could not save global data for player: " + globalProfile); - Logging.severe(e.getMessage()); - } + Logging.severe(throwable.getMessage()); + }); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index c6ac7dde..9df1f525 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -2,9 +2,16 @@ import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; +import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; +import org.mvplugins.multiverse.core.config.node.ConfigNode; +import org.mvplugins.multiverse.core.config.node.Node; +import org.mvplugins.multiverse.core.config.node.NodeGroup; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.config.handle.JsonConfigurationHandle; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.util.DataStrings; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -14,54 +21,29 @@ */ public final class GlobalProfile { - /** - * Creates a global profile object for the given player with default values. - * - * @param key the player to create the profile object for. - * @return a new GlobalProfile for the given player. - */ - static GlobalProfile createGlobalProfile(GlobalProfileKey key) { - return new GlobalProfile(key.getPlayerName(), key.getPlayerUUID()); - } - - /** - * Creates a global profile object for the given player with default values. - * - * @param playerUUID the UUID of the player to create the profile for. - * @param playerName the player to create the profile object for. - * @return a new GlobalProfile for the given player. - */ - static GlobalProfile createGlobalProfile(UUID playerUUID, String playerName) { - return new GlobalProfile(playerName, playerUUID); - } - private final UUID uuid; - private String lastWorld = null; - private String lastKnownName; - private boolean loadOnLogin = false; + private final Nodes nodes; + private final JsonConfigurationHandle handle; + private final StringPropertyHandle stringPropertyHandle; - private GlobalProfile(String name, UUID uuid) { + GlobalProfile(UUID uuid, Path configPath) { this.uuid = uuid; - this.lastKnownName = name; + this.nodes = new Nodes(); + this.handle = JsonConfigurationHandle.builder(configPath, nodes.nodes).build(); + this.stringPropertyHandle = new StringPropertyHandle(handle); + this.handle.load(); } - public GlobalProfile(UUID uuid, String lastWorld, String lastKnownName, boolean loadOnLogin) { - this.uuid = uuid; - this.lastWorld = lastWorld; - this.lastKnownName = lastKnownName; - this.loadOnLogin = loadOnLogin; + Try load() { + return handle.load(); } - /** - * Returns the name of the player. - * - * @return The name of the player. - * @deprecated Use {@link #getPlayerUUID()} to uniquely identify a player. - * If you need player name, use {@link #getLastKnownName()}. - */ - @Deprecated - public String getPlayerName() { - return this.lastKnownName; + Try save() { + return handle.save(); + } + + public StringPropertyHandle getStringPropertyHandle() { + return stringPropertyHandle; } /** @@ -79,7 +61,7 @@ public UUID getPlayerUUID() { * @return the last name the player was known to have. */ public String getLastKnownName() { - return lastKnownName; + return handle.get(nodes.lastKnownName); } /** @@ -89,8 +71,8 @@ public String getLastKnownName() { * * @param lastKnownName the last known name for the player. */ - public void setLastKnownName(String lastKnownName) { - this.lastKnownName = lastKnownName; + public Try setLastKnownName(String lastKnownName) { + return handle.set(nodes.lastKnownName, lastKnownName); } /** @@ -99,7 +81,16 @@ public void setLastKnownName(String lastKnownName) { * @return The last world the player was in or null if not set. */ public String getLastWorld() { - return this.lastWorld; + return handle.get(nodes.lastWorld); + } + + /** + * Sets the last world the player was known to be in. This is done automatically on world change. + * + * @param world The world the player is in. + */ + public Try setLastWorld(String world) { + return handle.set(nodes.lastWorld, world); } /** @@ -109,7 +100,7 @@ public String getLastWorld() { * @return true if player data should be loaded when they log in. */ public boolean shouldLoadOnLogin() { - return loadOnLogin; + return handle.get(nodes.loadOnLogin); } /** @@ -117,59 +108,41 @@ public boolean shouldLoadOnLogin() { * * @param loadOnLogin true if player data should be loaded when they log in. */ - public void setLoadOnLogin(boolean loadOnLogin) { - this.loadOnLogin = loadOnLogin; - } - - /** - * Sets the last world the player was known to be in. This is done automatically on world change. - * - * @param world The world the player is in. - */ - public void setLastWorld(String world) { - this.lastWorld = world; + public Try setLoadOnLogin(boolean loadOnLogin) { + return handle.set(nodes.loadOnLogin, loadOnLogin); } @Override public String toString() { return "GlobalProfile{" + "uuid=" + uuid + - ", lastWorld='" + lastWorld + '\'' + - ", lastKnownName='" + lastKnownName + '\'' + - ", loadOnLogin=" + loadOnLogin + + ", lastWorld='" + getLastWorld() + '\'' + + ", lastKnownName='" + getLastKnownName() + '\'' + + ", loadOnLogin=" + shouldLoadOnLogin() + '}'; } - /** - * Converts a global profile to a map that can be serialized into the profile data file. - * - * @param profile The global profile data. - * @return The serialized profile map. - */ - Map serialize(GlobalProfile profile) { - Map result = new HashMap<>(3); - if (profile.getLastWorld() != null) { - result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); + private static final class Nodes { + private final NodeGroup nodes = new NodeGroup(); + + private N node(N node) { + nodes.add(node); + return node; } - result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); - result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); - return result; - } - /** - * Converts a configuration section to a global profile. - * - * @param playerName The player name. - * @param playerUUID The player UUID. - * @param data The configuration section to convert. - * @return The global profile. - */ - static GlobalProfile deserialize(String playerName, UUID playerUUID, ConfigurationSection data) { - return new GlobalProfile( - playerUUID, - data.getString(DataStrings.PLAYER_LAST_WORLD, null), - data.getString(DataStrings.PLAYER_LAST_KNOWN_NAME, playerName), - data.getBoolean(DataStrings.PLAYER_SHOULD_LOAD, false) - ); + private final ConfigNode lastWorld = node(ConfigNode.builder("playerData.lastWorld", String.class) + .defaultValue("") + .name("last-world") + .build()); + + private final ConfigNode lastKnownName = node(ConfigNode.builder("playerData.lastKnownName", String.class) + .defaultValue("") + .name("last-known-name") + .build()); + + private final ConfigNode loadOnLogin = node(ConfigNode.builder("playerData.loadOnLogin", Boolean.class) + .defaultValue(false) + .name("load-on-login") + .build()); } } From 70bed2a6bd4f4203cb6062e4a77a23d1ffbcf685 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:49:37 +0800 Subject: [PATCH 160/180] Fix global profile clear command --- .../commands/bulkedit/globalprofile/ClearCommand.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java index 10649f76..deaec4a0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java @@ -9,11 +9,13 @@ import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; @@ -21,7 +23,7 @@ import java.util.concurrent.CompletableFuture; @Service -final class ClearCommand { +final class ClearCommand extends InventoriesCommand { private final CommandQueueManager commandQueueManager; private final ProfileDataSource profileDataSource; @@ -40,6 +42,7 @@ final class ClearCommand { @Subcommand("bulkedit globalprofile clear") @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("@players @flags:groupName=" + Flags.NAME) @Syntax("") void onCommand( MVCommandIssuer issuer, @@ -67,9 +70,11 @@ private void doClear(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKey @Service private static final class Flags extends FlagBuilder { + private static final String NAME = "mvinvbulkeditglobalprofileclear"; + @Inject - private Flags(@NotNull String name, @NotNull CommandFlagsManager flagsManager) { - super(name, flagsManager); + private Flags(@NotNull CommandFlagsManager flagsManager) { + super(NAME, flagsManager); } private final CommandFlag clearAllPlayerprofiles = flag(CommandFlag.builder("--clear-all-playerprofiles") From 02819b35ad4a9eaf6bacf95681191c65fa617b45 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:54:52 +0800 Subject: [PATCH 161/180] Whoops command injection test again --- .../java/org/mvplugins/multiverse/inventories/InjectionTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 699b0ef2..8e406ba8 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(24, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(26, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test From 8095efc3f7160deb12805c89a824556190d08784 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:14:27 +0800 Subject: [PATCH 162/180] Implement player name mapper --- .../inventories/MultiverseInventories.java | 8 +- .../profile/FlatFileProfileDataSource.java | 18 ++- .../profile/PlayerNamesMapper.java | 136 ++++++++++++++++++ .../profile/FilePerformanceTest.kt | 6 +- 4 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index e17226b4..e41b691e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -3,10 +3,8 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; -import org.mvplugins.multiverse.core.MultiverseCoreApi; import org.mvplugins.multiverse.core.config.CoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; -import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory; import org.mvplugins.multiverse.core.module.MultiverseModule; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.command.MVInvCommandConditions; @@ -21,6 +19,7 @@ import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; @@ -31,8 +30,6 @@ import org.mvplugins.multiverse.inventories.util.ItemStackConverter; import org.mvplugins.multiverse.inventories.util.Perm; import org.bukkit.Bukkit; -import org.mvplugins.multiverse.core.command.MVCommandManager; -import org.mvplugins.multiverse.core.inject.PluginServiceLocator; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jakarta.inject.Provider; import org.jvnet.hk2.annotations.Service; @@ -61,6 +58,8 @@ public class MultiverseInventories extends MultiverseModule { @Inject private Provider worldGroupManager; @Inject + private Provider playerNamesMapperProvider; + @Inject private Provider profileDataSource; @Inject private Provider profileCacheManager; @@ -119,6 +118,7 @@ public final void onEnable() { // Init other extensions this.hookLuckPerms(); this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); + this.playerNamesMapperProvider.get().loadMap(); Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(this.getDescription().getAuthors())); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 0f508fad..1201b630 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -34,16 +34,19 @@ final class FlatFileProfileDataSource implements ProfileDataSource { private final AsyncFileIO asyncFileIO; private final ProfileFilesLocator profileFilesLocator; private final ProfileCacheManager profileCacheManager; + private final PlayerNamesMapper playerNamesMapper; @Inject FlatFileProfileDataSource( @NotNull AsyncFileIO asyncFileIO, @NotNull ProfileFilesLocator profileFilesLocator, - @NotNull ProfileCacheManager profileCacheManager + @NotNull ProfileCacheManager profileCacheManager, + @NotNull PlayerNamesMapper playerNamesMapper ) { this.asyncFileIO = asyncFileIO; this.profileFilesLocator = profileFilesLocator; this.profileCacheManager = profileCacheManager; + this.playerNamesMapper = playerNamesMapper; } private FileConfiguration loadFileToJsonConfiguration(File file) { @@ -263,7 +266,7 @@ public CompletableFuture getGlobalProfile(GlobalProfileKey key) { globalProfile.setLastKnownName(key.getPlayerName()); return CompletableFuture.completedFuture(globalProfile); } - return asyncFileIO.queueFileCallable(globalFile, () -> getGlobalProfileFromDisk(key.getPlayerUUID(), key.getPlayerName(), globalFile)); + return asyncFileIO.queueFileCallable(globalFile, () -> getGlobalProfileFromDisk(key.getPlayerUUID(), globalFile)); }); } @@ -289,10 +292,8 @@ private void migrateGlobalProfileToUUID(UUID playerUUID, String playerName) { } } - private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, String playerName, File globalFile) { - GlobalProfile globalProfile = new GlobalProfile(playerUUID, globalFile.toPath()); - globalProfile.setLastKnownName(playerName); - return globalProfile; + private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, File globalFile) { + return new GlobalProfile(playerUUID, globalFile.toPath()); } /** @@ -314,7 +315,10 @@ private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, @Override public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); - return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile)); + return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile)) + .thenCompose(ignore -> playerNamesMapper.setPlayerName(globalProfile.getPlayerUUID(), globalProfile.getLastKnownName()) + ? asyncFileIO.queueFileAction(playerNamesMapper.getFile(), playerNamesMapper::savePlayerNames) + : CompletableFuture.completedFuture(null)); } private void processGlobalProfileWrite(GlobalProfile globalProfile) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java new file mode 100644 index 00000000..8fd8e5d8 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java @@ -0,0 +1,136 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.base.Strings; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.JSONParser; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jakarta.inject.Provider; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public final class PlayerNamesMapper { + + private static final String FILENAME = "playernames.json"; + + private final MultiverseInventories inventories; + private final Provider profileDataSourceProvider; + private final Provider profileCacheManagerProvider; + + private final File playerNamesFile; + private final Map playerNamesMap; + private final Map playerUUIDMap; + + private Map playerNamesJson; + + @Inject + PlayerNamesMapper( + @NotNull MultiverseInventories inventories, + @NotNull Provider profileDataSourceProvider, + @NotNull Provider profileCacheManagerProvider + ) { + this.inventories = inventories; + this.profileDataSourceProvider = profileDataSourceProvider; + this.profileCacheManagerProvider = profileCacheManagerProvider; + + this.playerNamesFile = new File(inventories.getDataFolder(), FILENAME); + this.playerNamesMap = new ConcurrentHashMap<>(); + this.playerUUIDMap = new ConcurrentHashMap<>(); + } + + public void loadMap() { + Logging.config("Loading player names map..."); + playerNamesMap.clear(); + playerUUIDMap.clear(); + if (playerNamesFile.exists()) { + loadFromPlayerNamesFile(); + } else { + buildPlayerNamesMap(); + } + } + + private void loadFromPlayerNamesFile() { + try (FileReader fileReader = new FileReader(playerNamesFile)) { + playerNamesJson = new ConcurrentHashMap<>((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(fileReader)); + if (playerNamesJson.isEmpty()) { + buildPlayerNamesMap(); + return; + } + playerNamesJson.forEach((String uuid, Object name) -> { + UUID playerUUID = UUID.fromString(uuid); + String playerName = String.valueOf(name); + GlobalProfileKey globalProfileKey = GlobalProfileKey.create(playerUUID, playerName); + playerNamesMap.put(playerName, globalProfileKey); + playerUUIDMap.put(playerUUID, globalProfileKey); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void buildPlayerNamesMap() { + Logging.info("Generating player names map... This may take a while."); + playerNamesJson = new ConcurrentHashMap<>(); + + ProfileDataSource profileDataSource = profileDataSourceProvider.get(); + CompletableFuture[] futures = profileDataSource.listGlobalProfileUUIDs().stream() + .map(uuid -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(uuid)) + .thenAccept(globalProfile -> setPlayerName(uuid, globalProfile.getLastKnownName()))) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).thenRun(this::savePlayerNames).join(); + Logging.info("Generated player names map."); + } + + boolean setPlayerName(UUID uuid, String name) { + Logging.finer("Setting player name mapping for %s to %s", uuid, name); + if (Strings.isNullOrEmpty(name)) { + return false; + } + if (getKey(name).filter(g -> g.getPlayerUUID().equals(uuid)).isDefined()) { + return false; + } + GlobalProfileKey globalProfileKey = GlobalProfileKey.create(uuid, name); + playerNamesJson.put(uuid.toString(), name); + playerNamesMap.put(name, globalProfileKey); + playerUUIDMap.put(uuid, globalProfileKey); + return true; + } + + void savePlayerNames() { + try (FileWriter fileWriter = new FileWriter(playerNamesFile)) { + fileWriter.write(JSONValue.toJSONString(playerNamesJson)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + File getFile() { + return playerNamesFile; + } + + public Option getKey(String playerName) { + return Option.of(playerNamesMap.get(playerName)); + } + + public Option getKey(UUID playerUUID) { + return Option.of(playerUUIDMap.get(playerUUID)); + } + + public List getKeys() { + return playerNamesMap.values().stream().toList(); + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index e4e10b65..1ce4407f 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -48,10 +48,10 @@ class FilePerformanceTest : TestWithMockBukkit() { } @Test - fun `Test 10K global profiles`() { + fun `Test 1K global profiles`() { val startTime = System.nanoTime() - val futures = ArrayList>(10000) - for (i in 0..9999) { + val futures = ArrayList>(1000) + for (i in 0..1000) { futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(UUID.randomUUID()), { globalProfile -> globalProfile.setLoadOnLogin(true) })) From bffb291453c827be6adac44adde92befe5f85b38 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:38:36 +0800 Subject: [PATCH 163/180] Improve player name mapper loading and add tests --- .../profile/FlatFileProfileDataSource.java | 3 ++- .../profile/PlayerNamesMapper.java | 21 +++++++++++++++---- .../profile/PlayerNameChangeTest.kt | 15 +++++++------ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 1201b630..f9afe7d5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -315,8 +315,9 @@ private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, @Override public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); + boolean didPlayerNameChange = playerNamesMapper.setPlayerName(globalProfile.getPlayerUUID(), globalProfile.getLastKnownName()); return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile)) - .thenCompose(ignore -> playerNamesMapper.setPlayerName(globalProfile.getPlayerUUID(), globalProfile.getLastKnownName()) + .thenCompose(ignore -> didPlayerNameChange ? asyncFileIO.queueFileAction(playerNamesMapper.getFile(), playerNamesMapper::savePlayerNames) : CompletableFuture.completedFuture(null)); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java index 8fd8e5d8..c316ef16 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java @@ -27,7 +27,6 @@ public final class PlayerNamesMapper { private static final String FILENAME = "playernames.json"; - private final MultiverseInventories inventories; private final Provider profileDataSourceProvider; private final Provider profileCacheManagerProvider; @@ -43,7 +42,6 @@ public final class PlayerNamesMapper { @NotNull Provider profileDataSourceProvider, @NotNull Provider profileCacheManagerProvider ) { - this.inventories = inventories; this.profileDataSourceProvider = profileDataSourceProvider; this.profileCacheManagerProvider = profileCacheManagerProvider; @@ -92,30 +90,45 @@ private void buildPlayerNamesMap() { .thenAccept(globalProfile -> setPlayerName(uuid, globalProfile.getLastKnownName()))) .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures).thenRun(this::savePlayerNames).join(); + profileCacheManagerProvider.get().clearAllGlobalProfileCaches(); Logging.info("Generated player names map."); } boolean setPlayerName(UUID uuid, String name) { - Logging.finer("Setting player name mapping for %s to %s", uuid, name); + if (playerNamesJson == null) { + throw new IllegalStateException("Player names mapper has not been loaded yet."); + } if (Strings.isNullOrEmpty(name)) { return false; } if (getKey(name).filter(g -> g.getPlayerUUID().equals(uuid)).isDefined()) { return false; } + + Logging.finer("Setting player name mapping for %s to %s", uuid, name); GlobalProfileKey globalProfileKey = GlobalProfileKey.create(uuid, name); - playerNamesJson.put(uuid.toString(), name); + + // Handle remove of old playername + Object oldName = playerNamesJson.put(uuid.toString(), name); + playerNamesMap.remove(String.valueOf(oldName)); + playerNamesMap.put(name, globalProfileKey); playerUUIDMap.put(uuid, globalProfileKey); return true; } void savePlayerNames() { + if (playerNamesJson == null) { + throw new IllegalStateException("Player names mapper has not been loaded yet."); + } + + Logging.finer("Saving player names map..."); try (FileWriter fileWriter = new FileWriter(playerNamesFile)) { fileWriter.write(JSONValue.toJSONString(playerNamesJson)); } catch (Exception e) { e.printStackTrace(); } + Logging.finer("Saving player names map... Done!"); } File getFile() { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt index 4afaa294..115945ce 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt @@ -11,22 +11,20 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey import org.mvplugins.multiverse.inventories.util.FutureNow import java.nio.file.Path -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue +import kotlin.test.* class PlayerNameChangeTest : TestWithMockBukkit() { private lateinit var profileDataSource: ProfileDataSource + private lateinit var playerNamesMapper: PlayerNamesMapper private lateinit var player: PlayerMock @BeforeTest fun setUp() { profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { throw IllegalStateException("ProfileDataSource is not available as a service") } + playerNamesMapper = serviceLocator.getService(PlayerNamesMapper::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("PlayerNamesMapper is not available as a service") } val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { throw IllegalStateException("WorldManager is not available as a service") } @@ -40,6 +38,7 @@ class PlayerNameChangeTest : TestWithMockBukkit() { assertTrue(worldGroupManager.load().isSuccess) player = server.addPlayer("Benji_0224") + assertEquals(GlobalProfileKey.create(player.uniqueId, "Benji_0224"), playerNamesMapper.getKey("Benji_0224").orNull) } @Test @@ -81,5 +80,9 @@ class PlayerNameChangeTest : TestWithMockBukkit() { // check player profile assertEquals("benthecat10", FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)))?.lastKnownName) + + // check name mapper updated + assertEquals(GlobalProfileKey.create(player.uniqueId, "benthecat10"), playerNamesMapper.getKey("benthecat10").orNull) + assertNull(playerNamesMapper.getKey("Benji_0224").orNull) } } From d60259d35db280c0300fa8e46918a53355dac6ac Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 14 Apr 2025 21:05:12 +0800 Subject: [PATCH 164/180] Use player names mapper data to tab complete and parse profile for bulkedit --- .../command/MVInvCommandCompletion.java | 31 ++++++++++++++++++- .../command/MVInvCommandContexts.java | 21 ++++++++----- .../bulkedit/globalprofile/ClearCommand.java | 5 +-- .../bulkedit/globalprofile/ModifyCommand.java | 2 ++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java index ba25c35a..5ea0aadf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java @@ -5,18 +5,23 @@ import org.mvplugins.multiverse.core.command.MVCommandCompletions; import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction; +import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.share.Sharables; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -28,21 +33,26 @@ public final class MVInvCommandCompletion { private final InventoriesConfig inventoriesConfig; private final WorldGroupManager worldGroupManager; private final DataImportManager dataImportManager; + private final PlayerNamesMapper playerNamesMapper; @Inject private MVInvCommandCompletion( @NotNull InventoriesConfig inventoriesConfig, @NotNull WorldGroupManager worldGroupManager, @NotNull DataImportManager dataImportManager, - @NotNull MVCommandManager mvCommandManager) { + @NotNull MVCommandManager mvCommandManager, + @NotNull PlayerNamesMapper playerNamesMapper + ) { this.inventoriesConfig = inventoriesConfig; this.worldGroupManager = worldGroupManager; this.dataImportManager = dataImportManager; + this.playerNamesMapper = playerNamesMapper; MVCommandCompletions commandCompletions = mvCommandManager.getCommandCompletions(); commandCompletions.registerAsyncCompletion("dataimporters", this::suggestDataImporters); commandCompletions.registerStaticCompletion("mvinvconfigs", inventoriesConfig.getStringPropertyHandle().getAllPropertyNames()); commandCompletions.registerAsyncCompletion("mvinvconfigvalues", this::suggestConfigValues); + commandCompletions.registerAsyncCompletion("mvinvplayernames", this::suggestPlayerNames); commandCompletions.registerAsyncCompletion("sharables", this::suggestSharables); commandCompletions.registerAsyncCompletion("shares", this::suggestShares); commandCompletions.registerAsyncCompletion("worldGroups", this::suggestWorldGroups); @@ -60,6 +70,25 @@ private Collection suggestConfigValues(BukkitCommandCompletionContext co .getOrElse(Collections.emptyList()); } + private Collection suggestPlayerNames(BukkitCommandCompletionContext context) { + if (Objects.equals(context.getInput(), "@all")) { + return Collections.emptyList(); + } + List playerNames = getPlayerNames(); + if (context.getInput().indexOf(',') == -1) { + playerNames.add("@all"); + return playerNames; + } + return StringFormatter.addonToCommaSeperated(context.getInput(), playerNames); + } + + private List getPlayerNames() { + return playerNamesMapper.getKeys() + .stream() + .map(GlobalProfileKey::getPlayerName) + .collect(Collectors.toList()); + } + private Collection suggestSharables(BukkitCommandCompletionContext context) { String scope = context.getConfig("scope", "enabled"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java index 2d050826..763d9f25 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -4,12 +4,14 @@ import org.bukkit.Bukkit; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.utils.REPatterns; import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; import org.mvplugins.multiverse.external.acf.commands.CommandContexts; import org.mvplugins.multiverse.external.acf.commands.InvalidCommandArgument; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; @@ -25,10 +27,16 @@ public final class MVInvCommandContexts { private final WorldGroupManager worldGroupManager; + private final PlayerNamesMapper playerNamesMapper; @Inject - private MVInvCommandContexts(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + private MVInvCommandContexts( + @NotNull MVCommandManager commandManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull PlayerNamesMapper playerNamesMapper + ) { this.worldGroupManager = worldGroupManager; + this.playerNamesMapper = playerNamesMapper; CommandContexts commandContexts = commandManager.getCommandContexts(); commandContexts.registerContext(GlobalProfileKey[].class, this::parseGlobalProfileKeys); @@ -40,15 +48,14 @@ private MVInvCommandContexts(@NotNull MVCommandManager commandManager, @NotNull private GlobalProfileKey[] parseGlobalProfileKeys(BukkitCommandExecutionContext context) { String profileStrings = context.popFirstArg(); if (profileStrings.equals("@all")) { - return Arrays.stream(Bukkit.getOfflinePlayers()) - .map(GlobalProfileKey::create) - .toArray(GlobalProfileKey[]::new); + return playerNamesMapper.getKeys().toArray(GlobalProfileKey[]::new); } - String[] profileNames = profileStrings.split(","); + String[] profileNames = REPatterns.COMMA.split(profileStrings); return Arrays.stream(profileNames) - .map(Bukkit::getOfflinePlayer) - .map(GlobalProfileKey::create) + .map(playerNamesMapper::getKey) + .filter(Option::isDefined) + .map(Option::get) .toArray(GlobalProfileKey[]::new); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java index deaec4a0..24c8a73c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java @@ -42,14 +42,15 @@ final class ClearCommand extends InventoriesCommand { @Subcommand("bulkedit globalprofile clear") @CommandPermission("multiverse.inventories.bulkedit") - @CommandCompletion("@players @flags:groupName=" + Flags.NAME) - @Syntax("") + @CommandCompletion("@mvinvplayernames @flags:groupName=" + Flags.NAME) + @Syntax(" [--clear-all-playerprofiles]") void onCommand( MVCommandIssuer issuer, @Syntax("") GlobalProfileKey[] globalProfileKeys, + @Syntax("[--clear-all-playerprofiles]") String[] flagArray ) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java index 8ef3cc4e..bd419200 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java @@ -6,6 +6,7 @@ import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction; import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; @@ -32,6 +33,7 @@ final class ModifyCommand extends InventoriesCommand { @Subcommand("bulkedit globalprofile modify") @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("load-on-login|last-world @empty @mvinvplayernames") @Syntax(" ") void onCommand( MVCommandIssuer issuer, From df08d841f5260b5c0afd8eb5eda6fb273a72e7f9 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 14 Apr 2025 21:11:16 +0800 Subject: [PATCH 165/180] Improve itemstack empty check --- .../inventories/MultiverseInventories.java | 2 +- .../share/InventorySerializer.java | 8 ++---- .../inventories/util/ItemStackConverter.java | 28 ++++++++++++------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index e17226b4..7deb9415 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -103,7 +103,7 @@ public final void onEnable() { Sharables.init(this); Perm.register(this); ItemStackConverter.init(this); - Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport); + Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport()); this.reloadConfig(); inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index 5c982c64..5e7db125 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -33,11 +33,9 @@ public Object serialize(ItemStack[] itemStacks) { private Map mapSlots(ItemStack[] itemStacks) { Map result = new HashMap<>(itemStacks.length); for (int i = 0; i < itemStacks.length; i++) { - if (itemStacks[i] != null && itemStacks[i].getType() != Material.AIR) { - Object serialize = ItemStackConverter.serialize(itemStacks[i]); - if (serialize != null) { - result.put(Integer.toString(i), serialize); - } + Object serialize = ItemStackConverter.serialize(itemStacks[i]); + if (serialize != null) { + result.put(Integer.toString(i), serialize); } } return result; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java index b8ff1046..666b15a5 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -12,7 +12,7 @@ public final class ItemStackConverter { - public final static boolean hasByteSerializeSupport; + private final static boolean hasByteSerializeSupport; static { hasByteSerializeSupport = Try.run(() -> ItemStack.class.getMethod("deserializeBytes", byte[].class)) @@ -27,6 +27,10 @@ public static void init(MultiverseInventories plugin) { config = plugin.getServiceLocator().getService(InventoriesConfig.class); } + public static boolean isEmptyItemStack(@Nullable ItemStack itemStack) { + return itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0; + } + @Nullable public static ItemStack deserialize(Object obj) { if (obj instanceof ItemStack itemStack) { @@ -42,16 +46,20 @@ public static ItemStack deserialize(Object obj) { @Nullable public static Object serialize(ItemStack itemStack) { - if (config != null && config.getUseByteSerializationForInventoryData() && hasByteSerializeSupport) { - if (itemStack.getType() == Material.AIR) { - return null; - } - return Try.of(() -> Base64.getEncoder().encodeToString(itemStack.serializeAsBytes())) - .onFailure(e -> Logging.severe("Could not serialize item stack: %s", e.getMessage())) - .getOrNull(); + if (isEmptyItemStack(itemStack)) { + return null; + } + if (config == null || !config.getUseByteSerializationForInventoryData() || !hasByteSerializeSupport) { + // let ConfigurationSerialization handle it + return itemStack; } - // let ConfigurationSerialization handle it - return itemStack; + return Try.of(() -> Base64.getEncoder().encodeToString(itemStack.serializeAsBytes())) + .onFailure(e -> Logging.severe("Could not byte serialize item stack: %s", e.getMessage())) + .getOrNull(); + } + + public static boolean hasByteSerializeSupport() { + return hasByteSerializeSupport; } private ItemStackConverter() { From cc9fb130b78a49bac0b12ded2faf4385989c2594 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 19 Apr 2025 15:24:14 +0800 Subject: [PATCH 166/180] Implement basic clear and delete playerprofile bulkedit --- .../command/MVInvCommandCompletion.java | 19 ++++ .../command/MVInvCommandContexts.java | 75 +++++++++++++-- .../bulkedit/globalprofile/ClearCommand.java | 1 + .../bulkedit/playerprofile/ClearCommand.java | 96 +++++++++++++++++++ .../bulkedit/playerprofile/DeleteCommand.java | 91 ++++++++++++++++++ .../IncludeGroupsWorldsFlag.java | 21 ++++ .../profile/bulkedit/ProfilesAggregator.java | 91 ++++++++++++++++++ .../inventories/profile/key/ContainerKey.java | 46 +++++++++ .../profile/key/GlobalProfileKey.java | 2 +- .../profile/key/ProfileFileKey.java | 2 +- .../inventories/profile/key/ProfileTypes.java | 32 ++++++- 11 files changed, 460 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java index 5ea0aadf..c41a299f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java @@ -15,6 +15,8 @@ import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.share.Sharables; import java.util.Arrays; @@ -53,6 +55,7 @@ private MVInvCommandCompletion( commandCompletions.registerStaticCompletion("mvinvconfigs", inventoriesConfig.getStringPropertyHandle().getAllPropertyNames()); commandCompletions.registerAsyncCompletion("mvinvconfigvalues", this::suggestConfigValues); commandCompletions.registerAsyncCompletion("mvinvplayernames", this::suggestPlayerNames); + commandCompletions.registerAsyncCompletion("mvinvprofiletypes", this::suggestProfileTypes); commandCompletions.registerAsyncCompletion("sharables", this::suggestSharables); commandCompletions.registerAsyncCompletion("shares", this::suggestShares); commandCompletions.registerAsyncCompletion("worldGroups", this::suggestWorldGroups); @@ -89,6 +92,22 @@ private List getPlayerNames() { .collect(Collectors.toList()); } + private Collection suggestProfileTypes(BukkitCommandCompletionContext context) { + if (Objects.equals(context.getInput(), "@all")) { + return Collections.emptyList(); + } + List profileTypes = ProfileTypes.getApplicableTypes() + .stream() + .map(ProfileType::getName) + .map(String::toLowerCase) + .collect(Collectors.toList()); + if (context.getInput().indexOf(',') == -1) { + profileTypes.add("@all"); + return profileTypes; + } + return StringFormatter.addonToCommaSeperated(context.getInput(), profileTypes); + } + private Collection suggestSharables(BukkitCommandCompletionContext context) { String scope = context.getConfig("scope", "enabled"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java index 763d9f25..8c4a2a38 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -1,7 +1,6 @@ package org.mvplugins.multiverse.inventories.command; import com.google.common.base.Strings; -import org.bukkit.Bukkit; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.utils.REPatterns; @@ -11,47 +10,92 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; import org.mvplugins.multiverse.inventories.share.Shares; import org.mvplugins.multiverse.inventories.util.MVInvi18n; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Objects; +import java.util.Collections; +import java.util.List; @Service public final class MVInvCommandContexts { private final WorldGroupManager worldGroupManager; private final PlayerNamesMapper playerNamesMapper; + private final InventoriesConfig config; + private final ProfileDataSource profileDataSource; @Inject private MVInvCommandContexts( @NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager, - @NotNull PlayerNamesMapper playerNamesMapper + @NotNull PlayerNamesMapper playerNamesMapper, + @NotNull InventoriesConfig config, + @NotNull ProfileDataSource profileDataSource ) { this.worldGroupManager = worldGroupManager; this.playerNamesMapper = playerNamesMapper; + this.config = config; + this.profileDataSource = profileDataSource; CommandContexts commandContexts = commandManager.getCommandContexts(); - commandContexts.registerContext(GlobalProfileKey[].class, this::parseGlobalProfileKeys); + commandContexts.registerContext(ContainerKey[].class, this::parseContainerKeyArray); + commandContexts.registerContext(GlobalProfileKey[].class, this::parseGlobalProfileKeyArray); + commandContexts.registerIssuerAwareContext(ProfileType[].class, this::parseProfileTypeArray); commandContexts.registerContext(Sharable.class, this::parseSharable); commandContexts.registerContext(Shares.class, this::parseShares); commandContexts.registerContext(WorldGroup.class, this::parseWorldGroup); } - private GlobalProfileKey[] parseGlobalProfileKeys(BukkitCommandExecutionContext context) { - String profileStrings = context.popFirstArg(); - if (profileStrings.equals("@all")) { - return playerNamesMapper.getKeys().toArray(GlobalProfileKey[]::new); + private ContainerKey[] parseContainerKeyArray(BukkitCommandExecutionContext context) { + String keyStrings = context.popFirstArg(); + if (keyStrings.equals("@all")) { + return Arrays.stream(ContainerType.values()).flatMap(containerType -> + profileDataSource.listContainerDataNames(containerType) + .stream() + .map(containerName -> ContainerKey.create(containerType, containerName))) + .toArray(ContainerKey[]::new); } + List containerKeys = new ArrayList<>(); + String[] typesSplit = REPatterns.SEMICOLON.split(keyStrings); + for (String typeSplit : typesSplit) { + String[] keyValueSplit = REPatterns.EQUALS.split(typeSplit, 2); + if (keyValueSplit.length != 2) { + // todo: Probably error invalid format + continue; + } + ContainerType containerType = ContainerType.valueOf(keyValueSplit[0].toUpperCase()); + String[] dataNameSplit = REPatterns.COMMA.split(keyValueSplit[1]); + List availableDataNames = profileDataSource.listContainerDataNames(containerType); + for (String dataName : dataNameSplit) { + if (availableDataNames.contains(dataName)) { + containerKeys.add(ContainerKey.create(containerType, dataName)); + } + } + } + return containerKeys.toArray(new ContainerKey[0]); + } - String[] profileNames = REPatterns.COMMA.split(profileStrings); + private GlobalProfileKey[] parseGlobalProfileKeyArray(BukkitCommandExecutionContext context) { + String keyStrings = context.popFirstArg(); + if (keyStrings.equals("@all")) { + return playerNamesMapper.getKeys().toArray(GlobalProfileKey[]::new); + } + // todo: UUID parsing + String[] profileNames = REPatterns.COMMA.split(keyStrings); return Arrays.stream(profileNames) .map(playerNamesMapper::getKey) .filter(Option::isDefined) @@ -59,6 +103,19 @@ private GlobalProfileKey[] parseGlobalProfileKeys(BukkitCommandExecutionContext .toArray(GlobalProfileKey[]::new); } + private ProfileType[] parseProfileTypeArray(BukkitCommandExecutionContext context) { + String keyStrings = context.popFirstArg(); + if (keyStrings.equals("@all")) { + return ProfileTypes.getTypes().toArray(ProfileType[]::new); + } + String[] profileNames = REPatterns.COMMA.split(keyStrings); + return Arrays.stream(profileNames) + .map(ProfileTypes::forName) + .filter(Option::isDefined) + .map(Option::get) + .toArray(ProfileType[]::new); + } + private Sharable parseSharable(BukkitCommandExecutionContext context) { String sharableName = context.popFirstArg(); Sharable targetSharable = Sharables.all().stream() diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java index 24c8a73c..c737315a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java @@ -61,6 +61,7 @@ void onCommand( } private void doClear(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfile) { + //TODO: Check lastWorld and online CompletableFuture[] futures = Arrays.stream(globalProfileKeys) .map(globalProfileKey -> profileDataSource.deleteGlobalProfile(globalProfileKey, clearPlayerProfile)) .toArray(CompletableFuture[]::new); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java new file mode 100644 index 00000000..9797e42f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java @@ -0,0 +1,96 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.bulkedit.ProfilesAggregator; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Service +final class ClearCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + private final ProfilesAggregator profilesAggregator; + private final IncludeGroupsWorldsFlag flags; + + @Inject + ClearCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull ProfileDataSource profileDataSource, + @NotNull ProfilesAggregator profilesAggregator, + @NotNull IncludeGroupsWorldsFlag flags + ) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + this.profilesAggregator = profilesAggregator; + this.flags = flags; + } + + @Subcommand("bulkedit playerprofile clear") + @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("@mvinvplayernames @empty @mvinvprofiletypes @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) + @Syntax(" [profile-type] [--include-groups-worlds]") + void onCommand( + MVCommandIssuer issuer, + GlobalProfileKey[] globalProfileKeys, + ContainerKey[] containerKeys, + ProfileType[] profileTypes, + String[] flagArray + ) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + issuer.sendMessage("Players: " + StringFormatter.join(List.of(globalProfileKeys), ", ")); + issuer.sendMessage("Containers: " + StringFormatter.join(List.of(containerKeys), ", ")); + issuer.sendMessage("Profile Types: " + StringFormatter.join(List.of(profileTypes), ", ")); + + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to delete the above selected data?")) + .action(() -> runDelete(issuer, globalProfileKeys, containerKeys, profileTypes, parsedFlags))); + } + + private void runDelete(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, ContainerKey[] containerKeys, ProfileType[] profileTypes, ParsedCommandFlags parsedFlags) { + //TODO: Check lastWorld and online + if (ProfileTypes.isAll(profileTypes)) { + doFileDelete(issuer, globalProfileKeys, containerKeys, parsedFlags.hasFlag(flags.includeGroupsWorlds)); + } else { + doProfileDelete(issuer, globalProfileKeys, containerKeys, profileTypes, parsedFlags.hasFlag(flags.includeGroupsWorlds)); + } + } + + private void doFileDelete(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, ContainerKey[] containerKeys, boolean includeGroupsWorlds) { + List profileFileKeys = profilesAggregator.getProfileFileKeys(globalProfileKeys, containerKeys, includeGroupsWorlds); + CompletableFuture.allOf(profileFileKeys.stream() + .map(profileDataSource::deletePlayerFile) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> issuer.sendMessage("Successfully deleted %d profiles.".formatted(profileFileKeys.size()))); + } + + private void doProfileDelete(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, ContainerKey[] containerKeys, ProfileType[] profileTypes, boolean includeGroupsWorlds) { + List playerProfileKeys = profilesAggregator.getPlayerProfileKeys(globalProfileKeys, containerKeys, profileTypes, includeGroupsWorlds); + CompletableFuture.allOf(playerProfileKeys.stream() + .map(profileDataSource::deletePlayerProfile) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> issuer.sendMessage("Successfully deleted %d profiles.".formatted(playerProfileKeys.size()))); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java new file mode 100644 index 00000000..254b2ea1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -0,0 +1,91 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.bulkedit.ProfilesAggregator; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.share.Sharable; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Service +final class DeleteCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + private final ProfilesAggregator profilesAggregator; + private final IncludeGroupsWorldsFlag flags; + + @Inject + DeleteCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull ProfileDataSource profileDataSource, + @NotNull ProfilesAggregator profilesAggregator, + @NotNull IncludeGroupsWorldsFlag flags + ) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + this.profilesAggregator = profilesAggregator; + this.flags = flags; + } + + @Subcommand("bulkedit playerprofile delete") + @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("@shares @mvinvplayernames @empty @mvinvprofiletypes @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) + @Syntax(" [profile-type] [--include-groups-worlds]") + void onCommand( + MVCommandIssuer issuer, + Sharable sharable, + GlobalProfileKey[] globalProfileKeys, + ContainerKey[] containerKeys, + ProfileType[] profileTypes, + String[] flagArray + ) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + issuer.sendMessage("Players: " + StringFormatter.join(List.of(globalProfileKeys), ", ")); + issuer.sendMessage("Containers: " + StringFormatter.join(List.of(containerKeys), ", ")); + issuer.sendMessage("Profile Types: " + StringFormatter.join(List.of(profileTypes), ", ")); + + List playerProfileKeys = profilesAggregator.getPlayerProfileKeys( + globalProfileKeys, containerKeys, profileTypes, parsedFlags.hasFlag(flags.includeGroupsWorlds)); + + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to delete %s?".formatted(sharable.getNames()[0]))) + .action(() -> runDelete(issuer, sharable, playerProfileKeys))); + } + + private void runDelete(MVCommandIssuer issuer, Sharable sharable, List playerProfileKeys) { + //TODO: Check lastWorld and online + List> futures = new ArrayList<>(); + for (ProfileKey playerProfileKey : playerProfileKeys) { + profileDataSource.getPlayerProfile(playerProfileKey) + .thenCompose(playerProfile -> { + playerProfile.set(sharable, null); + return profileDataSource.updatePlayerProfile(playerProfile); + }); + } + CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).thenRun(() -> { + issuer.sendMessage("Successfully deleted %s from %d profiles.".formatted(sharable.getNames()[0], playerProfileKeys.size())); + }); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java new file mode 100644 index 00000000..b344cb48 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java @@ -0,0 +1,21 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.flag.CommandFlag; +import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; +import org.mvplugins.multiverse.core.command.flag.FlagBuilder; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; + +@Service +final class IncludeGroupsWorldsFlag extends FlagBuilder { + static final String NAME = "mvinvincludegroupsworlds"; + + @Inject + private IncludeGroupsWorldsFlag(CommandFlagsManager flagsManager) { + super(NAME, flagsManager); + } + + final CommandFlag includeGroupsWorlds = flag(CommandFlag.builder("--include-groups-worlds") + .addAlias("-i") + .build()); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java new file mode 100644 index 00000000..58eebc63 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java @@ -0,0 +1,91 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import com.google.common.collect.Sets; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@Service +public final class ProfilesAggregator { + + private final WorldGroupManager worldGroupManager; + + @Inject + ProfilesAggregator(WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + public List getProfileFileKeys( + @NotNull GlobalProfileKey[] globalProfileKeys, + @NotNull ContainerKey[] containerKeys, + boolean includeGroupsWorlds + ) { + if (includeGroupsWorlds) { + containerKeys = includeGroupsWorlds(containerKeys); + } + List profileFileKeys = new ArrayList<>(globalProfileKeys.length * containerKeys.length); + for (GlobalProfileKey globalProfileKey : globalProfileKeys) { + for (ContainerKey containerKey : containerKeys) { + profileFileKeys.add(ProfileFileKey.create( + containerKey.getContainerType(), + containerKey.getDataName(), + globalProfileKey.getPlayerUUID())); + } + } + return profileFileKeys; + } + + public List getPlayerProfileKeys( + @NotNull GlobalProfileKey[] globalProfileKeys, + @NotNull ContainerKey[] containerKeys, + @NotNull ProfileType[] profileTypes, + boolean includeGroupsWorlds + ) { + if (includeGroupsWorlds) { + containerKeys = includeGroupsWorlds(containerKeys); + } + List profileKeys = new ArrayList<>(globalProfileKeys.length * containerKeys.length * profileTypes.length); + for (GlobalProfileKey globalProfileKey : globalProfileKeys) { + for (ContainerKey containerKey : containerKeys) { + for (ProfileType profileType : profileTypes) { + profileKeys.add(ProfileKey.create( + containerKey.getContainerType(), + containerKey.getDataName(), + profileType, + globalProfileKey.getPlayerUUID(), + globalProfileKey.getPlayerName())); + } + } + } + return profileKeys; + } + + private ContainerKey[] includeGroupsWorlds(ContainerKey[] containerKeys) { + Set containerKeyList = Sets.newHashSet(containerKeys); + for (ContainerKey containerKey : containerKeys) { + if (containerKey.getContainerType() != ContainerType.GROUP) { + continue; + } + WorldGroup group = worldGroupManager.getGroup(containerKey.getDataName()); + if (group == null) { + continue; + } + containerKeyList.addAll(group.getWorlds().stream() + .map(worldName -> ContainerKey.create(ContainerType.WORLD, worldName)) + .toList()); + } + return containerKeyList.toArray(ContainerKey[]::new); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java new file mode 100644 index 00000000..1903bc83 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java @@ -0,0 +1,46 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import java.util.Objects; + +public final class ContainerKey { + + public static ContainerKey create(ContainerType containerType, String dataName) { + return new ContainerKey(containerType, dataName); + } + + private final ContainerType containerType; + private final String dataName; + + private ContainerKey(ContainerType containerType, String dataName) { + this.containerType = containerType; + this.dataName = dataName; + } + + public ContainerType getContainerType() { + return containerType; + } + + public String getDataName() { + return dataName; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + ContainerKey that = (ContainerKey) o; + return containerType == that.containerType && Objects.equals(dataName, that.dataName); + } + + @Override + public int hashCode() { + return Objects.hash(containerType, dataName); + } + + @Override + public String toString() { + return "ContainerKey{" + + "containerType=" + containerType + + ", dataName='" + dataName + '\'' + + '}'; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java index 976b1897..0db14ea2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java @@ -6,7 +6,7 @@ import java.util.Objects; import java.util.UUID; -public class GlobalProfileKey { +public final class GlobalProfileKey { public static GlobalProfileKey create(UUID playerUUID) { OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerUUID); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java index 254d99f7..53048a62 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java @@ -9,7 +9,7 @@ import java.util.UUID; -public class ProfileFileKey { +public sealed class ProfileFileKey permits ProfileKey { public static ProfileFileKey create( ContainerType containerType, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java index d1bb7b08..4ece9246 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java @@ -1,12 +1,17 @@ package org.mvplugins.multiverse.inventories.profile.key; +import com.google.common.collect.Sets; import org.bukkit.GameMode; import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Static class for profile type lookup and protected registration. @@ -26,10 +31,6 @@ private static ProfileType createProfileType(String name) { return type; } - public static List getTypes() { - return types; - } - /** * The profile type for the SURVIVAL Game Mode. */ @@ -50,6 +51,17 @@ public static List getTypes() { */ public static final ProfileType SPECTATOR = createProfileType("SPECTATOR"); + public static List getTypes() { + return types; + } + + public static List getApplicableTypes() { + if (config != null && config.getEnableGamemodeShareHandling()) { + return types; + } + return List.of(getDefault()); + } + public static ProfileType getDefault() { return SURVIVAL; } @@ -61,6 +73,12 @@ public static ProfileType forPlayer(Player player) { return getDefault(); } + public static Option forName(String name) { + return Option.ofOptional(types.stream() + .filter(type -> type.getName().equalsIgnoreCase(name)) + .findFirst()); + } + /** * Returns the appropriate ProfileType for the given game mode. * @@ -77,7 +95,11 @@ public static ProfileType forGameMode(GameMode mode) { }; } + public static boolean isAll(ProfileType[] otherTypes) { + return Sets.intersection(Set.of(otherTypes), Set.of(types)).size() == types.size(); + } + private ProfileTypes() { - throw new AssertionError(); + throw new IllegalStateException(); } } From dae2d8eafd270d31f6e66c5546fc31475823a318 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:00:21 +0800 Subject: [PATCH 167/180] Implement bulkedit action abstraction --- .../commands/bulkedit/BulkEditCommand.java | 35 +++++++++ .../bulkedit/playerprofile/ClearCommand.java | 68 +++++------------ .../bulkedit/playerprofile/DeleteCommand.java | 61 ++++++--------- .../MigrateInventorySerializationCommand.java | 2 +- .../handleshare/ReadOnlyShareHandler.java | 5 +- .../handleshare/SingleShareReader.java | 6 ++ .../profile/FlatFileProfileDataSource.java | 30 ++++++-- .../profile/PlayerNamesMapper.java | 2 +- .../profile/ProfileDataSource.java | 5 +- ...gator.java => BulkProfilesAggregator.java} | 52 ++++++------- .../profile/bulkedit/BulkProfilesPayload.java | 39 ++++++++++ .../bulkedit/action/BulkEditAction.java | 70 +++++++++++++++++ .../bulkedit/action/BulkEditResult.java | 38 ++++++++++ .../bulkedit/action/PlayerFileAction.java | 33 ++++++++ .../bulkedit/action/PlayerProfileAction.java | 36 +++++++++ .../action/PlayerProfileClearAction.java | 70 +++++++++++++++++ .../action/PlayerProfileDeleteAction.java | 76 +++++++++++++++++++ .../profile/bulkedit/action/package-info.java | 4 + .../profile/bulkedit/package-info.java | 4 + .../inventories/profile/group/WorldGroup.java | 2 +- .../profile/key/GlobalProfileKey.java | 24 +++--- .../profile/key/ProfileFileKey.java | 22 +----- .../inventories/profile/key/ProfileType.java | 8 +- .../inventories/profile/key/ProfileTypes.java | 9 ++- .../multiverse/inventories/InjectionTest.kt | 2 +- .../profile/FilePerformanceTest.kt | 2 +- 26 files changed, 543 insertions(+), 162 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{ProfilesAggregator.java => BulkProfilesAggregator.java} (64%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java new file mode 100644 index 00000000..8bfe8a54 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java @@ -0,0 +1,35 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.bulkedit.action.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.action.BulkEditResult; + +@ApiStatus.Internal +public abstract class BulkEditCommand extends InventoriesCommand { + + protected void outputActionSummary(MVCommandIssuer issuer, BulkEditAction bulkEditAction) { + issuer.sendMessage("Summary of affected profiles:"); + bulkEditAction.getActionSummary().forEach((key, value) -> + issuer.sendMessage(" %s: %s".formatted(key, value.size() > 10 + ? value.size() + : StringFormatter.join(value, ", ")))); + + } + + protected void runBulkEditAction(MVCommandIssuer issuer, BulkEditAction bulkEditAction) { + issuer.sendMessage("Starting bulk edit action..."); + bulkEditAction.execute() + .thenAccept(result -> outputResult(issuer, result)); + } + + protected void outputResult(MVCommandIssuer issuer, BulkEditResult bulkEditResult) { + issuer.sendMessage("Successfully processed %d profiles!".formatted(bulkEditResult.getSuccessCount())); + if (bulkEditResult.getFailureCount() > 0) { + issuer.sendError("Failed to process %d profiles! See log for details.".formatted(bulkEditResult.getFailureCount())); + } + issuer.sendMessage("Bulk edit action completed in %.4f ms.".formatted(bulkEditResult.getTimeTaken())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java index 9797e42f..28a1311d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java @@ -6,44 +6,35 @@ import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.locale.message.Message; -import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.bulkedit.ProfilesAggregator; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.bulkedit.action.PlayerProfileClearAction; import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; -import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; -import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; -import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; - -import java.util.List; -import java.util.concurrent.CompletableFuture; @Service -final class ClearCommand extends InventoriesCommand { +final class ClearCommand extends BulkEditCommand { + private final MultiverseInventories inventories; private final CommandQueueManager commandQueueManager; - private final ProfileDataSource profileDataSource; - private final ProfilesAggregator profilesAggregator; private final IncludeGroupsWorldsFlag flags; @Inject ClearCommand( + @NotNull MultiverseInventories inventories, @NotNull CommandQueueManager commandQueueManager, - @NotNull ProfileDataSource profileDataSource, - @NotNull ProfilesAggregator profilesAggregator, @NotNull IncludeGroupsWorldsFlag flags ) { + this.inventories = inventories; this.commandQueueManager = commandQueueManager; - this.profileDataSource = profileDataSource; - this.profilesAggregator = profilesAggregator; this.flags = flags; } @@ -60,37 +51,20 @@ void onCommand( ) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); - issuer.sendMessage("Players: " + StringFormatter.join(List.of(globalProfileKeys), ", ")); - issuer.sendMessage("Containers: " + StringFormatter.join(List.of(containerKeys), ", ")); - issuer.sendMessage("Profile Types: " + StringFormatter.join(List.of(profileTypes), ", ")); - - commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) - .prompt(Message.of("Are you sure you want to delete the above selected data?")) - .action(() -> runDelete(issuer, globalProfileKeys, containerKeys, profileTypes, parsedFlags))); - } - - private void runDelete(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, ContainerKey[] containerKeys, ProfileType[] profileTypes, ParsedCommandFlags parsedFlags) { - //TODO: Check lastWorld and online - if (ProfileTypes.isAll(profileTypes)) { - doFileDelete(issuer, globalProfileKeys, containerKeys, parsedFlags.hasFlag(flags.includeGroupsWorlds)); - } else { - doProfileDelete(issuer, globalProfileKeys, containerKeys, profileTypes, parsedFlags.hasFlag(flags.includeGroupsWorlds)); - } - } + PlayerProfileClearAction bulkEditAction = new PlayerProfileClearAction( + inventories, + new BulkProfilesPayload( + globalProfileKeys, + containerKeys, + profileTypes, + parsedFlags.hasFlag(flags.includeGroupsWorlds) + ) + ); - private void doFileDelete(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, ContainerKey[] containerKeys, boolean includeGroupsWorlds) { - List profileFileKeys = profilesAggregator.getProfileFileKeys(globalProfileKeys, containerKeys, includeGroupsWorlds); - CompletableFuture.allOf(profileFileKeys.stream() - .map(profileDataSource::deletePlayerFile) - .toArray(CompletableFuture[]::new)) - .thenRun(() -> issuer.sendMessage("Successfully deleted %d profiles.".formatted(profileFileKeys.size()))); - } + outputActionSummary(issuer, bulkEditAction); - private void doProfileDelete(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, ContainerKey[] containerKeys, ProfileType[] profileTypes, boolean includeGroupsWorlds) { - List playerProfileKeys = profilesAggregator.getPlayerProfileKeys(globalProfileKeys, containerKeys, profileTypes, includeGroupsWorlds); - CompletableFuture.allOf(playerProfileKeys.stream() - .map(profileDataSource::deletePlayerProfile) - .toArray(CompletableFuture[]::new)) - .thenRun(() -> issuer.sendMessage("Successfully deleted %d profiles.".formatted(playerProfileKeys.size()))); + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to clear the selected profiles?")) + .action(() -> runBulkEditAction(issuer, bulkEditAction))); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java index 254b2ea1..3f565eec 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -13,38 +13,30 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.bulkedit.ProfilesAggregator; -import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.bulkedit.action.PlayerProfileDeleteAction; import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; -import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.share.Sharable; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - @Service -final class DeleteCommand extends InventoriesCommand { +final class DeleteCommand extends BulkEditCommand { + private final MultiverseInventories inventories; private final CommandQueueManager commandQueueManager; - private final ProfileDataSource profileDataSource; - private final ProfilesAggregator profilesAggregator; private final IncludeGroupsWorldsFlag flags; @Inject DeleteCommand( + @NotNull MultiverseInventories inventories, @NotNull CommandQueueManager commandQueueManager, - @NotNull ProfileDataSource profileDataSource, - @NotNull ProfilesAggregator profilesAggregator, @NotNull IncludeGroupsWorldsFlag flags ) { + this.inventories = inventories; this.commandQueueManager = commandQueueManager; - this.profileDataSource = profileDataSource; - this.profilesAggregator = profilesAggregator; this.flags = flags; } @@ -62,30 +54,23 @@ void onCommand( ) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); - issuer.sendMessage("Players: " + StringFormatter.join(List.of(globalProfileKeys), ", ")); - issuer.sendMessage("Containers: " + StringFormatter.join(List.of(containerKeys), ", ")); - issuer.sendMessage("Profile Types: " + StringFormatter.join(List.of(profileTypes), ", ")); + PlayerProfileDeleteAction bulkEditAction = new PlayerProfileDeleteAction( + inventories, + sharable, + new BulkProfilesPayload( + globalProfileKeys, + containerKeys, + profileTypes, + parsedFlags.hasFlag(flags.includeGroupsWorlds) + ) + ); - List playerProfileKeys = profilesAggregator.getPlayerProfileKeys( - globalProfileKeys, containerKeys, profileTypes, parsedFlags.hasFlag(flags.includeGroupsWorlds)); + issuer.sendMessage("Summary of affected profiles:"); + bulkEditAction.getActionSummary().forEach((key, value) -> + issuer.sendMessage(" %s: %s".formatted(key, value.size() > 10 ? value.size() : StringFormatter.join(value, ", ")))); commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) - .prompt(Message.of("Are you sure you want to delete %s?".formatted(sharable.getNames()[0]))) - .action(() -> runDelete(issuer, sharable, playerProfileKeys))); - } - - private void runDelete(MVCommandIssuer issuer, Sharable sharable, List playerProfileKeys) { - //TODO: Check lastWorld and online - List> futures = new ArrayList<>(); - for (ProfileKey playerProfileKey : playerProfileKeys) { - profileDataSource.getPlayerProfile(playerProfileKey) - .thenCompose(playerProfile -> { - playerProfile.set(sharable, null); - return profileDataSource.updatePlayerProfile(playerProfile); - }); - } - CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).thenRun(() -> { - issuer.sendMessage("Successfully deleted %s from %d profiles.".formatted(sharable.getNames()[0], playerProfileKeys.size())); - }); + .prompt(Message.of("Are you sure you want to delete %s from the selected profiles?".formatted(sharable.getNames()[0]))) + .action(() -> runBulkEditAction(issuer, bulkEditAction))); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java index 5b497e24..bf9a9aca 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -71,7 +71,7 @@ private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { AtomicLong profileCounter = new AtomicLong(0); CompletableFuture.allOf(profileDataSource.listGlobalProfileUUIDs() .stream() - .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID)) + .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID, "")) .thenCompose(profile -> run(profile, profileCounter)) .exceptionally(throwable -> { issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java index 3cdf04c1..272dff8a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java @@ -10,8 +10,9 @@ import java.util.List; -final class ReadOnlyShareHandler extends ShareHandler { - ReadOnlyShareHandler(MultiverseInventories inventories, Player player) { +public final class ReadOnlyShareHandler extends ShareHandler { + + public ReadOnlyShareHandler(MultiverseInventories inventories, Player player) { super(inventories, player); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java index 394051a5..c4908d5f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java @@ -1,12 +1,14 @@ package org.mvplugins.multiverse.inventories.handleshare; import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.share.Sharable; import java.util.List; @@ -14,6 +16,10 @@ public final class SingleShareReader { + public static SingleShareReader of(MultiverseInventories inventories, Player player, Sharable sharable) { + return new SingleShareReader<>(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player), sharable); + } + public static SingleShareReader of(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { return new SingleShareReader<>(inventories, player, worldName, profileType, sharable); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index f9afe7d5..4e418881 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -11,6 +11,7 @@ import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.bukkit.configuration.ConfigurationSection; @@ -19,6 +20,7 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Array; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -60,7 +62,7 @@ private FileConfiguration loadFileToJsonConfiguration(File file) { return jsonConfiguration; } - private FileConfiguration getOrLoadPlayerProfileFile(ProfileKey profileKey, File playerFile) { + private FileConfiguration getOrLoadPlayerProfileFile(ProfileFileKey profileKey, File playerFile) { ProfileKey fileProfileKey = profileKey.forProfileType(null); return Try.of(() -> profileCacheManager.getOrLoadPlayerFile(fileProfileKey, (key) -> playerFile.exists() @@ -176,13 +178,31 @@ private void savePlayerProfileToDisk(ProfileKey profileKey, File playerFile, Pla public CompletableFuture deletePlayerProfile(ProfileKey profileKey) { File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); profileCacheManager.getCachedPlayerProfile(profileKey).peek(profile -> profile.getData().clear()); - return asyncFileIO.queueFileAction(playerFile, () -> deletePlayerProfileFromDisk(profileKey, playerFile)); + return asyncFileIO.queueFileAction(playerFile, () -> + deletePlayerProfileFromDisk(profileKey, playerFile, new ProfileType[]{profileKey.getProfileType()})); } - private void deletePlayerProfileFromDisk(ProfileKey profileKey, File playerFile) { + @Override + public CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes) { + if (ProfileTypes.isAll(profileTypes)) { + Logging.finer("Deleting profile: " + profileKey + " for all profile-types"); + return deletePlayerFile(profileKey); + } + for (var profileType : profileTypes) { + profileCacheManager.getCachedPlayerProfile(profileKey.forProfileType(profileType)) + .peek(profile -> profile.getData().clear()); + } + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + return asyncFileIO.queueFileAction(playerFile, () -> + deletePlayerProfileFromDisk(profileKey, playerFile, profileTypes)); + } + + private void deletePlayerProfileFromDisk(ProfileFileKey profileKey, File playerFile, ProfileType[] profileTypes) { try { FileConfiguration playerData = getOrLoadPlayerProfileFile(profileKey, playerFile); - playerData.set(profileKey.getProfileType().getName(), null); + for (var profileType : profileTypes) { + playerData.set(profileType.getName(), null); + } playerData.save(playerFile); } catch (IOException e) { Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() @@ -202,7 +222,7 @@ public CompletableFuture deletePlayerFile(ProfileFileKey profileKey) { } File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); if (!playerFile.exists()) { - Logging.warning("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + Logging.finer("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); return CompletableFuture.completedFuture(null); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java index c316ef16..5072f537 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java @@ -86,7 +86,7 @@ private void buildPlayerNamesMap() { ProfileDataSource profileDataSource = profileDataSourceProvider.get(); CompletableFuture[] futures = profileDataSource.listGlobalProfileUUIDs().stream() - .map(uuid -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(uuid)) + .map(uuid -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(uuid, "")) .thenAccept(globalProfile -> setPlayerName(uuid, globalProfile.getLastKnownName()))) .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures).thenRun(this::savePlayerNames).join(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 2c188273..ac17c3f2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -1,15 +1,14 @@ package org.mvplugins.multiverse.inventories.profile; -import org.bukkit.OfflinePlayer; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import java.io.IOException; -import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -38,6 +37,8 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { CompletableFuture deletePlayerProfile(ProfileKey profileKey); + CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes); + CompletableFuture deletePlayerFile(ProfileFileKey profileKey); /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java similarity index 64% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java index 58eebc63..cd14f66b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/ProfilesAggregator.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java @@ -1,8 +1,10 @@ package org.mvplugins.multiverse.inventories.profile.bulkedit; import com.google.common.collect.Sets; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; @@ -12,54 +14,52 @@ import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; -import javax.inject.Inject; import java.util.ArrayList; import java.util.List; import java.util.Set; +@ApiStatus.Experimental @Service -public final class ProfilesAggregator { +public final class BulkProfilesAggregator { private final WorldGroupManager worldGroupManager; @Inject - ProfilesAggregator(WorldGroupManager worldGroupManager) { + BulkProfilesAggregator(WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } - public List getProfileFileKeys( - @NotNull GlobalProfileKey[] globalProfileKeys, - @NotNull ContainerKey[] containerKeys, - boolean includeGroupsWorlds - ) { - if (includeGroupsWorlds) { - containerKeys = includeGroupsWorlds(containerKeys); - } - List profileFileKeys = new ArrayList<>(globalProfileKeys.length * containerKeys.length); - for (GlobalProfileKey globalProfileKey : globalProfileKeys) { + public List getProfileFileKeys(BulkProfilesPayload payload) { + var containerKeys = payload.includeGroupsWorlds() + ? includeGroupsWorlds(payload.containerKeys()) + : payload.containerKeys(); + + List profileFileKeys = new ArrayList<>( + payload.globalProfileKeys().length * containerKeys.length); + + for (GlobalProfileKey globalProfileKey : payload.globalProfileKeys()) { for (ContainerKey containerKey : containerKeys) { profileFileKeys.add(ProfileFileKey.create( containerKey.getContainerType(), containerKey.getDataName(), - globalProfileKey.getPlayerUUID())); + globalProfileKey.getPlayerUUID(), + globalProfileKey.getPlayerName())); } } return profileFileKeys; } - public List getPlayerProfileKeys( - @NotNull GlobalProfileKey[] globalProfileKeys, - @NotNull ContainerKey[] containerKeys, - @NotNull ProfileType[] profileTypes, - boolean includeGroupsWorlds - ) { - if (includeGroupsWorlds) { - containerKeys = includeGroupsWorlds(containerKeys); - } - List profileKeys = new ArrayList<>(globalProfileKeys.length * containerKeys.length * profileTypes.length); - for (GlobalProfileKey globalProfileKey : globalProfileKeys) { + public List getPlayerProfileKeys(BulkProfilesPayload payload) { + var containerKeys = payload.includeGroupsWorlds() + ? includeGroupsWorlds(payload.containerKeys()) + : payload.containerKeys(); + + List profileKeys = new ArrayList<>( + payload.globalProfileKeys().length * containerKeys.length * payload.profileTypes().length); + + for (GlobalProfileKey globalProfileKey : payload.globalProfileKeys()) { for (ContainerKey containerKey : containerKeys) { - for (ProfileType profileType : profileTypes) { + for (ProfileType profileType : payload.profileTypes()) { profileKeys.add(ProfileKey.create( containerKey.getContainerType(), containerKey.getDataName(), diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java new file mode 100644 index 00000000..6773c10b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@ApiStatus.Experimental +public record BulkProfilesPayload(@NotNull GlobalProfileKey[] globalProfileKeys, + @NotNull ContainerKey[] containerKeys, + @NotNull ProfileType[] profileTypes, + boolean includeGroupsWorlds) { + + public Map> getSummary() { + return Map.of( + "Players", Arrays.stream(globalProfileKeys) + .map(GlobalProfileKey::getPlayerName) + .toList(), + "Worlds", Arrays.stream(containerKeys) + .filter(c -> c.getContainerType() == ContainerType.WORLD) + .map(ContainerKey::getDataName) + .toList(), + "Groups", Arrays.stream(containerKeys) + .filter(c -> c.getContainerType() == ContainerType.GROUP) + .map(ContainerKey::getDataName) + .toList(), + "Profile Types", Arrays.stream(profileTypes) + .map(ProfileType::getName) + .map(String::toLowerCase) + .toList() + ); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java new file mode 100644 index 00000000..be435ba4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java @@ -0,0 +1,70 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +@ApiStatus.Experimental +public abstract class BulkEditAction { + + protected final MultiverseInventories inventories; + protected final ProfileDataSource profileDataSource; + protected final GlobalProfileKey[] globalProfileKeys; + + BulkEditAction(MultiverseInventories inventories, GlobalProfileKey[] globalProfileKeys) { + this.inventories = inventories; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.globalProfileKeys = globalProfileKeys; + } + + public CompletableFuture execute() { + BulkEditResult bulkEditResult = new BulkEditResult(); + List targetKeys = aggregateKeys(); + Set onlinePlayers = new HashSet<>(); + return CompletableFuture.allOf(targetKeys.stream() + .map(key -> { + Player player = key.getOnlinePlayer(); + if (player != null && isOnlinePlayerAffected(key, player)) { + onlinePlayers.add(player); + } + return performAction(key) + .thenRun(bulkEditResult::incrementSuccess) + .exceptionally(throwable -> { + bulkEditResult.incrementFailure(); + throwable.printStackTrace(); + return null; + }); + }) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> + Bukkit.getScheduler().runTask(inventories, () -> + onlinePlayers.forEach(this::updateOnlinePlayerNow))) + .thenApply(ignore -> bulkEditResult); + } + + protected abstract List aggregateKeys(); + + protected abstract CompletableFuture performAction(K key); + + protected boolean isOnlinePlayerAffected(K key, Player player) { + return key.getPlayerUUID().equals(player.getUniqueId()); + } + + protected abstract void updateOnlinePlayerNow(Player player); + + public Map> getActionSummary() { + return Map.of("Players", Arrays.stream(globalProfileKeys) + .map(GlobalProfileKey::getPlayerName) + .toList()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java new file mode 100644 index 00000000..97bdf270 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java @@ -0,0 +1,38 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.concurrent.atomic.AtomicInteger; + +@ApiStatus.Experimental +public final class BulkEditResult { + + private final long startTime = System.nanoTime(); + private final AtomicInteger successCount = new AtomicInteger(0); + private final AtomicInteger failureCount = new AtomicInteger(0); + + void incrementSuccess() { + successCount.incrementAndGet(); + } + + void incrementFailure() { + failureCount.incrementAndGet(); + } + + public int getSuccessCount() { + return successCount.get(); + } + + public int getFailureCount() { + return failureCount.get(); + } + + /** + * In milliseconds + * + * @return Gets the time taken + */ + public double getTimeTaken() { + return (double) (System.nanoTime() - startTime) / 1_000_000.0; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java new file mode 100644 index 00000000..c4499ff4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java @@ -0,0 +1,33 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesAggregator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; + +import java.util.List; +import java.util.Map; + +@ApiStatus.Experimental +public abstract class PlayerFileAction extends BulkEditAction { + + private final BulkProfilesAggregator profilesAggregator; + protected final BulkProfilesPayload bulkProfilesPayload; + + PlayerFileAction(MultiverseInventories inventories, BulkProfilesPayload bulkProfilesPayload) { + super(inventories, bulkProfilesPayload.globalProfileKeys()); + this.profilesAggregator = inventories.getServiceLocator().getService(BulkProfilesAggregator.class); + this.bulkProfilesPayload = bulkProfilesPayload; + } + + @Override + protected List aggregateKeys() { + return profilesAggregator.getProfileFileKeys(bulkProfilesPayload); + } + + @Override + public Map> getActionSummary() { + return bulkProfilesPayload.getSummary(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java new file mode 100644 index 00000000..156d8526 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java @@ -0,0 +1,36 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesAggregator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; + +import java.util.List; +import java.util.Map; + +@ApiStatus.Experimental +public abstract class PlayerProfileAction extends BulkEditAction { + + private final BulkProfilesAggregator profilesAggregator; + private final BulkProfilesPayload bulkProfilesPayload; + + protected PlayerProfileAction( + MultiverseInventories inventories, + BulkProfilesPayload bulkProfilesPayload + ) { + super(inventories, bulkProfilesPayload.globalProfileKeys()); + this.profilesAggregator = inventories.getServiceLocator().getService(BulkProfilesAggregator.class); + this.bulkProfilesPayload = bulkProfilesPayload; + } + + @Override + protected List aggregateKeys() { + return profilesAggregator.getPlayerProfileKeys(bulkProfilesPayload); + } + + @Override + public Map> getActionSummary() { + return bulkProfilesPayload.getSummary(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java new file mode 100644 index 00000000..feef9357 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java @@ -0,0 +1,70 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.ReadOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +@ApiStatus.Experimental +public final class PlayerProfileClearAction extends PlayerFileAction { + + private final WorldGroupManager worldGroupManager; + private final Set profileTypesSet; + + public PlayerProfileClearAction(MultiverseInventories inventories, BulkProfilesPayload bulkProfilesPayload) { + super(inventories, bulkProfilesPayload); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.profileTypesSet = Set.of(bulkProfilesPayload.profileTypes()); + } + + @Override + protected CompletableFuture performAction(ProfileFileKey key) { + return profileDataSource.deletePlayerProfiles(key, bulkProfilesPayload.profileTypes()) + .thenCompose(ignore -> profileDataSource.modifyGlobalProfile( + key, + profile -> profile.setLoadOnLogin(true) + )); + } + + @Override + protected boolean isOnlinePlayerAffected(ProfileFileKey key, Player player) { + if (!profileTypesSet.contains(ProfileTypes.forPlayer(player))) { + return false; + } + + // Gets groups that share this sharable + List groups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()); + + Shares unhandledSharables = Sharables.enabledOf(); + for (WorldGroup worldGroup : groups) { + unhandledSharables.removeAll(worldGroup.getApplicableShares()); + } + + if (!unhandledSharables.isEmpty()) { + return key.getContainerType() == ContainerType.WORLD && player.getWorld().getName().equals(key.getDataName()); + } + + // Using group for sharable + return key.getContainerType() == ContainerType.GROUP && groups.stream() + .anyMatch(group -> group.getName().equals(key.getDataName())); + } + + @Override + protected void updateOnlinePlayerNow(Player player) { + new ReadOnlyShareHandler(inventories, player).handleSharing(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java new file mode 100644 index 00000000..74285ce9 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java @@ -0,0 +1,76 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSnapshot; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.util.FutureNow; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@ApiStatus.Experimental +public final class PlayerProfileDeleteAction extends PlayerProfileAction { + + + private final WorldGroupManager worldGroupManager; + private final Sharable sharable; + + public PlayerProfileDeleteAction( + MultiverseInventories inventories, + Sharable sharable, + BulkProfilesPayload bulkProfilesPayload + ) { + super(inventories, bulkProfilesPayload); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.sharable = sharable; + } + + @Override + protected CompletableFuture performAction(ProfileKey key) { + return profileDataSource.getPlayerProfile(key) + .thenCompose(playerProfile -> { + playerProfile.set(sharable, null); + return profileDataSource.updatePlayerProfile(playerProfile); + }) + .thenCompose(ignore -> profileDataSource.modifyGlobalProfile(key, profile -> { + profile.setLoadOnLogin(true); + })); + } + + @Override + protected boolean isOnlinePlayerAffected(ProfileKey key, Player player) { + if (!ProfileTypes.forPlayer(player).equals(key.getProfileType())) { + return false; + } + + // Gets groups that share this sharable + List groups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()).stream() + .filter(group -> group.isSharing(sharable)) + .toList(); + + if (groups.isEmpty()) { + // Using world itself for sharable + return key.getContainerType() == ContainerType.WORLD && player.getWorld().getName().equals(key.getDataName()); + } + + // Using group for sharable + return key.getContainerType() == ContainerType.GROUP && groups.stream() + .anyMatch(group -> group.getName().equals(key.getDataName())); + } + + @Override + protected void updateOnlinePlayerNow(Player player) { + ProfileDataSnapshot profileDataSnapshot = new ProfileDataSnapshot(); + profileDataSnapshot.set(sharable, FutureNow.get(SingleShareReader.of(inventories, player, sharable).read())); + sharable.getHandler().updatePlayer(player, profileDataSnapshot); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java new file mode 100644 index 00000000..d17b3875 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Experimental +package org.mvplugins.multiverse.inventories.profile.bulkedit.action; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java new file mode 100644 index 00000000..bae46c1a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Experimental +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index 727f0697..d0829c4e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -180,7 +180,7 @@ public Set getWorlds() { * @return true is the sharable is shared for this group. */ public boolean isSharing(Sharable sharable) { - return getShares().isSharing(sharable); + return getApplicableShares().isSharing(sharable); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java index 0db14ea2..058d9cfc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java @@ -2,16 +2,14 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.UUID; -public final class GlobalProfileKey { - - public static GlobalProfileKey create(UUID playerUUID) { - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerUUID); - return create(offlinePlayer); - } +public sealed class GlobalProfileKey permits ProfileFileKey { public static GlobalProfileKey create(OfflinePlayer offlinePlayer) { return create(offlinePlayer.getUniqueId(), offlinePlayer.getName()); @@ -21,10 +19,10 @@ public static GlobalProfileKey create(UUID playerUUID, String playerName) { return new GlobalProfileKey(playerUUID, playerName); } - private final UUID playerUUID; - private final String playerName; + protected final UUID playerUUID; + protected final String playerName; - private GlobalProfileKey(UUID playerUUID, String playerName) { + protected GlobalProfileKey(UUID playerUUID, String playerName) { this.playerUUID = playerUUID; this.playerName = playerName; } @@ -37,6 +35,14 @@ public String getPlayerName() { return playerName; } + public @NotNull OfflinePlayer getOfflinePlayer() { + return Bukkit.getOfflinePlayer(playerUUID); + } + + public @Nullable Player getOnlinePlayer() { + return Bukkit.getPlayer(playerUUID); + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java index 53048a62..e8e9d79c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java @@ -9,7 +9,7 @@ import java.util.UUID; -public sealed class ProfileFileKey permits ProfileKey { +public sealed class ProfileFileKey extends GlobalProfileKey permits ProfileKey { public static ProfileFileKey create( ContainerType containerType, @@ -26,13 +26,6 @@ public static ProfileFileKey create( return new ProfileFileKey(containerType, dataName, offlinePlayer.getUniqueId(), offlinePlayer.getName()); } - public static ProfileFileKey create( - ContainerType containerType, - String dataName, - UUID playerUUID) { - return new ProfileFileKey(containerType, dataName, playerUUID, Bukkit.getOfflinePlayer(playerUUID).getName()); - } - public static ProfileFileKey fromPlayerProfile(PlayerProfile profile) { return new ProfileFileKey( profile.getContainerType(), @@ -44,8 +37,6 @@ public static ProfileFileKey fromPlayerProfile(PlayerProfile profile) { protected final ContainerType containerType; protected final String dataName; - protected final String playerName; - protected final UUID playerUUID; protected final int hashCode; private ProfileFileKey(ContainerType containerType, String dataName, UUID playerUUID, String playerName) { @@ -57,10 +48,9 @@ private ProfileFileKey(ContainerType containerType, String dataName, UUID player } protected ProfileFileKey(ContainerType containerType, String dataName, UUID playerUUID, String playerName, int hashCode) { + super(playerUUID, playerName); this.containerType = containerType; this.dataName = dataName; - this.playerUUID = playerUUID; - this.playerName = playerName; this.hashCode = hashCode; } @@ -80,14 +70,6 @@ public String getDataName() { return dataName; } - public String getPlayerName() { - return playerName; - } - - public UUID getPlayerUUID() { - return playerUUID; - } - public boolean isSameFile(ProfileFileKey other) { return Objects.equal(getContainerType(), other.getContainerType()) && Objects.equal(getDataName(), other.getDataName()) && diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java index 4b129810..a837c839 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java @@ -23,13 +23,13 @@ public String getName() { } @Override - public final boolean equals(Object o) { - return o instanceof ProfileType && ((ProfileType) o).getName().equals(this.getName()); + public boolean equals(Object o) { + return o instanceof ProfileType otherType && otherType.getName().equals(this.getName()); } @Override - public final int hashCode() { - return getName().hashCode(); + public int hashCode() { + return name.hashCode(); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java index 4ece9246..1640f4fa 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java @@ -9,6 +9,7 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -18,7 +19,7 @@ */ public final class ProfileTypes { - private static final List types = new ArrayList<>(); + private static final Set types = new HashSet<>(); private static InventoriesConfig config; public static void init(MultiverseInventories plugin) { @@ -51,11 +52,11 @@ private static ProfileType createProfileType(String name) { */ public static final ProfileType SPECTATOR = createProfileType("SPECTATOR"); - public static List getTypes() { + public static Collection getTypes() { return types; } - public static List getApplicableTypes() { + public static Collection getApplicableTypes() { if (config != null && config.getEnableGamemodeShareHandling()) { return types; } @@ -96,7 +97,7 @@ public static ProfileType forGameMode(GameMode mode) { } public static boolean isAll(ProfileType[] otherTypes) { - return Sets.intersection(Set.of(otherTypes), Set.of(types)).size() == types.size(); + return Set.of(otherTypes).equals(types); } private ProfileTypes() { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt index 8e406ba8..36d7c644 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -15,7 +15,7 @@ class InjectionTest : TestWithMockBukkit() { @Test fun `InventoriesCommand are available as a service`() { - assertEquals(26, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + assertEquals(28, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) } @Test diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 1ce4407f..84826cf6 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -52,7 +52,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val startTime = System.nanoTime() val futures = ArrayList>(1000) for (i in 0..1000) { - futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(UUID.randomUUID()), { globalProfile -> + futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(UUID.randomUUID(), ""), { globalProfile -> globalProfile.setLoadOnLogin(true) })) } From 2eb11f3d5ab6282a6e8b2b8019ede7e452401d27 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:52:07 +0800 Subject: [PATCH 168/180] Improve `/mvinv give` tab complete and add profile type support --- .../inventories/MultiverseInventories.java | 4 ++ .../command/MVInvCommandCompletion.java | 9 +++- .../command/MVInvCommandContexts.java | 10 +++++ .../command/MVInvCommandPermissions.java | 20 +++++++++ .../inventories/commands/GiveCommand.java | 42 ++++++++++++------- .../bulkedit/playerprofile/ClearCommand.java | 2 +- .../bulkedit/playerprofile/DeleteCommand.java | 2 +- 7 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index cad3582e..15c6e231 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -8,6 +8,7 @@ import org.mvplugins.multiverse.core.module.MultiverseModule; import org.mvplugins.multiverse.core.utils.StringFormatter; import org.mvplugins.multiverse.inventories.command.MVInvCommandConditions; +import org.mvplugins.multiverse.inventories.command.MVInvCommandPermissions; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; import org.mvplugins.multiverse.inventories.command.MVInvCommandCompletion; import org.mvplugins.multiverse.inventories.command.MVInvCommandContexts; @@ -73,6 +74,8 @@ public class MultiverseInventories extends MultiverseModule { private Provider mvInvCommandContexts; @Inject private Provider mvInvCommandConditions; + @Inject + private Provider mvInvCommandPermissions; private InventoriesDupingPatch dupingPatch; private boolean usingSpawnChangeEvent = false; @@ -167,6 +170,7 @@ private void registerCommands() { mvInvCommandCompletion.get(); mvInvCommandContexts.get(); mvInvCommandConditions.get(); + mvInvCommandPermissions.get(); }).onFailure(e -> { Logging.warning("Failed to register command completers: %s", e.getMessage()); }); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java index c41a299f..7f18bd1f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java @@ -93,10 +93,17 @@ private List getPlayerNames() { } private Collection suggestProfileTypes(BukkitCommandCompletionContext context) { + if (!context.hasConfig("multiple")) { + return ProfileTypes.getTypes().stream() + .map(ProfileType::getName) + .map(String::toLowerCase) + .toList(); + } + if (Objects.equals(context.getInput(), "@all")) { return Collections.emptyList(); } - List profileTypes = ProfileTypes.getApplicableTypes() + List profileTypes = ProfileTypes.getTypes() .stream() .map(ProfileType::getName) .map(String::toLowerCase) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java index 8c4a2a38..dbe0a7ef 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -54,12 +54,22 @@ private MVInvCommandContexts( CommandContexts commandContexts = commandManager.getCommandContexts(); commandContexts.registerContext(ContainerKey[].class, this::parseContainerKeyArray); commandContexts.registerContext(GlobalProfileKey[].class, this::parseGlobalProfileKeyArray); + commandContexts.registerIssuerAwareContext(ProfileType.class, this::parseProfileType); commandContexts.registerIssuerAwareContext(ProfileType[].class, this::parseProfileTypeArray); commandContexts.registerContext(Sharable.class, this::parseSharable); commandContexts.registerContext(Shares.class, this::parseShares); commandContexts.registerContext(WorldGroup.class, this::parseWorldGroup); } + private ProfileType parseProfileType(BukkitCommandExecutionContext context) { + if (!config.getEnableGamemodeShareHandling()) { + return ProfileTypes.getDefault(); + } + String profileType = context.popFirstArg(); + return ProfileTypes.forName(profileType) + .getOrElseThrow(() -> new InvalidCommandArgument("Invalid profile type: " + profileType)); + } + private ContainerKey[] parseContainerKeyArray(BukkitCommandExecutionContext context) { String keyStrings = context.popFirstArg(); if (keyStrings.equals("@all")) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java new file mode 100644 index 00000000..5a88faa9 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java @@ -0,0 +1,20 @@ +package org.mvplugins.multiverse.inventories.command; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandPermissions; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +@Service +public class MVInvCommandPermissions { + + @Inject + MVInvCommandPermissions(@NotNull MVCommandManager commandManager, @NotNull InventoriesConfig config) { + + MVCommandPermissions commandPermissions = commandManager.getCommandPermissions(); + commandPermissions.registerPermissionChecker("mvinv-gamemode-profile-true", commandIssuer -> config.getEnableGamemodeShareHandling()); + commandPermissions.registerPermissionChecker("mvinv-gamemode-profile-false", commandIssuer -> !config.getEnableGamemodeShareHandling()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index 13073621..f0954753 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -2,18 +2,16 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.Bukkit; -import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.utils.REPatterns; import org.mvplugins.multiverse.core.world.MultiverseWorld; -import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; @@ -22,7 +20,6 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; @@ -30,6 +27,7 @@ import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.PlayerStats; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; @@ -49,17 +47,30 @@ final class GiveCommand extends InventoriesCommand { this.profileDataSource = profileDataSource; } - // TODO Support custom gamemode when gamemode profile is enabled // TODO Better offline player parsing @Subcommand("give") @CommandPermission("multiverse.inventories.give") - @CommandCompletion("@players @mvworlds:scope=both @materials @range:64") - @Syntax(" [amount]") + @CommandCompletion("@players " + + "@mvworlds:scope=both " + + "@mvinvprofiletypes:checkPermissions=@mvinv-gamemode-profile-true|@materials:checkPermissions=@mvinv-gamemode-profile-false " + + "@materials:checkPermissions=@mvinv-gamemode-profile-true|@range:64,checkPermissions=@mvinv-gamemode-profile-false " + + "@range:64,checkPermissions=@mvinv-gamemode-profile-true|@empty " + + "@empty") + @Syntax(" [gamemode] [amount]") @Description("World and Group Information") void onGiveCommand( MVCommandIssuer issuer, + + @Syntax("") OfflinePlayer player, + + @Syntax("") MultiverseWorld world, + + @Syntax("[gamemode]") + ProfileType profileType, + + @Syntax(" [amount]") String item ) { ItemStack itemStack = parseItemFromString(issuer, item); @@ -69,16 +80,16 @@ void onGiveCommand( Logging.finer("Giving player " + player.getName() + " item: " + itemStack); // Giving online player in same world - // TODO check for gamemode as well if gamemode-profile is enabled. Player onlinePlayer = player.getPlayer(); - if (onlinePlayer != null && world.getName().equals(onlinePlayer.getWorld().getName())) { + if (onlinePlayer != null + && world.getName().equals(onlinePlayer.getWorld().getName()) + && ProfileTypes.forPlayer(onlinePlayer).equals(profileType)) { onlinePlayer.getInventory().addItem(itemStack); issuer.sendInfo("Gave player %s %s %s in world %s." .formatted(player.getName(), itemStack.getAmount(), itemStack, world.getName())); return; } - ProfileType profileType = ProfileTypes.getDefault(); SingleShareReader.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) .read() .thenCompose(inventory -> updatePlayerInventory(issuer, player, world, profileType, inventory, itemStack)) @@ -88,7 +99,7 @@ void onGiveCommand( }); } - private ItemStack parseItemFromString(MVCommandIssuer issuer, String item) { + private @Nullable ItemStack parseItemFromString(MVCommandIssuer issuer, String item) { // Get amount int amount = 1; AtomicBoolean endIsAmount = new AtomicBoolean(false); @@ -137,8 +148,8 @@ private CompletableFuture updatePlayerInventory( OfflinePlayer player, MultiverseWorld world, ProfileType profileType, - ItemStack[] inventory, - ItemStack itemStack + @Nullable ItemStack[] inventory, + @NotNull ItemStack itemStack ) { putItemInInventory(inventory, itemStack); return SingleShareWriter.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) @@ -150,7 +161,10 @@ private CompletableFuture updatePlayerInventory( .formatted(player.getName(), itemStack.getAmount(), itemStack.getI18NDisplayName(), world.getName()))); } - private void putItemInInventory(ItemStack[] inventory, ItemStack itemStack) { + private void putItemInInventory(@Nullable ItemStack[] inventory, @NotNull ItemStack itemStack) { + if (inventory == null) { + inventory = new ItemStack[PlayerStats.INVENTORY_SIZE]; + } int amountLeft = itemStack.getAmount(); for (int i = 0; i < inventory.length; i++) { if (inventory[i] == null || inventory[i].getType() == Material.AIR) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java index 28a1311d..19978982 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java @@ -40,7 +40,7 @@ final class ClearCommand extends BulkEditCommand { @Subcommand("bulkedit playerprofile clear") @CommandPermission("multiverse.inventories.bulkedit") - @CommandCompletion("@mvinvplayernames @empty @mvinvprofiletypes @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) + @CommandCompletion("@mvinvplayernames @empty @mvinvprofiletypes:multiple @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) @Syntax(" [profile-type] [--include-groups-worlds]") void onCommand( MVCommandIssuer issuer, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java index 3f565eec..276fa4b7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -42,7 +42,7 @@ final class DeleteCommand extends BulkEditCommand { @Subcommand("bulkedit playerprofile delete") @CommandPermission("multiverse.inventories.bulkedit") - @CommandCompletion("@shares @mvinvplayernames @empty @mvinvprofiletypes @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) + @CommandCompletion("@shares @mvinvplayernames @empty @mvinvprofiletypes:multiple @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) @Syntax(" [profile-type] [--include-groups-worlds]") void onCommand( MVCommandIssuer issuer, From f070647c08a2627020e652578ce0d3cf12864643 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:40:37 +0800 Subject: [PATCH 169/180] Keys to use playernamemapper instead of bukkit's offlineplayer api --- .../inventories/MultiverseInventories.java | 2 +- .../inventories/commands/GiveCommand.java | 2 +- .../MigrateInventorySerializationCommand.java | 11 +---- .../perworldinventory/PwiImportHelper.java | 6 +-- .../handleshare/ShareHandleListener.java | 14 +++--- .../profile/FlatFileProfileDataSource.java | 15 ++++-- .../profile/PlayerNamesMapper.java | 46 +++++++++++-------- .../bulkedit/BulkProfilesAggregator.java | 5 +- .../profile/container/ProfileContainer.java | 8 ++-- .../profile/key/GlobalProfileKey.java | 18 ++++++-- .../profile/key/ProfileFileKey.java | 36 +++++++++------ .../inventories/profile/key/ProfileKey.java | 45 ++++++++++-------- .../inventories/util/ItemStackConverter.java | 11 +++++ .../handleshare/ShareHandlingUpdaterTest.kt | 4 +- .../profile/FilePerformanceTest.kt | 10 ++-- .../profile/PlayerNameChangeTest.kt | 7 ++- .../profile/ProfileDataSourceTest.kt | 2 +- 17 files changed, 144 insertions(+), 98 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index 15c6e231..fcf6d545 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -139,7 +139,7 @@ public void onDisable() { new WriteOnlyShareHandler(this, player).handleSharing(); if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { profileDataSource.get().modifyGlobalProfile( - GlobalProfileKey.create(player), profile -> profile.setLoadOnLogin(true)); + GlobalProfileKey.of(player), profile -> profile.setLoadOnLogin(true)); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java index f0954753..9556d071 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -156,7 +156,7 @@ private CompletableFuture updatePlayerInventory( .write(inventory, true) .thenCompose(ignore -> player.isOnline() ? CompletableFuture.completedFuture(null) - : profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(player), profile -> profile.setLoadOnLogin(true))) + : profileDataSource.modifyGlobalProfile(GlobalProfileKey.of(player), profile -> profile.setLoadOnLogin(true))) .thenRun(() -> issuer.sendInfo("Gave player %s %s %s in world %s." .formatted(player.getName(), itemStack.getAmount(), itemStack.getI18NDisplayName(), world.getName()))); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java index bf9a9aca..593695ad 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -1,14 +1,10 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; -import com.dumptruckman.minecraft.util.Logging; -import org.checkerframework.checker.units.qual.N; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.locale.message.Message; -import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -17,15 +13,12 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; -import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import java.util.Arrays; -import java.util.Collection; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicLong; @@ -71,7 +64,7 @@ private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { AtomicLong profileCounter = new AtomicLong(0); CompletableFuture.allOf(profileDataSource.listGlobalProfileUUIDs() .stream() - .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(playerUUID, "")) + .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.of(playerUUID, "")) .thenCompose(profile -> run(profile, profileCounter)) .exceptionally(throwable -> { issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); @@ -89,7 +82,7 @@ private CompletableFuture run(GlobalProfile profile, AtomicLong profileCou return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) .flatMap(containerType -> profileDataSource.listContainerDataNames(containerType).stream() .flatMap(dataName -> ProfileTypes.getTypes().stream() - .map(profileType -> profileDataSource.getPlayerProfile(ProfileKey.create( + .map(profileType -> profileDataSource.getPlayerProfile(ProfileKey.of( containerType, dataName, profileType, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 71b4a4c5..3f0b47c7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -182,7 +182,7 @@ private void saveMVDataForGroup(Group group) throws DataImportException { } private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throws DataImportException { - GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(offlinePlayer))); + GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(offlinePlayer))); globalProfile.setLoadOnLogin(pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); profileDataSource.updateGlobalProfile(globalProfile); for (GameMode gameMode : GameMode.values()) { @@ -208,10 +208,10 @@ private List getMVPlayerData( @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { List profiles = new ArrayList<>(); profiles.add(FutureNow.get(profileDataSource.getPlayerProfile(org.mvplugins.multiverse.inventories.profile.key.ProfileKey - .create(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); + .of(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); for (var worldName : group.getWorlds()) { profiles.add(FutureNow.get(profileDataSource.getPlayerProfile(org.mvplugins.multiverse.inventories.profile.key.ProfileKey - .create(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); + .of(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); } return profiles; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 5a0e5385..8d9c8d04 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -91,9 +91,9 @@ void playerPreLogin(AsyncPlayerPreLoginEvent event) { long startTime = System.nanoTime(); List> profileFutures = new ArrayList<>(); config.getPreloadDataOnJoinWorlds().forEach(worldName -> profileFutures.add(profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, worldName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); + ProfileKey.of(ContainerType.WORLD, worldName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); config.getPreloadDataOnJoinGroups().forEach(groupName -> profileFutures.add(profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.GROUP, groupName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); + ProfileKey.of(ContainerType.GROUP, groupName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); Try.run(() -> CompletableFuture.allOf(profileFutures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS)) .onSuccess(ignore -> Logging.finer("Preloaded data for Player{name:'%s', uuid:'%s'}. Time taken: %4.4f ms", event.getName(), event.getUniqueId(), (System.nanoTime() - startTime) / 1000000.0)) @@ -111,7 +111,7 @@ void playerJoin(final PlayerJoinEvent event) { // Just in case AsyncPlayerPreLoginEvent was still the old name verifyCorrectPlayerName(player.getUniqueId(), player.getName()); - final GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player))); + final GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(player))); if (globalProfile.shouldLoadOnLogin()) { new ReadOnlyShareHandler(inventories, player).handleSharing(); } @@ -121,7 +121,7 @@ void playerJoin(final PlayerJoinEvent event) { } private void verifyCorrectPlayerName(UUID uuid, String name) { - FutureNow.get(profileDataSource.getExistingGlobalProfile(GlobalProfileKey.create(uuid, name))).peek(globalProfile -> { + FutureNow.get(profileDataSource.getExistingGlobalProfile(GlobalProfileKey.of(uuid, name))).peek(globalProfile -> { if (globalProfile.getLastKnownName().equals(name)) { return; } @@ -151,7 +151,7 @@ void playerQuit(final PlayerQuitEvent event) { final Player player = event.getPlayer(); final String world = event.getPlayer().getWorld().getName(); - CompletableFuture globalProfile = profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)); + CompletableFuture globalProfile = profileDataSource.getGlobalProfile(GlobalProfileKey.of(player)); globalProfile.thenAccept(p -> p.setLastWorld(world)); // Write last location as its possible for players to join at a different world @@ -215,7 +215,7 @@ void playerChangedWorld(PlayerChangedWorldEvent event) { new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); profileDataSource.modifyGlobalProfile( - GlobalProfileKey.create(player), profile -> profile.setLastWorld(toWorld.getName())); + GlobalProfileKey.of(player), profile -> profile.setLastWorld(toWorld.getName())); } /** @@ -280,7 +280,7 @@ void playerRespawn(PlayerRespawnEvent event) { () -> verifyCorrectWorld( player, player.getWorld().getName(), - FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)))), + FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(player)))), 2L); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index 4e418881..d4b3f1b9 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -2,6 +2,7 @@ import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; import com.dumptruckman.minecraft.util.Logging; +import com.google.common.base.Strings; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.exceptions.MultiverseException; @@ -20,7 +21,6 @@ import java.io.File; import java.io.IOException; -import java.lang.reflect.Array; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -80,6 +80,9 @@ private FileConfiguration getOrLoadPlayerProfileFile(ProfileFileKey profileKey, @Override public CompletableFuture getPlayerProfile(ProfileKey profileKey) { try { + if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Player name cannot be null or empty. " + profileKey)); + } return profileCacheManager.getOrLoadPlayerProfile(profileKey, (key, executor) -> { File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); if (!playerFile.exists()) { @@ -176,6 +179,9 @@ private void savePlayerProfileToDisk(ProfileKey profileKey, File playerFile, Pla */ @Override public CompletableFuture deletePlayerProfile(ProfileKey profileKey) { + if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Player name cannot be null or empty. " + profileKey)); + } File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); profileCacheManager.getCachedPlayerProfile(profileKey).peek(profile -> profile.getData().clear()); return asyncFileIO.queueFileAction(playerFile, () -> @@ -184,6 +190,9 @@ public CompletableFuture deletePlayerProfile(ProfileKey profileKey) { @Override public CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes) { + if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Player name cannot be null or empty. " + profileKey)); + } if (ProfileTypes.isAll(profileTypes)) { Logging.finer("Deleting profile: " + profileKey + " for all profile-types"); return deletePlayerFile(profileKey); @@ -338,7 +347,7 @@ public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) boolean didPlayerNameChange = playerNamesMapper.setPlayerName(globalProfile.getPlayerUUID(), globalProfile.getLastKnownName()); return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile)) .thenCompose(ignore -> didPlayerNameChange - ? asyncFileIO.queueFileAction(playerNamesMapper.getFile(), playerNamesMapper::savePlayerNames) + ? playerNamesMapper.savePlayerNames() : CompletableFuture.completedFuture(null)); } @@ -387,7 +396,7 @@ private CompletableFuture clearAllPlayerProfileFiles(UUID playerUUID, Stri return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) .flatMap(containerType -> listContainerDataNames(containerType) .stream() - .map(containerName -> deletePlayerFile(ProfileFileKey.create( + .map(containerName -> deletePlayerFile(ProfileFileKey.of( containerType, containerName, playerUUID, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java index 5072f537..7601f167 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java @@ -18,6 +18,7 @@ import java.io.FileWriter; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -25,8 +26,15 @@ @Service public final class PlayerNamesMapper { + private static PlayerNamesMapper instance; + + public static PlayerNamesMapper getInstance() { + return Objects.requireNonNull(instance); + } + private static final String FILENAME = "playernames.json"; + private final AsyncFileIO asyncFileIO; private final Provider profileDataSourceProvider; private final Provider profileCacheManagerProvider; @@ -37,17 +45,21 @@ public final class PlayerNamesMapper { private Map playerNamesJson; @Inject - PlayerNamesMapper( + private PlayerNamesMapper( @NotNull MultiverseInventories inventories, + @NotNull AsyncFileIO asyncFileIO, @NotNull Provider profileDataSourceProvider, @NotNull Provider profileCacheManagerProvider ) { + this.asyncFileIO = asyncFileIO; this.profileDataSourceProvider = profileDataSourceProvider; this.profileCacheManagerProvider = profileCacheManagerProvider; this.playerNamesFile = new File(inventories.getDataFolder(), FILENAME); this.playerNamesMap = new ConcurrentHashMap<>(); this.playerUUIDMap = new ConcurrentHashMap<>(); + + instance = this; } public void loadMap() { @@ -71,7 +83,7 @@ private void loadFromPlayerNamesFile() { playerNamesJson.forEach((String uuid, Object name) -> { UUID playerUUID = UUID.fromString(uuid); String playerName = String.valueOf(name); - GlobalProfileKey globalProfileKey = GlobalProfileKey.create(playerUUID, playerName); + GlobalProfileKey globalProfileKey = GlobalProfileKey.of(playerUUID, playerName); playerNamesMap.put(playerName, globalProfileKey); playerUUIDMap.put(playerUUID, globalProfileKey); }); @@ -86,10 +98,10 @@ private void buildPlayerNamesMap() { ProfileDataSource profileDataSource = profileDataSourceProvider.get(); CompletableFuture[] futures = profileDataSource.listGlobalProfileUUIDs().stream() - .map(uuid -> profileDataSource.getGlobalProfile(GlobalProfileKey.create(uuid, "")) + .map(uuid -> profileDataSource.getGlobalProfile(GlobalProfileKey.of(uuid, "")) .thenAccept(globalProfile -> setPlayerName(uuid, globalProfile.getLastKnownName()))) .toArray(CompletableFuture[]::new); - CompletableFuture.allOf(futures).thenRun(this::savePlayerNames).join(); + CompletableFuture.allOf(futures).thenCompose(ignore -> savePlayerNames()).join(); profileCacheManagerProvider.get().clearAllGlobalProfileCaches(); Logging.info("Generated player names map."); } @@ -106,7 +118,7 @@ boolean setPlayerName(UUID uuid, String name) { } Logging.finer("Setting player name mapping for %s to %s", uuid, name); - GlobalProfileKey globalProfileKey = GlobalProfileKey.create(uuid, name); + GlobalProfileKey globalProfileKey = GlobalProfileKey.of(uuid, name); // Handle remove of old playername Object oldName = playerNamesJson.put(uuid.toString(), name); @@ -117,22 +129,20 @@ boolean setPlayerName(UUID uuid, String name) { return true; } - void savePlayerNames() { + CompletableFuture savePlayerNames() { if (playerNamesJson == null) { throw new IllegalStateException("Player names mapper has not been loaded yet."); } - - Logging.finer("Saving player names map..."); - try (FileWriter fileWriter = new FileWriter(playerNamesFile)) { - fileWriter.write(JSONValue.toJSONString(playerNamesJson)); - } catch (Exception e) { - e.printStackTrace(); - } - Logging.finer("Saving player names map... Done!"); - } - - File getFile() { - return playerNamesFile; + return asyncFileIO.queueFileAction(playerNamesFile, () -> { + Logging.finer("Saving player names map..."); + try (FileWriter fileWriter = new FileWriter(playerNamesFile)) { + fileWriter.write(JSONValue.toJSONString(playerNamesJson)); + Logging.finer("Saving player names map... Done!"); + } catch (Exception e) { + Logging.severe("Could not save player names map."); + e.printStackTrace(); + } + }); } public Option getKey(String playerName) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java index cd14f66b..c136f0cc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java @@ -2,7 +2,6 @@ import com.google.common.collect.Sets; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; @@ -39,7 +38,7 @@ public List getProfileFileKeys(BulkProfilesPayload payload) { for (GlobalProfileKey globalProfileKey : payload.globalProfileKeys()) { for (ContainerKey containerKey : containerKeys) { - profileFileKeys.add(ProfileFileKey.create( + profileFileKeys.add(ProfileFileKey.of( containerKey.getContainerType(), containerKey.getDataName(), globalProfileKey.getPlayerUUID(), @@ -60,7 +59,7 @@ public List getPlayerProfileKeys(BulkProfilesPayload payload) { for (GlobalProfileKey globalProfileKey : payload.globalProfileKeys()) { for (ContainerKey containerKey : containerKeys) { for (ProfileType profileType : payload.profileTypes()) { - profileKeys.add(ProfileKey.create( + profileKeys.add(ProfileKey.of( containerKey.getContainerType(), containerKey.getDataName(), profileType, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index 8ded96f2..ae24a03b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -44,7 +44,7 @@ public ProfileKey getProfileKey(Player player) { } public ProfileKey getProfileKey(ProfileType profileType, OfflinePlayer player) { - return ProfileKey.create(getContainerType(), getContainerName(), profileType, player); + return ProfileKey.of(getContainerType(), getContainerName(), profileType, player); } public CompletableFuture getPlayerData(Player player) { @@ -75,7 +75,7 @@ public PlayerProfile getPlayerProfileNow(Player player) { * @return The profile of the given type for the given player. */ public PlayerProfile getPlayerProfileNow(ProfileType profileType, OfflinePlayer player) { - return FutureNow.get(profileDataSource.getPlayerProfile(ProfileKey.create( + return FutureNow.get(profileDataSource.getPlayerProfile(ProfileKey.of( getContainerType(), getContainerName(), profileType, @@ -89,7 +89,7 @@ public PlayerProfile getPlayerProfileNow(ProfileType profileType, OfflinePlayer * @return */ public CompletableFuture deletePlayerFile(OfflinePlayer player) { - return profileDataSource.deletePlayerFile(ProfileFileKey.create(type, name, player)); + return profileDataSource.deletePlayerFile(ProfileFileKey.of(type, name, player)); } /** @@ -100,7 +100,7 @@ public CompletableFuture deletePlayerFile(OfflinePlayer player) { * @return */ public CompletableFuture deletePlayerProfile(ProfileType profileType, OfflinePlayer player) { - return profileDataSource.deletePlayerProfile(ProfileKey.create(type, name, profileType, player)); + return profileDataSource.deletePlayerProfile(ProfileKey.of(type, name, profileType, player)); } /** diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java index 058d9cfc..382a09fe 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java @@ -5,17 +5,29 @@ import org.bukkit.entity.Player; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; import java.util.Objects; import java.util.UUID; public sealed class GlobalProfileKey permits ProfileFileKey { - public static GlobalProfileKey create(OfflinePlayer offlinePlayer) { - return create(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + /** + * Gets a GlobalProfileKey from a playerUUID. NOTE: Player name may be empty if player has never logged in before. + * + * @param playerUUID The player's UUID + * @return A GlobalProfileKey + */ + public static GlobalProfileKey of(UUID playerUUID) { + return PlayerNamesMapper.getInstance().getKey(playerUUID) + .getOrElse(() -> new GlobalProfileKey(playerUUID, "")); } - public static GlobalProfileKey create(UUID playerUUID, String playerName) { + public static GlobalProfileKey of(OfflinePlayer offlinePlayer) { + return of(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + + public static GlobalProfileKey of(UUID playerUUID, String playerName) { return new GlobalProfileKey(playerUUID, playerName); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java index e8e9d79c..50bc4620 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java @@ -1,7 +1,6 @@ package org.mvplugins.multiverse.inventories.profile.key; import com.google.common.base.Objects; -import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,28 +10,35 @@ public sealed class ProfileFileKey extends GlobalProfileKey permits ProfileKey { - public static ProfileFileKey create( + public static ProfileFileKey fromPlayerProfile(PlayerProfile profile) { + return of( + profile.getContainerType(), + profile.getContainerName(), + profile.getPlayerUUID(), + profile.getPlayerName() + ); + } + + public static ProfileFileKey of( ContainerType containerType, String dataName, - UUID playerUUID, - String playerName) { - return new ProfileFileKey(containerType, dataName, playerUUID, playerName); + GlobalProfileKey globalProfileKey) { + return of(containerType, dataName, globalProfileKey.getPlayerUUID(), globalProfileKey.getPlayerName()); } - public static ProfileFileKey create( + public static ProfileFileKey of( ContainerType containerType, String dataName, OfflinePlayer offlinePlayer) { - return new ProfileFileKey(containerType, dataName, offlinePlayer.getUniqueId(), offlinePlayer.getName()); + return of(containerType, dataName, offlinePlayer.getUniqueId(), offlinePlayer.getName()); } - public static ProfileFileKey fromPlayerProfile(PlayerProfile profile) { - return new ProfileFileKey( - profile.getContainerType(), - profile.getContainerName(), - profile.getPlayerUUID(), - profile.getPlayerName() - ); + public static ProfileFileKey of( + ContainerType containerType, + String dataName, + UUID playerUUID, + String playerName) { + return new ProfileFileKey(containerType, dataName, playerUUID, playerName); } protected final ContainerType containerType; @@ -55,7 +61,7 @@ protected ProfileFileKey(ContainerType containerType, String dataName, UUID play } public ProfileKey forProfileType(@Nullable ProfileType profileType) { - return ProfileKey.create(containerType, dataName, profileType, playerUUID, playerName); + return ProfileKey.of(containerType, dataName, profileType, playerUUID, playerName); } public ProfileFileKey forContainerType(@NotNull ContainerType containerType) { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java index b9cb1feb..64ccd42e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java @@ -3,7 +3,6 @@ import com.google.common.base.Objects; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.bukkit.Bukkit; import org.mvplugins.multiverse.inventories.profile.PlayerProfile; @@ -11,39 +10,47 @@ public final class ProfileKey extends ProfileFileKey { - public static ProfileKey create( + public static ProfileKey fromPlayerProfile(PlayerProfile profile) { + return of( + profile.getContainerType(), + profile.getContainerName(), + profile.getProfileType(), + profile.getPlayerUUID(), + profile.getPlayerName() + ); + } + + public static ProfileKey of( ContainerType containerType, String dataName, ProfileType profileType, - UUID playerUUID, - String playerName) { - return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); + UUID playerUUID) { + return of(containerType, dataName, profileType, GlobalProfileKey.of(playerUUID)); } - public static ProfileKey create( + public static ProfileKey of( ContainerType containerType, String dataName, ProfileType profileType, - OfflinePlayer offlinePlayer) { - return new ProfileKey(containerType, dataName, profileType, offlinePlayer.getUniqueId(), offlinePlayer.getName()); + GlobalProfileKey globalProfileKey) { + return of(containerType, dataName, profileType, globalProfileKey.getPlayerUUID(), globalProfileKey.getPlayerName()); } - public static ProfileKey create( + public static ProfileKey of( ContainerType containerType, String dataName, ProfileType profileType, - UUID playerUUID) { - return new ProfileKey(containerType, dataName, profileType, playerUUID, Bukkit.getOfflinePlayer(playerUUID).getName()); + OfflinePlayer offlinePlayer) { + return of(containerType, dataName, profileType, offlinePlayer.getUniqueId(), offlinePlayer.getName()); } - public static ProfileKey fromPlayerProfile(PlayerProfile profile) { - return new ProfileKey( - profile.getContainerType(), - profile.getContainerName(), - profile.getProfileType(), - profile.getPlayerUUID(), - profile.getPlayerName() - ); + public static ProfileKey of( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID, + String playerName) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); } private final ProfileType profileType; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java index 666b15a5..afc4c4d7 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -31,6 +31,17 @@ public static boolean isEmptyItemStack(@Nullable ItemStack itemStack) { return itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0; } +// /** +// * Format: `material[custom properties] amount` +// * Example: iron_sword[attribute_modifiers=[{type:"generic.attack_damage",id:"op_damage",amount:10000,operation:"add_value",slot:"mainhand"}]] 1 +// * +// * @return +// */ +// @Nullable +// public static ItemStack fromString(String item) { +// +// } + @Nullable public static ItemStack deserialize(Object obj) { if (obj instanceof ItemStack itemStack) { diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt index a3aa64a7..30f2285b 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -30,7 +30,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { player.health = 4.4 player.maxHealth = 15.1 - val playerProfileKey = ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + val playerProfileKey = ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfileKey)) val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile(playerProfileKey)) assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) @@ -40,7 +40,7 @@ class ShareHandlingUpdaterTest : TestWithMockBukkit() { @Test fun `Test updating player`() { val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId))) + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId))) playerProfile.set(Sharables.HEALTH, 4.4) playerProfile.set(Sharables.MAX_HEALTH, 15.1) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 84826cf6..3120960d 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -52,7 +52,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val startTime = System.nanoTime() val futures = ArrayList>(1000) for (i in 0..1000) { - futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.create(UUID.randomUUID(), ""), { globalProfile -> + futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.of(UUID.randomUUID(), ""), { globalProfile -> globalProfile.setLoadOnLogin(true) })) } @@ -71,7 +71,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) playerProfile.set(Sharables.HEALTH, 5.0) playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) playerProfile.set(Sharables.INVENTORY, arrayOf( @@ -101,7 +101,7 @@ class FilePerformanceTest : TestWithMockBukkit() { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { futures2.add(profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) } } for (future in futures2) { @@ -117,9 +117,9 @@ class FilePerformanceTest : TestWithMockBukkit() { val player = server.getPlayer(i) for (gameMode in GameMode.entries) { futures3.add(profileDataSource.deletePlayerProfile( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( - ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) assertNull(playerProfile.get(Sharables.HEALTH)) assertNull(playerProfile.get(Sharables.OFF_HAND)) } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt index 115945ce..9bc9cbb6 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt @@ -1,6 +1,5 @@ package org.mvplugins.multiverse.inventories.profile -import com.dumptruckman.minecraft.util.Logging import org.bukkit.Material import org.bukkit.inventory.ItemStack import org.mockbukkit.mockbukkit.entity.PlayerMock @@ -38,7 +37,7 @@ class PlayerNameChangeTest : TestWithMockBukkit() { assertTrue(worldGroupManager.load().isSuccess) player = server.addPlayer("Benji_0224") - assertEquals(GlobalProfileKey.create(player.uniqueId, "Benji_0224"), playerNamesMapper.getKey("Benji_0224").orNull) + assertEquals(GlobalProfileKey.of(player.uniqueId, "Benji_0224"), playerNamesMapper.getKey("Benji_0224").orNull) } @Test @@ -79,10 +78,10 @@ class PlayerNameChangeTest : TestWithMockBukkit() { assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "Benji_0224.json").toFile().exists()) // check player profile - assertEquals("benthecat10", FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.create(player)))?.lastKnownName) + assertEquals("benthecat10", FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(player)))?.lastKnownName) // check name mapper updated - assertEquals(GlobalProfileKey.create(player.uniqueId, "benthecat10"), playerNamesMapper.getKey("benthecat10").orNull) + assertEquals(GlobalProfileKey.of(player.uniqueId, "benthecat10"), playerNamesMapper.getKey("benthecat10").orNull) assertNull(playerNamesMapper.getKey("Benji_0224").orNull) } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt index f31ae5eb..ad99ee16 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt @@ -22,7 +22,7 @@ class ProfileDataSourceTest : TestWithMockBukkit() { fun `getPlayerData called twice`() { server.setPlayers(1) writeResourceToConfigFile("/playerdata.json", "worlds/world/Player0.json") - val key = ProfileKey.create(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, server.getPlayer("Player0")) + val key = ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, server.getPlayer("Player0")) profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } From c13fd1dd9b54322acd190f07eb76cd65438a9e3d Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:22:09 +0800 Subject: [PATCH 170/180] Add javadocs to ProfileDataSource --- .../profile/ProfileDataSource.java | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index ac17c3f2..415f4c73 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -19,12 +19,12 @@ */ @Contract public sealed interface ProfileDataSource permits FlatFileProfileDataSource { + /** - * Retrieves a PlayerProfile from the data source. + * Retrieves a PlayerProfile from the disk. If no data was found, a new PlayerProfile will be created. * * @param profileKey The key of the profile to retrieve. - * @return The player as returned from data. If no data was found, a new PlayerProfile will be - * created. + * @return The player profile data. */ CompletableFuture getPlayerProfile(ProfileKey profileKey); @@ -32,13 +32,41 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * Updates the persisted data for a player for a specific profile to disk. * * @param playerProfile The profile for the player that is being updated. + * @return Future that completes when the profile has been updated. */ CompletableFuture updatePlayerProfile(PlayerProfile playerProfile); + /** + * Clears all data of the specified profile, and deletes profile data from disk. + *
+ * This action is irreversible. + * + * @param profileKey The key of the profile to delete + * @return Future that completes when the profile has been deleted. + */ CompletableFuture deletePlayerProfile(ProfileKey profileKey); + /** + * Clears all data for multiple {@link ProfileType} of the specified profile, and deletes profile data from disk. + * If all profiles are deleted, the profile file itself will be deleted. + *
+ * This action is irreversible. + * + * @param profileKey The key of the profile to delete + * @param profileTypes The list of profile types to delete + * @return Future that completes when the profile has been deleted. + */ CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes); + /** + * Deletes the profile file for a player's profile from disk. Essentially same as + * {@link #deletePlayerProfiles(ProfileFileKey, ProfileType[])} with all profile types. + *
+ * This action is irreversible. + * + * @param profileKey The key of the profile to delete + * @return Future that completes when the profile has been deleted. + */ CompletableFuture deletePlayerFile(ProfileFileKey profileKey); /** @@ -78,11 +106,17 @@ public sealed interface ProfileDataSource permits FlatFileProfileDataSource { * Update the file for a player's global profile to disk. * * @param globalProfile The GlobalProfile object to update the file for. + * @return A CompletableFuture that completes when the global profile has been updated. */ CompletableFuture updateGlobalProfile(GlobalProfile globalProfile); - CompletableFuture deleteGlobalProfile(GlobalProfileKey key); - + /** + * Deletes the file for a player's global profile from disk. Optionally clears the player's profile data files as well. + * + * @param key The key of the player. + * @param clearPlayerFiles Whether to clear the player's profile data files as well. + * @return A CompletableFuture that completes when the global profile has been deleted. + */ CompletableFuture deleteGlobalProfile(GlobalProfileKey key, boolean clearPlayerFiles); /** From 723a8fd51ff636cd0590d2d88e8d27cf57e6b6d1 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:23:54 +0800 Subject: [PATCH 171/180] Refactor ProfileData classes to separate package --- .../multiinv/MultiInvImportHelper.java | 2 +- .../perworldinventory/PwiImportHelper.java | 2 +- .../WorldInventoriesImportHelper.java | 2 +- .../handleshare/AffectedProfiles.java | 2 - .../handleshare/PersistingProfile.java | 4 +- .../handleshare/ShareHandleListener.java | 2 +- .../inventories/handleshare/ShareHandler.java | 5 +- .../handleshare/ShareHandlingUpdater.java | 2 - .../handleshare/SingleShareWriter.java | 2 +- .../profile/FlatFileProfileDataSource.java | 44 +++----------- .../profile/PlayerProfileJsonSerializer.java | 3 +- .../profile/ProfileCacheManager.java | 1 + .../inventories/profile/ProfileData.java | 27 --------- .../profile/ProfileDataSource.java | 1 + .../action/PlayerProfileDeleteAction.java | 9 +-- .../profile/container/ProfileContainer.java | 2 +- .../profile/data/EmptyProfileData.java | 44 ++++++++++++++ .../profile/{ => data}/PlayerProfile.java | 4 +- .../inventories/profile/data/ProfileData.java | 59 +++++++++++++++++++ .../{ => data}/ProfileDataSnapshot.java | 11 +++- .../profile/data/SingleSharableData.java | 53 +++++++++++++++++ .../profile/key/ProfileFileKey.java | 2 +- .../inventories/profile/key/ProfileKey.java | 3 +- .../inventories/share/SharableHandler.java | 4 +- .../inventories/share/Sharables.java | 2 +- .../profile/FilePerformanceTest.kt | 1 + 26 files changed, 197 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ => data}/PlayerProfile.java (95%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java rename src/main/java/org/mvplugins/multiverse/inventories/profile/{ => data}/ProfileDataSnapshot.java (82%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java index 9ea2b492..9c4baf63 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportException; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java index 3f0b47c7..0ff6c039 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -25,7 +25,7 @@ import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportException; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java index fcaa1d8f..363fc9ca 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java @@ -11,7 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.dataimport.DataImportException; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java index c609949a..2ab8e55d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -1,12 +1,10 @@ package org.mvplugins.multiverse.inventories.handleshare; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.share.Shares; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.CompletableFuture; public final class AffectedProfiles { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java index 71efd955..2bf96b2a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java @@ -1,11 +1,9 @@ package org.mvplugins.multiverse.inventories.handleshare; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.share.Shares; -import java.util.concurrent.CompletableFuture; - /** * Simple class for groups that are going to be saved/loaded. This is used specifically for when a user's world * change is being handled. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java index 8d9c8d04..0a2f2f41 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -15,7 +15,7 @@ import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.GlobalProfile; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.share.Sharables; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 5ef2ee4b..e2137649 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -4,7 +4,7 @@ import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSnapshot; +import org.mvplugins.multiverse.inventories.profile.data.ProfileDataSnapshot; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; @@ -13,9 +13,6 @@ import org.mvplugins.multiverse.inventories.share.Sharables; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.mvplugins.multiverse.inventories.util.FutureNow; - -import java.util.concurrent.CompletableFuture; /** * Abstract class for handling sharing of data between worlds and game modes. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java index 292d5ea8..9439f55c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -3,7 +3,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.share.Sharable; import org.bukkit.entity.Player; @@ -12,7 +11,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; public final class ShareHandlingUpdater { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java index 23866b55..1d0c3c9e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -5,7 +5,7 @@ import org.bukkit.entity.Player; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java index d4b3f1b9..1b20796a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -9,6 +9,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; @@ -17,7 +18,6 @@ import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; -import org.mvplugins.multiverse.inventories.util.DataStrings; import java.io.File; import java.io.IOException; @@ -87,7 +87,7 @@ public CompletableFuture getPlayerProfile(ProfileKey profileKey) File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); if (!playerFile.exists()) { Logging.fine("Not found on disk: %s", playerFile); - return CompletableFuture.completedFuture(PlayerProfile.createPlayerProfile(key)); + return CompletableFuture.completedFuture(PlayerProfile.newProfile(key)); } Logging.finer("%s not cached. loading from disk...", profileKey); return asyncFileIO.queueFileCallable(playerFile, () -> getPlayerProfileFromDisk(key, playerFile)); @@ -101,38 +101,13 @@ public CompletableFuture getPlayerProfile(ProfileKey profileKey) private PlayerProfile getPlayerProfileFromDisk(ProfileKey key, File playerFile) { FileConfiguration playerData = getOrLoadPlayerProfileFile(key, playerFile); - - // Migrate from none profile-type data - if (migrateToProfileType(playerData)) { - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + key.getPlayerName() - + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); - e.printStackTrace(); - } - } - ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); - if (section == null) { - section = playerData.createSection(key.getProfileType().getName()); + if (section == null || section.getKeys(false).isEmpty()) { + return PlayerProfile.newProfile(key); } return PlayerProfileJsonSerializer.deserialize(key, convertSection(section)); } - private boolean migrateToProfileType(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection(DataStrings.PLAYER_DATA); - if (section == null) { - return false; - } - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set(DataStrings.PLAYER_DATA, null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; - } - private Map convertSection(ConfigurationSection section) { Set keys = section.getKeys(false); Map resultMap = new HashMap<>(keys.size()); @@ -188,6 +163,9 @@ public CompletableFuture deletePlayerProfile(ProfileKey profileKey) { deletePlayerProfileFromDisk(profileKey, playerFile, new ProfileType[]{profileKey.getProfileType()})); } + /** + * {@inheritDoc} + */ @Override public CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes) { if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { @@ -358,14 +336,6 @@ private void processGlobalProfileWrite(GlobalProfile globalProfile) { }); } - /** - * {@inheritDoc} - */ - @Override - public CompletableFuture deleteGlobalProfile(GlobalProfileKey key) { - return deleteGlobalProfile(key, true); - } - /** * {@inheritDoc} */ diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java index 53d8543e..73c5d075 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java @@ -4,6 +4,7 @@ import net.minidev.json.JSONObject; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.share.ProfileEntry; import org.mvplugins.multiverse.inventories.share.Sharable; @@ -49,7 +50,7 @@ static Map serialize(PlayerProfile playerProfile) { } static PlayerProfile deserialize(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey); + PlayerProfile profile = PlayerProfile.newProfile(pKey); for (Object keyObj : playerData.keySet()) { String key = keyObj.toString(); final Object value = playerData.get(key); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java index 0a3d411b..1c2b5814 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java @@ -11,6 +11,7 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import java.util.HashMap; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java deleted file mode 100644 index 6da935a8..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileData.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.mvplugins.multiverse.inventories.profile; - -import org.mvplugins.multiverse.inventories.share.Sharable; - -import java.util.Map; - -public interface ProfileData { - /** - * Retrieves the profile's value of the {@link Sharable} passed in. - * - * @param sharable Represents the key for the data wanted from the profile. - * @param This indicates the type of return value to be expected. - * @return The value of the sharable for this profile. Null if no value is set. - */ - T get(Sharable sharable); - - /** - * Sets the profile's value for the {@link Sharable} passed in. - * - * @param sharable Represents the key for the data to store. - * @param value The value of the data. - * @param The type of value to be expected. - */ - void set(Sharable sharable, T value); - - Map getData(); -} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java index 415f4c73..c77bbe1d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -2,6 +2,7 @@ import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java index 74285ce9..8e33ecb1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java @@ -4,8 +4,10 @@ import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; -import org.mvplugins.multiverse.inventories.profile.ProfileDataSnapshot; +import org.mvplugins.multiverse.inventories.profile.data.ProfileData; +import org.mvplugins.multiverse.inventories.profile.data.ProfileDataSnapshot; import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.data.SingleSharableData; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; @@ -69,8 +71,7 @@ protected boolean isOnlinePlayerAffected(ProfileKey key, Player player) { @Override protected void updateOnlinePlayerNow(Player player) { - ProfileDataSnapshot profileDataSnapshot = new ProfileDataSnapshot(); - profileDataSnapshot.set(sharable, FutureNow.get(SingleShareReader.of(inventories, player, sharable).read())); - sharable.getHandler().updatePlayer(player, profileDataSnapshot); + ProfileData sharableData = new SingleSharableData<>(sharable, FutureNow.get(SingleShareReader.of(inventories, player, sharable).read())); + sharable.getHandler().updatePlayer(player, sharableData); } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java index ae24a03b..3ed7eccf 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -7,7 +7,7 @@ import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java new file mode 100644 index 00000000..ff9d101f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java @@ -0,0 +1,44 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.Map; + +final class EmptyProfileData implements ProfileData { + private static final EmptyProfileData INSTANCE = new EmptyProfileData(); + + static EmptyProfileData getInstance() { + return INSTANCE; + } + + @Override + public T get(Sharable sharable) { + return null; + } + + @Override + public void set(Sharable sharable, T value) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getData() { + return Map.of(); + } + + @Override + public void update(ProfileData snapshot) { + throw new UnsupportedOperationException(); + } + + @Override + public void update(ProfileData snapshot, Shares shares) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/PlayerProfile.java similarity index 95% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/data/PlayerProfile.java index c452aec5..be47b959 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/PlayerProfile.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.profile.data; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; @@ -11,7 +11,7 @@ */ public final class PlayerProfile extends ProfileDataSnapshot { - static PlayerProfile createPlayerProfile(ProfileKey profileKey) { + public static PlayerProfile newProfile(ProfileKey profileKey) { return new PlayerProfile( profileKey.getContainerType(), profileKey.getDataName(), diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java new file mode 100644 index 00000000..bede58c1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java @@ -0,0 +1,59 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.Map; + +public interface ProfileData { + + /** + * Gets an immutable empty profile data constant. + * + * @return An empty profile data. + */ + static ProfileData empty() { + return EmptyProfileData.getInstance(); + } + + /** + * Retrieves the profile's value of the {@link Sharable} passed in. + * + * @param sharable Represents the key for the data wanted from the profile. + * @param This indicates the type of return value to be expected. + * @return The value of the sharable for this profile. Null if no value is set. + */ + T get(Sharable sharable); + + /** + * Sets the profile's value for the {@link Sharable} passed in. + * + * @param sharable Represents the key for the data to store. + * @param value The value of the data. + * @param The type of value to be expected. + */ + void set(Sharable sharable, T value); + + Map getData(); + + /** + * Updates this profile with the data from another profile data. + * + * @param snapshot The snapshot data to update from. + */ + void update(ProfileData snapshot); + + /** + * Updates this profile with the data from another profile data for a specific set of {@link Sharable}s. + * @param snapshot The snapshot data to update from. + * @param shares The set of {@link Sharable}s to update. + */ + void update(ProfileData snapshot, Shares shares); + + /** + * Checks whether this profile contains any data. + * + * @return True if the profile is empty. + */ + boolean isEmpty(); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileDataSnapshot.java similarity index 82% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileDataSnapshot.java index 7a9167cb..a3f900be 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSnapshot.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileDataSnapshot.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.profile; +package org.mvplugins.multiverse.inventories.profile.data; import org.mvplugins.multiverse.inventories.share.Sharable; import org.mvplugins.multiverse.inventories.share.Sharables; @@ -7,7 +7,7 @@ import java.util.HashMap; import java.util.Map; -public class ProfileDataSnapshot implements Cloneable, ProfileData { +public sealed class ProfileDataSnapshot implements Cloneable, ProfileData permits PlayerProfile { private final Map data; @@ -30,10 +30,12 @@ public Map getData() { return data; } + @Override public void update(ProfileData snapshot) { this.data.putAll(snapshot.getData()); } + @Override public void update(ProfileData snapshot, Shares shares) { shares.forEach(sharable -> { Object data = snapshot.getData().get(sharable); @@ -43,6 +45,11 @@ public void update(ProfileData snapshot, Shares shares) { }); } + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + @Override public ProfileDataSnapshot clone() { try { diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java new file mode 100644 index 00000000..3d93288e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.Map; + +public final class SingleSharableData implements ProfileData { + + private final Sharable sharable; + private T value; + + public SingleSharableData(Sharable sharable, T value) { + this.sharable = sharable; + this.value = value; + } + + @Override + public S get(Sharable sharable) { + return sharable.equals(this.sharable) ? (S) this.value : null; + } + + @Override + public void set(Sharable sharable, S value) { + if (sharable.equals(this.sharable)) { + this.value = (T) value; + } + } + + @Override + public Map getData() { + return Map.of(sharable, value); + } + + @Override + public void update(ProfileData snapshot) { + if (snapshot.get(sharable) != null) { + this.value = snapshot.get(sharable); + } + } + + @Override + public void update(ProfileData snapshot, Shares shares) { + if (shares.contains(sharable)) { + this.value = snapshot.get(sharable); + } + } + + @Override + public boolean isEmpty() { + return value == null; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java index 50bc4620..47537f8a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java @@ -4,7 +4,7 @@ import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import java.util.UUID; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java index 64ccd42e..557f02dc 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java @@ -3,8 +3,7 @@ import com.google.common.base.Objects; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; -import org.bukkit.Bukkit; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import java.util.UUID; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java index bf4a342b..925bd4f6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.inventories.share; -import org.mvplugins.multiverse.inventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.bukkit.entity.Player; -import org.mvplugins.multiverse.inventories.profile.ProfileData; +import org.mvplugins.multiverse.inventories.profile.data.ProfileData; /** * This class is used to handle the transition of data from a player profile to a player and vice versa, typically diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 7869d888..2fdb2ee1 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -8,7 +8,7 @@ import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.config.InventoriesConfig; -import org.mvplugins.multiverse.inventories.profile.ProfileData; +import org.mvplugins.multiverse.inventories.profile.data.ProfileData; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.util.DataStrings; diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt index 3120960d..2a604235 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -12,6 +12,7 @@ import org.mvplugins.multiverse.core.utils.CoreLogging import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile import org.mvplugins.multiverse.inventories.profile.key.ContainerType import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey import org.mvplugins.multiverse.inventories.profile.key.ProfileKey From 4bcee54e630ad081d68c45b7379d966ac5fa4099 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:50:21 +0800 Subject: [PATCH 172/180] Implement MultiverseInventoriesApi --- .../inventories/MultiverseInventories.java | 4 + .../inventories/MultiverseInventoriesApi.java | 124 ++++++++++++++++++ .../profile/PlayerNamesMapper.java | 5 +- .../profile/group/WorldGroupManager.java | 1 - 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index fcf6d545..f4f233da 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -123,6 +123,9 @@ public final void onEnable() { this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); this.playerNamesMapperProvider.get().loadMap(); + // Init api + MultiverseInventoriesApi.init(this.serviceLocator); + Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(this.getDescription().getAuthors())); } @@ -143,6 +146,7 @@ public void onDisable() { } } + MultiverseInventoriesApi.shutdown(); this.dupingPatch.disable(); this.shutdownDependencyInjection(); Logging.shutdown(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java new file mode 100644 index 00000000..46c32213 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java @@ -0,0 +1,124 @@ +package org.mvplugins.multiverse.inventories; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.inject.PluginServiceLocator; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.Objects; + +/** + * Provides access to the Multiverse-Inventories API. + */ +public final class MultiverseInventoriesApi { + + private static MultiverseInventoriesApi instance; + + static void init(@NotNull PluginServiceLocator serviceLocator) { + if (instance != null) { + throw new IllegalStateException("MultiverseCoreApi has already been initialized!"); + } + instance = new MultiverseInventoriesApi(serviceLocator); + } + + static void shutdown() { + instance = null; + } + + /** + * Gets the MultiverseInventoriesApi. This will throw an exception if the Multiverse-Inventories has not been initialized. + * + * @return The MultiverseInventoriesApi + */ + public static @NotNull MultiverseInventoriesApi get() { + if (instance == null) { + throw new IllegalStateException("MultiverseInventoriesApi has not been initialized!"); + } + return instance; + } + + private final PluginServiceLocator serviceLocator; + + private MultiverseInventoriesApi(@NotNull PluginServiceLocator serviceProvider) { + this.serviceLocator = serviceProvider; + } + + /** + * Gets instance of our DataImportManager api. + * + * @return The DataImportManager instance + */ + public @NotNull DataImportManager getDataImportManager() { + return Objects.requireNonNull(serviceLocator.getService(DataImportManager.class)); + } + + /** + * Gets instance of our InventoriesConfig api. + * + * @return The InventoriesConfig instance + */ + public @NotNull InventoriesConfig getInventoriesConfig() { + return Objects.requireNonNull(serviceLocator.getService(InventoriesConfig.class)); + } + + /** + * Gets instance of our PlayerNamesMapper api. + * + * @return The PlayerNamesMapper instance + */ + public @NotNull PlayerNamesMapper getPlayerNamesMapper() { + return Objects.requireNonNull(serviceLocator.getService(PlayerNamesMapper.class)); + } + + /** + * Gets instance of our ProfileCacheManager api. + * + * @return The ProfileCacheManager instance + */ + public @NotNull ProfileCacheManager getProfileCacheManager() { + return Objects.requireNonNull(serviceLocator.getService(ProfileCacheManager.class)); + } + + /** + * Gets instance of our ProfileContainerStoreProvider api. + * + * @return The ProfileContainerStoreProvider instance + */ + public @NotNull ProfileContainerStoreProvider getProfileContainerStoreProvider() { + return Objects.requireNonNull(serviceLocator.getService(ProfileContainerStoreProvider.class)); + } + + /** + * Gets instance of our ProfileDataSource api. + * + * @return The ProfileDataSource instance + */ + public @NotNull ProfileDataSource getProfileDataSource() { + return Objects.requireNonNull(serviceLocator.getService(ProfileDataSource.class)); + } + + /** + * Gets instance of our WorldGroupManager api. + * + * @return The WorldGroupManager instance + */ + public @NotNull WorldGroupManager getWorldGroupManager() { + return Objects.requireNonNull(serviceLocator.getService(WorldGroupManager.class)); + } + + /** + * Gets the instance of Multiverse-Inventories's PluginServiceLocator. + *
+ * You can use this to hook into the hk2 dependency injection system used by Multiverse-Inventories. + * + * @return The Multiverse-Inventories's PluginServiceLocator + */ + public @NotNull PluginServiceLocator getServiceLocator() { + return serviceLocator; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java index 7601f167..f6397134 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java @@ -29,7 +29,10 @@ public final class PlayerNamesMapper { private static PlayerNamesMapper instance; public static PlayerNamesMapper getInstance() { - return Objects.requireNonNull(instance); + if (instance == null) { + throw new IllegalStateException("Player names mapper has not been initialized yet."); + } + return instance; } private static final String FILENAME = "playernames.json"; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index a75f8365..0f488fb4 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -115,4 +115,3 @@ public sealed interface WorldGroupManager permits AbstractWorldGroupManager { */ void recalculateApplicableShares(); } - From 83d9675f205ea9fde4d56b1816174b2a3d43a921 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:36:53 +0800 Subject: [PATCH 173/180] Refactor bulkedit api to expose less classes, and implement BulkEditCreator --- .../commands/bulkedit/BulkEditCommand.java | 15 ++++++++-- .../bulkedit/playerprofile/ClearCommand.java | 16 +++++------ .../bulkedit/playerprofile/DeleteCommand.java | 20 ++++++------- .../bulkedit/{action => }/BulkEditAction.java | 4 +-- .../profile/bulkedit/BulkEditCreator.java | 28 +++++++++++++++++++ .../bulkedit/{action => }/BulkEditResult.java | 4 ++- .../{action => }/PlayerFileAction.java | 15 ++++------ .../{action => }/PlayerProfileAction.java | 15 ++++------ .../PlayerProfileClearAction.java | 9 ++---- .../PlayerProfileDeleteAction.java | 9 ++---- ...tor.java => PlayerProfilesAggregator.java} | 9 +++--- ...ayload.java => PlayerProfilesPayload.java} | 8 +++--- .../profile/bulkedit/action/package-info.java | 4 --- 13 files changed, 88 insertions(+), 68 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{action => }/BulkEditAction.java (93%) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{action => }/BulkEditResult.java (90%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{action => }/PlayerFileAction.java (53%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{action => }/PlayerProfileAction.java (58%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{action => }/PlayerProfileClearAction.java (88%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{action => }/PlayerProfileDeleteAction.java (88%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{BulkProfilesAggregator.java => PlayerProfilesAggregator.java} (92%) rename src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/{BulkProfilesPayload.java => PlayerProfilesPayload.java} (82%) delete mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java index 8bfe8a54..e071d9ed 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java @@ -1,15 +1,26 @@ package org.mvplugins.multiverse.inventories.commands.bulkedit; import org.jetbrains.annotations.ApiStatus; +import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; -import org.mvplugins.multiverse.inventories.profile.bulkedit.action.BulkEditAction; -import org.mvplugins.multiverse.inventories.profile.bulkedit.action.BulkEditResult; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditResult; +@Contract @ApiStatus.Internal public abstract class BulkEditCommand extends InventoriesCommand { + protected final BulkEditCreator bulkEditCreator; + + @Inject + protected BulkEditCommand(BulkEditCreator bulkEditCreator) { + this.bulkEditCreator = bulkEditCreator; + } + protected void outputActionSummary(MVCommandIssuer issuer, BulkEditAction bulkEditAction) { issuer.sendMessage("Summary of affected profiles:"); bulkEditAction.getActionSummary().forEach((key, value) -> diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java index 19978982..ee32445e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java @@ -12,10 +12,10 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; -import org.mvplugins.multiverse.inventories.profile.bulkedit.action.PlayerProfileClearAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.PlayerProfilesPayload; import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; @@ -23,17 +23,16 @@ @Service final class ClearCommand extends BulkEditCommand { - private final MultiverseInventories inventories; private final CommandQueueManager commandQueueManager; private final IncludeGroupsWorldsFlag flags; @Inject ClearCommand( - @NotNull MultiverseInventories inventories, + @NotNull BulkEditCreator bulkEditCreator, @NotNull CommandQueueManager commandQueueManager, @NotNull IncludeGroupsWorldsFlag flags ) { - this.inventories = inventories; + super(bulkEditCreator); this.commandQueueManager = commandQueueManager; this.flags = flags; } @@ -51,9 +50,8 @@ void onCommand( ) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); - PlayerProfileClearAction bulkEditAction = new PlayerProfileClearAction( - inventories, - new BulkProfilesPayload( + BulkEditAction bulkEditAction = bulkEditCreator.playerProfileClear( + new PlayerProfilesPayload( globalProfileKeys, containerKeys, profileTypes, diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java index 276fa4b7..06de6550 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -13,10 +13,10 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; -import org.mvplugins.multiverse.inventories.profile.bulkedit.action.PlayerProfileDeleteAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.PlayerProfilesPayload; import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; @@ -25,17 +25,16 @@ @Service final class DeleteCommand extends BulkEditCommand { - private final MultiverseInventories inventories; private final CommandQueueManager commandQueueManager; private final IncludeGroupsWorldsFlag flags; @Inject DeleteCommand( - @NotNull MultiverseInventories inventories, + @NotNull BulkEditCreator bulkEditCreator, @NotNull CommandQueueManager commandQueueManager, @NotNull IncludeGroupsWorldsFlag flags ) { - this.inventories = inventories; + super(bulkEditCreator); this.commandQueueManager = commandQueueManager; this.flags = flags; } @@ -54,15 +53,14 @@ void onCommand( ) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); - PlayerProfileDeleteAction bulkEditAction = new PlayerProfileDeleteAction( - inventories, - sharable, - new BulkProfilesPayload( + BulkEditAction bulkEditAction = bulkEditCreator.playerProfileDeleteSharable( + new PlayerProfilesPayload( globalProfileKeys, containerKeys, profileTypes, parsedFlags.hasFlag(flags.includeGroupsWorlds) - ) + ), + sharable ); issuer.sendMessage("Summary of affected profiles:"); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java similarity index 93% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java index be435ba4..1d03ee80 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; +package org.mvplugins.multiverse.inventories.profile.bulkedit; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -15,7 +15,7 @@ import java.util.concurrent.CompletableFuture; @ApiStatus.Experimental -public abstract class BulkEditAction { +public sealed abstract class BulkEditAction permits PlayerFileAction, PlayerProfileAction { protected final MultiverseInventories inventories; protected final ProfileDataSource profileDataSource; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java new file mode 100644 index 00000000..812131e9 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.share.Sharable; + +@Service +@ApiStatus.Experimental +public final class BulkEditCreator { + + private final MultiverseInventories inventories; + + @Inject + BulkEditCreator(@NotNull MultiverseInventories inventories) { + this.inventories = inventories; + } + + public BulkEditAction playerProfileClear(PlayerProfilesPayload bulkProfilesPayload) { + return new PlayerProfileClearAction(inventories, bulkProfilesPayload); + } + + public BulkEditAction playerProfileDeleteSharable(PlayerProfilesPayload bulkProfilesPayload, Sharable sharable) { + return new PlayerProfileDeleteAction(inventories, sharable, bulkProfilesPayload); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditResult.java similarity index 90% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditResult.java index 97bdf270..3985aeaa 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/BulkEditResult.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditResult.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; +package org.mvplugins.multiverse.inventories.profile.bulkedit; import org.jetbrains.annotations.ApiStatus; @@ -11,6 +11,8 @@ public final class BulkEditResult { private final AtomicInteger successCount = new AtomicInteger(0); private final AtomicInteger failureCount = new AtomicInteger(0); + BulkEditResult() { } + void incrementSuccess() { successCount.incrementAndGet(); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerFileAction.java similarity index 53% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerFileAction.java index c4499ff4..94a24e18 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerFileAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerFileAction.java @@ -1,23 +1,20 @@ -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; +package org.mvplugins.multiverse.inventories.profile.bulkedit; import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesAggregator; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import java.util.List; import java.util.Map; -@ApiStatus.Experimental -public abstract class PlayerFileAction extends BulkEditAction { +abstract sealed class PlayerFileAction extends BulkEditAction permits PlayerProfileClearAction { - private final BulkProfilesAggregator profilesAggregator; - protected final BulkProfilesPayload bulkProfilesPayload; + private final PlayerProfilesAggregator profilesAggregator; + protected final PlayerProfilesPayload bulkProfilesPayload; - PlayerFileAction(MultiverseInventories inventories, BulkProfilesPayload bulkProfilesPayload) { + PlayerFileAction(MultiverseInventories inventories, PlayerProfilesPayload bulkProfilesPayload) { super(inventories, bulkProfilesPayload.globalProfileKeys()); - this.profilesAggregator = inventories.getServiceLocator().getService(BulkProfilesAggregator.class); + this.profilesAggregator = inventories.getServiceLocator().getService(PlayerProfilesAggregator.class); this.bulkProfilesPayload = bulkProfilesPayload; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileAction.java similarity index 58% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileAction.java index 156d8526..86825eb6 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileAction.java @@ -1,26 +1,23 @@ -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; +package org.mvplugins.multiverse.inventories.profile.bulkedit; import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.inventories.MultiverseInventories; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesAggregator; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; import java.util.List; import java.util.Map; -@ApiStatus.Experimental -public abstract class PlayerProfileAction extends BulkEditAction { +abstract sealed class PlayerProfileAction extends BulkEditAction permits PlayerProfileDeleteAction { - private final BulkProfilesAggregator profilesAggregator; - private final BulkProfilesPayload bulkProfilesPayload; + private final PlayerProfilesAggregator profilesAggregator; + private final PlayerProfilesPayload bulkProfilesPayload; protected PlayerProfileAction( MultiverseInventories inventories, - BulkProfilesPayload bulkProfilesPayload + PlayerProfilesPayload bulkProfilesPayload ) { super(inventories, bulkProfilesPayload.globalProfileKeys()); - this.profilesAggregator = inventories.getServiceLocator().getService(BulkProfilesAggregator.class); + this.profilesAggregator = inventories.getServiceLocator().getService(PlayerProfilesAggregator.class); this.bulkProfilesPayload = bulkProfilesPayload; } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileClearAction.java similarity index 88% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileClearAction.java index feef9357..45d44acb 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileClearAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileClearAction.java @@ -1,14 +1,12 @@ -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; +package org.mvplugins.multiverse.inventories.profile.bulkedit; import org.bukkit.entity.Player; import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.handleshare.ReadOnlyShareHandler; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; import org.mvplugins.multiverse.inventories.profile.key.ContainerType; -import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; import org.mvplugins.multiverse.inventories.profile.key.ProfileType; import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; @@ -19,13 +17,12 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; -@ApiStatus.Experimental -public final class PlayerProfileClearAction extends PlayerFileAction { +final class PlayerProfileClearAction extends PlayerFileAction { private final WorldGroupManager worldGroupManager; private final Set profileTypesSet; - public PlayerProfileClearAction(MultiverseInventories inventories, BulkProfilesPayload bulkProfilesPayload) { + public PlayerProfileClearAction(MultiverseInventories inventories, PlayerProfilesPayload bulkProfilesPayload) { super(inventories, bulkProfilesPayload); this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); this.profileTypesSet = Set.of(bulkProfilesPayload.profileTypes()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileDeleteAction.java similarity index 88% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileDeleteAction.java index 8e33ecb1..79c44a44 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/PlayerProfileDeleteAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileDeleteAction.java @@ -1,12 +1,10 @@ -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; +package org.mvplugins.multiverse.inventories.profile.bulkedit; import org.bukkit.entity.Player; import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.inventories.MultiverseInventories; import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; import org.mvplugins.multiverse.inventories.profile.data.ProfileData; -import org.mvplugins.multiverse.inventories.profile.data.ProfileDataSnapshot; -import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkProfilesPayload; import org.mvplugins.multiverse.inventories.profile.data.SingleSharableData; import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; @@ -19,8 +17,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -@ApiStatus.Experimental -public final class PlayerProfileDeleteAction extends PlayerProfileAction { +final class PlayerProfileDeleteAction extends PlayerProfileAction { private final WorldGroupManager worldGroupManager; @@ -29,7 +26,7 @@ public final class PlayerProfileDeleteAction extends PlayerProfileAction { public PlayerProfileDeleteAction( MultiverseInventories inventories, Sharable sharable, - BulkProfilesPayload bulkProfilesPayload + PlayerProfilesPayload bulkProfilesPayload ) { super(inventories, bulkProfilesPayload); this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesAggregator.java similarity index 92% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesAggregator.java index c136f0cc..6a2b856b 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesAggregator.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesAggregator.java @@ -17,18 +17,17 @@ import java.util.List; import java.util.Set; -@ApiStatus.Experimental @Service -public final class BulkProfilesAggregator { +final class PlayerProfilesAggregator { private final WorldGroupManager worldGroupManager; @Inject - BulkProfilesAggregator(WorldGroupManager worldGroupManager) { + PlayerProfilesAggregator(WorldGroupManager worldGroupManager) { this.worldGroupManager = worldGroupManager; } - public List getProfileFileKeys(BulkProfilesPayload payload) { + List getProfileFileKeys(PlayerProfilesPayload payload) { var containerKeys = payload.includeGroupsWorlds() ? includeGroupsWorlds(payload.containerKeys()) : payload.containerKeys(); @@ -48,7 +47,7 @@ public List getProfileFileKeys(BulkProfilesPayload payload) { return profileFileKeys; } - public List getPlayerProfileKeys(BulkProfilesPayload payload) { + List getPlayerProfileKeys(PlayerProfilesPayload payload) { var containerKeys = payload.includeGroupsWorlds() ? includeGroupsWorlds(payload.containerKeys()) : payload.containerKeys(); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesPayload.java similarity index 82% rename from src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesPayload.java index 6773c10b..1ee22426 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkProfilesPayload.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesPayload.java @@ -12,10 +12,10 @@ import java.util.Map; @ApiStatus.Experimental -public record BulkProfilesPayload(@NotNull GlobalProfileKey[] globalProfileKeys, - @NotNull ContainerKey[] containerKeys, - @NotNull ProfileType[] profileTypes, - boolean includeGroupsWorlds) { +public record PlayerProfilesPayload(@NotNull GlobalProfileKey[] globalProfileKeys, + @NotNull ContainerKey[] containerKeys, + @NotNull ProfileType[] profileTypes, + boolean includeGroupsWorlds) { public Map> getSummary() { return Map.of( diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java deleted file mode 100644 index d17b3875..00000000 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/action/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@ApiStatus.Experimental -package org.mvplugins.multiverse.inventories.profile.bulkedit.action; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file From f136dcc26b047290243a728ee6425d1496d7c9cc Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:06:50 +0800 Subject: [PATCH 174/180] Implement global profile clear as a BulkEditAction --- .../bulkedit/globalprofile/ClearCommand.java | 33 ++++++++-------- .../bulkedit/playerprofile/DeleteCommand.java | 4 +- .../profile/bulkedit/BulkEditAction.java | 2 +- .../profile/bulkedit/BulkEditCreator.java | 5 +++ .../bulkedit/GlobalProfileClearAction.java | 39 +++++++++++++++++++ 5 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java index c737315a..7be2c960 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java @@ -16,34 +16,36 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import java.util.Arrays; import java.util.concurrent.CompletableFuture; @Service -final class ClearCommand extends InventoriesCommand { +final class ClearCommand extends BulkEditCommand { private final CommandQueueManager commandQueueManager; - private final ProfileDataSource profileDataSource; private final Flags flags; @Inject ClearCommand( + @NotNull BulkEditCreator bulkEditCreator, @NotNull CommandQueueManager commandQueueManager, - @NotNull ProfileDataSource profileDataSource, @NotNull Flags flags ) { + super(bulkEditCreator); this.commandQueueManager = commandQueueManager; - this.profileDataSource = profileDataSource; this.flags = flags; } @Subcommand("bulkedit globalprofile clear") @CommandPermission("multiverse.inventories.bulkedit") @CommandCompletion("@mvinvplayernames @flags:groupName=" + Flags.NAME) - @Syntax(" [--clear-all-playerprofiles]") + @Syntax(" [--clear-all-player-profiles]") void onCommand( MVCommandIssuer issuer, @@ -55,19 +57,16 @@ void onCommand( ) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); - commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) - .prompt(Message.of("Are you sure you want to clear %d profiles?".formatted(globalProfileKeys.length))) - .action(() -> doClear(issuer, globalProfileKeys, parsedFlags.hasFlag(flags.clearAllPlayerprofiles)))); - } + BulkEditAction bulkEditAction = bulkEditCreator.globalProfileClear( + globalProfileKeys, + parsedFlags.hasFlag(flags.clearAllPlayerProfiles) + ); - private void doClear(MVCommandIssuer issuer, GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfile) { - //TODO: Check lastWorld and online - CompletableFuture[] futures = Arrays.stream(globalProfileKeys) - .map(globalProfileKey -> profileDataSource.deleteGlobalProfile(globalProfileKey, clearPlayerProfile)) - .toArray(CompletableFuture[]::new); + outputActionSummary(issuer, bulkEditAction); - CompletableFuture.allOf(futures) - .thenRun(() -> issuer.sendMessage("Successfully cleared %d profiles.".formatted(globalProfileKeys.length))); + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to clear the selected global profiles?")) + .action(() -> runBulkEditAction(issuer, bulkEditAction))); } @Service @@ -79,7 +78,7 @@ private Flags(@NotNull CommandFlagsManager flagsManager) { super(NAME, flagsManager); } - private final CommandFlag clearAllPlayerprofiles = flag(CommandFlag.builder("--clear-all-playerprofiles") + private final CommandFlag clearAllPlayerProfiles = flag(CommandFlag.builder("--clear-all-player-profiles") .addAlias("-a") .build()); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java index 06de6550..7308e7be 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -63,9 +63,7 @@ void onCommand( sharable ); - issuer.sendMessage("Summary of affected profiles:"); - bulkEditAction.getActionSummary().forEach((key, value) -> - issuer.sendMessage(" %s: %s".formatted(key, value.size() > 10 ? value.size() : StringFormatter.join(value, ", ")))); + outputActionSummary(issuer, bulkEditAction); commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) .prompt(Message.of("Are you sure you want to delete %s from the selected profiles?".formatted(sharable.getNames()[0]))) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java index 1d03ee80..9f840a7f 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java @@ -15,7 +15,7 @@ import java.util.concurrent.CompletableFuture; @ApiStatus.Experimental -public sealed abstract class BulkEditAction permits PlayerFileAction, PlayerProfileAction { +public sealed abstract class BulkEditAction permits GlobalProfileClearAction, PlayerFileAction, PlayerProfileAction { protected final MultiverseInventories inventories; protected final ProfileDataSource profileDataSource; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java index 812131e9..851bd8af 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java @@ -5,6 +5,7 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; import org.mvplugins.multiverse.inventories.share.Sharable; @Service @@ -18,6 +19,10 @@ public final class BulkEditCreator { this.inventories = inventories; } + public BulkEditAction globalProfileClear(GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfiles) { + return new GlobalProfileClearAction(inventories, globalProfileKeys, clearPlayerProfiles); + } + public BulkEditAction playerProfileClear(PlayerProfilesPayload bulkProfilesPayload) { return new PlayerProfileClearAction(inventories, bulkProfilesPayload); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java new file mode 100644 index 00000000..363a2c62 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.ReadOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +final class GlobalProfileClearAction extends BulkEditAction { + + private final boolean clearPlayerProfile; + + GlobalProfileClearAction(MultiverseInventories inventories, GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfiles) { + super(inventories, globalProfileKeys); + this.clearPlayerProfile = clearPlayerProfiles; + } + + @Override + protected List aggregateKeys() { + return List.of(globalProfileKeys); + } + + @Override + protected CompletableFuture performAction(GlobalProfileKey key) { + return profileDataSource.deleteGlobalProfile(key, clearPlayerProfile); + } + + @Override + protected boolean isOnlinePlayerAffected(GlobalProfileKey key, Player player) { + return super.isOnlinePlayerAffected(key, player) && clearPlayerProfile; + } + + @Override + protected void updateOnlinePlayerNow(Player player) { + new ReadOnlyShareHandler(inventories, player).handleSharing(); + } +} From e0b73cbce1599dc649f5a5e6f88bb356d5308ac7 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:12:42 +0800 Subject: [PATCH 175/180] Add more tests for share handling --- .../handleshare/GameModeShareHandler.java | 4 +- .../inventories/share/Sharables.java | 23 +-- .../inventories/TestWithMockBukkit.kt | 13 ++ .../handleshare/GameModeChangeTest.kt | 160 ++++++++++++++---- .../handleshare/WorldChangeTest.kt | 101 ++++++++++- .../gameplay/world_change_groups.yml | 5 + 6 files changed, 252 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java index 5b48d3fd..fc0b1ae2 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -82,7 +82,9 @@ private boolean isPlayerBypassingChange() { private void addProfiles() { Shares handledShares = Sharables.noneOf(); worldGroups.forEach(worldGroup -> addProfilesForWorldGroup(handledShares,worldGroup)); - Shares unhandledShares = Sharables.enabledOf().setSharing(handledShares, false); + Shares unhandledShares = (worldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standardOf() : Sharables.enabledOf(); + unhandledShares.removeAll(handledShares); if (!unhandledShares.isEmpty()) { affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getProfileKey(toType, player), unhandledShares); } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 2fdb2ee1..dc94c24a 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -74,22 +74,15 @@ public final class Sharables implements Shares { * @param inventories the instance of Inventories. */ public static void init(MultiverseInventories inventories) { - if (Sharables.inventories == null) { - Sharables.inventories = inventories; - } - if (Sharables.economist == null) { - Sharables.economist = inventories.getServiceLocator().getService(MVEconomist.class); - } - if (Sharables.safetyTeleporter == null) { - Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); - } - if (Sharables.inventoriesConfig == null) { - Sharables.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); - } - if (Sharables.worldGroupManager == null) { - Sharables.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); - } + Sharables.inventories = inventories; + Sharables.economist = inventories.getServiceLocator().getService(MVEconomist.class); + Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); + Sharables.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); + Sharables.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + initMaxHealthAttr(); + } + private static void initMaxHealthAttr() { Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); if (Sharables.maxHealthAttr == null) { // Old key for older minecraft version (<1.21) diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt index 5eae4579..fe6ba014 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -1,9 +1,11 @@ package org.mvplugins.multiverse.inventories +import com.dumptruckman.minecraft.util.Logging import org.bukkit.Location import org.bukkit.configuration.MemorySection import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.configuration.serialization.ConfigurationSerialization +import org.bukkit.inventory.ItemStack import org.mockbukkit.mockbukkit.MockBukkit import org.mockbukkit.mockbukkit.inventory.ItemStackMock import org.mvplugins.multiverse.core.MultiverseCore @@ -16,6 +18,7 @@ import kotlin.io.path.absolutePathString import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -46,6 +49,7 @@ abstract class TestWithMockBukkit { server.pluginManager.disablePlugin(multiverseInventories) server.pluginManager.disablePlugin(multiverseCore) MockBukkit.unmock() + Logging.warning("Unmocked") } fun getResourceAsText(path: String): String? = object {}.javaClass.getResource(path)?.readText() @@ -98,4 +102,13 @@ abstract class TestWithMockBukkit { assertEquals(expected?.yaw, actual?.yaw, "Yaw values don't match for location comparison ($expected, $actual)") assertEquals(expected?.pitch, actual?.pitch, "Pitch values don't match for location comparison ($expected, $actual)") } + + fun assertInventoryEquals(expected: Array, actual: Array) { + for (i in expected.indices) { + if (expected[i]?.isEmpty ?: true && expected[i]?.isEmpty ?: true) { + continue + } + assertEquals(expected[i], actual[i]) + } + } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt index 3a554f15..1003cd3c 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt @@ -1,64 +1,162 @@ package org.mvplugins.multiverse.inventories.handleshare +import org.bukkit.Bukkit +import org.bukkit.Location import org.bukkit.Material import org.bukkit.inventory.ItemStack -import org.junit.jupiter.api.Assertions.assertEquals import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.share.Sharables +import org.mvplugins.multiverse.inventories.util.PlayerStats +import kotlin.arrayOfNulls import kotlin.test.BeforeTest import kotlin.test.Test -import kotlin.test.assertNotEquals +import kotlin.test.assertEquals import kotlin.test.assertTrue class GameModeChangeTest : TestWithMockBukkit() { + private lateinit var inventoriesConfig: InventoriesConfig + private var survivalItems = arrayOfNulls(PlayerStats.INVENTORY_SIZE) + private var creativeItems = arrayOfNulls(PlayerStats.INVENTORY_SIZE) + @BeforeTest fun setUp() { + inventoriesConfig = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") + } val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { throw IllegalStateException("WorldManager is not available as a service") } - assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) - val inventoriesConfig = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { - throw IllegalStateException("InventoriesConfig is not available as a service") - } + writeResourceToConfigFile("/gameplay/gamemode_change_groups.yml", "groups.yml") multiverseInventories.reloadConfig() - inventoriesConfig.setEnableGamemodeShareHandling(true) + inventoriesConfig.enableGamemodeShareHandling = true + + survivalItems[0] = ItemStack.of(Material.STONE_BRICKS, 64) + survivalItems[1] = ItemStack.of(Material.SAND, 64) + + creativeItems[0] = ItemStack.of(Material.HOPPER, 64) + creativeItems[1] = ItemStack.of(Material.CAMPFIRE, 64) + + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("ungrouped")).isSuccess) + } + + @Test + fun `Test change game mode for grouped worlds with total_xp disabled share`() { + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(Bukkit.getWorld("world")!!.spawnLocation)) + + player.inventory.contents = survivalItems.clone() + player.totalExperience = 123 + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertEquals(123, player.totalExperience) + + player.inventory.contents = creativeItems.clone() + player.totalExperience = 321 + + player.gameMode = org.bukkit.GameMode.SURVIVAL + Thread.sleep(5) + assertInventoryEquals(survivalItems, player.inventory.contents) + assertEquals(321, player.totalExperience) + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(creativeItems, player.inventory.contents) + assertEquals(321, player.totalExperience) } @Test - fun `Test change game mode`() { + fun `Test change game mode for ungrouped worlds`() { val player = server.addPlayer("Benji_0224") - val survivalItems = arrayOf( - ItemStack.of(Material.STONE_BRICKS, 64), - ItemStack.of(Material.SAND, 64), - ) - player.inventory.contents = survivalItems + assertTrue(player.teleport(Bukkit.getWorld("ungrouped")!!.spawnLocation)) + + player.inventory.contents = survivalItems.clone() + player.totalExperience = 123 player.gameMode = org.bukkit.GameMode.CREATIVE - Thread.sleep(10) - assertNotEquals(survivalItems[0], player.inventory.getItem(0)) - assertNotEquals(survivalItems[1], player.inventory.getItem(1)) - val creativeItems = arrayOf( - ItemStack.of(Material.OBSIDIAN, 64), - ItemStack.of(Material.HOPPER, 64), - ) - player.inventory.contents = creativeItems + Thread.sleep(5) + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertEquals(0, player.totalExperience) + + player.inventory.contents = creativeItems.clone() + player.totalExperience = 321 + + player.gameMode = org.bukkit.GameMode.SURVIVAL + Thread.sleep(5) + assertInventoryEquals(survivalItems, player.inventory.contents) + assertEquals(123, player.totalExperience) + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(creativeItems, player.inventory.contents) + assertEquals(321, player.totalExperience) + } + + @Test + fun `Test change game mode for ungrouped worlds with last_location`() { + inventoriesConfig.defaultUngroupedWorlds = false + inventoriesConfig.useOptionalsForUngroupedWorlds = true + inventoriesConfig.activeOptionalShares = Sharables.fromSharables(Sharables.LAST_LOCATION) + + val survivalLocation = Location(Bukkit.getWorld("ungrouped")!!, 1.0, 2.0, 3.0) + val creativeLocation = Location(Bukkit.getWorld("ungrouped")!!, 4.0, 5.0, 6.0) + + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(survivalLocation.clone())) + player.inventory.contents = survivalItems.clone() + + player.gameMode = org.bukkit.GameMode.CREATIVE + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertLocationEquals(survivalLocation, player.location) + + assertTrue(player.teleport(creativeLocation.clone())) + player.inventory.contents = creativeItems.clone() + + player.gameMode = org.bukkit.GameMode.SURVIVAL + assertInventoryEquals(survivalItems, player.inventory.contents) + assertLocationEquals(survivalLocation, player.location) + + player.gameMode = org.bukkit.GameMode.CREATIVE + assertInventoryEquals(creativeItems, player.inventory.contents) + assertLocationEquals(creativeLocation, player.location) + } + + @Test + fun `Test change game mode for ungrouped worlds with useOptionalsForUngroupedWorlds disabled`() { + inventoriesConfig.defaultUngroupedWorlds = false + inventoriesConfig.useOptionalsForUngroupedWorlds = false + inventoriesConfig.activeOptionalShares = Sharables.fromSharables(Sharables.LAST_LOCATION) + + val survivalLocation = Location(Bukkit.getWorld("ungrouped")!!, 1.0, 2.0, 3.0) + val creativeLocation = Location(Bukkit.getWorld("ungrouped")!!, 4.0, 5.0, 6.0) + + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(survivalLocation.clone())) + player.inventory.contents = survivalItems.clone() + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertLocationEquals(survivalLocation, player.location) + + assertTrue(player.teleport(creativeLocation.clone())) + player.inventory.contents = creativeItems.clone() player.gameMode = org.bukkit.GameMode.SURVIVAL - Thread.sleep(10) - assertEquals(survivalItems[0], player.inventory.getItem(0)) - assertEquals(survivalItems[1], player.inventory.getItem(1)) - assertNotEquals(creativeItems[0], player.inventory.getItem(0)) - assertNotEquals(creativeItems[1], player.inventory.getItem(1)) + Thread.sleep(5) + assertInventoryEquals(survivalItems, player.inventory.contents) + assertLocationEquals(creativeLocation, player.location) player.gameMode = org.bukkit.GameMode.CREATIVE - Thread.sleep(10) - assertEquals(creativeItems[0], player.inventory.getItem(0)) - assertEquals(creativeItems[1], player.inventory.getItem(1)) - assertNotEquals(survivalItems[0], player.inventory.getItem(0)) - assertNotEquals(survivalItems[1], player.inventory.getItem(1)) + Thread.sleep(5) + assertInventoryEquals(creativeItems, player.inventory.contents) + assertLocationEquals(creativeLocation, player.location) } } diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt index 6d619da0..97b816bd 100644 --- a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt @@ -1,37 +1,48 @@ package org.mvplugins.multiverse.inventories.handleshare -import com.dumptruckman.minecraft.util.Logging +import org.bukkit.Bukkit +import org.bukkit.Location import org.bukkit.Material import org.bukkit.inventory.ItemStack import org.mvplugins.multiverse.core.world.WorldManager import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.share.Sharables import kotlin.test.* class WorldChangeTest : TestWithMockBukkit() { + private lateinit var inventoriesConfig: InventoriesConfig + @BeforeTest fun setUp() { + inventoriesConfig = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") + } val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { throw IllegalStateException("WorldManager is not available as a service") } + + writeResourceToConfigFile("/gameplay/world_change_groups.yml", "groups.yml") + multiverseInventories.reloadConfig() + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world1")).isSuccess) assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world3")).isSuccess) assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world4")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("ungrouped")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("default")).isSuccess) } @Test - fun `Shares updates correctly on world change`() { - writeResourceToConfigFile("/gameplay/world_change_groups.yml", "groups.yml") - multiverseInventories.reloadConfig() + fun `World change between groups`() { val player = server.addPlayer("Benji_0224") - Logging.fine("player world: " + server.getPlayer("Benji_0224")?.world?.name) + server.getWorld("world1")?.let { player.teleport(it.spawnLocation) } val stack = ItemStack.of(Material.STONE_BRICKS, 64) player.inventory.setItem(0, stack) player.health = 5.5 player.totalExperience = 10 - val startTime = System.nanoTime() server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } assertEquals(stack, player.inventory.getItem(0)) assertEquals(5.5, player.health) @@ -46,7 +57,83 @@ class WorldChangeTest : TestWithMockBukkit() { assertEquals(stack, player.inventory.getItem(0)) assertEquals(5.5, player.health) assertEquals(player.totalExperience, 0) + } + + @Test + fun `World change between group and ungrouped worlds`() { + val player = server.addPlayer("Benji_0224") + + server.getWorld("world3")?.let { player.teleport(it.spawnLocation) } + val stack = ItemStack.of(Material.STONE_BRICKS, 64) + player.inventory.setItem(0, stack) + player.health = 5.5 + player.totalExperience = 10 + + server.getWorld("ungrouped")?.let { player.teleport(it.spawnLocation) } + assertEquals(ItemStack.empty(), player.inventory.getItem(0)) + assertEquals(20.0, player.health) + assertEquals(0, player.totalExperience) + + val stack2 = ItemStack.of(Material.HOPPER, 23) + player.inventory.setItem(0, stack2) + player.health = 2.5 + player.totalExperience = 5 + + server.getWorld("world3")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + + server.getWorld("ungrouped")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack2, player.inventory.getItem(0)) + assertEquals(2.5, player.health) + assertEquals(5, player.totalExperience) + } + + @Test + fun `World change with defaultUngroupedWorlds enabled`() { + inventoriesConfig.defaultUngroupedWorlds = true + inventoriesConfig.save() + + val player = server.addPlayer("Benji_0224") + + server.getWorld("default")?.let { player.teleport(it.spawnLocation) } + val stack = ItemStack.of(Material.WATER_BUCKET, 64) + player.inventory.setItem(0, stack) + player.health = 5.5 + player.totalExperience = 10 + + server.getWorld("ungrouped")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + + server.getWorld("default")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + } + + @Test + fun `World change with last_location optional share`() { + inventoriesConfig.activeOptionalShares = Sharables.fromSharables(Sharables.LAST_LOCATION) + + val world1Loc = Location(Bukkit.getWorld("world1")!!, 1.0, 2.0, 3.0) + val world2Loc = Location(Bukkit.getWorld("world2")!!, 4.0, 5.0, 6.0) + + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(world1Loc.clone())) + assertLocationEquals(world1Loc, player.location) + + player.teleport(Bukkit.getWorld("world2")!!.spawnLocation.clone()) + assertLocationEquals(Bukkit.getWorld("world2")!!.spawnLocation, player.location) + player.teleport(world2Loc.clone()) + assertLocationEquals(world2Loc, player.location) + + player.teleport(Bukkit.getWorld("world1")!!.spawnLocation.clone()) + assertLocationEquals(world1Loc, player.location) - Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + player.teleport(Bukkit.getWorld("world2")!!.spawnLocation.clone()) + assertLocationEquals(world2Loc, player.location) } } diff --git a/src/test/resources/gameplay/world_change_groups.yml b/src/test/resources/gameplay/world_change_groups.yml index e0faea3f..7a53b916 100644 --- a/src/test/resources/gameplay/world_change_groups.yml +++ b/src/test/resources/gameplay/world_change_groups.yml @@ -1,4 +1,9 @@ groups: + default: + worlds: + - default + shares: + - all group1: worlds: - world1 From 50545ee6e409685fade7e336bfb4816c16cff7b3 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:14:42 +0800 Subject: [PATCH 176/180] Rename import command to migrate command --- ...ImportCommand.java => MigrateCommand.java} | 20 +++++++++---------- .../inventories/util/MVInvi18n.java | 10 +++++----- .../multiverse-inventories_en.properties | 12 +++++------ 3 files changed, 20 insertions(+), 22 deletions(-) rename src/main/java/org/mvplugins/multiverse/inventories/commands/{ImportCommand.java => MigrateCommand.java} (75%) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java similarity index 75% rename from src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java rename to src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java index 0a62ff2b..42d233d0 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java @@ -3,11 +3,9 @@ import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; import org.mvplugins.multiverse.core.locale.message.Message; -import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; @@ -22,13 +20,13 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service -final class ImportCommand extends InventoriesCommand { +final class MigrateCommand extends InventoriesCommand { private final DataImportManager dataImportManager; private final CommandQueueManager commandQueueManager; @Inject - ImportCommand( + MigrateCommand( @NotNull DataImportManager dataImportManager, @NotNull CommandQueueManager commandQueueManager ) { @@ -36,12 +34,12 @@ final class ImportCommand extends InventoriesCommand { this.commandQueueManager = commandQueueManager; } - @Subcommand("import") + @Subcommand("migrate") @Syntax("") @CommandPermission("multiverse.inventories.import") @CommandCompletion("@dataimporters") @Description("Import inventories from MultiInv/WorldInventories/PerWorldInventory plugin.") - void onImportCommand( + void onMigrateCommand( MVCommandIssuer issuer, @Single @@ -49,23 +47,23 @@ void onImportCommand( String pluginName) { dataImportManager.getImporter(pluginName) - .onEmpty(() -> issuer.sendError(MVInvi18n.IMPORT_UNSUPPORTEDPLUGIN, replace("{plugin}").with(pluginName))) + .onEmpty(() -> issuer.sendError(MVInvi18n.MIGRATE_UNSUPPORTEDPLUGIN, replace("{plugin}").with(pluginName))) .peek(dataImporter -> { if (!dataImporter.isEnabled()) { - issuer.sendError(MVInvi18n.IMPORT_PLUGINNOTENABLED, replace("{plugin}").with(pluginName)); + issuer.sendError(MVInvi18n.MIGRATE_PLUGINNOTENABLED, replace("{plugin}").with(pluginName)); return; } commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) - .prompt(Message.of(MVInvi18n.IMPORT_CONFIRMPROMPT, replace("{plugin}").with(pluginName))) + .prompt(Message.of(MVInvi18n.MIGRATE_CONFIRMPROMPT, replace("{plugin}").with(pluginName))) .action(() -> doDataImport(issuer, dataImporter))); }); } void doDataImport(MVCommandIssuer issuer, DataImporter dataImporter) { if (dataImporter.importData()) { - issuer.sendInfo(MVInvi18n.IMPORT_SUCCESS, replace("{plugin}").with(dataImporter.getPluginName())); + issuer.sendInfo(MVInvi18n.MIGRATE_SUCCESS, replace("{plugin}").with(dataImporter.getPluginName())); } else { - issuer.sendError(MVInvi18n.IMPORT_FAILED, replace("{plugin}").with(dataImporter.getPluginName())); + issuer.sendError(MVInvi18n.MIGRATE_FAILED, replace("{plugin}").with(dataImporter.getPluginName())); } } } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java index 2a2b707b..7e1dd403 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java @@ -88,11 +88,11 @@ public enum MVInvi18n implements MessageKeyProvider { GROUP_NONCONVERSABLE, GROUP_INVALIDOPTION, - IMPORT_PLUGINNOTENABLED, - IMPORT_UNSUPPORTEDPLUGIN, - IMPORT_CONFIRMPROMPT, - IMPORT_SUCCESS, - IMPORT_FAILED, + MIGRATE_PLUGINNOTENABLED, + MIGRATE_UNSUPPORTEDPLUGIN, + MIGRATE_CONFIRMPROMPT, + MIGRATE_SUCCESS, + MIGRATE_FAILED, DELETEGROUP_CONFIRMPROMPT, DELETEGROUP_SUCCESS, diff --git a/src/main/resources/multiverse-inventories_en.properties b/src/main/resources/multiverse-inventories_en.properties index cf197e1a..a2f4553f 100644 --- a/src/main/resources/multiverse-inventories_en.properties +++ b/src/main/resources/multiverse-inventories_en.properties @@ -91,12 +91,12 @@ mv-inventories.group.updated=&2Group has been updated! mv-inventories.group.nonconversable=You are not allowed to access conversations (remote console?) mv-inventories.group.invalidoption=&cThat is not a valid option! Type &f##&c to stop working on groups. -# Import command -mv-inventories.import.pluginnotenabled=&f{plugin} &6is not enabled so you may not import data from it! -mv-inventories.import.unsupportedplugin=&6Sorry, ''&f{plugin}&6'' is not supported for importing. -mv-inventories.import.confirmprompt=&6Are you sure you want to import data from &f{plugin}&6? This will override existing Multiverse-Inventories playerdata!!! -mv-inventories.import.success=&2Successfully imported data from &f{plugin}! -mv-inventories.import.failed=Failed to import data from &f{plugin}! +# Migrate command +mv-inventories.migrate.pluginnotenabled=&f{plugin} &6is not enabled so you may not import data from it! +mv-inventories.migrate.unsupportedplugin=&6Sorry, ''&f{plugin}&6'' is not supported for importing. +mv-inventories.migrate.confirmprompt=&6Are you sure you want to import data from &f{plugin}&6? This will override existing Multiverse-Inventories playerdata!!! +mv-inventories.migrate.success=&2Successfully imported data from &f{plugin}! +mv-inventories.migrate.failed=Failed to import data from &f{plugin}! # Deletegroup command mv-inventories.deletegroup.confirmprompt=Are you sure you want to delete group &f{group}&6? From f7b7f1dbd1a3ddcde8c301b3ee9d4fcb2718a89f Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:20:18 +0800 Subject: [PATCH 177/180] Improve error message of global profile load/save --- .../multiverse/inventories/profile/GlobalProfile.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java index 9df1f525..0c2ab559 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.inventories.profile; +import com.dumptruckman.minecraft.util.Logging; import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; @@ -31,15 +32,19 @@ public final class GlobalProfile { this.nodes = new Nodes(); this.handle = JsonConfigurationHandle.builder(configPath, nodes.nodes).build(); this.stringPropertyHandle = new StringPropertyHandle(handle); - this.handle.load(); + load(); } Try load() { - return handle.load(); + return handle.load().onFailure(e -> { + Logging.severe("Failed to load global profile for player %s: %s", uuid, e.getMessage()); + }); } Try save() { - return handle.save(); + return handle.save().onFailure(e -> { + Logging.severe("Failed to save global profile for player %s: %s", uuid, e.getMessage()); + }); } public StringPropertyHandle getStringPropertyHandle() { From 4dcbaff68a8800331dc844abd1294820a3ba52d4 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:25:44 +0800 Subject: [PATCH 178/180] Change migrate command permissions --- .../multiverse/inventories/commands/MigrateCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java index 42d233d0..62965a1c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java @@ -36,7 +36,7 @@ final class MigrateCommand extends InventoriesCommand { @Subcommand("migrate") @Syntax("") - @CommandPermission("multiverse.inventories.import") + @CommandPermission("multiverse.inventories.migrate") @CommandCompletion("@dataimporters") @Description("Import inventories from MultiInv/WorldInventories/PerWorldInventory plugin.") void onMigrateCommand( From da4000b2b7503ca052c664308d5565ad7931ed4b Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:26:06 +0800 Subject: [PATCH 179/180] Bump multiverse gradle plugin version --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2e5f862d..127b7194 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ plugins { id 'checkstyle' - id 'org.mvplugins.multiverse-plugin' version '1.0.2' - id 'org.mvplugins.kotlin-test-only' version '1.0.2' + id 'org.mvplugins.multiverse-plugin' version '1.1.0' } group = 'org.mvplugins.multiverse.inventories' From 0733654f002112057439e3dcd5c878294884fd12 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:27:44 +0800 Subject: [PATCH 180/180] A cool new banner! --- README.md | 10 ++++------ config/multiverse-banner.png | Bin 0 -> 177458 bytes 2 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 config/multiverse-banner.png diff --git a/README.md b/README.md index 4ff8241e..9a730db7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@

-Multiverse Logo +Multiverse Logo

-# Multiverse Inventories - [![Modrinth](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/modrinth_vector.svg)](https://modrinth.com/plugin/multiverse-inventories) [![Hangar](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/hangar_vector.svg)](https://hangar.papermc.io/Multiverse/Multiverse-Inventories) [![Bukkit](https://raw.githubusercontent.com/intergrav/devins-badges/refs/heads/v3/assets/cozy/available/bukkit_vector.svg)](https://dev.bukkit.org/projects/multiverse-inventories) @@ -17,11 +15,11 @@ [Multiverse Inventories](https://dev.bukkit.org/projects/multiverse-inventories) is an add-on Plugin for [Multiverse core](https://dev.bukkit.org/projects/multiverse-core) that lets players have separate inventories **per world!** This makes it possible to create multi gamemode servers without Velocity or Bungee! -Now it's time to create your very own server with Multiverse Inventories, do check out our [Wiki](https://github.com/Multiverse/Multiverse-Core/wiki/Home-(Inventories)) and [Usage Guide](https://github.com/Multiverse/Multiverse-Core/wiki/Basics-(Inventories)) to get started. Feel free to hop onto our [Discord](https://discord.gg/NZtfKky) if you have any questions or just want to have a chat with us! +Now it's time to create your very own server with Multiverse Inventories, do check out our [Wiki](https://mvplugins.org) and [Usage Guide](https://mvplugins.org/inventories/fundamentals/basic-usage/) to get started. Feel free to hop onto our [Discord](https://discord.gg/NZtfKky) if you have any questions or just want to have a chat with us! ## Our other amazing sub-modules: -With just [Multiverse Core](https://github.com/multiverse/multiverse) and any of the below plugins, you can access all of these other related features in the Multiverse ecosystem. +With just [Multiverse Core](https://github.com/multiverse/multiverse-core) and any of the below plugins, you can access all of these other related features in the Multiverse ecosystem. * [Multiverse-NetherPortals](https://github.com/Multiverse/Multiverse-NetherPortals) -> Have separate nether and end worlds for each of your overworlds! * [Multiverse-Portals](https://github.com/Multiverse/Multiverse-Portals) -> Make custom portals to go to any destination! @@ -38,7 +36,7 @@ Simply build the source with Gradle: **Want to help improve Multiverse Inventories?** There are several ways you can support and contribute to the project. * Take a look at our "Bug: Unconfirmed" issues, where you can find issues that need extra testing and investigation. * Want others to love Multiverse too? You can join the [Multiverse Discord community](https://discord.gg/NZtfKky) and help others with issues and setup! -* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/Multiverse-Core/wiki) with your latest tips, tricks and guides! The wiki open for all to edit and improve. +* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/multiverse-web) with your latest tips, tricks and guides! The wiki open for all to edit and improve. * Love coding? You could look at ["State: Open to PR"](https://github.com/Multiverse/Multiverse-Inventories/labels/State%3A%20Open%20to%20PR) and ["Resolution: Accepted"](https://github.com/Multiverse/Multiverse-Inventories/labels/Resolution%3A%20Accepted) issues. We're always happy to receive bug fixes and feature additions as [pull requests](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3/). * If you'd like to make a financial contribution to the project, do consider joining our [Patreon](https://www.patreon.com/dumptruckman) or make a one-time donation [here](https://paypal.me/dumptruckman)! diff --git a/config/multiverse-banner.png b/config/multiverse-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..7587742080d1fa4923c3c9b301aa99fcb785c58a GIT binary patch literal 177458 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

j}n`ZY@wJ=_$gAO#7bapNR3U3k-?*PG2Wjt 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 P00000NkvXXu0mjfZ`)qU literal 0 HcmV?d00001