Implement new AsyncSafetyTeleporter

This commit is contained in:
Ben Woo 2023-09-12 16:33:43 +08:00
parent 0e43444523
commit 245fac98e5
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
26 changed files with 587 additions and 669 deletions

View File

@ -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,34 @@ 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.
* @param radius The radius.
* @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 +73,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 +81,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 +89,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 +97,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 +105,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.
*/

View File

@ -44,15 +44,6 @@ public interface Destination<T extends DestinationInstance> {
*
* @return True if the SafeTeleporter will be used, false if not.
*/
// TODO: Check this in AsyncSafetyTeleporter
boolean checkTeleportSafety();
/**
* Returns the teleporter to use for this destination.
*
* <p>By default, Multiverse will automatically use SafeTeleporter. If you want to use a different teleporter, you can
* override this method.</p>
*
* @return The custom teleporter to use for this destination. Return null to use the default teleporter.
*/
@Nullable Teleporter getTeleporter();
}

View File

@ -14,6 +14,7 @@ import org.mvplugins.multiverse.core.teleportation.TeleportResult;
/**
* Used to safely teleport people.
*/
@Deprecated
@Contract
public interface SafeTTeleporter extends Teleporter {

View File

@ -9,6 +9,7 @@ import org.jvnet.hk2.annotations.Contract;
import org.mvplugins.multiverse.core.destination.ParsedDestination;
import org.mvplugins.multiverse.core.teleportation.TeleportResult;
@Deprecated
@Contract
public interface Teleporter {
/**

View File

@ -49,6 +49,6 @@ public 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);
}
}

View File

@ -3,7 +3,6 @@ package org.mvplugins.multiverse.core.commands;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
@ -14,24 +13,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")
public class TeleportCommand extends MultiverseCommand {
private final DestinationsProvider destinationsProvider;
private final CorePermissionsChecker permissionsChecker;
private final AsyncSafetyTeleporter safetyTeleporter;
@Inject
public TeleportCommand(MVCommandManager commandManager, DestinationsProvider destinationsProvider) {
public TeleportCommand(
@NotNull MVCommandManager commandManager,
@NotNull CorePermissionsChecker permissionsChecker,
@NotNull AsyncSafetyTeleporter safetyTeleporter) {
super(commandManager);
this.destinationsProvider = destinationsProvider;
this.permissionsChecker = permissionsChecker;
this.safetyTeleporter = safetyTeleporter;
}
@CommandAlias("mvtp")
@ -39,34 +46,40 @@ public class TeleportCommand extends MultiverseCommand {
@CommandCompletion("@players|@mvworlds:playerOnly|@destinations:playerOnly @mvworlds|@destinations")
@Syntax("[player] <destination>")
@Description("{@@mv-core.teleport.description}")
public void onTeleportCommand(BukkitCommandIssuer issuer,
public void onTeleportCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[player]")
@Description("{@@mv-core.teleport.player.description}")
Player[] players,
@Flags("resolve=issuerAware")
@Syntax("[player]")
@Description("{@@mv-core.teleport.player.description}")
Player[] players,
@Syntax("<destination>")
@Description("{@@mv-core.teleport.destination.description}")
ParsedDestination<?> destination
) {
@Syntax("<destination>")
@Description("{@@mv-core.teleport.destination.description}")
ParsedDestination<?> destination) {
// TODO: Add warning if teleporting too many players at once.
String playerName = players.length == 1
? 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))
.map(player -> safetyTeleporter.teleportSafely(issuer.getIssuer(), player, destination))
.toArray(CompletableFuture[]::new))
.thenRun(() -> Logging.finer("Async teleport completed."));
}
@Override
public boolean hasPermission(CommandIssuer issuer) {
return this.destinationsProvider.hasAnyTeleportPermission(issuer);
return permissionsChecker.hasAnyTeleportPermission(issuer.getIssuer());
}
}

View File

@ -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<String, Destination<?>> 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<TeleportResult> 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<TeleportResult> 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<Destination<?>> getDestinations() {
return this.destinationMap.values();
}
}

View File

@ -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<AnchorDestinationInstance>
public boolean checkTeleportSafety() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public @Nullable Teleporter getTeleporter() {
return null;
}
}

View File

@ -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<BedDestinationInstance> {
public boolean checkTeleportSafety() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public @Nullable Teleporter getTeleporter() {
return null;
}
}

View File

@ -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.worldnew.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.worldnew.WorldManager;
@ -97,12 +96,4 @@ public class CannonDestination implements Destination<CannonDestinationInstance>
public boolean checkTeleportSafety() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public @Nullable Teleporter getTeleporter() {
return null;
}
}

View File

@ -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.worldnew.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.worldnew.WorldManager;
@ -100,12 +99,4 @@ public class ExactDestination implements Destination<ExactDestinationInstance> {
public boolean checkTeleportSafety() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public @Nullable Teleporter getTeleporter() {
return null;
}
}

View File

@ -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<PlayerDestinationInstance>
public boolean checkTeleportSafety() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public @Nullable Teleporter getTeleporter() {
return null;
}
}

View File

@ -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.worldnew.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.worldnew.WorldManager;
@ -76,12 +75,4 @@ public class WorldDestination implements Destination<WorldDestinationInstance> {
public boolean checkTeleportSafety() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public @Nullable Teleporter getTeleporter() {
return null;
}
}

View File

@ -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,7 +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.permissions.CorePermissionsChecker;
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.worldnew.LoadedMultiverseWorld;
@ -48,7 +49,6 @@ import org.mvplugins.multiverse.core.worldnew.WorldManager;
import org.mvplugins.multiverse.core.worldnew.entrycheck.EntryFeeResult;
import org.mvplugins.multiverse.core.worldnew.entrycheck.WorldEntryCheckerProvider;
import org.mvplugins.multiverse.core.worldnew.helpers.EnforcementHandler;
import org.mvplugins.multiverse.core.worldnew.helpers.PlayerWorldTeleporter;
/**
* Multiverse's Listener for players.
@ -58,7 +58,8 @@ public class MVPlayerListener implements InjectableListener {
private final Plugin plugin;
private final MVCoreConfig config;
private final Provider<WorldManager> 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;
@ -74,7 +75,8 @@ public class MVPlayerListener implements InjectableListener {
MultiverseCore plugin,
MVCoreConfig config,
Provider<WorldManager> worldManagerProvider,
SafeTTeleporter safetyTeleporter,
BlockSafety blockSafety,
AsyncSafetyTeleporter safetyTeleporter,
Server server,
TeleportQueue teleportQueue,
MVEconomist economist,
@ -85,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;
@ -213,7 +216,7 @@ public class MVPlayerListener implements InjectableListener {
}
// Finally, teleport the player
safetyTeleporter.teleportAsync(getCommandManager().getCommandIssuer(player), player, joinDestination);
safetyTeleporter.teleportSafely(player, player, joinDestination);
}
/**
@ -302,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) {
@ -354,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);
}

View File

@ -5,4 +5,5 @@ public class CorePermissions {
public static String WORLD_EXEMPT = "multiverse.exempt";
public static String GAMEMODE_BYPASS = "mv.bypass.gamemode";
public static String PLAYERLIMIT_BYPASS = "mv.bypass.playerlimit";
public static final String TELEPORT = "multiverse.teleport";
}

View File

@ -1,16 +1,29 @@
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.api.MVWorld;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.destination.ParsedDestination;
import org.mvplugins.multiverse.core.worldnew.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.worldnew.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()));
}
@ -37,7 +50,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);
}

View File

@ -0,0 +1,142 @@
package org.mvplugins.multiverse.core.teleportation;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import com.dumptruckman.minecraft.util.Logging;
import io.papermc.lib.PaperLib;
import io.vavr.control.Try;
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.Result;
@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 CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
return teleportSafely(null, teleportee, destination);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
if (destination == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_DESTINATION));
}
return teleportSafely(teleporter, teleportee, destination.getLocation(teleportee));
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@NotNull Entity teleportee,
@Nullable Location location) {
return teleportSafely(null, teleportee, location);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable Location location) {
if (location == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_LOCATION));
}
Location safeLocation = blockSafety.getSafeLocation(location);
if (safeLocation == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.UNSAFE_LOCATION));
}
return teleport(teleporter, teleportee, safeLocation);
}
public <T extends Entity> List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>> teleport(
@NotNull List<T> teleportees,
@Nullable ParsedDestination<?> destination) {
return teleportees.stream()
.map(teleportee -> teleport(teleportee, destination))
.toList();
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
return teleport(null, teleportee, destination);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
if (destination == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_DESTINATION));
}
return teleport(teleporter, teleportee, destination.getLocation(teleportee));
}
public <T extends Entity> List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>> teleport(
@NotNull List<T> teleportees,
@Nullable Location location) {
return teleportees.stream()
.map(teleportee -> teleport(teleportee, location))
.toList();
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@NotNull Entity teleportee,
@Nullable Location location) {
return teleport(null, teleportee, location);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable Location location) {
if (location == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_LOCATION));
}
boolean shouldAddToQueue = teleporter != null && teleportee instanceof Player;
if (shouldAddToQueue) {
teleportQueue.addToQueue(teleporter.getName(), teleportee.getName());
}
CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> future = new CompletableFuture<>();
Try.run(() -> PaperLib.teleportAsync(teleportee, location).thenAccept(result -> {
Logging.fine("Teleported %s to %s", teleportee.getName(), location);
future.complete(result
? Result.success(TeleportResult.Success.SUCCESS)
: Result.failure(TeleportResult.Failure.TELEPORT_FAILED));
}).exceptionally(exception -> {
Logging.fine("Failed to teleport %s to %s: %s", teleportee.getName(), location, exception.getMessage());
future.completeExceptionally(exception);
return null;
})).onFailure(exception -> {
Logging.fine("Failed to teleport %s to %s: %s", teleportee.getName(), location, exception.getMessage());
future.complete(Result.failure(TeleportResult.Failure.TELEPORT_FAILED_EXCEPTION));
}).andFinally(() -> {
if (shouldAddToQueue) {
teleportQueue.popFromQueue(teleportee.getName());
}
});
return future;
}
}

View File

@ -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<BlockFace> 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<BlockFace> 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());
}
}

View File

@ -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<TeleportResult> 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<TeleportResult> 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<TeleportResult> teleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination) {
return safelyTeleportAsync(teleporter, teleportee, destination);
}
}

View File

@ -12,7 +12,7 @@ public class TeleportQueue {
private final Map<String, String> teleportQueue;
public TeleportQueue() {
TeleportQueue() {
teleportQueue = new HashMap<>();
}

View File

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

View File

@ -0,0 +1,39 @@
/******************************************************************************
* 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.
*/
@Deprecated
public enum TeleportResultOld {
/**
* 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
}

View File

@ -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.worldnew.config.NullLocation;
import org.mvplugins.multiverse.core.worldnew.config.SpawnLocation;
import org.mvplugins.multiverse.core.worldnew.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...

View File

@ -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();

View File

@ -1,8 +1,9 @@
package org.mvplugins.multiverse.core.worldnew.helpers;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.inject.Inject;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -11,7 +12,9 @@ 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.Result;
import org.mvplugins.multiverse.core.worldnew.LoadedMultiverseWorld;
/**
@ -19,10 +22,10 @@ import org.mvplugins.multiverse.core.worldnew.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;
}
@ -31,10 +34,11 @@ public class PlayerWorldTeleporter {
*
* @param world The world to remove all players from.
*/
public void removeFromWorld(@NotNull LoadedMultiverseWorld world) {
public List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>>
removeFromWorld(@NotNull LoadedMultiverseWorld world) {
// TODO: Better handling of fallback world
World toWorld = Bukkit.getWorlds().get(0);
transferFromWorldTo(world, toWorld);
return transferFromWorldTo(world, toWorld);
}
/**
@ -43,8 +47,9 @@ public class PlayerWorldTeleporter {
* @param from The world to transfer players from.
* @param to The location to transfer players to.
*/
public void transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull LoadedMultiverseWorld to) {
transferAllFromWorldToLocation(from, to.getSpawnLocation());
public List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>>
transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull LoadedMultiverseWorld to) {
return transferAllFromWorldToLocation(from, to.getSpawnLocation());
}
/**
@ -53,23 +58,23 @@ public class PlayerWorldTeleporter {
* @param from The world to transfer players from.
* @param to The world to transfer players to.
*/
public void transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull World to) {
transferAllFromWorldToLocation(from, to.getSpawnLocation());
public List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>>
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 futures that represent the teleportation 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 List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>>
transferAllFromWorldToLocation(@NotNull LoadedMultiverseWorld world, @NotNull Location location) {
return world.getPlayers()
.map(players -> safetyTeleporter.teleport(players, location))
.getOrElse(Collections.emptyList());
}
/**
@ -78,12 +83,9 @@ public class PlayerWorldTeleporter {
* @param players The players to teleport.
* @param world The world to teleport players to.
*/
public void teleportPlayersToWorld(@NotNull List<Player> players, @NotNull LoadedMultiverseWorld world) {
players.forEach(player -> {
Location spawnLocation = world.getSpawnLocation();
if (player.isOnline()) {
safetyTeleporter.safelyTeleport(null, player, spawnLocation, true);
}
});
public List<CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>>
teleportPlayersToWorld(@NotNull List<Player> players, @NotNull LoadedMultiverseWorld world) {
Location spawnLocation = world.getSpawnLocation();
return safetyTeleporter.teleport(players, spawnLocation);
}
}

View File

@ -5,7 +5,6 @@ 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
@ -14,7 +13,6 @@ import org.mvplugins.multiverse.core.economy.MVEconomist
import org.mvplugins.multiverse.core.listeners.*
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.UnsafeCallWrapper
import org.mvplugins.multiverse.core.utils.metrics.MetricsConfigurator
@ -50,12 +48,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))