diff --git a/src/main/java/org/mvplugins/multiverse/core/api/BlockSafety.java b/src/main/java/org/mvplugins/multiverse/core/api/BlockSafety.java index 1c6e7921..162ef5f9 100644 --- a/src/main/java/org/mvplugins/multiverse/core/api/BlockSafety.java +++ b/src/main/java/org/mvplugins/multiverse/core/api/BlockSafety.java @@ -4,6 +4,7 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Minecart; import org.bukkit.entity.Vehicle; +import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Contract; /** @@ -37,8 +38,35 @@ public interface BlockSafety { */ boolean playerCanSpawnHereSafely(Location l); + /** + * Finds a portal-block next to the specified {@link Location}. + * + * @param location The {@link Location} + * @return The next portal-block's {@link Location}. + */ + Location findPortalBlockNextTo(Location location); + + /** + * Gets the next safe location around the given location. + * + * @param location A {@link Location}. + * @return A safe {@link Location}. + */ + @Nullable Location getSafeLocation(Location location); + + /** + * Gets the next safe location around the given location. + * + * @param location A {@link Location}. + * @param tolerance The tolerance of how far up and down to search. + * @param radius The radius around given location to search. + * @return A safe {@link Location}. + */ + @Nullable Location getSafeLocation(Location location, int tolerance, int radius); + /** * Gets a safe bed spawn location OR null if the bed is invalid. + * * @param l The location of the bead head (block with the pillow on it). * @return Safe location around the bed or null if no location was found. */ @@ -46,6 +74,7 @@ public interface BlockSafety { /** * Gets the location of the top block at the specified {@link Location}. + * * @param l Any {@link Location}. * @return The {@link Location} of the top-block. */ @@ -53,6 +82,7 @@ public interface BlockSafety { /** * Gets the location of the top block at the specified {@link Location}. + * * @param l Any {@link Location}. * @return The {@link Location} of the top-block. */ @@ -60,6 +90,7 @@ public interface BlockSafety { /** * Checks if an entity would be on track at the specified {@link Location}. + * * @param l The {@link Location}. * @return True if an entity would be on tracks at the specified {@link Location}. */ @@ -67,6 +98,7 @@ public interface BlockSafety { /** * Checks if the specified {@link Minecart} can spawn safely. + * * @param cart The {@link Minecart}. * @return True if the minecart can spawn safely. */ @@ -74,6 +106,7 @@ public interface BlockSafety { /** * Checks if the specified {@link Vehicle} can spawn safely. + * * @param vehicle The {@link Vehicle}. * @return True if the vehicle can spawn safely. */ diff --git a/src/main/java/org/mvplugins/multiverse/core/api/Destination.java b/src/main/java/org/mvplugins/multiverse/core/api/Destination.java index 6afa9dc3..b5dd193c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/api/Destination.java +++ b/src/main/java/org/mvplugins/multiverse/core/api/Destination.java @@ -45,14 +45,4 @@ public interface Destination { * @return True if the SafeTeleporter will be used, false if not. */ boolean checkTeleportSafety(); - - /** - * Returns the teleporter to use for this destination. - * - *

By default, Multiverse will automatically use SafeTeleporter. If you want to use a different teleporter, you can - * override this method.

- * - * @return The custom teleporter to use for this destination. Return null to use the default teleporter. - */ - @Nullable Teleporter getTeleporter(); } diff --git a/src/main/java/org/mvplugins/multiverse/core/api/SafeTTeleporter.java b/src/main/java/org/mvplugins/multiverse/core/api/SafeTTeleporter.java deleted file mode 100644 index 8702c835..00000000 --- a/src/main/java/org/mvplugins/multiverse/core/api/SafeTTeleporter.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.mvplugins.multiverse.core.api; - -import java.util.concurrent.CompletableFuture; - -import co.aikar.commands.BukkitCommandIssuer; -import org.bukkit.Location; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.jvnet.hk2.annotations.Contract; - -import org.mvplugins.multiverse.core.destination.ParsedDestination; -import org.mvplugins.multiverse.core.teleportation.TeleportResult; - -/** - * Used to safely teleport people. - */ -@Contract -public interface SafeTTeleporter extends Teleporter { - - /** - * Gets the next safe location around the given location. - * @param l A {@link Location}. - * @return A safe {@link Location}. - */ - Location getSafeLocation(Location l); - - /** - * Gets the next safe location around the given location. - * @param l A {@link Location}. - * @param tolerance The tolerance. - * @param radius The radius. - * @return A safe {@link Location}. - */ - Location getSafeLocation(Location l, int tolerance, int radius); - - /** - * Safely teleport the entity to the MVDestination. This will perform checks to see if the place is safe, and if - * it's not, will adjust the final destination accordingly. - * - * @param teleporter Person who performed the teleport command. - * @param teleportee Entity to teleport - * @param destination Destination to teleport them to - * @return true for success, false for failure - */ - @Deprecated - TeleportResult safelyTeleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination); - - /** - * Safely teleport the entity to the MVDestination. This will perform checks to see if the place is safe, and if - * it's not, will adjust the final destination accordingly. - * - * @param teleporter Person who performed the teleport command. - * @param teleportee Entity to teleport - * @param destination Destination to teleport them to - * @return true for success, false for failure - */ - CompletableFuture safelyTeleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination); - - /** - * Safely teleport the entity to the Location. This may perform checks to - * see if the place is safe, and if - * it's not, will adjust the final destination accordingly. - * - * @param teleporter Person who issued the teleport command. - * @param teleportee Entity to teleport. - * @param location Location to teleport them to. - * @param safely Should the destination be checked for safety before teleport? - * @return true for success, false for failure. - */ - TeleportResult safelyTeleport(CommandSender teleporter, Entity teleportee, Location location, - boolean safely); - - /** - * Returns a safe location for the entity to spawn at. - * - * @param entity The entity to spawn - * @param destination The MVDestination to take the entity to. - * @return A new location to spawn the entity at. - */ - Location getSafeLocation(Entity entity, DestinationInstance destination); - - /** - * Finds a portal-block next to the specified {@link Location}. - * @param l The {@link Location} - * @return The next portal-block's {@link Location}. - */ - Location findPortalBlockNextTo(Location l); -} diff --git a/src/main/java/org/mvplugins/multiverse/core/api/Teleporter.java b/src/main/java/org/mvplugins/multiverse/core/api/Teleporter.java deleted file mode 100644 index bb950e5d..00000000 --- a/src/main/java/org/mvplugins/multiverse/core/api/Teleporter.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.mvplugins.multiverse.core.api; - -import java.util.concurrent.CompletableFuture; - -import co.aikar.commands.BukkitCommandIssuer; -import org.bukkit.entity.Entity; -import org.jvnet.hk2.annotations.Contract; - -import org.mvplugins.multiverse.core.destination.ParsedDestination; -import org.mvplugins.multiverse.core.teleportation.TeleportResult; - -@Contract -public interface Teleporter { - /** - * Teleport the entity to the Multiverse Destination. - * - * @param teleporter Person who performed the teleport command. - * @param teleportee Entity to teleport - * @param destination Destination to teleport them to - * @return true for success, false for failure - */ - @Deprecated - TeleportResult teleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination); - - /** - * Teleport the entity to the Multiverse Destination. - * - * @param teleporter Person who performed the teleport command. - * @param teleportee Entity to teleport - * @param destination Destination to teleport them to - * @return true for success, false for failure - */ - CompletableFuture teleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination); -} diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/CheckCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/CheckCommand.java index 6a23f8c1..0a2f165b 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/CheckCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/CheckCommand.java @@ -49,6 +49,6 @@ class CheckCommand extends MultiverseCommand { "{player}", player.getName(), "{destination}", destination.toString()); // TODO: More detailed output on permissions required. - this.destinationsProvider.checkTeleportPermissions(issuer, player, destination); + // this.destinationsProvider.checkTeleportPermissions(issuer, player, destination); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java index a59f624b..db4d448f 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java @@ -1,11 +1,14 @@ package org.mvplugins.multiverse.core.commands; +import java.util.Collections; + import co.aikar.commands.MessageType; 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.Optional; import co.aikar.commands.annotation.Single; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; @@ -17,48 +20,82 @@ 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.MultiverseCommand; +import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag; +import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags; import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand; import org.mvplugins.multiverse.core.utils.MVCorei18n; +import org.mvplugins.multiverse.core.utils.result.Async; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; @Service @CommandAlias("mv") class DeleteCommand extends MultiverseCommand { private final WorldManager worldManager; + private final PlayerWorldTeleporter playerWorldTeleporter; + + private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players") + .addAlias("-r") + .build()); @Inject - DeleteCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) { + DeleteCommand( + @NotNull MVCommandManager commandManager, + @NotNull WorldManager worldManager, + @NotNull PlayerWorldTeleporter playerWorldTeleporter) { super(commandManager); this.worldManager = worldManager; + this.playerWorldTeleporter = playerWorldTeleporter; } @Subcommand("delete") @CommandPermission("multiverse.core.delete") - @CommandCompletion("@mvworlds:scope=both") + @CommandCompletion("@mvworlds:scope=loaded @flags:groupName=mvdeletecommand") @Syntax("") @Description("{@@mv-core.delete.description}") void onDeleteCommand( MVCommandIssuer issuer, @Single - @Conditions("worldname:scope=both") + @Conditions("worldname:scope=loaded") @Syntax("") @Description("The world you want to delete.") - String worldName) { + LoadedMultiverseWorld world, + + @Optional + @Syntax("[--remove-players]") + @Description("") + String[] flags) { + ParsedCommandFlags parsedFlags = parseFlags(flags); + this.commandManager.getCommandQueueManager().addToQueue(new QueuedCommand( issuer.getIssuer(), () -> { - issuer.sendInfo(MVCorei18n.DELETE_DELETING, "{world}", worldName); - worldManager.deleteWorld(worldName) - .onSuccess(deletedWorldName -> { - Logging.fine("World delete success: " + deletedWorldName); - issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, "{world}", deletedWorldName); - }).onFailure(failure -> { - Logging.fine("World delete failure: " + failure); - issuer.sendError(failure.getFailureMessage()); - }); - }, this.commandManager.formatMessage( - issuer, MessageType.INFO, MVCorei18n.DELETE_PROMPT, "{world}", worldName))); + runDeleteCommand(issuer, world, parsedFlags); + }, this.commandManager.formatMessage(issuer, MessageType.INFO, MVCorei18n.DELETE_PROMPT, + "{world}", world.getName()))); + } + + private void runDeleteCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) { + issuer.sendInfo(MVCorei18n.DELETE_DELETING, "{world}", world.getName()); + + var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG) + ? playerWorldTeleporter.removeFromWorld(world) + : Async.completedFuture(Collections.emptyList()); + + future.thenRun(() -> doWorldDeleting(issuer, world)); + } + + private void doWorldDeleting(MVCommandIssuer issuer, LoadedMultiverseWorld world) { + worldManager.deleteWorld(world) + .onSuccess(deletedWorldName -> { + Logging.fine("World delete success: " + deletedWorldName); + issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, "{world}", deletedWorldName); + }).onFailure(failure -> { + Logging.fine("World delete failure: " + failure); + issuer.sendError(failure.getFailureMessage()); + }); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java index a66c98a1..23f56a14 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.core.commands; import java.util.Collections; +import java.util.List; import java.util.Random; import co.aikar.commands.MessageType; @@ -13,6 +14,7 @@ import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; import com.dumptruckman.minecraft.util.Logging; import jakarta.inject.Inject; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; @@ -24,8 +26,10 @@ import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag; import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags; import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand; import org.mvplugins.multiverse.core.utils.MVCorei18n; +import org.mvplugins.multiverse.core.utils.result.Async; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; import org.mvplugins.multiverse.core.world.options.RegenWorldOptions; @Service @@ -33,6 +37,7 @@ import org.mvplugins.multiverse.core.world.options.RegenWorldOptions; class RegenCommand extends MultiverseCommand { private final WorldManager worldManager; + private final PlayerWorldTeleporter playerWorldTeleporter; private final CommandValueFlag SEED_FLAG = flag(CommandValueFlag.builder("--seed", String.class) .addAlias("-s") @@ -51,10 +56,18 @@ class RegenCommand extends MultiverseCommand { .addAlias("-wb") .build()); + private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players") + .addAlias("-r") + .build()); + @Inject - RegenCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) { + RegenCommand( + @NotNull MVCommandManager commandManager, + @NotNull WorldManager worldManager, + @NotNull PlayerWorldTeleporter playerWorldTeleporter) { super(commandManager); this.worldManager = worldManager; + this.playerWorldTeleporter = playerWorldTeleporter; } @Subcommand("regen") @@ -77,23 +90,43 @@ class RegenCommand extends MultiverseCommand { this.commandManager.getCommandQueueManager().addToQueue(new QueuedCommand( issuer.getIssuer(), - () -> { - issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, "{world}", world.getName()); - worldManager.regenWorld(RegenWorldOptions.world(world) - .randomSeed(parsedFlags.hasFlag(SEED_FLAG)) - .seed(parsedFlags.flagValue(SEED_FLAG)) - .keepWorldConfig(!parsedFlags.hasFlag(RESET_WORLD_CONFIG_FLAG)) - .keepGameRule(!parsedFlags.hasFlag(RESET_GAMERULES_FLAG)) - .keepWorldBorder(!parsedFlags.hasFlag(RESET_WORLD_BORDER_FLAG)) - ).onSuccess(newWorld -> { - Logging.fine("World regen success: " + newWorld); - issuer.sendInfo(MVCorei18n.REGEN_SUCCESS, "{world}", newWorld.getName()); - }).onFailure(failure -> { - Logging.fine("World regen failure: " + failure); - issuer.sendError(failure.getFailureMessage()); - }); - }, + () -> runRegenCommand(issuer, world, parsedFlags), this.commandManager.formatMessage( issuer, MessageType.INFO, MVCorei18n.REGEN_PROMPT, "{world}", world.getName()))); } + + private void runRegenCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) { + issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, "{world}", world.getName()); + List worldPlayers = world.getPlayers().getOrElse(Collections.emptyList()); + + var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG) + ? playerWorldTeleporter.removeFromWorld(world) + : Async.completedFuture(Collections.emptyList()); + + future.thenRun(() -> doWorldRegening(issuer, world, parsedFlags, worldPlayers)); + } + + private void doWorldRegening( + MVCommandIssuer issuer, + LoadedMultiverseWorld world, + ParsedCommandFlags parsedFlags, + List worldPlayers) { + RegenWorldOptions regenWorldOptions = RegenWorldOptions.world(world) + .randomSeed(parsedFlags.hasFlag(SEED_FLAG)) + .seed(parsedFlags.flagValue(SEED_FLAG)) + .keepWorldConfig(!parsedFlags.hasFlag(RESET_WORLD_CONFIG_FLAG)) + .keepGameRule(!parsedFlags.hasFlag(RESET_GAMERULES_FLAG)) + .keepWorldBorder(!parsedFlags.hasFlag(RESET_WORLD_BORDER_FLAG)); + + worldManager.regenWorld(regenWorldOptions).onSuccess(newWorld -> { + Logging.fine("World regen success: " + newWorld); + issuer.sendInfo(MVCorei18n.REGEN_SUCCESS, "{world}", newWorld.getName()); + if (parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)) { + playerWorldTeleporter.teleportPlayersToWorld(worldPlayers, newWorld); + } + }).onFailure(failure -> { + Logging.fine("World regen failure: " + failure); + issuer.sendError(failure.getFailureMessage()); + }); + } } diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java index 86bf4b23..4888082c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java @@ -1,10 +1,13 @@ package org.mvplugins.multiverse.core.commands; +import java.util.Collections; + 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.Optional; import co.aikar.commands.annotation.Single; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; @@ -16,20 +19,33 @@ 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.MultiverseCommand; +import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag; +import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags; import org.mvplugins.multiverse.core.utils.MVCorei18n; +import org.mvplugins.multiverse.core.utils.result.Async; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; @Service @CommandAlias("mv") class RemoveCommand extends MultiverseCommand { private final WorldManager worldManager; + private final PlayerWorldTeleporter playerWorldTeleporter; + + private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players") + .addAlias("-r") + .build()); @Inject - RemoveCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) { + RemoveCommand( + @NotNull MVCommandManager commandManager, + @NotNull WorldManager worldManager, + @NotNull PlayerWorldTeleporter playerWorldTeleporter) { super(commandManager); this.worldManager = worldManager; + this.playerWorldTeleporter = playerWorldTeleporter; } @Subcommand("remove") @@ -41,10 +57,27 @@ class RemoveCommand extends MultiverseCommand { MVCommandIssuer issuer, @Single - @Conditions("mvworlds:scope=both") + @Conditions("mvworlds:scope=both @flags:groupName=mvremovecommand") @Syntax("") @Description("{@@mv-core.remove.world.description}") - MultiverseWorld world) { + MultiverseWorld world, + + @Optional + @Syntax("[--remove-players]") + @Description(/* TODO */"") + String[] flags) { + ParsedCommandFlags parsedFlags = parseFlags(flags); + + var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG) + ? worldManager.getLoadedWorld(world) + .map(playerWorldTeleporter::removeFromWorld) + .getOrElse(Async.completedFuture(Collections.emptyList())) + : Async.completedFuture(Collections.emptyList()); + + future.thenRun(() -> doWorldRemoving(issuer, world)); + } + + private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world) { worldManager.removeWorld(world) .onSuccess(removedWorldName -> { Logging.fine("World remove success: " + removedWorldName); diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/TeleportCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/TeleportCommand.java index ad58977b..70426d15 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/TeleportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/TeleportCommand.java @@ -1,9 +1,7 @@ package org.mvplugins.multiverse.core.commands; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; +import java.util.List; -import co.aikar.commands.BukkitCommandIssuer; import co.aikar.commands.CommandIssuer; import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandCompletion; @@ -14,24 +12,32 @@ import co.aikar.commands.annotation.Syntax; import com.dumptruckman.minecraft.util.Logging; import jakarta.inject.Inject; import org.bukkit.entity.Player; +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.MultiverseCommand; -import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.destination.ParsedDestination; +import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.utils.MVCorei18n; @Service @CommandAlias("mv") class TeleportCommand extends MultiverseCommand { - private final DestinationsProvider destinationsProvider; + private final CorePermissionsChecker permissionsChecker; + private final AsyncSafetyTeleporter safetyTeleporter; @Inject - TeleportCommand(MVCommandManager commandManager, DestinationsProvider destinationsProvider) { + TeleportCommand( + @NotNull MVCommandManager commandManager, + @NotNull CorePermissionsChecker permissionsChecker, + @NotNull AsyncSafetyTeleporter safetyTeleporter) { super(commandManager); - this.destinationsProvider = destinationsProvider; + this.permissionsChecker = permissionsChecker; + this.safetyTeleporter = safetyTeleporter; } @CommandAlias("mvtp") @@ -40,7 +46,7 @@ class TeleportCommand extends MultiverseCommand { @Syntax("[player] ") @Description("{@@mv-core.teleport.description}") void onTeleportCommand( - BukkitCommandIssuer issuer, + MVCommandIssuer issuer, @Flags("resolve=issuerAware") @Syntax("[player]") @@ -56,17 +62,25 @@ class TeleportCommand extends MultiverseCommand { ? issuer.getPlayer() == players[0] ? "you" : players[0].getName() : players.length + " players"; + // TODO: Multi player permission checking + if (!permissionsChecker.checkTeleportPermissions(issuer.getIssuer(), players[0], destination)) { + issuer.sendMessage("You do not have teleport permissions"); + return; + } + issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS, "{player}", playerName, "{destination}", destination.toString()); - CompletableFuture.allOf(Arrays.stream(players) - .map(player -> this.destinationsProvider.playerTeleportAsync(issuer, player, destination)) - .toArray(CompletableFuture[]::new)) - .thenRun(() -> Logging.finer("Async teleport completed.")); + safetyTeleporter.teleportSafely(issuer.getIssuer(), List.of(players), destination) + .thenAccept(attempts -> Logging.fine("Async teleport completed: %s", attempts)) + .exceptionally(throwable -> { + Logging.severe("Error while teleporting %s to %s: %s", + playerName, destination, throwable.getMessage()); + }); } @Override public boolean hasPermission(CommandIssuer issuer) { - return this.destinationsProvider.hasAnyTeleportPermission(issuer); + return permissionsChecker.hasAnyTeleportPermission(issuer.getIssuer()); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java index a74b6eb0..42a85aa4 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java @@ -1,5 +1,7 @@ package org.mvplugins.multiverse.core.commands; +import java.util.Collections; + import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.CommandPermission; @@ -18,8 +20,10 @@ import org.mvplugins.multiverse.core.commandtools.MultiverseCommand; import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag; import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags; import org.mvplugins.multiverse.core.utils.MVCorei18n; +import org.mvplugins.multiverse.core.utils.result.Async; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions; @Service @@ -27,6 +31,7 @@ import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions; class UnloadCommand extends MultiverseCommand { private final WorldManager worldManager; + private final PlayerWorldTeleporter playerWorldTeleporter; private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players") .addAlias("-r") @@ -37,9 +42,13 @@ class UnloadCommand extends MultiverseCommand { .build()); @Inject - UnloadCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) { + UnloadCommand( + @NotNull MVCommandManager commandManager, + @NotNull WorldManager worldManager, + @NotNull PlayerWorldTeleporter playerWorldTeleporter) { super(commandManager); this.worldManager = worldManager; + this.playerWorldTeleporter = playerWorldTeleporter; } @Subcommand("unload") @@ -61,8 +70,16 @@ class UnloadCommand extends MultiverseCommand { ParsedCommandFlags parsedFlags = parseFlags(flags); issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, "{world}", world.getAlias()); + + var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG) + ? playerWorldTeleporter.removeFromWorld(world) + : Async.completedFuture(Collections.emptyList()); + + future.thenRun(() -> doWorldUnloading(issuer, world, parsedFlags)); + } + + private void doWorldUnloading(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) { UnloadWorldOptions unloadWorldOptions = UnloadWorldOptions.world(world) - .removePlayers(parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)) .saveBukkitWorld(!parsedFlags.hasFlag(NO_SAVE_FLAG)); worldManager.unloadWorld(unloadWorldOptions) .onSuccess(loadedWorld -> { diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/DestinationsProvider.java b/src/main/java/org/mvplugins/multiverse/core/destination/DestinationsProvider.java index 2263be3b..50d75da3 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/DestinationsProvider.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/DestinationsProvider.java @@ -3,14 +3,10 @@ package org.mvplugins.multiverse.core.destination; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import co.aikar.commands.BukkitCommandIssuer; -import co.aikar.commands.CommandIssuer; import jakarta.inject.Inject; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; import org.bukkit.permissions.Permission; import org.bukkit.plugin.PluginManager; import org.jetbrains.annotations.NotNull; @@ -19,9 +15,6 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.Destination; import org.mvplugins.multiverse.core.api.DestinationInstance; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; -import org.mvplugins.multiverse.core.api.Teleporter; -import org.mvplugins.multiverse.core.teleportation.TeleportResult; /** * Provides destinations for teleportation. @@ -32,13 +25,11 @@ public class DestinationsProvider { private static final String PERMISSION_PREFIX = "multiverse.teleport."; private final PluginManager pluginManager; - private final SafeTTeleporter safetyTeleporter; private final Map> destinationMap; @Inject - DestinationsProvider(@NotNull PluginManager pluginManager, @NotNull SafeTTeleporter safetyTeleporter) { + DestinationsProvider(@NotNull PluginManager pluginManager) { this.pluginManager = pluginManager; - this.safetyTeleporter = safetyTeleporter; this.destinationMap = new HashMap<>(); } @@ -123,93 +114,11 @@ public class DestinationsProvider { } /** - * Teleports the teleportee to the destination. + * Gets all registered destinations. * - * @param teleporter The teleporter. - * @param teleportee The teleportee. - * @param destination The destination. - * @return The async teleport result. + * @return A collection of destinations. */ - public CompletableFuture playerTeleportAsync( - @NotNull BukkitCommandIssuer teleporter, - @NotNull Player teleportee, - @NotNull ParsedDestination destination) { - if (!checkTeleportPermissions(teleporter, teleportee, destination)) { - return CompletableFuture.completedFuture(TeleportResult.FAIL_PERMISSION); - } - return teleportAsync(teleporter, teleportee, destination); - } - - /** - * Teleports the teleportee to the destination. - * - * @param teleporter The teleporter. - * @param teleportee The teleportee. - * @param destination The destination. - * @return The async teleport result. - */ - public CompletableFuture teleportAsync( - @NotNull BukkitCommandIssuer teleporter, - @NotNull Entity teleportee, - @NotNull ParsedDestination destination) { - Teleporter teleportHandler = destination.getDestination().getTeleporter(); - if (teleportHandler == null) { - teleportHandler = safetyTeleporter; - } - return teleportHandler.teleportAsync(teleporter, teleportee, destination); - } - - /** - * Checks if the teleporter has permission to teleport the teleportee to the destination. - * - * @param teleporter The teleporter. - * @param teleportee The teleportee. - * @param destination The destination. - * @return True if the teleporter has permission, false otherwise. - */ - public boolean checkTeleportPermissions( - CommandIssuer teleporter, Entity teleportee, ParsedDestination destination) { - // TODO: Move permission checking to a separate class - String permission = PERMISSION_PREFIX - + (teleportee.equals(teleporter.getIssuer()) ? "self" : "other") + "." - + destination.getDestination().getIdentifier(); - if (!teleporter.hasPermission(permission)) { - teleporter.sendMessage("You don't have permission to teleport to this destination."); - return false; - } - - // TODO: Config whether to use finer permission - String finerPermissionSuffix = destination.getDestinationInstance().getFinerPermissionSuffix(); - if (finerPermissionSuffix == null || finerPermissionSuffix.isEmpty()) { - return true; - } - - String finerPermission = permission + "." + finerPermissionSuffix; - if (!teleporter.hasPermission(finerPermission)) { - teleporter.sendMessage("You don't have permission to teleport to this destination."); - return false; - } - - return true; - } - - /** - * Checks if the issuer has permission to teleport to at least one destination. - * - * @param issuer The issuer. - * @return True if the issuer has permission, false otherwise. - */ - public boolean hasAnyTeleportPermission(CommandIssuer issuer) { - for (Destination destination : this.destinationMap.values()) { - String permission = PERMISSION_PREFIX + "self." + destination.getIdentifier(); - if (issuer.hasPermission(permission)) { - return true; - } - permission = PERMISSION_PREFIX + "other." + destination.getIdentifier(); - if (issuer.hasPermission(permission)) { - return true; - } - } - return false; + public @NotNull Collection> getDestinations() { + return this.destinationMap.values(); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/AnchorDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/AnchorDestination.java index 477d11c3..88ded3ba 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/AnchorDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/AnchorDestination.java @@ -11,7 +11,6 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.anchor.AnchorManager; import org.mvplugins.multiverse.core.api.Destination; -import org.mvplugins.multiverse.core.api.Teleporter; /** * {@link Destination} implementation for anchors. @@ -61,12 +60,4 @@ public class AnchorDestination implements Destination public boolean checkTeleportSafety() { return true; } - - /** - * {@inheritDoc} - */ - @Override - public @Nullable Teleporter getTeleporter() { - return null; - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java index b88932d1..2204703a 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java @@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.Destination; -import org.mvplugins.multiverse.core.api.Teleporter; import org.mvplugins.multiverse.core.utils.PlayerFinder; /** @@ -62,12 +61,4 @@ public class BedDestination implements Destination { public boolean checkTeleportSafety() { return false; } - - /** - * {@inheritDoc} - */ - @Override - public @Nullable Teleporter getTeleporter() { - return null; - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/CannonDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/CannonDestination.java index 5e7f2ff8..84c7db23 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/CannonDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/CannonDestination.java @@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.Destination; -import org.mvplugins.multiverse.core.api.Teleporter; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -97,12 +96,4 @@ public class CannonDestination implements Destination public boolean checkTeleportSafety() { return false; } - - /** - * {@inheritDoc} - */ - @Override - public @Nullable Teleporter getTeleporter() { - return null; - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/ExactDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/ExactDestination.java index f59e6a54..0eaaf2b5 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/ExactDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/ExactDestination.java @@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.Destination; -import org.mvplugins.multiverse.core.api.Teleporter; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -100,12 +99,4 @@ public class ExactDestination implements Destination { public boolean checkTeleportSafety() { return false; } - - /** - * {@inheritDoc} - */ - @Override - public @Nullable Teleporter getTeleporter() { - return null; - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java index 670c3feb..64253f8d 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java @@ -11,7 +11,6 @@ import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.Destination; -import org.mvplugins.multiverse.core.api.Teleporter; import org.mvplugins.multiverse.core.utils.PlayerFinder; /** @@ -60,12 +59,4 @@ public class PlayerDestination implements Destination public boolean checkTeleportSafety() { return true; } - - /** - * {@inheritDoc} - */ - @Override - public @Nullable Teleporter getTeleporter() { - return null; - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/WorldDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/WorldDestination.java index 2d8fb597..b28aec4c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/WorldDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/WorldDestination.java @@ -11,7 +11,6 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.Destination; import org.mvplugins.multiverse.core.api.LocationManipulation; -import org.mvplugins.multiverse.core.api.Teleporter; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -76,12 +75,4 @@ public class WorldDestination implements Destination { public boolean checkTeleportSafety() { return true; } - - /** - * {@inheritDoc} - */ - @Override - public @Nullable Teleporter getTeleporter() { - return null; - } } diff --git a/src/main/java/org/mvplugins/multiverse/core/event/MVTeleportEvent.java b/src/main/java/org/mvplugins/multiverse/core/event/MVTeleportEvent.java index ccf7f3f3..532213b3 100644 --- a/src/main/java/org/mvplugins/multiverse/core/event/MVTeleportEvent.java +++ b/src/main/java/org/mvplugins/multiverse/core/event/MVTeleportEvent.java @@ -14,8 +14,8 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; import org.mvplugins.multiverse.core.destination.ParsedDestination; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; /** * Event that gets called when a player use the /mvtp command. @@ -89,8 +89,8 @@ public class MVTeleportEvent extends Event implements Cancellable { } /** - * Looks if this {@link MVTeleportEvent} is using the {@link SafeTTeleporter}. - * @return True if this {@link MVTeleportEvent} is using the {@link SafeTTeleporter}. + * Looks if this {@link MVTeleportEvent} is using the {@link AsyncSafetyTeleporter}. + * @return True if this {@link MVTeleportEvent} is using the {@link AsyncSafetyTeleporter}. */ public boolean isUsingSafeTTeleporter() { return useSafeTeleport; diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java index 7027d7ad..ecb28c01 100644 --- a/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java @@ -31,8 +31,9 @@ import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; + import org.mvplugins.multiverse.core.MultiverseCore; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; +import org.mvplugins.multiverse.core.api.BlockSafety; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; @@ -40,6 +41,7 @@ import org.mvplugins.multiverse.core.destination.ParsedDestination; import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.event.MVRespawnEvent; import org.mvplugins.multiverse.core.inject.InjectableListener; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.TeleportQueue; import org.mvplugins.multiverse.core.utils.result.ResultChain; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; @@ -56,7 +58,8 @@ public class MVPlayerListener implements InjectableListener { private final Plugin plugin; private final MVCoreConfig config; private final Provider worldManagerProvider; - private final SafeTTeleporter safetyTeleporter; + private final BlockSafety blockSafety; + private final AsyncSafetyTeleporter safetyTeleporter; private final Server server; private final TeleportQueue teleportQueue; private final MVEconomist economist; @@ -72,7 +75,8 @@ public class MVPlayerListener implements InjectableListener { MultiverseCore plugin, MVCoreConfig config, Provider worldManagerProvider, - SafeTTeleporter safetyTeleporter, + BlockSafety blockSafety, + AsyncSafetyTeleporter safetyTeleporter, Server server, TeleportQueue teleportQueue, MVEconomist economist, @@ -83,6 +87,7 @@ public class MVPlayerListener implements InjectableListener { this.plugin = plugin; this.config = config; this.worldManagerProvider = worldManagerProvider; + this.blockSafety = blockSafety; this.safetyTeleporter = safetyTeleporter; this.server = server; this.teleportQueue = teleportQueue; @@ -211,7 +216,7 @@ public class MVPlayerListener implements InjectableListener { } // Finally, teleport the player - safetyTeleporter.teleportAsync(getCommandManager().getCommandIssuer(player), player, joinDestination); + safetyTeleporter.teleportSafely(player, player, joinDestination); } /** @@ -300,7 +305,7 @@ public class MVPlayerListener implements InjectableListener { // REMEMBER! getTo MAY be NULL HERE!!! // If the player was actually outside of the portal, adjust the from location if (event.getFrom().getWorld().getBlockAt(event.getFrom()).getType() != Material.NETHER_PORTAL) { - Location newloc = this.safetyTeleporter.findPortalBlockNextTo(event.getFrom()); + Location newloc = blockSafety.findPortalBlockNextTo(event.getFrom()); // TODO: Fix this. Currently, we only check for PORTAL blocks. I'll have to figure out what // TODO: we want to do here. if (newloc != null) { @@ -352,7 +357,7 @@ public class MVPlayerListener implements InjectableListener { new Runnable() { @Override public void run() { - safetyTeleporter.safelyTeleportAsync(getCommandManager().getCommandIssuer(player), player, parsedDestination); + safetyTeleporter.teleportSafely(player, player, parsedDestination); } }, 1L); } diff --git a/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissions.java b/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissions.java index 2af85ac1..cf1e35bb 100644 --- a/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissions.java +++ b/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissions.java @@ -1,8 +1,32 @@ package org.mvplugins.multiverse.core.permissions; -public class CorePermissions { - public static String WORLD_ACCESS = "multiverse.access"; - public static String WORLD_EXEMPT = "multiverse.exempt"; - public static String GAMEMODE_BYPASS = "mv.bypass.gamemode"; - public static String PLAYERLIMIT_BYPASS = "mv.bypass.playerlimit"; +final class CorePermissions { + /** + * Permission to access a world. + */ + static final String WORLD_ACCESS = "multiverse.access"; + + /** + * Permission to bypass the entry fee of a world. + */ + static final String WORLD_EXEMPT = "multiverse.exempt"; + + /** + * Permission to bypass the gamemode of a world. + */ + static final String GAMEMODE_BYPASS = "mv.bypass.gamemode"; + + /** + * Permission to bypass the player limit of a world. + */ + static final String PLAYERLIMIT_BYPASS = "mv.bypass.playerlimit"; + + /** + * Permission to teleport to a destination. + */ + static final String TELEPORT = "multiverse.teleport"; + + private CorePermissions() { + // Prevent instantiation as this is a static utility class + } } diff --git a/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissionsChecker.java b/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissionsChecker.java index 17337e8e..9e934535 100644 --- a/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissionsChecker.java +++ b/src/main/java/org/mvplugins/multiverse/core/permissions/CorePermissionsChecker.java @@ -1,15 +1,28 @@ package org.mvplugins.multiverse.core.permissions; import com.dumptruckman.minecraft.util.Logging; +import jakarta.inject.Inject; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.api.Destination; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.destination.ParsedDestination; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.MultiverseWorld; @Service public class CorePermissionsChecker { + + private DestinationsProvider destinationsProvider; + + @Inject + CorePermissionsChecker(@NotNull DestinationsProvider destinationsProvider) { + this.destinationsProvider = destinationsProvider; + } + public boolean hasWorldAccessPermission(@NotNull CommandSender sender, @NotNull MultiverseWorld world) { return hasPermission(sender, concatPermission(CorePermissions.WORLD_ACCESS, world.getName())); } @@ -26,7 +39,58 @@ public class CorePermissionsChecker { return hasPermission(sender, concatPermission(CorePermissions.GAMEMODE_BYPASS, world.getName())); } - private String concatPermission(String permission, String...child) { + /** + * Checks if the teleporter has permission to teleport the teleportee to the destination. + * + * @param teleporter The teleporter. + * @param teleportee The teleportee. + * @param destination The destination. + * @return True if the teleporter has permission, false otherwise. + */ + public boolean checkTeleportPermissions( + @NotNull CommandSender teleporter, + @NotNull Entity teleportee, + @NotNull ParsedDestination destination) { + + String permission = concatPermission( + CorePermissions.TELEPORT, + teleportee.equals(teleporter) ? "self" : "other", + destination.getDestination().getIdentifier()); + if (!hasPermission(teleporter, permission)) { + return false; + } + + // TODO: Config whether to use finer permission + String finerPermissionSuffix = destination.getDestinationInstance().getFinerPermissionSuffix(); + if (finerPermissionSuffix == null || finerPermissionSuffix.isEmpty()) { + return true; + } + + String finerPermission = concatPermission(permission, finerPermissionSuffix); + return hasPermission(teleporter, finerPermission); + } + + /** + * Checks if the issuer has permission to teleport to at least one destination. + * + * @param sender The sender to check. + * @return True if the issuer has permission, false otherwise. + */ + public boolean hasAnyTeleportPermission(CommandSender sender) { + for (Destination destination : destinationsProvider.getDestinations()) { + String permission = concatPermission(CorePermissions.TELEPORT, "self", destination.getIdentifier()); + if (hasPermission(sender, permission)) { + return true; + } + permission = concatPermission(CorePermissions.TELEPORT, "other", destination.getIdentifier()); + if (hasPermission(sender, permission)) { + return true; + } + } + return false; + } + + private String concatPermission(String permission, String... child) { return permission + "." + String.join(".", child); } diff --git a/src/main/java/org/mvplugins/multiverse/core/teleportation/AsyncSafetyTeleporter.java b/src/main/java/org/mvplugins/multiverse/core/teleportation/AsyncSafetyTeleporter.java new file mode 100644 index 00000000..4ee84d1a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/teleportation/AsyncSafetyTeleporter.java @@ -0,0 +1,158 @@ +package org.mvplugins.multiverse.core.teleportation; + +import java.util.List; + +import com.dumptruckman.minecraft.util.Logging; +import io.papermc.lib.PaperLib; +import jakarta.inject.Inject; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jvnet.hk2.annotations.Service; + +import org.mvplugins.multiverse.core.api.BlockSafety; +import org.mvplugins.multiverse.core.destination.ParsedDestination; +import org.mvplugins.multiverse.core.utils.result.Async; +import org.mvplugins.multiverse.core.utils.result.AsyncAttempt; +import org.mvplugins.multiverse.core.utils.result.Attempt; + +/** + * Teleports entities safely and asynchronously. + */ +@Service +public class AsyncSafetyTeleporter { + private final BlockSafety blockSafety; + private final TeleportQueue teleportQueue; + + @Inject + AsyncSafetyTeleporter( + BlockSafety blockSafety, + TeleportQueue teleportQueue) { + this.blockSafety = blockSafety; + this.teleportQueue = teleportQueue; + } + + public AsyncAttempt teleportSafely( + @NotNull Entity teleportee, + @Nullable ParsedDestination destination) { + return teleportSafely(null, teleportee, destination); + } + + public Async>> teleportSafely( + @Nullable CommandSender teleporter, + @NotNull List teleportees, + @Nullable ParsedDestination destination) { + return AsyncAttempt.allOf(teleportees.stream() + .map(teleportee -> teleportSafely(teleporter, teleportee, destination)) + .toList()); + } + + public AsyncAttempt teleportSafely( + @Nullable CommandSender teleporter, + @NotNull Entity teleportee, + @Nullable ParsedDestination destination) { + if (destination == null) { + return AsyncAttempt.failure(TeleportResult.Failure.NULL_DESTINATION); + } + return destination.getDestination().checkTeleportSafety() + ? teleportSafely(teleporter, teleportee, destination.getLocation(teleportee)) + : teleport(teleporter, teleportee, destination.getLocation(teleportee)); + } + + public AsyncAttempt teleportSafely( + @NotNull Entity teleportee, + @Nullable Location location) { + return teleportSafely(null, teleportee, location); + } + + public AsyncAttempt teleportSafely( + @Nullable CommandSender teleporter, + @NotNull Entity teleportee, + @Nullable Location location) { + if (location == null) { + return AsyncAttempt.failure(TeleportResult.Failure.NULL_LOCATION); + } + Location safeLocation = blockSafety.getSafeLocation(location); + if (safeLocation == null) { + return AsyncAttempt.failure(TeleportResult.Failure.UNSAFE_LOCATION); + } + return teleport(teleporter, teleportee, safeLocation); + } + + public Async>> teleport( + @NotNull List teleportees, + @Nullable ParsedDestination destination) { + return AsyncAttempt.allOf(teleportees.stream() + .map(teleportee -> teleport(teleportee, destination)) + .toList()); + } + + public AsyncAttempt teleport( + @NotNull Entity teleportee, + @Nullable ParsedDestination destination) { + return teleport(null, teleportee, destination); + } + + public AsyncAttempt teleport( + @Nullable CommandSender teleporter, + @NotNull Entity teleportee, + @Nullable ParsedDestination destination) { + if (destination == null) { + return AsyncAttempt.failure(TeleportResult.Failure.NULL_DESTINATION); + } + return teleport(teleporter, teleportee, destination.getLocation(teleportee)); + } + + public Async>> teleport( + @NotNull List teleportees, + @Nullable Location location) { + return AsyncAttempt.allOf(teleportees.stream() + .map(teleportee -> teleport(teleportee, location)) + .toList()); + } + + public AsyncAttempt teleport( + @NotNull Entity teleportee, + @Nullable Location location) { + return teleport(null, teleportee, location); + } + + public AsyncAttempt teleport( + @Nullable CommandSender teleporter, + @NotNull Entity teleportee, + @Nullable Location location) { + if (location == null) { + return AsyncAttempt.failure(TeleportResult.Failure.NULL_LOCATION); + } + + boolean shouldAddToQueue = teleporter != null && teleportee instanceof Player; + if (shouldAddToQueue) { + teleportQueue.addToQueue(teleporter.getName(), teleportee.getName()); + } + + return doAsyncTeleport(teleportee, location, shouldAddToQueue); + } + + private AsyncAttempt doAsyncTeleport( + @NotNull Entity teleportee, + @NotNull Location location, + boolean shouldAddToQueue) { + return AsyncAttempt.of(PaperLib.teleportAsync(teleportee, location), exception -> { + Logging.warning("Failed to teleport %s to %s: %s", + teleportee.getName(), location, exception.getMessage()); + return Attempt.failure(TeleportResult.Failure.TELEPORT_FAILED_EXCEPTION); + }).mapAttempt(result -> { + Logging.finer("Teleported async %s to %s", teleportee.getName(), location); + if (result) { + if (shouldAddToQueue) { + teleportQueue.popFromQueue(teleportee.getName()); + } + return Attempt.success(null); + } + return Attempt.failure(TeleportResult.Failure.TELEPORT_FAILED); + }); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleBlockSafety.java b/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleBlockSafety.java index e6031815..3195db24 100644 --- a/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleBlockSafety.java +++ b/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleBlockSafety.java @@ -8,7 +8,6 @@ package org.mvplugins.multiverse.core.teleportation; import java.util.EnumSet; -import java.util.Iterator; import java.util.Set; import com.dumptruckman.minecraft.util.Logging; @@ -16,11 +15,14 @@ import jakarta.inject.Inject; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Bed; import org.bukkit.entity.Minecart; import org.bukkit.entity.Vehicle; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.BlockSafety; @@ -31,9 +33,13 @@ import org.mvplugins.multiverse.core.api.LocationManipulation; */ @Service public class SimpleBlockSafety implements BlockSafety { - private final LocationManipulation locationManipulation; + private static final Vector DEFAULT_VECTOR = new Vector(); + private static final int DEFAULT_TOLERANCE = 6; + private static final int DEFAULT_RADIUS = 9; private static final Set AROUND_BLOCK = EnumSet.noneOf(BlockFace.class); + private final LocationManipulation locationManipulation; + static { AROUND_BLOCK.add(BlockFace.NORTH); AROUND_BLOCK.add(BlockFace.NORTH_EAST); @@ -46,7 +52,7 @@ public class SimpleBlockSafety implements BlockSafety { } @Inject - public SimpleBlockSafety(LocationManipulation locationManipulation) { + SimpleBlockSafety(LocationManipulation locationManipulation) { this.locationManipulation = locationManipulation; } @@ -113,6 +119,197 @@ public class SimpleBlockSafety implements BlockSafety { return true; } + /** + * {@inheritDoc} + */ + @Override + public Location findPortalBlockNextTo(Location location) { + Block b = location.getWorld().getBlockAt(location); + Location foundLocation = null; + if (b.getType() == Material.NETHER_PORTAL) { + return location; + } + if (b.getRelative(BlockFace.NORTH).getType() == Material.NETHER_PORTAL) { + foundLocation = getCloserBlock(location, b.getRelative(BlockFace.NORTH).getLocation(), foundLocation); + } + if (b.getRelative(BlockFace.SOUTH).getType() == Material.NETHER_PORTAL) { + foundLocation = getCloserBlock(location, b.getRelative(BlockFace.SOUTH).getLocation(), foundLocation); + } + if (b.getRelative(BlockFace.EAST).getType() == Material.NETHER_PORTAL) { + foundLocation = getCloserBlock(location, b.getRelative(BlockFace.EAST).getLocation(), foundLocation); + } + if (b.getRelative(BlockFace.WEST).getType() == Material.NETHER_PORTAL) { + foundLocation = getCloserBlock(location, b.getRelative(BlockFace.WEST).getLocation(), foundLocation); + } + return foundLocation; + } + + private static Location getCloserBlock(Location source, Location blockA, Location blockB) { + // If B wasn't given, return a. + if (blockB == null) { + return blockA; + } + // Center our calculations + blockA.add(.5, 0, .5); + blockB.add(.5, 0, .5); + + // Retrieve the distance to the normalized blocks + double testA = source.distance(blockA); + double testB = source.distance(blockB); + + // Compare and return + if (testA <= testB) { + return blockA; + } + return blockB; + } + + /** + * {@inheritDoc} + */ + @Override + public @Nullable Location getSafeLocation(Location location) { + return this.getSafeLocation(location, DEFAULT_TOLERANCE, DEFAULT_RADIUS); + } + + /** + * {@inheritDoc} + */ + @Override + public @Nullable Location getSafeLocation(Location location, int tolerance, int radius) { + // Check around the player first in a configurable radius: + // TODO: Make this configurable + Location safe = checkAboveAndBelowLocation(location, tolerance, radius); + if (safe != null) { + safe.setX(safe.getBlockX() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck + safe.setZ(safe.getBlockZ() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck + Logging.fine("Hey! I found one: " + locationManipulation.strCoordsRaw(safe)); + } else { + Logging.fine("Uh oh! No safe place found!"); + } + return safe; + } + + private Location checkAboveAndBelowLocation(Location l, int tolerance, int radius) { + // Tolerance must be an even number: + if (tolerance % 2 != 0) { + tolerance += 1; + } + // We want half of it, so we can go up and down + tolerance /= 2; + Logging.finer("Given Location of: " + locationManipulation.strCoordsRaw(l)); + Logging.finer("Checking +-" + tolerance + " with a radius of " + radius); + + // For now this will just do a straight up block. + Location locToCheck = l.clone(); + // Check the main level + Location safe = this.checkAroundLocation(locToCheck, radius); + if (safe != null) { + return safe; + } + // We've already checked zero right above this. + int currentLevel = 1; + while (currentLevel <= tolerance) { + // Check above + locToCheck = l.clone(); + locToCheck.add(0, currentLevel, 0); + safe = this.checkAroundLocation(locToCheck, radius); + if (safe != null) { + return safe; + } + + // Check below + locToCheck = l.clone(); + locToCheck.subtract(0, currentLevel, 0); + safe = this.checkAroundLocation(locToCheck, radius); + if (safe != null) { + return safe; + } + currentLevel++; + } + + return null; + } + + /* + * For my crappy algorithm, radius MUST be odd. + */ + private Location checkAroundLocation(Location l, int diameter) { + if (diameter % 2 == 0) { + diameter += 1; + } + Location checkLoc = l.clone(); + + // Start at 3, the min diameter around a block + int loopcounter = 3; + while (loopcounter <= diameter) { + boolean foundSafeArea = checkAroundSpecificDiameter(checkLoc, loopcounter); + // If a safe area was found: + if (foundSafeArea) { + // Return the checkLoc, it is the safe location. + return checkLoc; + } + // Otherwise, let's reset our location + checkLoc = l.clone(); + // And increment the radius + loopcounter += 2; + } + return null; + } + + private boolean checkAroundSpecificDiameter(Location checkLoc, int circle) { + // Adjust the circle to get how many blocks to step out. + // A radius of 3 makes the block step 1 + // A radius of 5 makes the block step 2 + // A radius of 7 makes the block step 3 + // ... + int adjustedCircle = ((circle - 1) / 2); + checkLoc.add(adjustedCircle, 0, 0); + if (playerCanSpawnHereSafely(checkLoc)) { + return true; + } + // Now we go to the right that adjustedCircle many + for (int i = 0; i < adjustedCircle; i++) { + checkLoc.add(0, 0, 1); + if (playerCanSpawnHereSafely(checkLoc)) { + return true; + } + } + + // Then down adjustedCircle *2 + for (int i = 0; i < adjustedCircle * 2; i++) { + checkLoc.add(-1, 0, 0); + if (playerCanSpawnHereSafely(checkLoc)) { + return true; + } + } + + // Then left adjustedCircle *2 + for (int i = 0; i < adjustedCircle * 2; i++) { + checkLoc.add(0, 0, -1); + if (playerCanSpawnHereSafely(checkLoc)) { + return true; + } + } + + // Then up Then left adjustedCircle *2 + for (int i = 0; i < adjustedCircle * 2; i++) { + checkLoc.add(1, 0, 0); + if (playerCanSpawnHereSafely(checkLoc)) { + return true; + } + } + + // Then finish up by doing adjustedCircle - 1 + for (int i = 0; i < adjustedCircle - 1; i++) { + checkLoc.add(0, 0, 1); + if (playerCanSpawnHereSafely(checkLoc)) { + return true; + } + } + return false; + } + /** * {@inheritDoc} */ @@ -122,7 +319,7 @@ public class SimpleBlockSafety implements BlockSafety { if (l == null) { return null; } - final Location trySpawn = this.getSafeSpawnAroundABlock(l); + final Location trySpawn = this.getSafeSpawnAroundBlock(l); if (trySpawn != null) { return trySpawn; } @@ -131,18 +328,17 @@ public class SimpleBlockSafety implements BlockSafety { return null; } // Now we have 2 locations, check around each, if the type is bed, skip it. - return this.getSafeSpawnAroundABlock(otherBlock); + return this.getSafeSpawnAroundBlock(otherBlock); } /** * Find a safe spawn around a location. (N,S,E,W,NE,NW,SE,SW) + * * @param l Location to check around * @return A safe location, or none if it wasn't found. */ - private Location getSafeSpawnAroundABlock(Location l) { - Iterator checkblock = AROUND_BLOCK.iterator(); - while (checkblock.hasNext()) { - final BlockFace face = checkblock.next(); + private Location getSafeSpawnAroundBlock(Location l) { + for (BlockFace face : AROUND_BLOCK) { if (this.playerCanSpawnHereSafely(l.getBlock().getRelative(face).getLocation())) { // Don't forget to center the player. return l.getBlock().getRelative(face).getLocation().add(.5, 0, .5); @@ -158,16 +354,15 @@ public class SimpleBlockSafety implements BlockSafety { */ private Location findOtherBedPiece(Location checkLoc) { BlockData data = checkLoc.getBlock().getBlockData(); - if (!(data instanceof Bed)) { + if (!(data instanceof Bed bed)) { return null; } - Bed b = (Bed) data; - if (b.getPart() == Bed.Part.HEAD) { - return checkLoc.getBlock().getRelative(b.getFacing().getOppositeFace()).getLocation(); + if (bed.getPart() == Bed.Part.HEAD) { + return checkLoc.getBlock().getRelative(bed.getFacing().getOppositeFace()).getLocation(); } // We shouldn't ever be looking at the foot, but here's the code for it. - return checkLoc.getBlock().getRelative(b.getFacing()).getLocation(); + return checkLoc.getBlock().getRelative(bed.getFacing()).getLocation(); } @@ -206,7 +401,7 @@ public class SimpleBlockSafety implements BlockSafety { /* * If someone has a better way of this... Please either tell us, or submit a pull request! */ - public static boolean isSolidBlock(Material type) { + private static boolean isSolidBlock(Material type) { return type.isSolid(); } @@ -237,7 +432,7 @@ public class SimpleBlockSafety implements BlockSafety { if (oneBelow.getBlock().getType() == Material.WATER) { Location twoBelow = oneBelow.clone(); twoBelow.subtract(0, 1, 0); - return (oneBelow.getBlock().getType() == Material.WATER); + return oneBelow.getBlock().getType() == Material.WATER; } if (oneBelow.getBlock().getType() != Material.AIR) { return false; @@ -253,10 +448,7 @@ public class SimpleBlockSafety implements BlockSafety { if (this.isBlockAboveAir(cart.getLocation())) { return true; } - if (this.isEntitiyOnTrack(locationManipulation.getNextBlock(cart))) { - return true; - } - return false; + return this.isEntitiyOnTrack(locationManipulation.getNextBlock(cart)); } /** @@ -264,10 +456,7 @@ public class SimpleBlockSafety implements BlockSafety { */ @Override public boolean canSpawnVehicleSafely(Vehicle vehicle) { - if (this.isBlockAboveAir(vehicle.getLocation())) { - return true; - } - return false; + return this.isBlockAboveAir(vehicle.getLocation()); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleSafeTTeleporter.java b/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleSafeTTeleporter.java deleted file mode 100644 index 8e319953..00000000 --- a/src/main/java/org/mvplugins/multiverse/core/teleportation/SimpleSafeTTeleporter.java +++ /dev/null @@ -1,378 +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.core.teleportation; - -import java.util.concurrent.CompletableFuture; - -import co.aikar.commands.BukkitCommandIssuer; -import com.dumptruckman.minecraft.util.Logging; -import io.papermc.lib.PaperLib; -import jakarta.inject.Inject; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Minecart; -import org.bukkit.entity.Player; -import org.bukkit.entity.Vehicle; -import org.bukkit.util.Vector; -import org.jvnet.hk2.annotations.Service; - -import org.mvplugins.multiverse.core.MultiverseCore; -import org.mvplugins.multiverse.core.api.BlockSafety; -import org.mvplugins.multiverse.core.api.DestinationInstance; -import org.mvplugins.multiverse.core.api.LocationManipulation; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; -import org.mvplugins.multiverse.core.destination.ParsedDestination; - -/** - * The default-implementation of {@link SafeTTeleporter}. - */ -@Service -public class SimpleSafeTTeleporter implements SafeTTeleporter { - private final MultiverseCore plugin; - private final LocationManipulation locationManipulation; - private final BlockSafety blockSafety; - private final TeleportQueue teleportQueue; - - @Inject - public SimpleSafeTTeleporter( - MultiverseCore plugin, - LocationManipulation locationManipulation, - BlockSafety blockSafety, - TeleportQueue teleportQueue - ) { - this.plugin = plugin; - this.locationManipulation = locationManipulation; - this.blockSafety = blockSafety; - this.teleportQueue = teleportQueue; - } - - private static final Vector DEFAULT_VECTOR = new Vector(); - private static final int DEFAULT_TOLERANCE = 6; - private static final int DEFAULT_RADIUS = 9; - - /** - * {@inheritDoc} - */ - @Override - public Location getSafeLocation(Location l) { - return this.getSafeLocation(l, DEFAULT_TOLERANCE, DEFAULT_RADIUS); - } - - /** - * {@inheritDoc} - */ - @Override - public Location getSafeLocation(Location l, int tolerance, int radius) { - // Check around the player first in a configurable radius: - // TODO: Make this configurable - Location safe = checkAboveAndBelowLocation(l, tolerance, radius); - if (safe != null) { - safe.setX(safe.getBlockX() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck - safe.setZ(safe.getBlockZ() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck - Logging.fine("Hey! I found one: " + locationManipulation.strCoordsRaw(safe)); - } else { - Logging.fine("Uh oh! No safe place found!"); - } - return safe; - } - - private Location checkAboveAndBelowLocation(Location l, int tolerance, int radius) { - // Tolerance must be an even number: - if (tolerance % 2 != 0) { - tolerance += 1; - } - // We want half of it, so we can go up and down - tolerance /= 2; - Logging.finer("Given Location of: " + locationManipulation.strCoordsRaw(l)); - Logging.finer("Checking +-" + tolerance + " with a radius of " + radius); - - // For now this will just do a straight up block. - Location locToCheck = l.clone(); - // Check the main level - Location safe = this.checkAroundLocation(locToCheck, radius); - if (safe != null) { - return safe; - } - // We've already checked zero right above this. - int currentLevel = 1; - while (currentLevel <= tolerance) { - // Check above - locToCheck = l.clone(); - locToCheck.add(0, currentLevel, 0); - safe = this.checkAroundLocation(locToCheck, radius); - if (safe != null) { - return safe; - } - - // Check below - locToCheck = l.clone(); - locToCheck.subtract(0, currentLevel, 0); - safe = this.checkAroundLocation(locToCheck, radius); - if (safe != null) { - return safe; - } - currentLevel++; - } - - return null; - } - - /* - * For my crappy algorithm, radius MUST be odd. - */ - private Location checkAroundLocation(Location l, int diameter) { - if (diameter % 2 == 0) { - diameter += 1; - } - Location checkLoc = l.clone(); - - // Start at 3, the min diameter around a block - int loopcounter = 3; - while (loopcounter <= diameter) { - boolean foundSafeArea = checkAroundSpecificDiameter(checkLoc, loopcounter); - // If a safe area was found: - if (foundSafeArea) { - // Return the checkLoc, it is the safe location. - return checkLoc; - } - // Otherwise, let's reset our location - checkLoc = l.clone(); - // And increment the radius - loopcounter += 2; - } - return null; - } - - private boolean checkAroundSpecificDiameter(Location checkLoc, int circle) { - // Adjust the circle to get how many blocks to step out. - // A radius of 3 makes the block step 1 - // A radius of 5 makes the block step 2 - // A radius of 7 makes the block step 3 - // ... - int adjustedCircle = ((circle - 1) / 2); - checkLoc.add(adjustedCircle, 0, 0); - if (blockSafety.playerCanSpawnHereSafely(checkLoc)) { - return true; - } - // Now we go to the right that adjustedCircle many - for (int i = 0; i < adjustedCircle; i++) { - checkLoc.add(0, 0, 1); - if (blockSafety.playerCanSpawnHereSafely(checkLoc)) { - return true; - } - } - - // Then down adjustedCircle *2 - for (int i = 0; i < adjustedCircle * 2; i++) { - checkLoc.add(-1, 0, 0); - if (blockSafety.playerCanSpawnHereSafely(checkLoc)) { - return true; - } - } - - // Then left adjustedCircle *2 - for (int i = 0; i < adjustedCircle * 2; i++) { - checkLoc.add(0, 0, -1); - if (blockSafety.playerCanSpawnHereSafely(checkLoc)) { - return true; - } - } - - // Then up Then left adjustedCircle *2 - for (int i = 0; i < adjustedCircle * 2; i++) { - checkLoc.add(1, 0, 0); - if (blockSafety.playerCanSpawnHereSafely(checkLoc)) { - return true; - } - } - - // Then finish up by doing adjustedCircle - 1 - for (int i = 0; i < adjustedCircle - 1; i++) { - checkLoc.add(0, 0, 1); - if (blockSafety.playerCanSpawnHereSafely(checkLoc)) { - return true; - } - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public TeleportResult safelyTeleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination) { - return safelyTeleportAsync(teleporter, teleportee, destination).join(); - } - - @Override - public CompletableFuture safelyTeleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination) { - if (destination == null) { - Logging.finer("Entity tried to teleport to an invalid destination"); - return CompletableFuture.completedFuture(TeleportResult.FAIL_INVALID); - } - - Player teleporteePlayer = null; - if (teleportee instanceof Player) { - teleporteePlayer = ((Player) teleportee); - } else if (teleportee.getPassenger() instanceof Player) { - teleporteePlayer = ((Player) teleportee.getPassenger()); - } - - if (teleporteePlayer == null) { - return CompletableFuture.completedFuture(TeleportResult.FAIL_INVALID); - } - - teleportQueue.addToQueue(teleporter.getIssuer().getName(), teleporteePlayer.getName()); - - Location safeLoc = destination.getLocation(teleportee); - if (destination.getDestination().checkTeleportSafety()) { - safeLoc = this.getSafeLocation(teleportee, destination.getDestinationInstance()); - } - - if (safeLoc == null) { - return CompletableFuture.completedFuture(TeleportResult.FAIL_UNSAFE); - } - - CompletableFuture future = new CompletableFuture<>(); - - PaperLib.teleportAsync(teleportee, safeLoc).thenAccept(result -> { - if (!result) { - future.complete(TeleportResult.FAIL_OTHER); - return; - } - Vector v = destination.getDestinationInstance().getVelocity(teleportee); - if (v != null && !DEFAULT_VECTOR.equals(v)) { - Bukkit.getScheduler().runTaskLater(this.plugin, () -> { - teleportee.setVelocity(v); - }, 1); - } - future.complete(TeleportResult.SUCCESS); - }); - - return future; - } - - /** - * {@inheritDoc} - */ - @Override - public TeleportResult safelyTeleport(CommandSender teleporter, Entity teleportee, Location location, boolean safely) { - if (safely) { - location = this.getSafeLocation(location); - } - - if (location != null) { - if (teleportee.teleport(location)) { - return TeleportResult.SUCCESS; - } - return TeleportResult.FAIL_OTHER; - } - return TeleportResult.FAIL_UNSAFE; - } - - @Override - public Location getSafeLocation(Entity entity, DestinationInstance destination) { - Location l = destination.getLocation(entity); - if (blockSafety.playerCanSpawnHereSafely(l)) { - Logging.fine("The first location you gave me was safe."); - return l; - } - if (entity instanceof Minecart) { - Minecart m = (Minecart) entity; - if (!blockSafety.canSpawnCartSafely(m)) { - return null; - } - } else if (entity instanceof Vehicle) { - Vehicle v = (Vehicle) entity; - if (!blockSafety.canSpawnVehicleSafely(v)) { - return null; - } - } - Location safeLocation = this.getSafeLocation(l); - if (safeLocation != null) { - // Add offset to account for a vehicle on dry land! - if (entity instanceof Minecart && !blockSafety.isEntitiyOnTrack(safeLocation)) { - safeLocation.setY(safeLocation.getBlockY() + .5); - Logging.finer("Player was inside a minecart. Offsetting Y location."); - } - Logging.finer("Had to look for a bit, but I found a safe place for ya!"); - return safeLocation; - } - if (entity instanceof Player) { - Player p = (Player) entity; - p.sendMessage("No safe locations found!"); - Logging.finer("No safe location found for " + p.getName()); - } else if (entity.getPassenger() instanceof Player) { - Player p = (Player) entity.getPassenger(); - p.sendMessage("No safe locations found!"); - Logging.finer("No safe location found for " + p.getName()); - } - Logging.fine("Sorry champ, you're basically trying to teleport into a minefield. I should just kill you now."); - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public Location findPortalBlockNextTo(Location l) { - Block b = l.getWorld().getBlockAt(l); - Location foundLocation = null; - if (b.getType() == Material.NETHER_PORTAL) { - return l; - } - if (b.getRelative(BlockFace.NORTH).getType() == Material.NETHER_PORTAL) { - foundLocation = getCloserBlock(l, b.getRelative(BlockFace.NORTH).getLocation(), foundLocation); - } - if (b.getRelative(BlockFace.SOUTH).getType() == Material.NETHER_PORTAL) { - foundLocation = getCloserBlock(l, b.getRelative(BlockFace.SOUTH).getLocation(), foundLocation); - } - if (b.getRelative(BlockFace.EAST).getType() == Material.NETHER_PORTAL) { - foundLocation = getCloserBlock(l, b.getRelative(BlockFace.EAST).getLocation(), foundLocation); - } - if (b.getRelative(BlockFace.WEST).getType() == Material.NETHER_PORTAL) { - foundLocation = getCloserBlock(l, b.getRelative(BlockFace.WEST).getLocation(), foundLocation); - } - return foundLocation; - } - - private static Location getCloserBlock(Location source, Location blockA, Location blockB) { - // If B wasn't given, return a. - if (blockB == null) { - return blockA; - } - // Center our calculations - blockA.add(.5, 0, .5); - blockB.add(.5, 0, .5); - - // Retrieve the distance to the normalized blocks - double testA = source.distance(blockA); - double testB = source.distance(blockB); - - // Compare and return - if (testA <= testB) { - return blockA; - } - return blockB; - } - - @Override - public TeleportResult teleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination) { - return safelyTeleport(teleporter, teleportee, destination); - } - - @Override - public CompletableFuture teleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination destination) { - return safelyTeleportAsync(teleporter, teleportee, destination); - } -} diff --git a/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java b/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java index c5fa68b4..99077226 100644 --- a/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java +++ b/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java @@ -12,7 +12,7 @@ public class TeleportQueue { private final Map teleportQueue; - public TeleportQueue() { + TeleportQueue() { teleportQueue = new HashMap<>(); } diff --git a/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportResult.java b/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportResult.java index c72992b3..93b37ff9 100644 --- a/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportResult.java +++ b/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportResult.java @@ -1,38 +1,19 @@ -/****************************************************************************** - * 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.core.teleportation; -/** - * An enum containing possible teleport-results. - */ -public enum TeleportResult { - /** - * Insufficient permissions. - */ - FAIL_PERMISSION, - /** - * The teleport was unsafe. - */ - FAIL_UNSAFE, - /** - * The player was to poor. - */ - FAIL_TOO_POOR, - /** - * The teleport was invalid. - */ - FAIL_INVALID, - /** - * Unknown reason. - */ - FAIL_OTHER, - /** - * The player was successfully teleported. - */ - SUCCESS +import org.mvplugins.multiverse.core.utils.result.FailureReason; +import org.mvplugins.multiverse.core.utils.result.SuccessReason; + +public class TeleportResult { + public enum Success implements SuccessReason { + SUCCESS + } + + public enum Failure implements FailureReason { + NULL_DESTINATION, + NULL_LOCATION, + UNSAFE_LOCATION, + TELEPORT_FAILED, + TELEPORT_FAILED_EXCEPTION, + PLAYER_OFFLINE, + } } diff --git a/src/main/java/org/mvplugins/multiverse/core/utils/result/Async.java b/src/main/java/org/mvplugins/multiverse/core/utils/result/Async.java new file mode 100644 index 00000000..4718a2f8 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/utils/result/Async.java @@ -0,0 +1,163 @@ +package org.mvplugins.multiverse.core.utils.result; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Represents the result of an asynchronous operation that wraps a {@link CompletableFuture}. + * + * @param The type of the value. + */ +public final class Async { + + /** + * Returns a new AsyncResult that is completed when all of the given AsyncResult complete. + * + * @param results The results to wait for. + * @return A new AsyncResult that is completed when all of the given AsyncResult complete. + */ + public static Async allOf(Async... results) { + return new Async<>(CompletableFuture.allOf(Arrays.stream(results) + .map(result -> result.future) + .toArray(CompletableFuture[]::new))); + } + + /** + * Returns a new AsyncResult that is completed when all of the given AsyncResult with the same type complete. + * + * @param results The results to wait for. + * @param The type of the AsyncResult. + * @return A new AsyncResult that is completed when all of the given AsyncResult complete. + */ + public static Async> allOf(List> results) { + return new Async<>(CompletableFuture.allOf(results.stream() + .map(result -> result.future) + .toArray(CompletableFuture[]::new)) + .thenApply(v -> results.stream() + .map(result -> result.future.join()) + .toList())); + } + + /** + * Wraps a CompletableFuture in an AsyncResult. + * + * @param future The future to wrap. + * @param The type of the future. + * @return A new AsyncResult that is completed when the given future completes. + */ + public static Async of(CompletableFuture future) { + return new Async<>(future); + } + + /** + * Returns a new AsyncResult that is already completed with the given value. + * + * @param value The value to complete the AsyncResult with. + * @param The type of the value. + * @return The completed AsyncResult. + */ + public static Async completedFuture(T value) { + return new Async<>(CompletableFuture.completedFuture(value)); + } + + /** + * Returns a new CompletableFuture that is already completed exceptionally with the given exception. + * + * @param throwable The exception to complete the AsyncResult with. + * @param The type of the value. + * @return The completed AsyncResult. + */ + public static Async failedFuture(Throwable throwable) { + return new Async<>(CompletableFuture.failedFuture(throwable)); + } + + private final CompletableFuture future; + + /** + * Creates a new AsyncResult. + */ + public Async() { + this(new CompletableFuture<>()); + } + + private Async(CompletableFuture future) { + this.future = future; + } + + /** + * If not already completed, sets the value returned by related methods to the given value. + * + * @param value The value to complete the AsyncResult with. + * @return true if this invocation caused this AsyncResult to transition to a completed state, else false. + */ + public boolean complete(T value) { + return future.complete(value); + } + + /** + * If not already completed, causes invocations of related methods to throw the given exception. + * + * @param throwable The exception to complete the AsyncResult with. + * @return true if this invocation caused this AsyncResult to transition to a completed state, else false. + */ + public boolean completeExceptionally(Throwable throwable) { + return future.completeExceptionally(throwable); + } + + /** + * Executes the given action when this AsyncResult completes. + * + * @param consumer The action to perform. + * @return This AsyncResult. + */ + public Async thenAccept(Consumer consumer) { + return new Async<>(future.thenAccept(consumer)); + } + + /** + * Executes the given action when this AsyncResult completes. + * + * @param runnable The action to perform. + * @return This AsyncResult. + */ + public Async thenRun(Runnable runnable) { + return new Async<>(future.thenRun(runnable)); + } + + /** + * Executes the given action when this AsyncResult completes and returns a new AsyncResult with the new value. + * + * @param function The action to perform. + * @param The type of the new value. + * @return A new AsyncResult with the new value. + */ + public Async thenApply(Function function) { + return new Async<>(future.thenApply(function)); + } + + /** + * Executes the given action when this AsyncResult completes with an exception. + * + * @param consumer The action to perform. + * @return This AsyncResult. + */ + public Async exceptionally(Consumer consumer) { + return new Async<>(future.exceptionally(throwable -> { + consumer.accept(throwable); + return null; + })); + } + + /** + * Executes the given action when this AsyncResult completes with an exception and returns a new AsyncResult with the new value. + * + * @param function The action to perform. + * @return A new AsyncResult with the new value. + */ + public Async exceptionally(Function function) { + return new Async<>(future.exceptionally(function)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/utils/result/AsyncAttempt.java b/src/main/java/org/mvplugins/multiverse/core/utils/result/AsyncAttempt.java new file mode 100644 index 00000000..67b8a9a0 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/utils/result/AsyncAttempt.java @@ -0,0 +1,73 @@ +package org.mvplugins.multiverse.core.utils.result; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.mvplugins.multiverse.core.utils.message.MessageReplacement; + +public final class AsyncAttempt { + + public static Async>> allOf(List> attempts) { + return Async.of(CompletableFuture.allOf(attempts.stream() + .map(attempt -> attempt.future) + .toArray(CompletableFuture[]::new)) + .thenApply(v -> attempts.stream() + .map(attempt -> attempt.future.join()) + .toList())); + } + + public static AsyncAttempt of( + CompletableFuture future, + BiFunction> completionHandler) { + return new AsyncAttempt<>(future.handle(completionHandler)); + } + + public static AsyncAttempt of( + CompletableFuture future, + Function> exceptionHandler) { + BiFunction> completionHandler = (result, exception) -> exception != null + ? exceptionHandler.apply(exception) + : Attempt.success(result); + return of(future, completionHandler); + } + + public static AsyncAttempt success() { + return new AsyncAttempt<>(CompletableFuture.completedFuture(null)); + } + + public static AsyncAttempt failure( + F failureReason, + MessageReplacement... messageReplacements) { + return new AsyncAttempt<>(CompletableFuture.completedFuture( + Attempt.failure(failureReason, messageReplacements))); + } + + private final CompletableFuture> future; + + private AsyncAttempt(CompletableFuture> future) { + this.future = future; + } + + public AsyncAttempt map(Function mapper) { + return new AsyncAttempt<>(future.thenApply(attempt -> attempt.map(mapper))); + } + + public AsyncAttempt mapAttempt(Function> mapper) { + return new AsyncAttempt<>(future.thenApply(attempt -> attempt.mapAttempt(mapper))); + } + + public AsyncAttempt mapAsyncAttempt(Function> mapper) { + return new AsyncAttempt<>(future.thenApplyAsync( + attempt -> attempt.mapAttempt(rasult -> mapper.apply(rasult).toAttempt()))); + } + + public AsyncAttempt onSuccess(Runnable runnable) { + return new AsyncAttempt<>(future.thenApply(attempt -> attempt.onSuccess(runnable))); + } + + public Attempt toAttempt() { + return future.join(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/core/world/LoadedMultiverseWorld.java b/src/main/java/org/mvplugins/multiverse/core/world/LoadedMultiverseWorld.java index 1e86205e..933c7032 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/LoadedMultiverseWorld.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/LoadedMultiverseWorld.java @@ -14,7 +14,6 @@ import org.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.core.api.BlockSafety; import org.mvplugins.multiverse.core.api.LocationManipulation; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; import org.mvplugins.multiverse.core.world.config.NullLocation; import org.mvplugins.multiverse.core.world.config.SpawnLocation; import org.mvplugins.multiverse.core.world.config.WorldConfig; @@ -29,19 +28,16 @@ public class LoadedMultiverseWorld extends MultiverseWorld { private final UUID worldUid; private final BlockSafety blockSafety; - private final SafeTTeleporter safetyTeleporter; private final LocationManipulation locationManipulation; LoadedMultiverseWorld( @NotNull World world, @NotNull WorldConfig worldConfig, @NotNull BlockSafety blockSafety, - @NotNull SafeTTeleporter safetyTeleporter, @NotNull LocationManipulation locationManipulation) { super(world.getName(), worldConfig); this.worldUid = world.getUID(); this.blockSafety = blockSafety; - this.safetyTeleporter = safetyTeleporter; this.locationManipulation = locationManipulation; setupWorldConfig(world); @@ -82,7 +78,7 @@ public class LoadedMultiverseWorld extends MultiverseWorld { // The location is not safe, so we need to find a better one. Logging.warning("Spawn location from world.dat file was unsafe. Adjusting..."); Logging.warning("Original Location: " + locationManipulation.strCoordsRaw(location)); - Location newSpawn = safetyTeleporter.getSafeLocation(location, + Location newSpawn = blockSafety.getSafeLocation(location, SPAWN_LOCATION_SEARCH_TOLERANCE, SPAWN_LOCATION_SEARCH_RADIUS); // I think we could also do this, as I think this is what Notch does. // Not sure how it will work in the nether... 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 1d5d1777..6e1a31e8 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java @@ -27,7 +27,6 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.api.BlockSafety; import org.mvplugins.multiverse.core.api.LocationManipulation; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; import org.mvplugins.multiverse.core.utils.message.MessageReplacement; import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.core.utils.result.FailureReason; @@ -75,7 +74,6 @@ public class WorldManager { private final PlayerWorldTeleporter playerWorldActions; private final FilesManipulator filesManipulator; private final BlockSafety blockSafety; - private final SafeTTeleporter safetyTeleporter; private final LocationManipulation locationManipulation; @Inject @@ -86,7 +84,6 @@ public class WorldManager { @NotNull PlayerWorldTeleporter playerWorldActions, @NotNull FilesManipulator filesManipulator, @NotNull BlockSafety blockSafety, - @NotNull SafeTTeleporter safetyTeleporter, @NotNull LocationManipulation locationManipulation) { this.worldsMap = new HashMap<>(); this.loadedWorldsMap = new HashMap<>(); @@ -99,7 +96,6 @@ public class WorldManager { this.playerWorldActions = playerWorldActions; this.filesManipulator = filesManipulator; this.blockSafety = blockSafety; - this.safetyTeleporter = safetyTeleporter; this.locationManipulation = locationManipulation; } @@ -297,7 +293,6 @@ public class WorldManager { world, worldConfig, blockSafety, - safetyTeleporter, locationManipulation); loadedWorldsMap.put(loadedWorld.getName(), loadedWorld); saveWorldsConfig(); @@ -354,7 +349,6 @@ public class WorldManager { world, worldConfig, blockSafety, - safetyTeleporter, locationManipulation); loadedWorldsMap.put(loadedWorld.getName(), loadedWorld); saveWorldsConfig(); @@ -377,10 +371,6 @@ public class WorldManager { return worldActionResult(UnloadFailureReason.WORLD_ALREADY_UNLOADING, world.getName()); } - if (options.removePlayers()) { - playerWorldActions.removeFromWorld(world); - } - return unloadBukkitWorld(world.getBukkitWorld().getOrNull(), options.saveBukkitWorld()).fold( exception -> worldActionResult(UnloadFailureReason.BUKKIT_UNLOAD_FAILED, world.getName(), exception), @@ -432,7 +422,7 @@ public class WorldManager { */ public Attempt removeWorld(@NotNull LoadedMultiverseWorld loadedWorld) { // TODO: Config option on removePlayers - return unloadWorld(UnloadWorldOptions.world(loadedWorld).removePlayers(true)) + return unloadWorld(UnloadWorldOptions.world(loadedWorld)) .transform(RemoveFailureReason.UNLOAD_FAILED) .mapAttempt(this::removeWorldFromConfig); } @@ -594,7 +584,6 @@ public class WorldManager { */ public Attempt regenWorld(@NotNull RegenWorldOptions options) { LoadedMultiverseWorld world = options.world(); - List playersInWorld = world.getPlayers().getOrElse(Collections.emptyList()); DataTransfer dataTransfer = transferData(options, world); boolean shouldKeepSpawnLocation = options.keepWorldConfig() && options.seed() == world.getSeed(); Location spawnLocation = world.getSpawnLocation(); @@ -617,7 +606,6 @@ public class WorldManager { // different seed. newWorld.setSpawnLocation(spawnLocation); } - playerWorldActions.teleportPlayersToWorld(playersInWorld, newWorld); saveWorldsConfig(); }); } @@ -681,12 +669,13 @@ public class WorldManager { unloadTracker.add(world.getName()); if (!Bukkit.unloadWorld(world, save)) { // TODO: Localize this, maybe with MultiverseException + if (!world.getPlayers().isEmpty()) { + throw new Exception("There are still players in the world! Please use --remove-players flag to " + + "your command if wish to teleport all players out of the world."); + } throw new Exception("Is this the default world? You can't unload the default world!"); } Logging.fine("Bukkit unloaded world: " + world.getName()); - }).onFailure(exception -> { - Logging.severe("Failed to unload bukkit world: " + world.getName()); - exception.printStackTrace(); }).andFinally(() -> unloadTracker.remove(world.getName())); } diff --git a/src/main/java/org/mvplugins/multiverse/core/world/helpers/PlayerWorldTeleporter.java b/src/main/java/org/mvplugins/multiverse/core/world/helpers/PlayerWorldTeleporter.java index 15160668..27bc87bf 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/helpers/PlayerWorldTeleporter.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/helpers/PlayerWorldTeleporter.java @@ -2,7 +2,6 @@ package org.mvplugins.multiverse.core.world.helpers; import java.util.List; -import com.dumptruckman.minecraft.util.Logging; import jakarta.inject.Inject; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -11,7 +10,10 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.api.SafeTTeleporter; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.TeleportResult; +import org.mvplugins.multiverse.core.utils.result.Async; +import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; /** @@ -19,10 +21,10 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; */ @Service public class PlayerWorldTeleporter { - private final SafeTTeleporter safetyTeleporter; + private final AsyncSafetyTeleporter safetyTeleporter; @Inject - PlayerWorldTeleporter(@NotNull SafeTTeleporter safetyTeleporter) { + PlayerWorldTeleporter(@NotNull AsyncSafetyTeleporter safetyTeleporter) { this.safetyTeleporter = safetyTeleporter; } @@ -30,11 +32,12 @@ public class PlayerWorldTeleporter { * Removes all players from the given world. * * @param world The world to remove all players from. + * @return A list of async futures that represent the teleportation result of each player. */ - public void removeFromWorld(@NotNull LoadedMultiverseWorld world) { + public Async>> removeFromWorld(@NotNull LoadedMultiverseWorld world) { // TODO: Better handling of fallback world World toWorld = Bukkit.getWorlds().get(0); - transferFromWorldTo(world, toWorld); + return transferFromWorldTo(world, toWorld); } /** @@ -42,9 +45,12 @@ public class PlayerWorldTeleporter { * * @param from The world to transfer players from. * @param to The location to transfer players to. + * @return A list of async futures that represent the teleportation result of each player. */ - public void transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull LoadedMultiverseWorld to) { - transferAllFromWorldToLocation(from, to.getSpawnLocation()); + public Async>> transferFromWorldTo( + @NotNull LoadedMultiverseWorld from, + @NotNull LoadedMultiverseWorld to) { + return transferAllFromWorldToLocation(from, to.getSpawnLocation()); } /** @@ -52,24 +58,28 @@ public class PlayerWorldTeleporter { * * @param from The world to transfer players from. * @param to The world to transfer players to. + * @return A list of async futures that represent the teleportation result of each player. */ - public void transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull World to) { - transferAllFromWorldToLocation(from, to.getSpawnLocation()); + public Async>> transferFromWorldTo( + @NotNull LoadedMultiverseWorld from, + @NotNull World to) { + return transferAllFromWorldToLocation(from, to.getSpawnLocation()); } /** * Transfers all players from the given world to the given location. * - * @param world The world to transfer players from. - * @param location The location to transfer players to. + * @param world The world to transfer players from. + * @param location The location to transfer players to. + * @return A list of async futures that represent the teleportation result of each player. */ - public void transferAllFromWorldToLocation(@NotNull LoadedMultiverseWorld world, @NotNull Location location) { - world.getPlayers().peek(players -> players.forEach(player -> { - if (player.isOnline()) { - Logging.fine("Teleporting player '%s' to world spawn: %s", player.getName(), location); - safetyTeleporter.safelyTeleport(null, player, location, true); - } - })); + public Async>> transferAllFromWorldToLocation( + @NotNull LoadedMultiverseWorld world, + @NotNull Location location) { + return world.getPlayers() + .map(players -> safetyTeleporter.teleport(players, location)) + .getOrElse(() -> Async.failedFuture( + new IllegalStateException("Unable to get players from world" + world.getName()))); } /** @@ -77,13 +87,12 @@ public class PlayerWorldTeleporter { * * @param players The players to teleport. * @param world The world to teleport players to. + * @return A list of async futures that represent the teleportation result of each player. */ - public void teleportPlayersToWorld(@NotNull List players, @NotNull LoadedMultiverseWorld world) { - players.forEach(player -> { - Location spawnLocation = world.getSpawnLocation(); - if (player.isOnline()) { - safetyTeleporter.safelyTeleport(null, player, spawnLocation, true); - } - }); + public Async>> teleportPlayersToWorld( + @NotNull List players, + @NotNull LoadedMultiverseWorld world) { + Location spawnLocation = world.getSpawnLocation(); + return safetyTeleporter.teleport(players, spawnLocation); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/world/options/UnloadWorldOptions.java b/src/main/java/org/mvplugins/multiverse/core/world/options/UnloadWorldOptions.java index f35abc4f..bd7324b4 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/options/UnloadWorldOptions.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/options/UnloadWorldOptions.java @@ -18,7 +18,6 @@ public class UnloadWorldOptions { } private final LoadedMultiverseWorld world; - private boolean removePlayers = false; private boolean saveBukkitWorld = true; UnloadWorldOptions(LoadedMultiverseWorld world) { @@ -34,26 +33,6 @@ public class UnloadWorldOptions { return world; } - /** - * Sets whether to teleport the players out from the world before unloading. - * - * @param removePlayersInput Whether to remove players from the world before unloading. - * @return This {@link UnloadWorldOptions} instance. - */ - public UnloadWorldOptions removePlayers(boolean removePlayersInput) { - this.removePlayers = removePlayersInput; - 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. * diff --git a/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt index d667baf4..0029b1c4 100644 --- a/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt @@ -5,16 +5,15 @@ import org.mvplugins.multiverse.core.anchor.AnchorManager import org.mvplugins.multiverse.core.api.BlockSafety import org.mvplugins.multiverse.core.api.Destination import org.mvplugins.multiverse.core.api.LocationManipulation -import org.mvplugins.multiverse.core.api.SafeTTeleporter import org.mvplugins.multiverse.core.commandtools.MVCommandManager import org.mvplugins.multiverse.core.commandtools.MultiverseCommand import org.mvplugins.multiverse.core.commandtools.PluginLocales import org.mvplugins.multiverse.core.config.MVCoreConfig import org.mvplugins.multiverse.core.economy.MVEconomist import org.mvplugins.multiverse.core.listeners.* +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter import org.mvplugins.multiverse.core.teleportation.SimpleBlockSafety import org.mvplugins.multiverse.core.teleportation.SimpleLocationManipulation -import org.mvplugins.multiverse.core.teleportation.SimpleSafeTTeleporter import org.mvplugins.multiverse.core.teleportation.TeleportQueue import org.mvplugins.multiverse.core.utils.metrics.MetricsConfigurator import org.mvplugins.multiverse.core.world.WorldManager @@ -27,6 +26,11 @@ class InjectionTest : TestWithMockBukkit() { assertNotNull(multiverseCore.getService(AnchorManager::class.java)) } + @Test + fun `AsyncSafetyTeleporter is available as a service`() { + assertNotNull(multiverseCore.getService(AsyncSafetyTeleporter::class.java)) + } + @Test fun `BlockSafety is available as a service`() { assertNotNull(multiverseCore.getService(BlockSafety::class.java)) @@ -49,12 +53,6 @@ class InjectionTest : TestWithMockBukkit() { assertNotNull(multiverseCore.getService(SimpleLocationManipulation::class.java)) } - @Test - fun `SafeTTeleporter is available as a service`() { - assertNotNull(multiverseCore.getService(SafeTTeleporter::class.java)) - assertNotNull(multiverseCore.getService(SimpleSafeTTeleporter::class.java)) - } - @Test fun `TeleportQueue is available as a service`() { assertNotNull(multiverseCore.getService(TeleportQueue::class.java))