diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java index 72fd6902..bb599c21 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java @@ -4,15 +4,19 @@ 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 com.dumptruckman.minecraft.util.Logging; import com.onarandombox.MultiverseCore.commandtools.MVCommandIssuer; import com.onarandombox.MultiverseCore.commandtools.MVCommandManager; import com.onarandombox.MultiverseCore.commandtools.MultiverseCommand; +import com.onarandombox.MultiverseCore.commandtools.flags.CommandFlag; +import com.onarandombox.MultiverseCore.commandtools.flags.ParsedCommandFlags; import com.onarandombox.MultiverseCore.utils.MVCorei18n; import com.onarandombox.MultiverseCore.worldnew.LoadedMultiverseWorld; import com.onarandombox.MultiverseCore.worldnew.WorldManager; +import com.onarandombox.MultiverseCore.worldnew.options.UnloadWorldOptions; import jakarta.inject.Inject; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; @@ -23,6 +27,14 @@ public class UnloadCommand extends MultiverseCommand { private final WorldManager worldManager; + private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players") + .addAlias("-r") + .build()); + + private final CommandFlag NO_SAVE_FLAG = flag(CommandFlag.builder("--no-save") + .addAlias("-n") + .build()); + @Inject public UnloadCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) { super(commandManager); @@ -31,17 +43,27 @@ public class UnloadCommand extends MultiverseCommand { @Subcommand("unload") @CommandPermission("multiverse.core.unload") - @CommandCompletion("@mvworlds") + @CommandCompletion("@mvworlds @flags:groupName=mvunloadcommand") @Syntax("") @Description("{@@mv-core.unload.description}") - public void onUnloadCommand(MVCommandIssuer issuer, + public void onUnloadCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("{@@mv-core.unload.world.description}") + LoadedMultiverseWorld world, + + @Optional + @Syntax("[--remove-players] [--no-save]") + @Description("{@@mv-core.gamerules.description.page}") + String[] flags) { + ParsedCommandFlags parsedFlags = parseFlags(flags); - @Syntax("") - @Description("{@@mv-core.unload.world.description}") - LoadedMultiverseWorld world - ) { issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, "{world}", world.getAlias()); - worldManager.unloadWorld(world) + UnloadWorldOptions unloadWorldOptions = UnloadWorldOptions.world(world) + .removePlayers(parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)) + .saveBukkitWorld(!parsedFlags.hasFlag(NO_SAVE_FLAG)); + worldManager.unloadWorld(unloadWorldOptions) .onSuccess(loadedWorld -> { Logging.fine("World unload success: " + loadedWorld); issuer.sendInfo(MVCorei18n.UNLOAD_SUCCESS, "{world}", loadedWorld.getName()); diff --git a/src/main/java/com/onarandombox/MultiverseCore/listeners/MVWorldListener.java b/src/main/java/com/onarandombox/MultiverseCore/listeners/MVWorldListener.java index 8e99cf17..8e36ca05 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/listeners/MVWorldListener.java +++ b/src/main/java/com/onarandombox/MultiverseCore/listeners/MVWorldListener.java @@ -10,6 +10,7 @@ package com.onarandombox.MultiverseCore.listeners; import com.dumptruckman.minecraft.util.Logging; import com.onarandombox.MultiverseCore.inject.InjectableListener; import com.onarandombox.MultiverseCore.worldnew.WorldManager; +import com.onarandombox.MultiverseCore.worldnew.options.UnloadWorldOptions; import com.onarandombox.MultiverseCore.worldnew.reasons.LoadFailureReason; import com.onarandombox.MultiverseCore.worldnew.reasons.UnloadFailureReason; import jakarta.inject.Inject; @@ -42,11 +43,12 @@ public class MVWorldListener implements InjectableListener { if (event.isCancelled()) { return; } - worldManager.unloadWorld(event.getWorld()).onFailure(failure -> { - if (failure.getFailureReason() != UnloadFailureReason.WORLD_ALREADY_UNLOADING) { - Logging.severe("Failed to unload world: " + failure); - } - }); + worldManager.getLoadedWorld(event.getWorld().getName()) + .peek(world -> worldManager.unloadWorld(UnloadWorldOptions.world(world)).onFailure(failure -> { + if (failure.getFailureReason() != UnloadFailureReason.WORLD_ALREADY_UNLOADING) { + Logging.severe("Failed to unload world: " + failure); + } + })); } /** diff --git a/src/main/java/com/onarandombox/MultiverseCore/worldnew/LoadedMultiverseWorld.java b/src/main/java/com/onarandombox/MultiverseCore/worldnew/LoadedMultiverseWorld.java index 0073fbc3..9de257cc 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/worldnew/LoadedMultiverseWorld.java +++ b/src/main/java/com/onarandombox/MultiverseCore/worldnew/LoadedMultiverseWorld.java @@ -12,8 +12,10 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.WorldType; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.UUID; public class LoadedMultiverseWorld extends MultiverseWorld { @@ -110,6 +112,10 @@ public class LoadedMultiverseWorld extends MultiverseWorld { return getBukkitWorld().map(World::canGenerateStructures); } + public Option> getPlayers() { + return getBukkitWorld().map(World::getPlayers); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java index 26c938b1..e309124f 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java @@ -19,6 +19,7 @@ import com.onarandombox.MultiverseCore.worldnew.options.CreateWorldOptions; import com.onarandombox.MultiverseCore.worldnew.options.ImportWorldOptions; import com.onarandombox.MultiverseCore.worldnew.options.KeepWorldSettingsOptions; import com.onarandombox.MultiverseCore.worldnew.options.RegenWorldOptions; +import com.onarandombox.MultiverseCore.worldnew.options.UnloadWorldOptions; import com.onarandombox.MultiverseCore.worldnew.reasons.CloneFailureReason; import com.onarandombox.MultiverseCore.worldnew.reasons.CreateFailureReason; import com.onarandombox.MultiverseCore.worldnew.reasons.DeleteFailureReason; @@ -31,9 +32,11 @@ import io.vavr.control.Option; import io.vavr.control.Try; import jakarta.inject.Inject; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.WorldType; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; @@ -354,47 +357,26 @@ public class WorldManager { }); } - /** - * Unloads an existing multiverse world. It will still remain as an unloaded world in mv config. - * - * @param world The bukkit world to unload. - * @return The result of the unload action. - */ - public Attempt unloadWorld(@NotNull World world) { - return unloadWorld(world.getName()); - } - - /** - * Unloads an existing multiverse world. It will still remain as an unloaded world in mv config. - * - * @param worldName The name of the world to unload. - * @return The result of the unload action. - */ - public Attempt unloadWorld(@NotNull String worldName) { - return getLoadedWorld(worldName) - .map(this::unloadWorld) - .getOrElse(() -> isUnloadedWorld(worldName) - ? worldActionResult(UnloadFailureReason.WORLD_UNLOADED, worldName) - : worldActionResult(UnloadFailureReason.WORLD_NON_EXISTENT, worldName)); - } - /** * Unloads an existing multiverse world. It will still remain as an unloaded world. * - * @param world The multiverse world to unload. + * @param options The options for customizing the unloading of a world. * @return The result of the unload action. */ - public Attempt unloadWorld( - @NotNull LoadedMultiverseWorld world) { + public Attempt unloadWorld(@NotNull UnloadWorldOptions options) { + LoadedMultiverseWorld world = options.world(); + if (unloadTracker.contains(world.getName())) { // This is to prevent recursive calls by WorldUnloadEvent Logging.fine("World already unloading: " + world.getName()); return worldActionResult(UnloadFailureReason.WORLD_ALREADY_UNLOADING, world.getName()); } - // TODO: removePlayersFromWorld? + if (options.removePlayers()) { + removePlayersFromWorld(world); + } - return unloadBukkitWorld(world.getBukkitWorld().getOrNull()).fold( + return unloadBukkitWorld(world.getBukkitWorld().getOrNull(), options.saveBukkitWorld()).fold( exception -> worldActionResult(UnloadFailureReason.BUKKIT_UNLOAD_FAILED, world.getName(), exception), success -> Option.of(loadedWorldsMap.remove(world.getName())).fold( @@ -409,6 +391,16 @@ public class WorldManager { })); } + private void removePlayersFromWorld(@NotNull LoadedMultiverseWorld world) { + world.getPlayers().peek(players -> players.forEach(player -> { + Location spawnLocation = Bukkit.getWorlds().get(0).getSpawnLocation(); + if (player.isOnline()) { + Logging.fine("Teleporting player '%s' to world spawn: %s", player.getName(), spawnLocation); + safetyTeleporter.safelyTeleport(null, player, spawnLocation, true); + } + })); + } + /** * Removes an existing multiverse world. It will be deleted from the worlds config and will no longer be an * unloaded world. World files will not be deleted. @@ -444,7 +436,8 @@ public class WorldManager { * @return The result of the remove. */ public Attempt removeWorld(@NotNull LoadedMultiverseWorld loadedWorld) { - return unloadWorld(loadedWorld) + // TODO: Config option on removePlayers + return unloadWorld(UnloadWorldOptions.world(loadedWorld).removePlayers(true)) .transform(RemoveFailureReason.UNLOAD_FAILED) .mapAttempt(this::removeWorldFromConfig); } @@ -603,9 +596,8 @@ public class WorldManager { * @return The result of the regeneration. */ public Attempt regenWorld(@NotNull RegenWorldOptions options) { - // TODO: Teleport players out of world, and back in after regen - LoadedMultiverseWorld world = options.world(); + List playersInWorld = world.getPlayers().getOrElse(Collections.emptyList()); DataTransfer dataTransfer = transferData(options, world); CreateWorldOptions createWorldOptions = CreateWorldOptions.worldName(world.getName()) .environment(world.getEnvironment()) @@ -619,10 +611,20 @@ public class WorldManager { .mapAttempt(() -> createWorld(createWorldOptions).transform(RegenFailureReason.CREATE_FAILED)) .onSuccess(newWorld -> { dataTransfer.pasteAllTo(newWorld); + teleportPlayersToWorld(playersInWorld, newWorld); saveWorldsConfig(); }); } + private void teleportPlayersToWorld(@NotNull List players, @NotNull LoadedMultiverseWorld world) { + players.forEach(player -> { + Location spawnLocation = world.getSpawnLocation(); + if (player.isOnline()) { + safetyTeleporter.safelyTeleport(null, player, spawnLocation, true); + } + }); + } + private Attempt worldActionResult(@NotNull T value) { return Attempt.success(value); } @@ -674,10 +676,13 @@ public class WorldManager { * @param world The bukkit world to unload. * @return The unloaded world. */ - private Try unloadBukkitWorld(World world) { + private Try unloadBukkitWorld(World world, boolean save) { return Try.run(() -> { + if (world == null) { + return; + } unloadTracker.add(world.getName()); - if (!Bukkit.unloadWorld(world, true)) { + if (!Bukkit.unloadWorld(world, save)) { // TODO: Localize this, maybe with MultiverseException throw new Exception("Is this the default world? You can't unload the default world!"); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/worldnew/options/UnloadWorldOptions.java b/src/main/java/com/onarandombox/MultiverseCore/worldnew/options/UnloadWorldOptions.java new file mode 100644 index 00000000..0856ab34 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/worldnew/options/UnloadWorldOptions.java @@ -0,0 +1,76 @@ +package com.onarandombox.MultiverseCore.worldnew.options; + +import com.onarandombox.MultiverseCore.worldnew.LoadedMultiverseWorld; + +/** + * Options for customizing the unloading of a world. + */ +public class UnloadWorldOptions { + + /** + * Creates a new {@link UnloadWorldOptions} instance with the given world. + * + * @param world The world to unload. + * @return A new {@link UnloadWorldOptions} instance. + */ + public static UnloadWorldOptions world(LoadedMultiverseWorld world) { + return new UnloadWorldOptions(world); + } + + private final LoadedMultiverseWorld world; + private boolean removePlayers = false; + private boolean saveBukkitWorld = true; + + UnloadWorldOptions(LoadedMultiverseWorld world) { + this.world = world; + } + + /** + * Gets the world to unload. + * + * @return The world to unload. + */ + public LoadedMultiverseWorld world() { + return world; + } + + /** + * Sets whether to teleport the players out from the world before unloading. + * + * @param removePlayers Whether to remove players from the world before unloading. + * @return This {@link UnloadWorldOptions} instance. + */ + public UnloadWorldOptions removePlayers(boolean removePlayers) { + this.removePlayers = removePlayers; + return this; + } + + /** + * Gets whether to teleport the players out from the world before unloading. + * + * @return Whether to remove players from the world before unloading. + */ + public boolean removePlayers() { + return removePlayers; + } + + /** + * Sets whether to save the bukkit world before unloading. + * + * @param saveBukkitWorld Whether to save the bukkit world before unloading. + * @return This {@link UnloadWorldOptions} instance. + */ + public UnloadWorldOptions saveBukkitWorld(boolean saveBukkitWorld) { + this.saveBukkitWorld = saveBukkitWorld; + return this; + } + + /** + * Gets whether to save the bukkit world before unloading. + * + * @return Whether to save the bukkit world before unloading. + */ + public boolean saveBukkitWorld() { + return saveBukkitWorld; + } +}