diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/LoadCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/LoadCommand.java index dfeed3196..7e2563eb2 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/LoadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/LoadCommand.java @@ -3,9 +3,8 @@ import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.CommandPermission; -import co.aikar.commands.annotation.Conditions; import co.aikar.commands.annotation.Description; -import co.aikar.commands.annotation.Single; +import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; import com.dumptruckman.minecraft.util.Logging; @@ -15,19 +14,26 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.flag.CommandFlag; +import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; +import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.options.LoadWorldOptions; @Service class LoadCommand extends CoreCommand { private final WorldManager worldManager; + private final LoadCommand.Flags flags; @Inject - LoadCommand(@NotNull WorldManager worldManager) { + LoadCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) { this.worldManager = worldManager; + this.flags = flags; } @Subcommand("load") @@ -38,13 +44,19 @@ class LoadCommand extends CoreCommand { void onLoadCommand( MVCommandIssuer issuer, - @Single - @Conditions("worldname:scope=unloaded") @Syntax("") @Description("{@@mv-core.load.world.description}") - String worldName) { - issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(worldName)); - worldManager.loadWorld(worldName) + MultiverseWorld world, + + @Optional + @Syntax("[--remove-players]") + @Description("") + String[] flagArray) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(world.getName())); + worldManager.loadWorld(LoadWorldOptions.world(world) + .doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck))) .onSuccess(newWorld -> { Logging.fine("World load success: " + newWorld); issuer.sendInfo(MVCorei18n.LOAD_SUCCESS, Replace.WORLD.with(newWorld.getName())); @@ -54,17 +66,32 @@ void onLoadCommand( }); } + @Service + private static final class Flags extends RemovePlayerFlags { + + private static final String NAME = "mvunload"; + + @Inject + private Flags(@NotNull CommandFlagsManager flagsManager) { + super(NAME, flagsManager); + } + + private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check") + .addAlias("-f") + .build()); + } + @Service private static final class LegacyAlias extends LoadCommand implements LegacyAliasCommand { @Inject - LegacyAlias(@NotNull WorldManager worldManager) { - super(worldManager); + LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) { + super(worldManager, flags); } @Override @CommandAlias("mvload") - void onLoadCommand(MVCommandIssuer issuer, String worldName) { - super.onLoadCommand(issuer, worldName); + void onLoadCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flagArray) { + super.onLoadCommand(issuer, world, flagArray); } } } diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVWorldListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVWorldListener.java index 55ab4ef4f..761b8c6d3 100644 --- a/src/main/java/org/mvplugins/multiverse/core/listeners/MVWorldListener.java +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVWorldListener.java @@ -18,6 +18,7 @@ import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority; import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.options.LoadWorldOptions; import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions; import org.mvplugins.multiverse.core.world.reasons.LoadFailureReason; import org.mvplugins.multiverse.core.world.reasons.UnloadFailureReason; @@ -68,7 +69,7 @@ void worldLoad(WorldLoadEvent event) { worldManager.getUnloadedWorld(event.getWorld().getName()) .peek(world -> { Logging.fine("Loading world: " + world.getName()); - worldManager.loadWorld(world).onFailure(failure -> { + worldManager.loadWorld(LoadWorldOptions.world(world)).onFailure(failure -> { if (failure.getFailureReason() != LoadFailureReason.WORLD_ALREADY_LOADING) { Logging.severe("Failed to load world: " + failure); } 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 952cfbfc6..75f665ca7 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java @@ -64,6 +64,7 @@ import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions; import org.mvplugins.multiverse.core.world.options.ImportWorldOptions; import org.mvplugins.multiverse.core.world.options.KeepWorldSettingsOptions; +import org.mvplugins.multiverse.core.world.options.LoadWorldOptions; import org.mvplugins.multiverse.core.world.options.RegenWorldOptions; import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions; import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions; @@ -226,8 +227,9 @@ private void autoLoadWorlds() { if (isLoadedWorld(world) || !world.isAutoLoad()) { return; } - loadWorld(world).onFailure(failure -> Logging.severe("Failed to load world %s: %s", - world.getName(), failure)); + loadWorld(LoadWorldOptions.world(world)) + .onFailure(failure -> + Logging.severe("Failed to load world %s: %s", world.getName(), failure)); }); } @@ -403,10 +405,14 @@ private void setDefaultEnvironmentScale(MultiverseWorld world) { * * @param worldName The name of the world to load. * @return The result of the load. + * + * @deprecated Use {@link #loadWorld(LoadWorldOptions)} instead. */ + @Deprecated(since = "5.2", forRemoval = true) + @ApiStatus.ScheduledForRemoval(inVersion = "6.0") public Attempt loadWorld(@NotNull String worldName) { return getWorld(worldName) - .map(this::loadWorld) + .map(world -> loadWorld(LoadWorldOptions.world(world))) .getOrElse(() -> worldNameChecker.isValidWorldFolder(worldName) ? worldActionResult(LoadFailureReason.WORLD_EXIST_FOLDER, worldName) : worldActionResult(LoadFailureReason.WORLD_NON_EXISTENT, worldName)); @@ -417,12 +423,30 @@ public Attempt loadWorld(@NotNull Stri * * @param world The world to load. * @return The result of the load. + * + * @deprecated Use {@link #loadWorld(LoadWorldOptions)} instead. */ + @Deprecated(since = "5.2", forRemoval = true) + @ApiStatus.ScheduledForRemoval(inVersion = "6.0") public Attempt loadWorld(@NotNull MultiverseWorld world) { - return validateWorldToLoad(world).mapAttempt(this::doLoadWorld); + return loadWorld(LoadWorldOptions.world(world)); } - private Attempt validateWorldToLoad(@NotNull MultiverseWorld mvWorld) { + /** + * Loads an existing world in config. + * + * @param options The options for customizing the loading of a world. + * @return The result of the load. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public Attempt loadWorld(@NotNull LoadWorldOptions options) { + return validateWorldToLoad(options).mapAttempt(this::doLoadWorld); + } + + private Attempt validateWorldToLoad(@NotNull LoadWorldOptions options) { + MultiverseWorld mvWorld = options.world(); if (loadTracker.contains(mvWorld.getName())) { // This is to prevent recursive calls by WorldLoadEvent Logging.fine("World already loading: " + mvWorld.getName()); @@ -431,21 +455,19 @@ private Attempt validateWorldToLoad(@NotNull Logging.severe("World already loaded: " + mvWorld.getName()); return worldActionResult(LoadFailureReason.WORLD_EXIST_LOADED, mvWorld.getName()); } - return worldActionResult(mvWorld); + return worldActionResult(options); } - private Attempt doLoadWorld(@NotNull MultiverseWorld mvWorld) { + private Attempt doLoadWorld(@NotNull LoadWorldOptions options) { + MultiverseWorld mvWorld = options.world(); + World bukkitWorld = Bukkit.getWorld(mvWorld.getName()); if (bukkitWorld != null) { - if (bukkitWorld.getEnvironment() != mvWorld.getEnvironment()) { - return Attempt.failure(LoadFailureReason.BUKKIT_ENVIRONMENT_MISMATCH, - Replace.WORLD.with(mvWorld.getName()), - replace("{bukkitEnvironment}").with(bukkitWorld.getEnvironment().name()), - replace("{mvEnvironment}").with(mvWorld.getEnvironment().name())); - } - // World already loaded, maybe by another plugin - Logging.finer("World already loaded in bukkit: " + mvWorld.getName()); - return newLoadedMultiverseWorld(mvWorld, bukkitWorld); + return doLoadBukkitWorld(bukkitWorld, mvWorld); + } + + if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(mvWorld.getName())) { + return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName()); } WorldCreator worldCreator = WorldCreator.name(mvWorld.getName()) @@ -458,6 +480,18 @@ private Attempt doLoadWorld(@NotNull M .mapAttempt(newBukkitWorld -> newLoadedMultiverseWorld(mvWorld, newBukkitWorld)); } + private @NotNull Attempt doLoadBukkitWorld(World bukkitWorld, MultiverseWorld mvWorld) { + if (bukkitWorld.getEnvironment() != mvWorld.getEnvironment()) { + return Attempt.failure(LoadFailureReason.BUKKIT_ENVIRONMENT_MISMATCH, + Replace.WORLD.with(mvWorld.getName()), + replace("{bukkitEnvironment}").with(bukkitWorld.getEnvironment().name()), + replace("{mvEnvironment}").with(mvWorld.getEnvironment().name())); + } + // World already loaded, maybe by another plugin + Logging.finer("World already loaded in bukkit: " + mvWorld.getName()); + return newLoadedMultiverseWorld(mvWorld, bukkitWorld); + } + private Attempt newLoadedMultiverseWorld(MultiverseWorld mvWorld, World bukkitWorld) { WorldConfig worldConfig = worldsConfigManager.getWorldConfig(mvWorld.getName()).get(); //TODO: null check here, but logically it should never be null. LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld( @@ -615,7 +649,7 @@ private Attempt removeWorldFromConfig(@NotNull Mult */ public Attempt deleteWorld(@NotNull DeleteWorldOptions options) { return getLoadedWorld(options.world()).fold( - () -> loadWorld(options.world()) + () -> loadWorld(LoadWorldOptions.world(options.world())) .transform(DeleteFailureReason.LOAD_FAILED) .mapAttempt(world -> doDeleteWorld(world, options)), world -> doDeleteWorld(world, options)); diff --git a/src/main/java/org/mvplugins/multiverse/core/world/options/LoadWorldOptions.java b/src/main/java/org/mvplugins/multiverse/core/world/options/LoadWorldOptions.java new file mode 100644 index 000000000..7a5b59fbe --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/world/options/LoadWorldOptions.java @@ -0,0 +1,72 @@ +package org.mvplugins.multiverse.core.world.options; + +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.core.world.MultiverseWorld; + +/** + * Options for customizing the loading of a world. + * + * @since 5.2 + */ +@ApiStatus.AvailableSince("5.2") +public final class LoadWorldOptions { + + /** + * Creates a new {@link LoadWorldOptions} instance with the given world. + * + * @param world The world to load. + * @return A new {@link LoadWorldOptions} instance. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public static LoadWorldOptions world(MultiverseWorld world) { + return new LoadWorldOptions(world); + } + + private final MultiverseWorld world; + private boolean doFolderCheck = true; + + LoadWorldOptions(MultiverseWorld world) { + this.world = world; + } + + /** + * Gets the world to load. + * + * @return The world to load. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public MultiverseWorld world() { + return world; + } + + /** + * Sets whether to check if the world folder is valid before loading the world. + *
+ * This helps to prevent deleted world folders from being re-created. + * + * @return Whether to check if the world folder is valid before loading the world. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public boolean doFolderCheck() { + return doFolderCheck; + } + + /** + * Sets whether to check if the world folder is valid before loading the world. + * + * @return This {@link LoadWorldOptions} instance. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public LoadWorldOptions doFolderCheck(boolean doFolderCheck) { + this.doFolderCheck = doFolderCheck; + return this; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/world/reasons/LoadFailureReason.java b/src/main/java/org/mvplugins/multiverse/core/world/reasons/LoadFailureReason.java index ab9df6307..aacda99c9 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/reasons/LoadFailureReason.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/reasons/LoadFailureReason.java @@ -26,6 +26,14 @@ public enum LoadFailureReason implements FailureReason { */ WORLD_EXIST_FOLDER(MVCorei18n.LOADWORLD_WORLDEXISTFOLDER), + /** + * The world folder is invalid. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + WORLD_FOLDER_INVALID(MVCorei18n.IMPORTWORLD_WORLDFOLDERINVALID), + /** * The world is already loaded. */ diff --git a/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt b/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt index 2aa872fe2..3066d5f5e 100644 --- a/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt @@ -10,6 +10,7 @@ import org.mvplugins.multiverse.core.event.world.* import org.mvplugins.multiverse.core.world.options.CloneWorldOptions import org.mvplugins.multiverse.core.world.options.CreateWorldOptions import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions +import org.mvplugins.multiverse.core.world.options.LoadWorldOptions import org.mvplugins.multiverse.core.world.options.RegenWorldOptions import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions @@ -133,13 +134,23 @@ class WorldManagerTest : TestWithMockBukkit() { assertTrue(worldManager.getWorld("world2").isDefined) assertTrue(worldManager.getUnloadedWorld("world2").isDefined) - assertTrue(worldManager.loadWorld("world2").isSuccess) + assertTrue(worldManager.loadWorld(LoadWorldOptions.world(world2)).isSuccess) assertTrue(world2.isLoaded) assertTrue(worldManager.getLoadedWorld("world2").flatMap{ w -> w.bukkitWorld }.isDefined) assertTrue(worldManager.getLoadedWorld("world2").isDefined) assertFalse(worldManager.getUnloadedWorld("world2").isDefined) } + @Test + fun `Load world failed - invalid world folder`() { + assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess) + File(Bukkit.getWorldContainer(), "world2/").deleteRecursively() + assertEquals( + LoadFailureReason.WORLD_FOLDER_INVALID, + worldManager.loadWorld(LoadWorldOptions.world(world2)).failureReason + ) + } + @Test fun `Load world failed - non-existent world`() { assertEquals(