/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.managed.teleport; import com.jcwhatever.nucleus.Nucleus; import com.jcwhatever.nucleus.events.teleport.PreTeleportEvent; import com.jcwhatever.nucleus.events.teleport.TeleportEvent; import com.jcwhatever.nucleus.managed.entity.meta.EntityMeta; import com.jcwhatever.nucleus.managed.entity.meta.IEntityMetaContext; import com.jcwhatever.nucleus.managed.scheduler.Scheduler; import com.jcwhatever.nucleus.managed.teleport.ITeleportLeashPair; import com.jcwhatever.nucleus.managed.teleport.ITeleportResult; import com.jcwhatever.nucleus.managed.teleport.TeleportMode; import com.jcwhatever.nucleus.utils.CollectionUtils; import com.jcwhatever.nucleus.utils.LeashUtils; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.coords.LocationUtils; import com.jcwhatever.nucleus.utils.observer.future.FutureAgent; import com.jcwhatever.nucleus.utils.observer.future.IFuture; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.Iterator; import java.util.List; /** * Implementation of {@link ITeleportResult} which also handles teleporting entities. */ class TeleportHandler implements ITeleportResult { private static final IEntityMetaContext META = EntityMeta.getContext(Nucleus.getPlugin()); private final Entity _entity; private final List<Entity> _teleported = new ArrayList<>(3); private final TeleportCause _cause; private final TeleportMode _mode; private final Location _from = new Location(null, 0, 0, 0); private final Location _to = new Location(null, 0, 0, 0); // results private List<ITeleportLeashPair> _leashPairs; private Deque<Entity> _mounts; private List<Entity> _rejectedMounts; private List<ITeleportLeashPair> _rejectedLeashPairs; // result caches private List<Player> _players; private List<Entity> _rejected; private Status _status = Status.PENDING; private Status _result = Status.PENDING; private boolean _isMultiTick; private FutureAgent _agent; /** * Constructor. * * @param entity The entity being teleported. * @param cause The teleport cause. * @param mode The teleport mode. */ TeleportHandler(Entity entity, TeleportCause cause, TeleportMode mode) { _entity = entity; _cause = cause; _mode = mode; entity.getLocation(_from); // get all mounts Entity rootEntity = getRootEntity(entity); if (entity.equals(rootEntity)) { _teleported.add(entity); } else { _mounts = new ArrayDeque<>(3); Entity e = rootEntity; while (e != null) { boolean isEntity = e.equals(entity); if ((mode.isMountsTeleport() || isEntity) && (isEntity || !META.has(e, InternalTeleportManager.TELEPORT_DENY_META_NAME))) { _teleported.add(e); _mounts.add(e); } else { if (_rejectedMounts == null) _rejectedMounts = new ArrayList<>(3); _rejectedMounts.add(e); } e = e.getPassenger(); } } // find leashed entities for (Entity e : _teleported) { if (!(e instanceof Player)) continue; Collection<Entity> leashed = LeashUtils.getLeashed((Player) e); for (Entity leash : leashed) { LeashPair pair = new LeashPair((LivingEntity) leash, (Player) e); if (mode.isLeashTeleport() && !META.has(leash, InternalTeleportManager.TELEPORT_DENY_META_NAME)) { if (_leashPairs == null) { _leashPairs = new ArrayList<>(3); } _leashPairs.add(pair); } else { if (_rejectedLeashPairs == null) _rejectedLeashPairs = new ArrayList<>(3); _rejectedLeashPairs.add(pair); } pair.unleash(); } } if (_leashPairs != null) { for (ITeleportLeashPair pair : _leashPairs) _teleported.add(pair.getLeashed()); } } public TeleportHandler teleport(Location destination) { LocationUtils.copy(destination, _to); PreTeleportEvent event = new PreTeleportEvent(_entity, this, _from, _to); if (META.has(_entity, InternalTeleportManager.TELEPORT_DENY_META_NAME)) event.setCancelled(true); Nucleus.getEventManager().callBukkit(this, event); if (event.isCancelled()) { _result = Status.CANCELLED; return this; } if (_mounts != null) { for (Entity e : _mounts) { addTeleporting(e, _to); e.eject(); } } else { addTeleporting(_entity, _to); } for (Entity e : _teleported) { boolean result = e.teleport(_to, _cause); if (e.equals(_entity)) { _result = result ? Status.SUCCESS : Status.CANCELLED; } } if (_result == Status.PENDING) { // main entity not teleported _result = Status.CANCELLED; } if (_result == Status.CANCELLED) { removeTeleporting(); return this; } if (_mounts == null && _leashPairs == null) { removeTeleporting(); callCompleteEvent(); return this; } _isMultiTick = true; _agent = new FutureAgent(); Scheduler.runTaskLater(Nucleus.getPlugin(), 2, new Runnable() { @Override public void run() { mountAll(); leashAll(); _agent.success(); removeTeleporting(); callCompleteEvent(); } }); return this; } @Override public boolean isMultiTick() { return _isMultiTick; } @Override public TeleportCause getCause() { return _cause; } @Override public Status getStatus() { return _status; } @Override public boolean isSuccess() { return _result == Status.SUCCESS; } @Override public boolean isCancelled() { return _result == Status.CANCELLED; } @Override public Location getFrom() { return LocationUtils.copy(_from); } @Override public Location getFrom(Location output) { PreCon.notNull(output); return LocationUtils.copy(_from, output); } @Override public Location getTo() { return LocationUtils.copy(_to); } @Override public Location getTo(Location output) { PreCon.notNull(output); return LocationUtils.copy(_to, output); } @Override public TeleportMode getMode() { return _mode; } @Override public Entity getEntity() { return _entity; } @Override public Collection<Player> getPlayers() { return getPlayers(new ArrayList<Player>(0)); } @Override public <T extends Collection<Player>> T getPlayers(T output) { PreCon.notNull(output); if (_players == null) { _players = new ArrayList<>(3); for (Entity e : _teleported) { if (e instanceof Player) { _players.add((Player)e); } } } output.addAll(_players); return output; } @Override public Collection<Entity> getMounts() { if (_mounts == null) { return CollectionUtils.unmodifiableList(); } return Collections.unmodifiableList(getMounts(new ArrayList<Entity>(_mounts.size()))); } @Override public <T extends Collection<Entity>> T getMounts(T output) { PreCon.notNull(output); if (_mounts != null) output.addAll(_mounts); return output; } @Override public Collection<ITeleportLeashPair> getLeashed() { if (_leashPairs == null) { return CollectionUtils.unmodifiableList(); } return Collections.unmodifiableList(_leashPairs); } @Override public <T extends Collection<ITeleportLeashPair>> T getLeashed(T output) { PreCon.notNull(output); if (_leashPairs != null) output.addAll(_leashPairs); return output; } @Override public Collection<Entity> getTeleports() { return Collections.unmodifiableList(_teleported); } @Override public <T extends Collection<Entity>> T getTeleports(T output) { PreCon.notNull(output); output.addAll(_teleported); return output; } @Override public Collection<Entity> getRejectedMounts() { if (_rejectedMounts == null) return CollectionUtils.unmodifiableList(); return Collections.unmodifiableList(_rejectedMounts); } @Override public <T extends Collection<Entity>> T getRejectedMounts(T output) { PreCon.notNull(output); if (_rejectedMounts != null) output.addAll(_rejectedMounts); return output; } @Override public Collection<ITeleportLeashPair> getRejectedLeashed() { if (_rejectedLeashPairs == null) return CollectionUtils.unmodifiableList(); return Collections.unmodifiableList(_rejectedLeashPairs); } @Override public <T extends Collection<ITeleportLeashPair>> T getRejectedLeashed(T output) { PreCon.notNull(output); if (_rejectedLeashPairs != null) output.addAll(_rejectedLeashPairs); return output; } @Override public Collection<Entity> getRejected() { return getRejected(new ArrayList<Entity>(0)); } @Override public <T extends Collection<Entity>> T getRejected(T output) { PreCon.notNull(output); if (_rejected == null) { int size = (_rejectedMounts == null ? 0 : _rejectedMounts.size()) + (_rejectedLeashPairs == null ? 0 : _rejectedLeashPairs.size()); _rejected = new ArrayList<>(size); if (_rejectedMounts != null) _rejected.addAll(_rejectedMounts); if (_rejectedLeashPairs != null) { for (ITeleportLeashPair pair : _rejectedLeashPairs) _rejected.add(pair.getLeashed()); } } output.addAll(_rejected); return output; } @Override public Collection<Entity> getAll() { return getAll(new ArrayList<Entity>(3)); } @Override public <T extends Collection<Entity>> T getAll(T output) { PreCon.notNull(output); getTeleports(output); getRejected(output); return output; } @Override public IFuture getFuture() { if (_agent != null) { return _agent.getFuture(); } switch (_status) { case SUCCESS: return new FutureAgent().success(); case CANCELLED: return new FutureAgent().cancel(); default: throw new AssertionError("Invalid status"); } } private void callCompleteEvent() { TeleportEvent event = new TeleportEvent(_entity, this); Nucleus.getEventManager().callBukkit(this, event); } private void mountAll() { if (_mounts == null) return; Iterator<Entity> iterator = _mounts.descendingIterator(); Entity previous = null; while (iterator.hasNext()) { Entity entity = iterator.next(); if (previous != null) { entity.setPassenger(previous); } previous = entity; } } private void leashAll() { if (_leashPairs == null) return; for (ITeleportLeashPair pair : _leashPairs) { ((LeashPair)pair).leash(); } } private void addTeleporting(Entity entity, Location destination) { InternalTeleportManager.TELEPORTS.put(entity, null); if (!entity.getWorld().equals(destination.getWorld())) InternalTeleportManager.CROSS_WORLD_TELEPORTS.put(entity, null); } private void removeTeleporting() { for (Entity entity : _teleported) { InternalTeleportManager.TELEPORTS.remove(entity); InternalTeleportManager.CROSS_WORLD_TELEPORTS.remove(entity); } _status = _result; } private static Entity getRootEntity(Entity entity) { while (entity.getVehicle() != null) { entity = entity.getVehicle(); } return entity; } private static class LeashPair implements ITeleportLeashPair { LivingEntity leashed; Player owner; LeashPair(LivingEntity leashed, Player owner) { this.leashed = leashed; this.owner = owner; } void leash() { leashed.setLeashHolder(owner); } void unleash() { leashed.setLeashHolder(null); } @Override public Entity getLeashed() { return leashed; } @Override public Entity getLeashHolder() { return owner; } } }