diff --git a/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java b/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java index eabed16..a875559 100644 --- a/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java +++ b/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java @@ -4,6 +4,7 @@ import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.container.BlockLocation; import net.knarcraft.stargate.portal.Portal; import net.knarcraft.stargate.portal.PortalHandler; +import net.knarcraft.stargate.portal.PortalTeleporter; import net.knarcraft.stargate.utility.BungeeHelper; import net.knarcraft.stargate.utility.MaterialHelper; import net.knarcraft.stargate.utility.PermissionHelper; @@ -56,7 +57,7 @@ public class PlayerEventListener implements Listener { return; } //Teleport the player to the stargate - portal.teleport(player, portal, null); + new PortalTeleporter(portal).teleport(player, portal, null); } /** @@ -92,9 +93,9 @@ public class PlayerEventListener implements Listener { horse.setTamed(true); horse.setOwner(player); } - destination.teleport((Vehicle) playerVehicle, entrancePortal); + new PortalTeleporter(destination).teleport((Vehicle) playerVehicle, entrancePortal); } else { - destination.teleport(player, entrancePortal, event); + new PortalTeleporter(destination).teleport(player, entrancePortal, event); } Stargate.sendSuccessMessage(player, Stargate.getString("teleportMsg")); entrancePortal.close(false); @@ -124,6 +125,11 @@ public class PlayerEventListener implements Listener { Portal destination = entrancePortal.getDestination(player); + //Catch always open portals without a valid destination to prevent the user for being teleported and denied + if (destination == null) { + return false; + } + //Decide if the anything stops the player from teleport if (PermissionHelper.playerCannotTeleport(entrancePortal, destination, player, event)) { return false; @@ -300,7 +306,7 @@ public class PlayerEventListener implements Listener { } //Teleport the player back to this gate, for sanity's sake - entrancePortal.teleport(player, entrancePortal, event); + new PortalTeleporter(entrancePortal).teleport(player, entrancePortal, event); //Send the SGBungee packet first, it will be queued by BC if required if (!BungeeHelper.sendTeleportationMessage(player, entrancePortal)) { diff --git a/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java b/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java index 6a63dcd..8779181 100644 --- a/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java +++ b/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java @@ -3,6 +3,7 @@ package net.knarcraft.stargate.listener; import net.knarcraft.stargate.container.FromTheEndTeleportation; import net.knarcraft.stargate.portal.Portal; import net.knarcraft.stargate.portal.PortalHandler; +import net.knarcraft.stargate.portal.PortalTeleporter; import net.knarcraft.stargate.utility.PermissionHelper; import org.bukkit.Location; import org.bukkit.Material; @@ -88,7 +89,8 @@ public class PortalEventListener implements Listener { Portal exitPortal = teleportation.getExit(); //Overwrite respawn location to respawn in front of the portal - event.setRespawnLocation(exitPortal.getExit(respawningPlayer, respawningPlayer.getLocation())); + event.setRespawnLocation(new PortalTeleporter(exitPortal).getExit(respawningPlayer, + respawningPlayer.getLocation())); //Properly close the portal to prevent it from staying in a locked state until it times out exitPortal.close(false); } diff --git a/src/main/java/net/knarcraft/stargate/portal/Portal.java b/src/main/java/net/knarcraft/stargate/portal/Portal.java index ae9dfa5..b8d7e28 100644 --- a/src/main/java/net/knarcraft/stargate/portal/Portal.java +++ b/src/main/java/net/knarcraft/stargate/portal/Portal.java @@ -3,35 +3,20 @@ package net.knarcraft.stargate.portal; import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.container.BlockChangeRequest; import net.knarcraft.stargate.container.BlockLocation; -import net.knarcraft.stargate.container.ChunkUnloadRequest; import net.knarcraft.stargate.container.RelativeBlockVector; import net.knarcraft.stargate.event.StargateActivateEvent; import net.knarcraft.stargate.event.StargateCloseEvent; import net.knarcraft.stargate.event.StargateDeactivateEvent; -import net.knarcraft.stargate.event.StargateEntityPortalEvent; import net.knarcraft.stargate.event.StargateOpenEvent; -import net.knarcraft.stargate.event.StargatePlayerPortalEvent; import net.knarcraft.stargate.utility.DirectionHelper; -import net.knarcraft.stargate.utility.EntityHelper; import net.knarcraft.stargate.utility.SignHelper; import org.bukkit.Axis; -import org.bukkit.Chunk; -import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.Sign; -import org.bukkit.block.data.Bisected; -import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Orientable; -import org.bukkit.block.data.type.Slab; -import org.bukkit.entity.AbstractHorse; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.entity.Vehicle; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.util.Vector; import java.util.ArrayList; import java.util.Collections; @@ -39,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; -import java.util.logging.Level; /** * This class represents a portal in space which points to one or several portals @@ -473,358 +457,6 @@ public class Portal { return player != null && player.getName().equalsIgnoreCase(this.player.getName()); } - /** - * Teleports a player to this portal - * - * @param player

