package net.knarcraft.stargate; import net.knarcraft.stargate.event.StargateActivateEvent; import net.knarcraft.stargate.event.StargateCloseEvent; import net.knarcraft.stargate.event.StargateDeactivateEvent; import net.knarcraft.stargate.event.StargateOpenEvent; import net.knarcraft.stargate.event.StargatePortalEvent; import org.bukkit.Axis; import org.bukkit.ChatColor; 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.Powerable; import org.bukkit.entity.Boat; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.entity.Vehicle; import org.bukkit.entity.minecart.RideableMinecart; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.util.Vector; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.logging.Level; public class Portal { // Gate location block info private final BlockLocation topLeft; private final int modX; private final int modZ; private final float rotX; private final Axis rot; // Block references private final BlockLocation id; private final Gate gate; private final World world; private BlockLocation button; private BlockLocation[] frame; private BlockLocation[] entrances; // Gate information private String name; private String destination; private String lastDestination = ""; private String network; private String ownerName; private UUID ownerUUID; private boolean verified; private boolean fixed; private Map options; // In-use information private Player player; private Player activePlayer; private List destinations = new ArrayList<>(); private boolean isOpen = false; private long openTime; /** * Instantiates a new portal * * @param topLeft

The top-left block of the portal. This is used to decide the positions of the rest of the portal

* @param modX

* @param modZ

* @param rotX

* @param id

The location of the portal's id block, which is the sign which activated the portal

* @param button

The location of the portal's open button

* @param destination

The destination defined on the sign's destination line

* @param name

The name of the portal defined on the sign's first line

* @param verified

Whether the portal's gate has been verified to match its template

* @param network

The network the portal belongs to, defined on the sign's network line

* @param gate

The gate template this portal uses

* @param ownerUUID

The UUID of the gate's owner

* @param ownerName

The name of the gate's owner

* @param options

A map containing all possible portal options

*/ Portal(BlockLocation topLeft, int modX, int modZ, float rotX, BlockLocation id, BlockLocation button, String destination, String name, boolean verified, String network, Gate gate, UUID ownerUUID, String ownerName, Map options) { this.topLeft = topLeft; this.modX = modX; this.modZ = modZ; this.rotX = rotX; this.rot = rotX == 0.0F || rotX == 180.0F ? Axis.X : Axis.Z; this.id = id; this.destination = destination; this.button = button; this.verified = verified; this.network = network; this.name = name; this.gate = gate; this.ownerUUID = ownerUUID; this.ownerName = ownerName; this.options = options; this.world = topLeft.getWorld(); this.fixed = destination.length() > 0 || this.isRandom() || this.isBungee(); if (this.isAlwaysOn() && !this.isFixed()) { this.options.put(PortalOption.ALWAYS_ON, false); Stargate.debug("Portal", "Can not create a non-fixed always-on gate. Setting AlwaysOn = false"); } if (this.isRandom() && !this.isAlwaysOn()) { this.options.put(PortalOption.ALWAYS_ON, true); Stargate.debug("Portal", "Gate marked as random, set to always-on"); } if (verified) { this.drawSign(); } } /** * Removes the special characters |, : and # from a portal name * * @param input

The name to filter

* @return

The filtered name

*/ public static String filterName(String input) { if (input == null) { return ""; } return input.replaceAll("[|:#]", "").trim(); } /** * Gets whether this portal is currently open * * @return

Whether this portal is open

*/ public boolean isOpen() { return isOpen || isAlwaysOn(); } /** * Gets whether this portal is always on * * @return

Whether this portal is always on

*/ public boolean isAlwaysOn() { return this.options.get(PortalOption.ALWAYS_ON); } /** * Gets whether this portal is hidden * * @return

Whether this portal is hidden

*/ public boolean isHidden() { return this.options.get(PortalOption.HIDDEN); } /** * Gets whether this portal is private * * @return

Whether this portal is private

*/ public boolean isPrivate() { return this.options.get(PortalOption.PRIVATE); } /** * Gets whether this portal is free * * @return

Whether this portal is free

*/ public boolean isFree() { return this.options.get(PortalOption.FREE); } /** * Gets whether this portal is backwards * *

A backwards portal is one where players exit through the back.

* * @return

Whether this portal is backwards

*/ public boolean isBackwards() { return this.options.get(PortalOption.BACKWARDS); } /** * Gets whether this portal is shown on the network even if it's always on * * @return

Whether portal gate is shown

*/ public boolean isShown() { return this.options.get(PortalOption.SHOW); } /** * Gets whether this portal shows no network * * @return

Whether this portal shows no network/p> */ public boolean isNoNetwork() { return this.options.get(PortalOption.NO_NETWORK); } /** * Gets whether this portal goes to a random location on the network * * @return

Whether this portal goes to a random location

*/ public boolean isRandom() { return this.options.get(PortalOption.RANDOM); } /** * Gets whether this portal is a bungee portal * * @return

Whether this portal is a bungee portal

*/ public boolean isBungee() { return this.options.get(PortalOption.BUNGEE); } /** * Gets the rotation of the portal in degrees * * @return

The rotation of the portal

*/ public float getRotation() { return rotX; } public Axis getAxis() { return rot; } public Player getActivePlayer() { return activePlayer; } public String getNetwork() { return network; } public void setNetwork(String network) { this.network = network; } public long getOpenTime() { return openTime; } public String getName() { return name; } public void setName(String name) { this.name = filterName(name); drawSign(); } public Portal getDestination(Player player) { if (isRandom()) { destinations = PortalHandler.getDestinations(this, player, getNetwork()); if (destinations.size() == 0) { return null; } String dest = destinations.get((new Random()).nextInt(destinations.size())); destinations.clear(); return PortalHandler.getByName(dest, getNetwork()); } return PortalHandler.getByName(destination, getNetwork()); } public Portal getDestination() { return getDestination(null); } public void setDestination(Portal destination) { setDestination(destination.getName()); } public void setDestination(String destination) { this.destination = destination; } public String getDestinationName() { return destination; } public Gate getGate() { return gate; } public String getOwnerName() { return ownerName; } public UUID getOwnerUUID() { return ownerUUID; } public void setOwner(UUID owner) { this.ownerUUID = owner; } public boolean isOwner(Player player) { if (this.ownerUUID != null) { return player.getUniqueId().compareTo(this.ownerUUID) == 0; } else { return player.getName().equalsIgnoreCase(this.ownerName); } } public BlockLocation[] getEntrances() { if (entrances == null) { RelativeBlockVector[] space = gate.getEntrances(); entrances = new BlockLocation[space.length]; int i = 0; for (RelativeBlockVector vector : space) { entrances[i++] = getBlockAt(vector); } } return entrances; } public BlockLocation[] getFrame() { if (frame == null) { RelativeBlockVector[] border = gate.getBorder(); frame = new BlockLocation[border.length]; int i = 0; for (RelativeBlockVector vector : border) { frame[i++] = getBlockAt(vector); } } return frame; } public BlockLocation getSign() { return id; } public World getWorld() { return world; } public BlockLocation getButton() { return button; } public void setButton(BlockLocation button) { this.button = button; } public boolean open(boolean force) { return open(null, force); } public boolean open(Player openFor, boolean force) { // Call the StargateOpenEvent StargateOpenEvent event = new StargateOpenEvent(openFor, this, force); Stargate.server.getPluginManager().callEvent(event); if (event.isCancelled()) return false; force = event.getForce(); if (isOpen() && !force) return false; Material openType = gate.getPortalBlockOpen(); Axis ax = openType == Material.NETHER_PORTAL ? rot : null; for (BlockLocation inside : getEntrances()) { Stargate.blockPopulatorQueue.add(new BloxPopulator(inside, openType, ax)); } isOpen = true; openTime = System.currentTimeMillis() / 1000; Stargate.openList.add(this); Stargate.activeList.remove(this); // Open remote gate if (!isAlwaysOn()) { player = openFor; Portal end = getDestination(); // Only open dest if it's not-fixed or points at this gate if (!isRandom() && end != null && (!end.isFixed() || end.getDestinationName().equalsIgnoreCase(getName())) && !end.isOpen()) { end.open(openFor, false); end.setDestination(this); if (end.isVerified()) end.drawSign(); } } return true; } public void close(boolean force) { if (!isOpen) return; // Call the StargateCloseEvent StargateCloseEvent event = new StargateCloseEvent(this, force); Stargate.server.getPluginManager().callEvent(event); if (event.isCancelled()) return; force = event.getForce(); if (isAlwaysOn() && !force) return; // Only close always-open if forced // Close this gate, then the dest gate. Material closedType = gate.getPortalBlockClosed(); for (BlockLocation inside : getEntrances()) { Stargate.blockPopulatorQueue.add(new BloxPopulator(inside, closedType)); } player = null; isOpen = false; Stargate.openList.remove(this); Stargate.activeList.remove(this); if (!isAlwaysOn()) { Portal end = getDestination(); if (end != null && end.isOpen()) { end.deactivate(); // Clear it's destination first. end.close(false); } } deactivate(); } public boolean isOpenFor(Player player) { if (!isOpen) { return false; } if ((isAlwaysOn()) || (this.player == null)) { return true; } return (player != null) && (player.getName().equalsIgnoreCase(this.player.getName())); } /** * Gets whether this portal points to a fixed exit portal * * @return

True if this portal points to a fixed exit portal

*/ public boolean isFixed() { return fixed; } public void setFixed(boolean fixed) { this.fixed = fixed; } public boolean isPowered() { RelativeBlockVector[] controls = gate.getControls(); for (RelativeBlockVector vector : controls) { BlockData data = getBlockAt(vector).getBlock().getBlockData(); if (data instanceof Powerable && ((Powerable) data).isPowered()) { return true; } } return false; } /** * 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 int adjust = 180; if (isBackwards() != origin.isBackwards()) { adjust = 0; } exit.setYaw(traveller.getYaw() - origin.getRotation() + this.getRotation() + adjust); // Call the StargatePortalEvent to allow plugins to change destination if (!origin.equals(this)) { StargatePortalEvent stargatePortalEvent = new StargatePortalEvent(player, origin, this, exit); Stargate.server.getPluginManager().callEvent(stargatePortalEvent); // Teleport is cancelled if (stargatePortalEvent.isCancelled()) { origin.teleport(player, origin, event); return; } // Update exit if needed exit = stargatePortalEvent.getExit(); } // If no event is passed in, assume it's a teleport, and act as such if (event == null) { exit.setYaw(this.getRotation()); player.teleport(exit); } else { // The new method to teleport in a move event is set the "to" field. event.setTo(exit); } } /** * Teleports a vehicle to this portal * * @param vehicle

The vehicle to teleport

*/ public void teleport(final Vehicle vehicle) { Location traveller = new Location(this.world, vehicle.getLocation().getX(), vehicle.getLocation().getY(), vehicle.getLocation().getZ()); Stargate.log.info(Stargate.getString("prefix") + "Location of vehicle is " + traveller); Location exit = getExit(vehicle, traveller); double velocity = vehicle.getVelocity().length(); // Stop and teleport vehicle.setVelocity(new Vector()); // Get new velocity final Vector newVelocity = new Vector(modX, 0.0F, modZ); newVelocity.multiply(velocity); List passengers = vehicle.getPassengers(); World vehicleWorld = exit.getWorld(); if (vehicleWorld == null) { Stargate.log.warning(Stargate.getString("prefix") + "Unable to get the world to teleport the vehicle to"); return; } if (!passengers.isEmpty()) { if (vehicle instanceof RideableMinecart || vehicle instanceof Boat) { putPlayerInNewVehicle(vehicle, passengers, vehicleWorld, exit, newVelocity); return; } vehicle.eject(); handleVehiclePassengers(vehicle, passengers, vehicle, exit); vehicle.teleport(exit); Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> vehicle.setVelocity(newVelocity), 3); } else { Stargate.log.info(Stargate.getString("prefix") + "Teleported vehicle to " + exit); vehicle.teleport(exit); Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> { vehicle.setVelocity(newVelocity); }, 1); } } private void putPlayerInNewVehicle(Vehicle vehicle, List passengers, World vehicleWorld, Location exit, Vector newVelocity) { Vehicle newVehicle = vehicleWorld.spawn(exit, vehicle.getClass()); vehicle.eject(); vehicle.remove(); handleVehiclePassengers(vehicle, passengers, newVehicle, exit); Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> newVehicle.setVelocity(newVelocity), 1); } private void handleVehiclePassengers(Vehicle sourceVehicle, List passengers, Vehicle targetVehicle, Location exit) { for (Entity passenger : passengers) { passenger.eject(); Stargate.log.info("Teleporting passenger" + passenger + " to " + exit); if (!passenger.teleport(exit)) { Stargate.log.info("Failed to teleport passenger"); } Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> targetVehicle.addPassenger(passenger), 1); } } /** * 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 if (gate.getExit() != null) { BlockLocation exit = getBlockAt(gate.getExit()); int back = (isBackwards()) ? -1 : 1; double entitySize = Math.ceil((float) Math.max(entity.getBoundingBox().getWidthX(), entity.getBoundingBox().getWidthZ())); exitLocation = exit.modRelativeLoc(0D, 0D, entitySize, traveller.getYaw(), traveller.getPitch(), modX * back, 1, modZ * back); } else { Stargate.log.log(Level.WARNING, Stargate.getString("prefix") + "Missing destination point in .gate file " + gate.getFilename()); } if (exitLocation != null) { //Prevent traveller from spawning inside a slab BlockData blockData = getWorld().getBlockAt(exitLocation).getBlockData(); if (blockData instanceof Bisected && ((Bisected) blockData).getHalf() == Bisected.Half.BOTTOM) { exitLocation.add(0, 0.5, 0); } exitLocation.setPitch(traveller.getPitch()); return exitLocation; } else { Stargate.log.log(Level.WARNING, Stargate.getString("prefix") + "Unable to generate exit location"); } return traveller; } /** * Checks whether the chunk the portal is located at is loaded * * @return

True if the chunk containing the portal is loaded

*/ public boolean isChunkLoaded() { //TODO: Improve this in the case where the portal sits between two chunks return getWorld().isChunkLoaded(topLeft.getBlock().getChunk()); } /** * Gets the identity (sign) location of the portal * * @return

The identity location of the portal

*/ public BlockLocation getId() { return this.id; } public int getModX() { return this.modX; } public int getModZ() { return this.modZ; } public float getRotX() { return this.rotX; } /** * Gets the location of the top-left block of the portal * * @return

The location of the top-left portal block

*/ public BlockLocation getTopLeft() { return this.topLeft; } /** * Verifies that all control blocks in this portal follows its gate template * * @return

True if all control blocks were verified

*/ public boolean isVerified() { verified = true; if (!Stargate.verifyPortals) { return true; } for (RelativeBlockVector control : gate.getControls()) { verified = verified && getBlockAt(control).getBlock().getType().equals(gate.getControlBlock()); } return verified; } /** * Gets the result of the last portal verification * * @return

True if this portal was verified

*/ public boolean wasVerified() { if (!Stargate.verifyPortals) { return true; } return verified; } /** * Checks if all blocks in a gate matches the gate template * * @return

True if all blocks match the gate template

*/ public boolean checkIntegrity() { if (!Stargate.verifyPortals) { return true; } return gate.matches(topLeft, modX, modZ); } /** * Activates this portal for the given player * * @param player

The player to activate the portal for

* @return

True if the portal was activated

*/ public boolean activate(Player player) { destinations.clear(); destination = ""; Stargate.activeList.add(this); activePlayer = player; String network = getNetwork(); destinations = PortalHandler.getDestinations(this, player, network); if (Stargate.sortLists) { Collections.sort(destinations); } if (Stargate.destMemory && !lastDestination.isEmpty() && destinations.contains(lastDestination)) { destination = lastDestination; } StargateActivateEvent event = new StargateActivateEvent(this, player, destinations, destination); Stargate.server.getPluginManager().callEvent(event); if (event.isCancelled()) { Stargate.activeList.remove(this); return false; } destination = event.getDestination(); destinations = event.getDestinations(); drawSign(); return true; } /** * Deactivates this portal */ public void deactivate() { StargateDeactivateEvent event = new StargateDeactivateEvent(this); Stargate.server.getPluginManager().callEvent(event); if (event.isCancelled()) { return; } Stargate.activeList.remove(this); if (isFixed()) { return; } destinations.clear(); destination = ""; activePlayer = null; drawSign(); } public boolean isActive() { return isFixed() || (destinations.size() > 0); } public void cycleDestination(Player player) { cycleDestination(player, 1); } public void cycleDestination(Player player, int dir) { boolean activate = false; if (!isActive() || getActivePlayer() != player) { // If the event is cancelled, return if (!activate(player)) { return; } Stargate.debug("cycleDestination", "Network Size: " + PortalHandler.getNetwork(network).size()); Stargate.debug("cycleDestination", "Player has access to: " + destinations.size()); activate = true; } if (destinations.size() == 0) { Stargate.sendMessage(player, Stargate.getString("destEmpty")); return; } if (!Stargate.destMemory || !activate || lastDestination.isEmpty()) { int index = destinations.indexOf(destination); index += dir; if (index >= destinations.size()) index = 0; else if (index < 0) index = destinations.size() - 1; destination = destinations.get(index); lastDestination = destination; } openTime = System.currentTimeMillis() / 1000; drawSign(); } public final void drawSign() { BlockState state = id.getBlock().getState(); if (!(state instanceof Sign)) { Stargate.log.warning(Stargate.getString("prefix") + "Sign block is not a Sign object"); Stargate.debug("Portal::drawSign", "Block: " + id.getBlock().getType() + " @ " + id.getBlock().getLocation()); return; } Sign sign = (Sign) state; Stargate.setLine(sign, 0, "-" + name + "-"); int max = destinations.size() - 1; int done = 0; if (!isActive()) { Stargate.setLine(sign, ++done, Stargate.getString("signRightClick")); Stargate.setLine(sign, ++done, Stargate.getString("signToUse")); if (!isNoNetwork()) { Stargate.setLine(sign, ++done, "(" + network + ")"); } } else { // Awesome new logic for Bungee gates if (isBungee()) { Stargate.setLine(sign, ++done, Stargate.getString("bungeeSign")); Stargate.setLine(sign, ++done, ">" + destination + "<"); Stargate.setLine(sign, ++done, "[" + network + "]"); } else if (isFixed()) { if (isRandom()) { Stargate.setLine(sign, ++done, "> " + Stargate.getString("signRandom") + " <"); } else { Stargate.setLine(sign, ++done, ">" + destination + "<"); } if (isNoNetwork()) { Stargate.setLine(sign, ++done, ""); } else { Stargate.setLine(sign, ++done, "(" + network + ")"); } Portal dest = PortalHandler.getByName(destination, network); if (dest == null && !isRandom()) { Stargate.setLine(sign, ++done, Stargate.getString("signDisconnected")); } else { Stargate.setLine(sign, ++done, ""); } } else { int index = destinations.indexOf(destination); if ((index == max) && (max > 1) && (++done <= 3)) { if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) { Portal dest = PortalHandler.getByName(destinations.get(index - 2), network); boolean green = Stargate.isFree(activePlayer, this, dest); Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index - 2)); } else { Stargate.setLine(sign, done, destinations.get(index - 2)); } } if ((index > 0) && (++done <= 3)) { if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) { Portal dest = PortalHandler.getByName(destinations.get(index - 1), network); boolean green = Stargate.isFree(activePlayer, this, dest); Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index - 1)); } else { Stargate.setLine(sign, done, destinations.get(index - 1)); } } if (++done <= 3) { if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) { Portal dest = PortalHandler.getByName(destination, network); boolean green = Stargate.isFree(activePlayer, this, dest); Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + ">" + destination + "<"); } else { Stargate.setLine(sign, done, " >" + destination + "< "); } } if ((max >= index + 1) && (++done <= 3)) { if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) { Portal dest = PortalHandler.getByName(destinations.get(index + 1), network); boolean green = Stargate.isFree(activePlayer, this, dest); Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index + 1)); } else { Stargate.setLine(sign, done, destinations.get(index + 1)); } } if ((max >= index + 2) && (++done <= 3)) { if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) { Portal dest = PortalHandler.getByName(destinations.get(index + 2), network); boolean green = Stargate.isFree(activePlayer, this, dest); Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index + 2)); } else { Stargate.setLine(sign, done, destinations.get(index + 2)); } } } } for (done++; done <= 3; done++) { sign.setLine(done, ""); } sign.update(); } /** * Gets the block at a relative block vector location * * @param vector

The relative block vector

* @return

The block at the given relative position

*/ BlockLocation getBlockAt(RelativeBlockVector vector) { return topLeft.modRelative(vector.getRight(), vector.getDepth(), vector.getDistance(), modX, 1, modZ); } @Override public String toString() { return String.format("Portal [id=%s, network=%s name=%s, type=%s]", id, network, name, gate.getFilename()); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((network == null) ? 0 : network.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Portal other = (Portal) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equalsIgnoreCase(other.name)) return false; if (network == null) { return other.network == null; } else { return network.equalsIgnoreCase(other.network); } } }