diff --git a/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java b/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java index 87385267..1f2c2653 100644 --- a/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java +++ b/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java @@ -368,7 +368,7 @@ public class MultiverseCore extends JavaPlugin implements MVCore { // TODO: Make this all Try return configProvider.get().save().isSuccess() && worldManagerProvider.get().saveWorldsConfig() - && anchorManagerProvider.get().saveAnchors(); + && anchorManagerProvider.get().saveAnchors().isSuccess(); } /** diff --git a/src/main/java/org/mvplugins/multiverse/core/anchor/AnchorManager.java b/src/main/java/org/mvplugins/multiverse/core/anchor/AnchorManager.java index 84c9f31c..72b7a4eb 100644 --- a/src/main/java/org/mvplugins/multiverse/core/anchor/AnchorManager.java +++ b/src/main/java/org/mvplugins/multiverse/core/anchor/AnchorManager.java @@ -16,126 +16,126 @@ import java.util.Map; import java.util.Set; import com.dumptruckman.minecraft.util.Logging; +import io.vavr.control.Try; import jakarta.inject.Inject; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.MultiverseCore; import org.mvplugins.multiverse.core.api.LocationManipulation; -import org.mvplugins.multiverse.core.config.MVCoreConfig; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider; /** * Manages anchors. */ @Service public class AnchorManager { - private Map anchors; - private FileConfiguration anchorConfig; + private static final String ANCHORS_SECTION_NAME = "anchors"; + private static final String ANCHORS_FILE_NAME = "anchors.yml"; - private final Plugin plugin; + private final Map anchors; + private final File anchorsFile; + private final FileConfiguration anchorConfig; private final LocationManipulation locationManipulation; - private final MVCoreConfig config; + private final WorldManager worldManager; + private final WorldEntryCheckerProvider worldEntryCheckerProvider; @Inject - public AnchorManager( - MultiverseCore plugin, - LocationManipulation locationManipulation, - MVCoreConfig config - ) { - this.plugin = plugin; + AnchorManager( + @NotNull MultiverseCore plugin, + @NotNull LocationManipulation locationManipulation, + @NotNull WorldManager worldManager, + @NotNull WorldEntryCheckerProvider worldEntryCheckerProvider) { + this.anchors = new HashMap<>(); + this.anchorsFile = new File(plugin.getDataFolder(), ANCHORS_FILE_NAME); + this.anchorConfig = new YamlConfiguration(); this.locationManipulation = locationManipulation; - this.config = config; - - this.anchors = new HashMap(); + this.worldManager = worldManager; + this.worldEntryCheckerProvider = worldEntryCheckerProvider; } /** * Loads all anchors. + * + * @return Empty {@link Try} if all anchors were successfully loaded, or the exception if an error occurred. */ - public void loadAnchors() { - this.anchors = new HashMap(); - this.anchorConfig = YamlConfiguration.loadConfiguration(new File(this.plugin.getDataFolder(), "anchors.yml")); - this.ensureConfigIsPrepared(); - ConfigurationSection anchorsSection = this.anchorConfig.getConfigurationSection("anchors"); - Set anchorKeys = anchorsSection.getKeys(false); - for (String key : anchorKeys) { - //world:x,y,z:pitch:yaw - Location anchorLocation = this.locationManipulation.stringToLocation(anchorsSection.getString(key, "")); - if (anchorLocation != null) { - Logging.config("Loading anchor: '%s'...", key); + public Try loadAnchors() { + return Try.run(() -> { + anchors.clear(); + anchorConfig.load(anchorsFile); + ConfigurationSection anchorsSection = getAnchorConfigSection(); + Set anchorKeys = anchorsSection.getKeys(false); + anchorKeys.forEach(key -> { + //world:x,y,z:pitch:yaw + String locationString = anchorsSection.getString(key, ""); + Location anchorLocation = this.locationManipulation.stringToLocation(locationString); + if (anchorLocation == null) { + Logging.warning("The location for anchor '%s' is INVALID.", key); + return; + } this.anchors.put(key, anchorLocation); - } else { - Logging.warning("The location for anchor '%s' is INVALID.", key); - } - - } - } - - private void ensureConfigIsPrepared() { - if (this.anchorConfig.getConfigurationSection("anchors") == null) { - this.anchorConfig.createSection("anchors"); - } + }); + }); } /** * Saves all anchors. + * * @return True if all anchors were successfully saved. */ - public boolean saveAnchors() { - try { - this.anchorConfig.save(new File(this.plugin.getDataFolder(), "anchors.yml")); - return true; - } catch (IOException e) { - Logging.severe("Failed to save anchors.yml. Please check your file permissions."); - return false; - } + public Try saveAnchors() { + return Try.run(() -> anchorConfig.save(anchorsFile)); } /** * Gets the {@link Location} associated with an anchor. - * @param anchor The name of the anchor. + * + * @param anchorName The name of the anchor. * @return The {@link Location}. */ - public Location getAnchorLocation(String anchor) { - if (this.anchors.containsKey(anchor)) { - return this.anchors.get(anchor); - } - return null; + public Location getAnchorLocation(@Nullable String anchorName) { + return this.anchors.getOrDefault(anchorName, null); } /** * Saves an anchor. - * @param anchor The name of the anchor. - * @param location The location of the anchor as string. - * @return True if the anchor was successfully saved. + * + * @param anchorName The name of the anchor. + * @param locationString The location of the anchor as string. + * @return Empty {@link Try} if all anchors were successfully loaded, or the exception if an error occurred. */ - public boolean saveAnchorLocation(String anchor, String location) { - Location parsed = this.locationManipulation.stringToLocation(location); - return parsed != null && this.saveAnchorLocation(anchor, parsed); + public Try saveAnchorLocation(String anchorName, String locationString) { + Location parsed = this.locationManipulation.stringToLocation(locationString); + if (parsed == null) { + return Try.failure(new IOException("Invalid location string: " + locationString)); + } + return this.saveAnchorLocation(anchorName, parsed).map(ignore -> getAnchorLocation(anchorName)); } /** * Saves an anchor. - * @param anchor The name of the anchor. - * @param l The {@link Location} of the anchor. - * @return True if the anchor was successfully saved. + * + * @param anchorName The name of the anchor. + * @param location The {@link Location} of the anchor. + * @return Empty {@link Try} if all anchors were successfully loaded, or the exception if an error occurred. */ - public boolean saveAnchorLocation(String anchor, Location l) { - if (l == null) { - return false; - } - this.anchorConfig.set("anchors." + anchor, this.locationManipulation.locationToString(l)); - this.anchors.put(anchor, l); - return this.saveAnchors(); + public Try saveAnchorLocation(@NotNull String anchorName, @NotNull Location location) { + getAnchorConfigSection().set(anchorName, this.locationManipulation.locationToString(location)); + this.anchors.put(anchorName, location); + return saveAnchors(); } /** * Gets all anchors. + * * @return An unmodifiable {@link Set} containing all anchors. */ public Set getAllAnchors() { @@ -144,46 +144,61 @@ public class AnchorManager { /** * Gets all anchors that the specified {@link Player} can access. - * @param p The {@link Player}. + * + * @param player The {@link Player}. * @return An unmodifiable {@link Set} containing all anchors the specified {@link Player} can access. */ - public Set getAnchors(Player p) { - if (p == null) { - return this.anchors.keySet(); + public Set getAnchors(@Nullable Player player) { + if (player == null) { + return getAllAnchors(); } - Set myAnchors = new HashSet(); - for (String anchor : this.anchors.keySet()) { - Location ancLoc = this.anchors.get(anchor); - if (ancLoc == null) { - continue; + + Set myAnchors = new HashSet<>(); + + this.anchors.forEach((anchorName, anchorLocation) -> { + World anchorWorld = anchorLocation.getWorld(); + if (anchorWorld == null) { + return; } - String worldPerm = "multiverse.access." + ancLoc.getWorld().getName(); - // Add to the list if we're not enforcing access - // OR - // We are enforcing access and the user has the permission. - if (!config.getEnforceAccess() || - (config.getEnforceAccess() && p.hasPermission(worldPerm))) { - myAnchors.add(anchor); - } else { - Logging.finer(String.format("Not adding anchor %s to the list, user %s doesn't have the %s " + - "permission and 'enforceaccess' is enabled!", - anchor, p.getName(), worldPerm)); - } - } - return Collections.unmodifiableSet(myAnchors); + + worldManager.getWorld(anchorWorld) + .map(world -> worldEntryCheckerProvider.forSender(player).canAccessWorld(world).isSuccess()) + .peek(success -> { + if (success) { + myAnchors.add(anchorName); + } else { + Logging.finer("Player '%s' cannot access anchor '%s'.", + player.getName(), anchorName); + } + }) + .onEmpty(() -> { + Logging.finer("Anchor '%s' located in world '%s is not in a Multiverse world.", + anchorName, anchorLocation.getWorld().getName()); + }); + }); + + return myAnchors; } /** * Deletes the specified anchor. - * @param s The name of the anchor. + * + * @param anchorName The name of the anchor. * @return True if the anchor was successfully deleted. */ - public boolean deleteAnchor(String s) { - if (this.anchors.containsKey(s)) { - this.anchors.remove(s); - this.anchorConfig.set("anchors." + s, null); + public Try deleteAnchor(String anchorName) { + if (this.anchors.containsKey(anchorName)) { + this.anchors.remove(anchorName); + getAnchorConfigSection().set(anchorName, null); return this.saveAnchors(); } - return false; + return Try.failure(new Exception("Anchor " + anchorName + " not found.")); + } + + private ConfigurationSection getAnchorConfigSection() { + ConfigurationSection anchorsSection = anchorConfig.getConfigurationSection(ANCHORS_SECTION_NAME); + return anchorsSection == null + ? anchorConfig.createSection(ANCHORS_SECTION_NAME) + : anchorsSection; } } diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/AnchorDeleteCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/AnchorDeleteCommand.java new file mode 100644 index 00000000..0b9b2c80 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/commands/AnchorDeleteCommand.java @@ -0,0 +1,45 @@ +package org.mvplugins.multiverse.core.commands; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import jakarta.inject.Inject; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; + +import org.mvplugins.multiverse.core.anchor.AnchorManager; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; + +@Service +@CommandAlias("mv") +class AnchorDeleteCommand extends MultiverseCommand { + + private final AnchorManager anchorManager; + + @Inject + AnchorDeleteCommand(@NotNull MVCommandManager commandManager, @NotNull AnchorManager anchorManager) { + super(commandManager); + this.anchorManager = anchorManager; + } + + @Subcommand("anchor delete") + @CommandPermission("multiverse.core.anchor.delete") + @CommandCompletion("@anchornames") + @Syntax("") + @Description("") + void onAnchorDeleteCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("") + String anchorName) { + anchorManager.deleteAnchor(anchorName) + .onSuccess(ignore -> issuer.sendMessage("&aAnchor &f'" + anchorName + "&a deleted.")) + .onFailure(e -> issuer.sendMessage("&cFailed to delete anchor. " + e.getMessage())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/AnchorListCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/AnchorListCommand.java new file mode 100644 index 00000000..e96981b6 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/commands/AnchorListCommand.java @@ -0,0 +1,103 @@ +package org.mvplugins.multiverse.core.commands; + +import java.util.List; + +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import jakarta.inject.Inject; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; + +import org.mvplugins.multiverse.core.anchor.AnchorManager; +import org.mvplugins.multiverse.core.api.LocationManipulation; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; +import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag; +import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags; +import org.mvplugins.multiverse.core.display.ContentDisplay; +import org.mvplugins.multiverse.core.display.filters.ContentFilter; +import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter; +import org.mvplugins.multiverse.core.display.filters.RegexContentFilter; +import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler; +import org.mvplugins.multiverse.core.display.parsers.ListContentProvider; + +@Service +@CommandAlias("mv") +class AnchorListCommand extends MultiverseCommand { + + private final AnchorManager anchorManager; + private final LocationManipulation locationManipulation; + + private final CommandValueFlag PAGE_FLAG = flag(CommandValueFlag + .builder("--page", Integer.class) + .addAlias("-p") + .context(value -> { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new InvalidCommandArgument("Invalid page number: " + value); + } + }) + .build()); + + private final CommandValueFlag FILTER_FLAG = flag(CommandValueFlag + .builder("--filter", ContentFilter.class) + .addAlias("-f") + .context(value -> { + try { + return RegexContentFilter.fromString(value); + } catch (IllegalArgumentException e) { + throw new InvalidCommandArgument("Invalid filter: " + value); + } + }) + .build()); + + @Inject + AnchorListCommand( + @NotNull MVCommandManager commandManager, + @NotNull AnchorManager anchorManager, + @NotNull LocationManipulation locationManipulation) { + super(commandManager); + this.anchorManager = anchorManager; + this.locationManipulation = locationManipulation; + } + + @Subcommand("anchor list") + @CommandPermission("multiverse.core.anchor.list") + @CommandCompletion("@flags:groupName=mvanchorlistcommand") + @Syntax("[--page ] [--filter ]") + @Description("") + void onAnchorListCommand( + MVCommandIssuer issuer, + + @Optional + @Syntax("[--page ] [--filter ]") + @Description("") + String[] flags) { + ParsedCommandFlags parsedFlags = parseFlags(flags); + ContentDisplay.create() + .addContent(ListContentProvider.forContent(getAnchors(issuer.getPlayer()))) + .withSendHandler(PagedSendHandler.create() + .withHeader("&3==== [ Multiverse Anchors ] ====") + .doPagination(true) + .withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1)) + .withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get()))) + .send(issuer); + } + + private List getAnchors(Player player) { + return anchorManager.getAnchors(player).stream().map(anchorName -> { + Location anchorLocation = anchorManager.getAnchorLocation(anchorName); + return "&a" + anchorName + "&7 - &f" + locationManipulation.locationToString(anchorLocation); + }).toList(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/AnchorSetCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/AnchorSetCommand.java new file mode 100644 index 00000000..295828bf --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/commands/AnchorSetCommand.java @@ -0,0 +1,84 @@ +package org.mvplugins.multiverse.core.commands; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Flags; +import co.aikar.commands.annotation.Single; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import jakarta.inject.Inject; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; + +import org.mvplugins.multiverse.core.anchor.AnchorManager; +import org.mvplugins.multiverse.core.api.LocationManipulation; +import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; +import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; + +@Service +@CommandAlias("mv") +class AnchorSetCommand extends MultiverseCommand { + + private final AnchorManager anchorManager; + private final LocationManipulation locationManipulation; + + @Inject + AnchorSetCommand( + @NotNull MVCommandManager commandManager, + @NotNull AnchorManager anchorManager, + @NotNull LocationManipulation locationManipulation) { + super(commandManager); + this.anchorManager = anchorManager; + this.locationManipulation = locationManipulation; + } + + @Subcommand("anchor set") + @CommandPermission("multiverse.core.anchor.create") + @CommandCompletion("@anchornames") + @Syntax(" [location]") + @Description("") + void onAnchorSetCommand( + MVCommandIssuer issuer, + + @Flags("resolve=issuerOnly") + Player player, + + @Single + @Syntax("") + @Description("") + //TODO: Check if anchor name is valid or exists already. + String anchorName) { + Location anchorLocation = player.getLocation(); + anchorManager.saveAnchorLocation(anchorName, anchorLocation) + .onSuccess(ignore -> issuer.sendMessage("&aAnchor &f" + anchorName + "&a set to &f" + + locationManipulation.locationToString(anchorLocation))) + .onFailure(e -> issuer.sendMessage("&cFailed to set anchor &f" + anchorName + "&c." + e.getMessage())); + } + + @Subcommand("anchor set") + @CommandPermission("multiverse.core.anchor.create") + @CommandCompletion("@anchornames @locations") + @Syntax(" [location]") + @Description("") + void onAnchorSetCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("") + String anchorName, + + @Single + @Syntax("[location]") + @Description("") + String locationString) { + anchorManager.saveAnchorLocation(anchorName, locationString) + .onSuccess(anchorLocation -> issuer.sendMessage("&aAnchor &f" + anchorName + "&a set to &f" + + locationManipulation.locationToString(anchorLocation))) + .onFailure(e -> issuer.sendMessage("&cFailed to set anchor '" + anchorName + "'&c. " + e.getMessage())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java index 63b9f69e..b5377b7e 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java +++ b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandCompletions.java @@ -26,6 +26,7 @@ import org.bukkit.World; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.anchor.AnchorManager; import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction; import org.mvplugins.multiverse.core.destination.DestinationsProvider; @@ -39,6 +40,7 @@ class MVCommandCompletions extends PaperCommandCompletions { private final MVCommandManager commandManager; private final WorldManager worldManager; + private final AnchorManager anchorManager; private final DestinationsProvider destinationsProvider; private final MVCoreConfig config; @@ -46,14 +48,17 @@ class MVCommandCompletions extends PaperCommandCompletions { MVCommandCompletions( @NotNull MVCommandManager mvCommandManager, @NotNull WorldManager worldManager, + @NotNull AnchorManager anchorManager, @NotNull DestinationsProvider destinationsProvider, @NotNull MVCoreConfig config) { super(mvCommandManager); this.commandManager = mvCommandManager; this.worldManager = worldManager; + this.anchorManager = anchorManager; this.destinationsProvider = destinationsProvider; this.config = config; + registerAsyncCompletion("anchornames", this::suggestAnchorNames); registerAsyncCompletion("commands", this::suggestCommands); registerAsyncCompletion("destinations", this::suggestDestinations); registerStaticCompletion("difficulties", suggestEnums(Difficulty.class)); @@ -77,6 +82,10 @@ class MVCommandCompletions extends PaperCommandCompletions { setDefaultCompletion("mvworlds", LoadedMultiverseWorld.class); } + private Collection suggestAnchorNames(BukkitCommandCompletionContext context) { + return anchorManager.getAnchors(context.getPlayer()); + } + private Collection suggestCommands(BukkitCommandCompletionContext context) { String rootCmdName = context.getConfig(); if (rootCmdName == null) { diff --git a/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java b/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java index 6e1a31e8..b46ec662 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java @@ -734,7 +734,7 @@ public class WorldManager { * @return The world if it exists. */ public Option getWorld(@Nullable World world) { - return Option.of(world).flatMap(this::getWorld); + return Option.of(world).map(World::getName).flatMap(this::getWorld); } /**