The player to teleport

- * @param origin

The portal the player teleports from

- * @param event

The player move event triggering the event

- */ - public void teleport(Player player, Portal origin, PlayerMoveEvent event) { - Location traveller = player.getLocation(); - Location exit = getExit(player, traveller); - - //Rotate the player to face out from the portal - adjustRotation(exit); - - //Call the StargatePlayerPortalEvent to allow plugins to change destination - if (!origin.equals(this)) { - StargatePlayerPortalEvent stargatePlayerPortalEvent = new StargatePlayerPortalEvent(player, origin, - this, exit); - Stargate.server.getPluginManager().callEvent(stargatePlayerPortalEvent); - //Teleport is cancelled. Teleport the player back to where it came from - if (stargatePlayerPortalEvent.isCancelled()) { - origin.teleport(player, origin, event); - return; - } - //Update exit if needed - exit = stargatePlayerPortalEvent.getExit(); - } - - //Load chunks to make sure not to teleport to the void - loadChunks(); - - //If no event is passed in, assume it's a teleport, and act as such - if (event == null) { - player.teleport(exit); - } else { - //The new method to teleport in a move event is set the "to" field. - event.setTo(exit); - } - } - - /** - * Adjusts the rotation of the player to face out from the portal - * - * @param exit

The location the player will exit from

- */ - private void adjustRotation(Location exit) { - int adjust = 0; - if (options.isBackwards()) { - adjust = 180; - } - float newYaw = (this.getYaw() + adjust) % 360; - Stargate.debug("Portal::adjustRotation", "Setting exit yaw to " + newYaw); - exit.setYaw(newYaw); - } - - /** - * Teleports a vehicle to this portal - * - * @param vehicle

The vehicle to teleport

- * @param origin

The portal the vehicle teleports from

- */ - public void teleport(final Vehicle vehicle, Portal origin) { - Location traveller = vehicle.getLocation(); - Location exit = getExit(vehicle, traveller); - - double velocity = vehicle.getVelocity().length(); - - //Stop and teleport - vehicle.setVelocity(new Vector()); - - //Get new velocity - Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(this.getYaw()); - Vector newVelocity = newVelocityDirection.multiply(velocity); - adjustRotation(exit); - - List passengers = vehicle.getPassengers(); - - //Call the StargateEntityPortalEvent to allow plugins to change destination - if (!origin.equals(this)) { - StargateEntityPortalEvent stargateEntityPortalEvent = new StargateEntityPortalEvent(vehicle, origin, - this, exit); - Stargate.server.getPluginManager().callEvent(stargateEntityPortalEvent); - //Teleport is cancelled. Teleport the entity back to where it came from - if (stargateEntityPortalEvent.isCancelled()) { - origin.teleport(vehicle, origin); - return; - } - //Update exit if needed - exit = stargateEntityPortalEvent.getExit(); - } - - //Load chunks to make sure not to teleport to the void - loadChunks(); - - if (!passengers.isEmpty()) { - if (!(vehicle instanceof LivingEntity)) { - World vehicleWorld = exit.getWorld(); - if (vehicleWorld == null) { - Stargate.logger.warning(Stargate.getString("prefix") + - "Unable to get the world to teleport the vehicle to"); - return; - } - putPassengersInNewVehicle(vehicle, passengers, vehicleWorld, exit, newVelocity); - } else { - teleportLivingVehicle(vehicle, exit, passengers); - } - } else { - vehicle.teleport(exit); - Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, - () -> vehicle.setVelocity(newVelocity), 1); - } - } - - /** - * Teleport a vehicle which is not a minecart or a boat - * - * @param vehicle

The vehicle to teleport

- * @param exit

The location the vehicle will exit

- * @param passengers

The passengers of the vehicle

- */ - private void teleportLivingVehicle(Vehicle vehicle, Location exit, List passengers) { - vehicle.eject(); - vehicle.teleport(exit); - handleVehiclePassengers(passengers, vehicle, 2); - } - - /** - * Creates a new vehicle equal to the player's previous vehicle and puts any passengers inside - * - *

While it is possible to teleport boats and minecarts using the same methods as "teleportLivingVehicle", this - * method works better with CraftBook with minecart options enabled. Using normal teleportation, CraftBook destroys - * the minecart once the player is ejected, causing the minecart to disappear and the player to teleport without it.

- * - * @param vehicle

The player's old vehicle

- * @param passengers

A list of all passengers in the vehicle

- * @param vehicleWorld

The world to spawn the new vehicle in

- * @param exit

The exit location to spawn the new vehicle on

- * @param newVelocity

The new velocity of the new vehicle

- */ - private void putPassengersInNewVehicle(Vehicle vehicle, List passengers, World vehicleWorld, Location exit, - Vector newVelocity) { - Vehicle newVehicle = vehicleWorld.spawn(exit, vehicle.getClass()); - vehicle.eject(); - vehicle.remove(); - newVehicle.setRotation(exit.getYaw(), exit.getPitch()); - handleVehiclePassengers(passengers, newVehicle, 1); - Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, - () -> newVehicle.setVelocity(newVelocity), 1); - } - - /** - * Ejects, teleports and adds all passengers to the target vehicle - * - * @param passengers

The passengers to handle

- * @param targetVehicle

The vehicle the passengers should be put into

- * @param delay

The amount of milliseconds to wait before adding the vehicle passengers

- */ - private void handleVehiclePassengers(List passengers, Vehicle targetVehicle, long delay) { - for (Entity passenger : passengers) { - passenger.eject(); - Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, - () -> teleportAndAddPassenger(targetVehicle, passenger), delay); - } - } - - /** - * Teleports and adds a passenger to a vehicle - * - *

Teleportation of living vehicles is really buggy if you wait between the teleportation and passenger adding, - * but there needs to be a delay between teleporting the vehicle and teleporting and adding the passenger.

- * - * @param targetVehicle

The vehicle to add the passenger to

- * @param passenger

The passenger to teleport and add

- */ - private void teleportAndAddPassenger(Vehicle targetVehicle, Entity passenger) { - if (!passenger.teleport(targetVehicle.getLocation())) { - Stargate.debug("handleVehiclePassengers", "Failed to teleport passenger"); - } - if (!targetVehicle.addPassenger(passenger)) { - Stargate.debug("handleVehiclePassengers", "Failed to add passenger"); - } - } - - /** - * Gets the exit location for a given entity and current location - * - * @param entity

The entity to teleport (used to determine distance from portal to avoid suffocation)

- * @param traveller

The location of the entity travelling

- * @return

The location the entity should be teleported to.

- */ - public Location getExit(Entity entity, Location traveller) { - Location exitLocation = null; - // Check if the gate has an exit block - RelativeBlockVector relativeExit = gate.getLayout().getExit(); - if (relativeExit != null) { - BlockLocation exit = getBlockAt(relativeExit); - float portalYaw = this.getYaw(); - if (options.isBackwards()) { - portalYaw += 180; - } - exitLocation = exit.getRelativeLocation(0D, 0D, 1, portalYaw); - - if (entity != null) { - double entitySize = EntityHelper.getEntityMaxSize(entity); - if (entitySize > 1) { - exitLocation = preventExitSuffocation(relativeExit, exitLocation, entity); - } - } - } else { - Stargate.logger.log(Level.WARNING, Stargate.getString("prefix") + - "Missing destination point in .gate file " + gate.getFilename()); - } - - return adjustExitLocation(traveller, exitLocation); - } - - /** - * Adjusts the positioning of the portal exit to prevent the given entity from suffocating - * - * @param relativeExit

The relative exit defined as the portal's exit

- * @param exitLocation

The currently calculated portal exit

- * @param entity

The travelling entity

- * @return

A location which won't suffocate the entity inside the portal

- */ - private Location preventExitSuffocation(RelativeBlockVector relativeExit, Location exitLocation, Entity entity) { - //Go left to find start of opening - RelativeBlockVector openingLeft = getPortalExitEdge(relativeExit, -1); - - //Go right to find the end of the opening - RelativeBlockVector openingRight = getPortalExitEdge(relativeExit, 1); - - //Get the width to check if the entity fits - int openingWidth = openingRight.getRight() - openingLeft.getRight() + 1; - int existingOffset = relativeExit.getRight() - openingLeft.getRight(); - double newOffset = (openingWidth - existingOffset) / 2D; - - //Remove the half offset for better centering - if (openingWidth > 1) { - newOffset -= 0.5; - } - exitLocation = DirectionHelper.moveLocation(exitLocation, newOffset, 0, 0, getYaw()); - - //Move large entities further from the portal, especially if this portal will teleport them at once - double entitySize = EntityHelper.getEntityMaxSize(entity); - int entityBoxSize = EntityHelper.getEntityMaxSizeInt(entity); - if (entitySize > 1) { - if (options.isAlwaysOn()) { - exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, (entityBoxSize / 2D), - getYaw()); - } else { - exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, - (entitySize / 2D) - 1, getYaw()); - } - } - - //If a horse has a player riding it, the player will spawn inside the roof of a standard portal unless it's - //moved one block out. - if (entity instanceof AbstractHorse) { - exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, 1, getYaw()); - } - - return exitLocation; - } - - /** - * Gets one of the edges of a portal's opening/exit - * - * @param relativeExit

The known exit to start from

- * @param direction

The direction to move (+1 for right, -1 for left)

- * @return

The right or left edge of the opening

- */ - private RelativeBlockVector getPortalExitEdge(RelativeBlockVector relativeExit, int direction) { - RelativeBlockVector openingEdge = relativeExit; - do { - RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.getRight() + direction, - openingEdge.getDepth(), openingEdge.getDistance()); - if (gate.getLayout().getExits().contains(possibleOpening)) { - openingEdge = possibleOpening; - } else { - break; - } - } while (true); - return openingEdge; - } - - /** - * Adjusts an exit location with rotation and slab height incrementation - * - * @param traveller

The location of the travelling entity

- * @param exitLocation

The exit location generated

- * @return

The location the travelling entity should be teleported to

- */ - private Location adjustExitLocation(Location traveller, Location exitLocation) { - if (exitLocation != null) { - BlockData blockData = getWorld().getBlockAt(exitLocation).getBlockData(); - if ((blockData instanceof Bisected && ((Bisected) blockData).getHalf() == Bisected.Half.BOTTOM) || - (blockData instanceof Slab) && ((Slab) blockData).getType() == Slab.Type.BOTTOM) { - //Prevent traveller from spawning inside a slab - Stargate.debug("adjustExitLocation", "Added a block to get above a slab"); - exitLocation.add(0, 1, 0); - } else if (blockData.getMaterial() == Material.WATER) { - //If there's water outside, go one up to allow for boat teleportation - Stargate.debug("adjustExitLocation", "Added a block to get above a block of water"); - exitLocation.add(0, 1, 0); - } - - exitLocation.setPitch(traveller.getPitch()); - return exitLocation; - } else { - Stargate.logger.log(Level.WARNING, Stargate.getString("prefix") + - "Unable to generate exit location"); - } - return traveller; - } - - /** - * Loads the chunks outside the portal's entrance - */ - private void loadChunks() { - for (Chunk chunk : getChunksToLoad()) { - chunk.addPluginChunkTicket(Stargate.stargate); - //Allow the chunk to unload after 3 seconds - Stargate.addChunkUnloadRequest(new ChunkUnloadRequest(chunk, 3000L)); - } - } - - /** - * Gets all relevant chunks near this portal's entrance which need to be loaded before teleportation - * - * @return

A list of chunks to load

- */ - private List getChunksToLoad() { - List chunksToLoad = new ArrayList<>(); - for (RelativeBlockVector vector : gate.getLayout().getEntrances()) { - BlockLocation entranceLocation = getBlockAt(vector); - Chunk chunk = entranceLocation.getChunk(); - if (!chunksToLoad.contains(chunk)) { - chunksToLoad.add(chunk); - } - - //Get the chunk in front of the gate corner - int blockOffset = options.isBackwards() ? -5 : 5; - Location fiveBlocksForward = DirectionHelper.moveLocation(entranceLocation, 0, 0, blockOffset, - getYaw()); - Chunk forwardChunk = fiveBlocksForward.getChunk(); - if (!chunksToLoad.contains(forwardChunk)) { - chunksToLoad.add(forwardChunk); - } - } - return chunksToLoad; - } - /** * Gets the identity (sign) location of the portal * @@ -1017,7 +649,7 @@ public class Portal { } /** - * Draws the sign on this portal + * Draws this portal's sign */ public final void drawSign() { BlockState state = getSignLocation().getBlock().getState(); @@ -1032,7 +664,7 @@ public class Portal { } /** - * Gets the block at a relative block vector location + * Gets the block at the given location relative to this portal's location * * @param vector

The relative block vector

* @return

The block at the given relative position

@@ -1057,15 +689,16 @@ public class Portal { /** * Gets a list of block locations from a list of relative block vectors * + *

The block locations will be calculated by using this portal's top-left block as the origin for the relative + * vectors..

+ * * @param vectors

The relative block vectors to convert

* @return

A list of block locations

*/ private BlockLocation[] relativeBlockVectorsToBlockLocations(RelativeBlockVector[] vectors) { BlockLocation[] locations = new BlockLocation[vectors.length]; - int i = 0; - - for (RelativeBlockVector vector : vectors) { - locations[i++] = getBlockAt(vector); + for (int i = 0; i < vectors.length; i++) { + locations[i] = getBlockAt(vectors[i]); } return locations; } @@ -1086,14 +719,14 @@ public class Portal { } @Override - public boolean equals(Object obj) { - if (this == obj) { + public boolean equals(Object object) { + if (this == object) { return true; } - if (obj == null || getClass() != obj.getClass()) { + if (object == null || getClass() != object.getClass()) { return false; } - Portal other = (Portal) obj; + Portal other = (Portal) object; if (name == null) { if (other.name != null) { return false; @@ -1101,6 +734,7 @@ public class Portal { } else if (!name.equalsIgnoreCase(other.name)) { return false; } + //If none of the portals have a name, check if the network is the same if (network == null) { return other.network == null; } else { diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalTeleporter.java b/src/main/java/net/knarcraft/stargate/portal/PortalTeleporter.java new file mode 100644 index 0000000..7004608 --- /dev/null +++ b/src/main/java/net/knarcraft/stargate/portal/PortalTeleporter.java @@ -0,0 +1,441 @@ +package net.knarcraft.stargate.portal; + +import net.knarcraft.stargate.Stargate; +import net.knarcraft.stargate.container.BlockLocation; +import net.knarcraft.stargate.container.ChunkUnloadRequest; +import net.knarcraft.stargate.container.RelativeBlockVector; +import net.knarcraft.stargate.event.StargateEntityPortalEvent; +import net.knarcraft.stargate.event.StargatePlayerPortalEvent; +import net.knarcraft.stargate.utility.DirectionHelper; +import net.knarcraft.stargate.utility.EntityHelper; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Slab; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +/** + * The portal teleporter takes care of the actual portal teleportation + */ +public class PortalTeleporter { + + private final Portal portal; + + /** + * Instantiates a new portal teleporter + * + * @param portal

The portal which is the target of the teleportation

+ */ + public PortalTeleporter(Portal portal) { + this.portal = portal; + } + + /** + * Teleports a vehicle to this teleporter's portal + * + *

It is assumed that if a vehicle contains any players, their permissions have already been validated before + * calling this method.

+ * + * @param vehicle

The vehicle to teleport

+ * @param origin

The portal the vehicle teleports from

+ */ + public void teleport(final Vehicle vehicle, Portal origin) { + Location traveller = vehicle.getLocation(); + Location exit = getExit(vehicle, traveller); + + double velocity = vehicle.getVelocity().length(); + + //Stop and teleport + vehicle.setVelocity(new Vector()); + + //Get new velocity + Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getYaw()); + Vector newVelocity = newVelocityDirection.multiply(velocity); + + //Make sure the vehicle points out from the portal + adjustRotation(exit); + + //Call the StargateEntityPortalEvent to allow plugins to change destination + if (!origin.equals(portal)) { + exit = triggerEntityPortalEvent(origin, exit, vehicle); + if (exit == null) { + return; + } + } + + //Teleport the vehicle + teleportVehicle(vehicle, exit, newVelocity); + } + + /** + * Teleports a vehicle with any passengers to the given location + * + * @param vehicle

The vehicle to teleport

+ * @param exit

The location the vehicle should be teleported to

+ * @param newVelocity

The velocity to give the vehicle right after teleportation

+ */ + private void teleportVehicle(Vehicle vehicle, Location exit, Vector newVelocity) { + //Load chunks to make sure not to teleport to the void + loadChunks(); + + List passengers = vehicle.getPassengers(); + if (!passengers.isEmpty()) { + if (!(vehicle instanceof LivingEntity)) { + //Teleport a normal vehicle with passengers (minecart or boat) + putPassengersInNewVehicle(vehicle, passengers, exit, newVelocity); + } else { + //Teleport a living vehicle with passengers (pig, horse, donkey, strider) + teleportLivingVehicle(vehicle, exit, passengers); + } + } else { + //Teleport an empty vehicle + vehicle.teleport(exit); + Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, + () -> vehicle.setVelocity(newVelocity), 1); + } + } + + /** + * Triggers the entity portal event to allow plugins to change the exit location + * + * @param origin

The origin portal teleported from

+ * @param exit

The exit location to teleport the vehicle to

+ * @param vehicle

The teleporting vehicle

+ * @return

The location the vehicle should be teleported to, or null if the event was cancelled

+ */ + private Location triggerEntityPortalEvent(Portal origin, Location exit, Vehicle vehicle) { + StargateEntityPortalEvent stargateEntityPortalEvent = new StargateEntityPortalEvent(vehicle, origin, + portal, exit); + Stargate.server.getPluginManager().callEvent(stargateEntityPortalEvent); + //Teleport is cancelled. Teleport the entity back to where it came from just for sanity's sake + if (stargateEntityPortalEvent.isCancelled()) { + new PortalTeleporter(origin).teleport(vehicle, origin); + return null; + } + return stargateEntityPortalEvent.getExit(); + } + + private Location triggerPlayerPortalEvent(Portal origin, Location exit, Player player, PlayerMoveEvent event) { + StargatePlayerPortalEvent stargatePlayerPortalEvent = new StargatePlayerPortalEvent(player, origin, + portal, exit); + Stargate.server.getPluginManager().callEvent(stargatePlayerPortalEvent); + //Teleport is cancelled. Teleport the player back to where it came from + if (stargatePlayerPortalEvent.isCancelled()) { + new PortalTeleporter(origin).teleport(player, origin, event); + return null; + } + return stargatePlayerPortalEvent.getExit(); + } + + /** + * Teleports a player to this teleporter's portal + * + * @param player

The player to teleport

+ * @param origin

The portal the player teleports from

+ * @param event

The player move event triggering the event

+ */ + public void teleport(Player player, Portal origin, PlayerMoveEvent event) { + Location traveller = player.getLocation(); + Location exit = getExit(player, traveller); + + //Rotate the player to face out from the portal + adjustRotation(exit); + + //Call the StargatePlayerPortalEvent to allow plugins to change destination + if (!origin.equals(portal)) { + exit = triggerPlayerPortalEvent(origin, exit, player, event); + if (exit == null) { + return; + } + } + + //Load chunks to make sure not to teleport to the void + loadChunks(); + + //If no event is passed in, assume it's a teleport, and act as such + if (event == null) { + player.teleport(exit); + } else { + //The new method to teleport in a move event is set the "to" field. + event.setTo(exit); + } + } + + /** + * Adjusts the rotation of the player to face out from the portal + * + * @param exit

The location the player will exit from

+ */ + private void adjustRotation(Location exit) { + int adjust = 0; + if (portal.getOptions().isBackwards()) { + adjust = 180; + } + float newYaw = (portal.getYaw() + adjust) % 360; + Stargate.debug("Portal::adjustRotation", "Setting exit yaw to " + newYaw); + exit.setYaw(newYaw); + } + + /** + * Teleport a vehicle which is not a minecart or a boat + * + * @param vehicle

The vehicle to teleport

+ * @param exit

The location the vehicle will exit

+ * @param passengers

The passengers of the vehicle

+ */ + private void teleportLivingVehicle(Vehicle vehicle, Location exit, List passengers) { + vehicle.eject(); + vehicle.teleport(exit); + handleVehiclePassengers(passengers, vehicle, 2); + } + + /** + * Creates a new vehicle equal to the player's previous vehicle and puts any passengers inside + * + *

While it is possible to teleport boats and minecarts using the same methods as "teleportLivingVehicle", this + * method works better with CraftBook with minecart options enabled. Using normal teleportation, CraftBook destroys + * the minecart once the player is ejected, causing the minecart to disappear and the player to teleport without it.

+ * + * @param vehicle

The player's old vehicle

+ * @param passengers

A list of all passengers in the vehicle

+ * @param exit

The exit location to spawn the new vehicle on

+ * @param newVelocity

The new velocity of the new vehicle

+ */ + private void putPassengersInNewVehicle(Vehicle vehicle, List passengers, Location exit, + Vector newVelocity) { + World vehicleWorld = exit.getWorld(); + if (vehicleWorld == null) { + Stargate.logger.warning(Stargate.getString("prefix") + + "Unable to get the world to teleport the vehicle to"); + return; + } + //Spawn a new vehicle + Vehicle newVehicle = vehicleWorld.spawn(exit, vehicle.getClass()); + //Remove the old vehicle + vehicle.eject(); + vehicle.remove(); + //Set rotation, add passengers and restore velocity + newVehicle.setRotation(exit.getYaw(), exit.getPitch()); + handleVehiclePassengers(passengers, newVehicle, 1); + Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, + () -> newVehicle.setVelocity(newVelocity), 1); + } + + /** + * Ejects, teleports and adds all passengers to the target vehicle + * + * @param passengers

The passengers to handle

+ * @param targetVehicle

The vehicle the passengers should be put into

+ * @param delay

The amount of milliseconds to wait before adding the vehicle passengers

+ */ + private void handleVehiclePassengers(List passengers, Vehicle targetVehicle, long delay) { + for (Entity passenger : passengers) { + passenger.eject(); + Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, + () -> teleportAndAddPassenger(targetVehicle, passenger), delay); + } + } + + /** + * Teleports and adds a passenger to a vehicle + * + *

Teleportation of living vehicles is really buggy if you wait between the teleportation and passenger adding, + * but there needs to be a delay between teleporting the vehicle and teleporting and adding the passenger.

+ * + * @param targetVehicle

The vehicle to add the passenger to

+ * @param passenger

The passenger to teleport and add

+ */ + private void teleportAndAddPassenger(Vehicle targetVehicle, Entity passenger) { + if (!passenger.teleport(targetVehicle.getLocation())) { + Stargate.debug("handleVehiclePassengers", "Failed to teleport passenger"); + } + if (!targetVehicle.addPassenger(passenger)) { + Stargate.debug("handleVehiclePassengers", "Failed to add passenger"); + } + } + + /** + * Gets the exit location for a given entity and current location + * + * @param entity

The entity to teleport (used to determine distance from portal to avoid suffocation)

+ * @param traveller

The location of the entity travelling

+ * @return

The location the entity should be teleported to.

+ */ + public Location getExit(Entity entity, Location traveller) { + Location exitLocation = null; + // Check if the gate has an exit block + RelativeBlockVector relativeExit = portal.getGate().getLayout().getExit(); + if (relativeExit != null) { + BlockLocation exit = portal.getBlockAt(relativeExit); + float portalYaw = portal.getYaw(); + if (portal.getOptions().isBackwards()) { + portalYaw += 180; + } + exitLocation = exit.getRelativeLocation(0D, 0D, 1, portalYaw); + + if (entity != null) { + double entitySize = EntityHelper.getEntityMaxSize(entity); + if (entitySize > 1) { + exitLocation = preventExitSuffocation(relativeExit, exitLocation, entity); + } + } + } else { + Stargate.logger.log(Level.WARNING, Stargate.getString("prefix") + + "Missing destination point in .gate file " + portal.getGate().getFilename()); + } + + return adjustExitLocation(traveller, exitLocation); + } + + /** + * Adjusts the positioning of the portal exit to prevent the given entity from suffocating + * + * @param relativeExit

The relative exit defined as the portal's exit

+ * @param exitLocation

The currently calculated portal exit

+ * @param entity

The travelling entity

+ * @return

A location which won't suffocate the entity inside the portal

+ */ + private Location preventExitSuffocation(RelativeBlockVector relativeExit, Location exitLocation, Entity entity) { + //Go left to find start of opening + RelativeBlockVector openingLeft = getPortalExitEdge(relativeExit, -1); + + //Go right to find the end of the opening + RelativeBlockVector openingRight = getPortalExitEdge(relativeExit, 1); + + //Get the width to check if the entity fits + int openingWidth = openingRight.getRight() - openingLeft.getRight() + 1; + int existingOffset = relativeExit.getRight() - openingLeft.getRight(); + double newOffset = (openingWidth - existingOffset) / 2D; + + //Remove the half offset for better centering + if (openingWidth > 1) { + newOffset -= 0.5; + } + exitLocation = DirectionHelper.moveLocation(exitLocation, newOffset, 0, 0, portal.getYaw()); + + //Move large entities further from the portal, especially if this teleporter's portal will teleport them at once + double entitySize = EntityHelper.getEntityMaxSize(entity); + int entityBoxSize = EntityHelper.getEntityMaxSizeInt(entity); + if (entitySize > 1) { + if (portal.getOptions().isAlwaysOn()) { + exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, (entityBoxSize / 2D), + portal.getYaw()); + } else { + exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, + (entitySize / 2D) - 1, portal.getYaw()); + } + } + + //If a horse has a player riding it, the player will spawn inside the roof of a standard portal unless it's + //moved one block out. + if (entity instanceof AbstractHorse) { + exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, 1, portal.getYaw()); + } + + return exitLocation; + } + + /** + * Gets one of the edges of a portal's opening/exit + * + * @param relativeExit

The known exit to start from

+ * @param direction

The direction to move (+1 for right, -1 for left)

+ * @return

The right or left edge of the opening

+ */ + private RelativeBlockVector getPortalExitEdge(RelativeBlockVector relativeExit, int direction) { + RelativeBlockVector openingEdge = relativeExit; + do { + RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.getRight() + direction, + openingEdge.getDepth(), openingEdge.getDistance()); + if (portal.getGate().getLayout().getExits().contains(possibleOpening)) { + openingEdge = possibleOpening; + } else { + break; + } + } while (true); + return openingEdge; + } + + /** + * Adjusts an exit location with rotation and slab height incrementation + * + * @param traveller

The location of the travelling entity

+ * @param exitLocation

The exit location generated

+ * @return

The location the travelling entity should be teleported to

+ */ + private Location adjustExitLocation(Location traveller, Location exitLocation) { + if (exitLocation != null) { + BlockData blockData = portal.getWorld().getBlockAt(exitLocation).getBlockData(); + if ((blockData instanceof Bisected && ((Bisected) blockData).getHalf() == Bisected.Half.BOTTOM) || + (blockData instanceof Slab) && ((Slab) blockData).getType() == Slab.Type.BOTTOM) { + //Prevent traveller from spawning inside a slab + Stargate.debug("adjustExitLocation", "Added a block to get above a slab"); + exitLocation.add(0, 1, 0); + } else if (blockData.getMaterial() == Material.WATER) { + //If there's water outside, go one up to allow for boat teleportation + Stargate.debug("adjustExitLocation", "Added a block to get above a block of water"); + exitLocation.add(0, 1, 0); + } + + exitLocation.setPitch(traveller.getPitch()); + return exitLocation; + } else { + Stargate.logger.log(Level.WARNING, Stargate.getString("prefix") + + "Unable to generate exit location"); + } + return traveller; + } + + /** + * Loads the chunks outside the portal's entrance + */ + private void loadChunks() { + for (Chunk chunk : getChunksToLoad()) { + chunk.addPluginChunkTicket(Stargate.stargate); + //Allow the chunk to unload after 3 seconds + Stargate.addChunkUnloadRequest(new ChunkUnloadRequest(chunk, 3000L)); + } + } + + /** + * Gets all relevant chunks near this teleporter's portal's entrance which need to be loaded before teleportation + * + * @return

A list of chunks to load

+ */ + private List getChunksToLoad() { + List chunksToLoad = new ArrayList<>(); + for (RelativeBlockVector vector : portal.getGate().getLayout().getEntrances()) { + BlockLocation entranceLocation = portal.getBlockAt(vector); + Chunk chunk = entranceLocation.getChunk(); + //Make sure not to load chunks twice + if (!chunksToLoad.contains(chunk)) { + chunksToLoad.add(chunk); + } + + //Get the chunk in front of the gate entrance + int blockOffset = portal.getOptions().isBackwards() ? -5 : 5; + Location fiveBlocksForward = DirectionHelper.moveLocation(entranceLocation, 0, 0, blockOffset, + portal.getYaw()); + //Load the chunk five blocks forward to make sure the teleported entity will never spawn in unloaded chunks + Chunk forwardChunk = fiveBlocksForward.getChunk(); + if (!chunksToLoad.contains(forwardChunk)) { + chunksToLoad.add(forwardChunk); + } + } + return chunksToLoad; + } + +} diff --git a/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java b/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java index 1137b08..8380c8b 100644 --- a/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java +++ b/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java @@ -3,6 +3,7 @@ package net.knarcraft.stargate.utility; import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.portal.Portal; import net.knarcraft.stargate.portal.PortalHandler; +import net.knarcraft.stargate.portal.PortalTeleporter; import org.bukkit.entity.Player; import java.io.ByteArrayInputStream; @@ -130,7 +131,7 @@ public final class BungeeHelper { Stargate.logger.info(Stargate.getString("prefix") + "Bungee gate " + destination + " does not exist"); return; } - destinationPortal.teleport(player, destinationPortal, null); + new PortalTeleporter(destinationPortal).teleport(player, destinationPortal, null); } } diff --git a/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java b/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java index 6038151..308dfcf 100644 --- a/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java +++ b/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java @@ -4,6 +4,7 @@ import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.event.StargateAccessEvent; import net.knarcraft.stargate.portal.Portal; import net.knarcraft.stargate.portal.PortalOption; +import net.knarcraft.stargate.portal.PortalTeleporter; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerMoveEvent; @@ -99,13 +100,15 @@ public final class PermissionHelper { public static boolean cannotAccessPortal(Player player, Portal entrancePortal, Portal destination) { boolean deny = false; // Check if player has access to this server for Bungee gates - if (entrancePortal.getOptions().isBungee() && !PermissionHelper.canAccessServer(player, entrancePortal.getNetwork())) { + if (entrancePortal.getOptions().isBungee() && !PermissionHelper.canAccessServer(player, + entrancePortal.getNetwork())) { Stargate.debug("cannotAccessPortal", "Cannot access server"); deny = true; } else if (PermissionHelper.cannotAccessNetwork(player, entrancePortal.getNetwork())) { Stargate.debug("cannotAccessPortal", "Cannot access network"); deny = true; - } else if (!entrancePortal.getOptions().isBungee() && PermissionHelper.cannotAccessWorld(player, destination.getWorld().getName())) { + } else if (!entrancePortal.getOptions().isBungee() && PermissionHelper.cannotAccessWorld(player, + destination.getWorld().getName())) { Stargate.debug("cannotAccessPortal", "Cannot access world"); deny = true; } @@ -387,7 +390,7 @@ public final class PermissionHelper { // Not open for this player if (!entrancePortal.isOpenFor(player)) { Stargate.sendErrorMessage(player, Stargate.getString("denyMsg")); - entrancePortal.teleport(player, entrancePortal, event); + new PortalTeleporter(entrancePortal).teleport(player, entrancePortal, event); return true; } @@ -399,13 +402,13 @@ public final class PermissionHelper { //Player cannot access portal if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) { Stargate.sendErrorMessage(player, Stargate.getString("denyMsg")); - entrancePortal.teleport(player, entrancePortal, event); + new PortalTeleporter(entrancePortal).teleport(player, entrancePortal, event); entrancePortal.close(false); return true; } //Player cannot pay for teleportation - int cost = EconomyHandler.getDefaultUseCost(player, entrancePortal, destination); + int cost = EconomyHandler.getUseCost(player, entrancePortal, destination); if (cost > 0) { return EconomyHelper.cannotPayTeleportFee(entrancePortal, player, cost); }