503 lines
19 KiB
Java
503 lines
19 KiB
Java
//
|
|
// Created by BONNe
|
|
// Copyright - 2022
|
|
//
|
|
|
|
|
|
package world.bentobox.bentobox.listeners.teleports;
|
|
|
|
|
|
import java.util.Objects;
|
|
import java.util.UUID;
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.World;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.entity.EntityType;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.EventPriority;
|
|
import org.bukkit.event.Listener;
|
|
import org.bukkit.event.entity.EntityPortalEnterEvent;
|
|
import org.bukkit.event.player.PlayerMoveEvent;
|
|
import org.bukkit.event.player.PlayerPortalEvent;
|
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
|
import org.bukkit.util.Vector;
|
|
import org.eclipse.jdt.annotation.NonNull;
|
|
|
|
import world.bentobox.bentobox.BentoBox;
|
|
import world.bentobox.bentobox.blueprints.Blueprint;
|
|
import world.bentobox.bentobox.blueprints.BlueprintPaster;
|
|
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle;
|
|
import world.bentobox.bentobox.database.objects.Island;
|
|
import world.bentobox.bentobox.util.Util;
|
|
import world.bentobox.bentobox.util.teleport.ClosestSafeSpotTeleport;
|
|
|
|
|
|
/**
|
|
* This class handles player teleportation between dimensions.
|
|
*
|
|
* @author tastybento and BONNe
|
|
*/
|
|
public class PlayerTeleportListener extends AbstractTeleportListener implements Listener
|
|
{
|
|
/**
|
|
* Instantiates a new Portal teleportation listener.
|
|
*
|
|
* @param plugin the plugin
|
|
*/
|
|
public PlayerTeleportListener(@NonNull BentoBox plugin)
|
|
{
|
|
super(plugin);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Section: Listeners
|
|
// ---------------------------------------------------------------------
|
|
|
|
|
|
/**
|
|
* This listener checks player portal events and triggers appropriate methods to transfer
|
|
* players to the correct location in other dimension.
|
|
* <p>
|
|
* This event is triggered when player is about to being teleported because of contact with the
|
|
* nether portal or end gateway portal (exit portal triggers respawn).
|
|
* <p>
|
|
* This event is not called if nether/end is disabled in server settings.
|
|
*
|
|
* @param event the player portal event.
|
|
*/
|
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
|
public void onPlayerPortalEvent(PlayerPortalEvent event)
|
|
{
|
|
switch (event.getCause())
|
|
{
|
|
case NETHER_PORTAL -> this.portalProcess(event, World.Environment.NETHER);
|
|
case END_PORTAL, END_GATEWAY -> this.portalProcess(event, World.Environment.THE_END);
|
|
default -> throw new IllegalArgumentException("Unexpected value: " + event.getCause());
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Fires the event if nether or end is disabled at the system level
|
|
*
|
|
* @param event - EntityPortalEnterEvent
|
|
*/
|
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
|
public void onPlayerPortal(EntityPortalEnterEvent event)
|
|
{
|
|
if (!EntityType.PLAYER.equals(event.getEntity().getType()))
|
|
{
|
|
// This handles only players.
|
|
return;
|
|
}
|
|
|
|
Entity entity = event.getEntity();
|
|
Material type = event.getLocation().getBlock().getType();
|
|
UUID uuid = entity.getUniqueId();
|
|
|
|
if (this.inPortal.contains(uuid) ||
|
|
!this.plugin.getIWM().inWorld(Util.getWorld(event.getLocation().getWorld())))
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.inPortal.add(uuid);
|
|
// Add original world for respawning.
|
|
this.teleportOrigin.put(uuid, event.getLocation().getWorld());
|
|
|
|
if (!Bukkit.getAllowNether() && type.equals(Material.NETHER_PORTAL))
|
|
{
|
|
// Schedule a time
|
|
Bukkit.getScheduler().runTaskLater(this.plugin, () ->
|
|
{
|
|
// Check again if still in portal
|
|
if (this.inPortal.contains(uuid))
|
|
{
|
|
// Create new PlayerPortalEvent
|
|
PlayerPortalEvent en = new PlayerPortalEvent((Player) entity,
|
|
event.getLocation(),
|
|
null,
|
|
PlayerTeleportEvent.TeleportCause.NETHER_PORTAL,
|
|
0,
|
|
false,
|
|
0);
|
|
|
|
this.portalProcess(en, World.Environment.NETHER);
|
|
}
|
|
}, 40);
|
|
return;
|
|
}
|
|
|
|
// End portals are instant transfer
|
|
if (!Bukkit.getAllowEnd() && (type.equals(Material.END_PORTAL) || type.equals(Material.END_GATEWAY)))
|
|
{
|
|
// Create new PlayerPortalEvent
|
|
PlayerPortalEvent en = new PlayerPortalEvent((Player) entity,
|
|
event.getLocation(),
|
|
null,
|
|
type.equals(Material.END_PORTAL) ? PlayerTeleportEvent.TeleportCause.END_PORTAL : PlayerTeleportEvent.TeleportCause.END_GATEWAY,
|
|
0,
|
|
false,
|
|
0);
|
|
|
|
this.portalProcess(en, World.Environment.THE_END);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove inPortal flag only when player exits the portal
|
|
*
|
|
* @param event player move event
|
|
*/
|
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
|
public void onExitPortal(PlayerMoveEvent event)
|
|
{
|
|
if (!this.inPortal.contains(event.getPlayer().getUniqueId()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (event.getTo() != null && !event.getTo().getBlock().getType().equals(Material.NETHER_PORTAL))
|
|
{
|
|
// Player exits nether portal.
|
|
this.inPortal.remove(event.getPlayer().getUniqueId());
|
|
this.inTeleport.remove(event.getPlayer().getUniqueId());
|
|
this.teleportOrigin.remove(event.getPlayer().getUniqueId());
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Player respawn event is triggered when player enters exit portal at the end.
|
|
* This will take over respawn mechanism and place player on island.
|
|
* @param event player respawn event
|
|
*/
|
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
public void onPlayerExitPortal(PlayerRespawnEvent event)
|
|
{
|
|
if (!this.teleportOrigin.containsKey(event.getPlayer().getUniqueId()))
|
|
{
|
|
// Player is already processed.
|
|
return;
|
|
}
|
|
|
|
World fromWorld = this.teleportOrigin.get(event.getPlayer().getUniqueId());
|
|
World overWorld = Util.getWorld(fromWorld);
|
|
|
|
if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld))
|
|
{
|
|
// Not teleporting from/to bentobox worlds.
|
|
return;
|
|
}
|
|
|
|
this.getIsland(overWorld, event.getPlayer()).ifPresentOrElse(island -> {
|
|
if (!island.onIsland(event.getRespawnLocation()))
|
|
{
|
|
// If respawn location is outside island protection range, change location to the
|
|
// spawn in overworld or home location.
|
|
Location location = island.getSpawnPoint(World.Environment.NORMAL);
|
|
|
|
if (location == null)
|
|
{
|
|
// No spawn point. Rare thing. Well, use island protection center.
|
|
location = island.getProtectionCenter();
|
|
}
|
|
|
|
event.setRespawnLocation(location);
|
|
}
|
|
},
|
|
() -> {
|
|
// Player does not an island. Try to get spawn island, and if that fails, use world spawn point.
|
|
// If spawn point is not safe, do nothing. Let server handle it.
|
|
|
|
Location spawnLocation = this.getSpawnLocation(overWorld);
|
|
|
|
if (spawnLocation != null)
|
|
{
|
|
event.setRespawnLocation(spawnLocation);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Section: Processors
|
|
// ---------------------------------------------------------------------
|
|
|
|
|
|
/**
|
|
* This method process player teleportation to new dimension.
|
|
* @param event Event that triggers teleportation.
|
|
* @param environment Environment of portal type.
|
|
*/
|
|
private void portalProcess(PlayerPortalEvent event, World.Environment environment)
|
|
{
|
|
World fromWorld = event.getFrom().getWorld();
|
|
World overWorld = Util.getWorld(fromWorld);
|
|
|
|
if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld))
|
|
{
|
|
// Not teleporting from/to bentobox worlds.
|
|
return;
|
|
}
|
|
|
|
if (!this.isAllowedInConfig(overWorld, environment))
|
|
{
|
|
// World is disabled in config. Do not teleport player.
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
if (!this.isAllowedOnServer(environment))
|
|
{
|
|
// World is disabled in bukkit. Event is not triggered, but cancel by chance.
|
|
event.setCancelled(true);
|
|
}
|
|
|
|
if (this.inTeleport.contains(event.getPlayer().getUniqueId()))
|
|
{
|
|
// Player is already in teleportation.
|
|
return;
|
|
}
|
|
|
|
this.inTeleport.add(event.getPlayer().getUniqueId());
|
|
|
|
if (fromWorld.equals(overWorld) && !this.isIslandWorld(overWorld, environment))
|
|
{
|
|
// This is not island world. Use standard nether or end world teleportation.
|
|
this.handleToStandardNetherOrEnd(event, overWorld, environment);
|
|
return;
|
|
}
|
|
|
|
if (!fromWorld.equals(overWorld) && !this.isIslandWorld(overWorld, environment))
|
|
{
|
|
// If entering a portal in the other world, teleport to a portal in overworld if
|
|
// there is one
|
|
this.handleFromStandardNetherOrEnd(event, overWorld, environment);
|
|
return;
|
|
}
|
|
|
|
// To the nether/end or overworld.
|
|
World toWorld = !fromWorld.getEnvironment().equals(environment) ?
|
|
this.getNetherEndWorld(overWorld, environment) : overWorld;
|
|
|
|
// Set whether portals should be created or not
|
|
event.setCanCreatePortal(this.isMakePortals(overWorld, environment));
|
|
// Default 16 is will always end up placing portal as close to X/8 coordinate as possible.
|
|
// In most situations, 2 block value should be enough... I hope.
|
|
event.setCreationRadius(2);
|
|
|
|
// Set the destination location
|
|
// If portals cannot be created, then destination is the spawn point, otherwise it's the vector
|
|
event.setTo(this.calculateLocation(event.getFrom(), fromWorld, toWorld, environment, event.getCanCreatePortal()));
|
|
|
|
// Find the distance from edge of island's protection and set the search radius
|
|
this.getIsland(event.getTo()).ifPresent(island ->
|
|
event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island)));
|
|
|
|
// Check if there is an island there or not
|
|
if (this.isPastingMissingIslands(overWorld) &&
|
|
this.isAllowedInConfig(overWorld, environment) &&
|
|
this.isIslandWorld(overWorld, environment) &&
|
|
this.getNetherEndWorld(overWorld, environment) != null &&
|
|
this.getIsland(event.getTo()).
|
|
filter(island -> !this.hasPartnerIsland(island, environment)).
|
|
map(island -> {
|
|
event.setCancelled(true);
|
|
this.pasteNewIsland(event.getPlayer(), event.getTo(), island, environment);
|
|
return true;
|
|
}).
|
|
orElse(false))
|
|
{
|
|
// If there is no island, then processor already created island. Nothing to do more.
|
|
return;
|
|
}
|
|
|
|
if (!event.isCancelled() && event.getCanCreatePortal())
|
|
{
|
|
// Let the server teleport
|
|
return;
|
|
}
|
|
|
|
if (World.Environment.THE_END.equals(environment))
|
|
{
|
|
// Prevent death from hitting the ground while calculating location.
|
|
event.getPlayer().setVelocity(new Vector(0,0,0));
|
|
event.getPlayer().setFallDistance(0);
|
|
}
|
|
|
|
// If we do not generate portals, teleportation should happen manually with safe spot builder.
|
|
// Otherwise, we could end up with situations when player is placed in mid air, if teleportation
|
|
// is done instantly.
|
|
// Our safe spot task is triggered in next tick, however, end teleportation happens in the same tick.
|
|
// It is placed outside THE_END check, as technically it could happen with the nether portal too.
|
|
|
|
// If there is a portal to go to already, then the player will go there
|
|
Bukkit.getScheduler().runTask(this.plugin, () -> {
|
|
if (!event.getPlayer().getWorld().equals(toWorld))
|
|
{
|
|
// Else manually teleport entity
|
|
ClosestSafeSpotTeleport.builder(this.plugin).
|
|
entity(event.getPlayer()).
|
|
location(event.getTo()).
|
|
portal().
|
|
successRunnable(() -> {
|
|
// Reset velocity just in case.
|
|
event.getPlayer().setVelocity(new Vector(0,0,0));
|
|
event.getPlayer().setFallDistance(0);
|
|
}).
|
|
build();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle teleport from or to standard nether or end
|
|
* @param event - PlayerPortalEvent
|
|
* @param overWorld - over world
|
|
* @param environment - environment involved
|
|
*/
|
|
private void handleToStandardNetherOrEnd(PlayerPortalEvent event,
|
|
World overWorld,
|
|
World.Environment environment)
|
|
{
|
|
World toWorld = Objects.requireNonNull(this.getNetherEndWorld(overWorld, environment));
|
|
Location spawnPoint = toWorld.getSpawnLocation();
|
|
|
|
// If going to the nether and nether portals are active then just teleport to approx location
|
|
if (World.Environment.NETHER.equals(environment) &&
|
|
this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals())
|
|
{
|
|
spawnPoint = event.getFrom().toVector().toLocation(toWorld);
|
|
}
|
|
|
|
// If spawn is set as 0,63,0 in the End then move it to 100, 50 ,0.
|
|
if (World.Environment.THE_END.equals(environment) && spawnPoint.getBlockX() == 0 && spawnPoint.getBlockZ() == 0)
|
|
{
|
|
// Set to the default end spawn
|
|
spawnPoint = new Location(toWorld, 100, 50, 0);
|
|
toWorld.setSpawnLocation(100, 50, 0);
|
|
}
|
|
|
|
if (this.isAllowedOnServer(environment))
|
|
{
|
|
// To Standard Nether or end
|
|
event.setTo(spawnPoint);
|
|
}
|
|
else
|
|
{
|
|
// Teleport to standard nether or end
|
|
ClosestSafeSpotTeleport.builder(this.plugin).
|
|
entity(event.getPlayer()).
|
|
location(spawnPoint).
|
|
portal().
|
|
build();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle teleport from or to standard nether or end (end is not possible because EXIT PORTAL triggers RESPAWN event)
|
|
* @param event - PlayerPortalEvent
|
|
* @param overWorld - over world
|
|
* @param environment - environment involved
|
|
*/
|
|
private void handleFromStandardNetherOrEnd(PlayerPortalEvent event, World overWorld, World.Environment environment)
|
|
{
|
|
if (World.Environment.NETHER.equals(environment) &&
|
|
this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals())
|
|
{
|
|
// Set to location directly to the from location.
|
|
event.setTo(event.getFrom().toVector().toLocation(overWorld));
|
|
|
|
// Update portal search radius.
|
|
this.getIsland(event.getTo()).ifPresent(island ->
|
|
event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island)));
|
|
|
|
event.setCanCreatePortal(true);
|
|
// event.setCreationRadius(16); 16 is default creation radius.
|
|
}
|
|
else
|
|
{
|
|
// Cannot be portal. Should recalculate position.
|
|
// TODO: Currently, it is always spawn location. However, default home must be assigned.
|
|
Location toLocation = this.getIsland(overWorld, event.getPlayer()).
|
|
map(island -> island.getSpawnPoint(World.Environment.NORMAL)).
|
|
orElseGet(() -> {
|
|
// If player do not have island, try spawn.
|
|
Location spawnLocation = this.getSpawnLocation(overWorld);
|
|
return spawnLocation == null ?
|
|
event.getFrom().toVector().toLocation(overWorld) :
|
|
spawnLocation;
|
|
});
|
|
|
|
event.setTo(toLocation);
|
|
}
|
|
|
|
if (!this.isAllowedOnServer(environment))
|
|
{
|
|
// Custom portal handling.
|
|
event.setCancelled(true);
|
|
|
|
// Teleport to standard nether or end
|
|
ClosestSafeSpotTeleport.builder(this.plugin).
|
|
entity(event.getPlayer()).
|
|
location(event.getTo()).
|
|
portal().
|
|
build();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Pastes the default nether or end island and teleports the player to the island's spawn point
|
|
* @param player - player to teleport after pasting
|
|
* @param to - the fallback location if a spawn point is not part of the blueprint
|
|
* @param island - the island
|
|
* @param environment - NETHER or THE_END
|
|
*/
|
|
private void pasteNewIsland(Player player,
|
|
Location to,
|
|
Island island,
|
|
World.Environment environment)
|
|
{
|
|
// Paste then teleport player
|
|
this.plugin.getIWM().getAddon(island.getWorld()).ifPresent(addon ->
|
|
{
|
|
// Get the default bundle's nether or end blueprint
|
|
BlueprintBundle blueprintBundle = plugin.getBlueprintsManager().getDefaultBlueprintBundle(addon);
|
|
|
|
if (blueprintBundle != null)
|
|
{
|
|
Blueprint bluePrint = this.plugin.getBlueprintsManager().getBlueprints(addon).
|
|
get(blueprintBundle.getBlueprint(environment));
|
|
|
|
if (bluePrint != null)
|
|
{
|
|
new BlueprintPaster(this.plugin, bluePrint, to.getWorld(), island).
|
|
paste().
|
|
thenAccept(state -> ClosestSafeSpotTeleport.builder(this.plugin).
|
|
entity(player).
|
|
location(island.getSpawnPoint(environment) == null ? to : island.getSpawnPoint(environment)).
|
|
portal().
|
|
build());
|
|
}
|
|
else
|
|
{
|
|
this.plugin.logError("Could not paste default island in nether or end. " +
|
|
"Is there a nether-island or end-island blueprint?");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|