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 originThe portal the player teleports from
- * @param eventThe 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 exitThe 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 vehicleThe vehicle to teleport
- * @param originThe 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); - - ListThe vehicle to teleport
- * @param exitThe location the vehicle will exit
- * @param passengersThe passengers of the vehicle
- */ - private void teleportLivingVehicle(Vehicle vehicle, Location exit, ListWhile 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 vehicleThe player's old vehicle
- * @param passengersA list of all passengers in the vehicle
- * @param vehicleWorldThe world to spawn the new vehicle in
- * @param exitThe exit location to spawn the new vehicle on
- * @param newVelocityThe new velocity of the new vehicle
- */ - private void putPassengersInNewVehicle(Vehicle vehicle, ListThe passengers to handle
- * @param targetVehicleThe vehicle the passengers should be put into
- * @param delayThe amount of milliseconds to wait before adding the vehicle passengers
- */ - private void handleVehiclePassengers(ListTeleportation 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 targetVehicleThe vehicle to add the passenger to
- * @param passengerThe 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 entityThe entity to teleport (used to determine distance from portal to avoid suffocation)
- * @param travellerThe location of the entity travelling
- * @returnThe 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 relativeExitThe relative exit defined as the portal's exit
- * @param exitLocationThe currently calculated portal exit
- * @param entityThe travelling entity
- * @returnA 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 relativeExitThe known exit to start from
- * @param directionThe direction to move (+1 for right, -1 for left)
- * @returnThe 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 travellerThe location of the travelling entity
- * @param exitLocationThe exit location generated
- * @returnThe 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 - * - * @returnA list of chunks to load
- */ - private ListThe relative block vector
* @returnThe 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 vectorsThe relative block vectors to convert
* @returnA 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 portalThe 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 vehicleThe vehicle to teleport
+ * @param originThe 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 vehicleThe vehicle to teleport
+ * @param exitThe location the vehicle should be teleported to
+ * @param newVelocityThe 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(); + + ListThe origin portal teleported from
+ * @param exitThe exit location to teleport the vehicle to
+ * @param vehicleThe teleporting vehicle
+ * @returnThe 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 playerThe player to teleport
+ * @param originThe portal the player teleports from
+ * @param eventThe 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 exitThe 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 vehicleThe vehicle to teleport
+ * @param exitThe location the vehicle will exit
+ * @param passengersThe passengers of the vehicle
+ */ + private void teleportLivingVehicle(Vehicle vehicle, Location exit, ListWhile 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 vehicleThe player's old vehicle
+ * @param passengersA list of all passengers in the vehicle
+ * @param exitThe exit location to spawn the new vehicle on
+ * @param newVelocityThe new velocity of the new vehicle
+ */ + private void putPassengersInNewVehicle(Vehicle vehicle, ListThe passengers to handle
+ * @param targetVehicleThe vehicle the passengers should be put into
+ * @param delayThe amount of milliseconds to wait before adding the vehicle passengers
+ */ + private void handleVehiclePassengers(ListTeleportation 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 targetVehicleThe vehicle to add the passenger to
+ * @param passengerThe 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 entityThe entity to teleport (used to determine distance from portal to avoid suffocation)
+ * @param travellerThe location of the entity travelling
+ * @returnThe 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 relativeExitThe relative exit defined as the portal's exit
+ * @param exitLocationThe currently calculated portal exit
+ * @param entityThe travelling entity
+ * @returnA 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 relativeExitThe known exit to start from
+ * @param directionThe direction to move (+1 for right, -1 for left)
+ * @returnThe 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 travellerThe location of the travelling entity
+ * @param exitLocationThe exit location generated
+ * @returnThe 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 + * + * @returnA list of chunks to load
+ */ + private List