Add support for removing players before unloading world

This commit is contained in:
Ben Woo 2023-09-11 15:39:06 +08:00
parent 379e0172c4
commit 481933b948
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
5 changed files with 157 additions and 46 deletions

View File

@ -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("<world>")
@Description("{@@mv-core.unload.description}")
public void onUnloadCommand(MVCommandIssuer issuer,
public void onUnloadCommand(
MVCommandIssuer issuer,
@Syntax("<world>")
@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("<world>")
@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());

View File

@ -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);
}
}));
}
/**

View File

@ -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<List<Player>> getPlayers() {
return getBukkitWorld().map(World::getPlayers);
}
/**
* {@inheritDoc}
*/

View File

@ -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<MultiverseWorld, UnloadFailureReason> 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<MultiverseWorld, UnloadFailureReason> 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<MultiverseWorld, UnloadFailureReason> unloadWorld(
@NotNull LoadedMultiverseWorld world) {
public Attempt<MultiverseWorld, UnloadFailureReason> 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<String, RemoveFailureReason> 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<LoadedMultiverseWorld, RegenFailureReason> regenWorld(@NotNull RegenWorldOptions options) {
// TODO: Teleport players out of world, and back in after regen
LoadedMultiverseWorld world = options.world();
List<Player> playersInWorld = world.getPlayers().getOrElse(Collections.emptyList());
DataTransfer<LoadedMultiverseWorld> 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<Player> players, @NotNull LoadedMultiverseWorld world) {
players.forEach(player -> {
Location spawnLocation = world.getSpawnLocation();
if (player.isOnline()) {
safetyTeleporter.safelyTeleport(null, player, spawnLocation, true);
}
});
}
private <T, F extends FailureReason> Attempt<T, F> 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<Void> unloadBukkitWorld(World world) {
private Try<Void> 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!");
}

View File

@ -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;
}
}