Package com.bergerkiller.bukkit.common.entity

Source Code of com.bergerkiller.bukkit.common.entity.CommonEntity

package com.bergerkiller.bukkit.common.entity;

import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;

import net.minecraft.server.Chunk;
import net.minecraft.server.Entity;
import net.minecraft.server.EntityTrackerEntry;
import net.minecraft.server.IInventory;
import net.minecraft.server.World;

import org.bukkit.Location;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;

import com.bergerkiller.bukkit.common.bases.ExtendedEntity;
import com.bergerkiller.bukkit.common.controller.DefaultEntityController;
import com.bergerkiller.bukkit.common.controller.DefaultEntityNetworkController;
import com.bergerkiller.bukkit.common.controller.EntityController;
import com.bergerkiller.bukkit.common.controller.EntityNetworkController;
import com.bergerkiller.bukkit.common.controller.ExternalEntityNetworkController;
import com.bergerkiller.bukkit.common.conversion.Conversion;
import com.bergerkiller.bukkit.common.entity.nms.NMSEntityHook;
import com.bergerkiller.bukkit.common.entity.nms.NMSEntityTrackerEntry;
import com.bergerkiller.bukkit.common.entity.type.CommonItem;
import com.bergerkiller.bukkit.common.entity.type.CommonLivingEntity;
import com.bergerkiller.bukkit.common.entity.type.CommonPlayer;
import com.bergerkiller.bukkit.common.internal.CommonNMS;
import com.bergerkiller.bukkit.common.internal.CommonPlugin;
import com.bergerkiller.bukkit.common.reflection.SafeField;
import com.bergerkiller.bukkit.common.reflection.classes.EntityPlayerRef;
import com.bergerkiller.bukkit.common.reflection.classes.EntityRef;
import com.bergerkiller.bukkit.common.reflection.classes.EntityTrackerEntryRef;
import com.bergerkiller.bukkit.common.reflection.classes.PlayerConnectionRef;
import com.bergerkiller.bukkit.common.reflection.classes.WorldRef;
import com.bergerkiller.bukkit.common.reflection.classes.WorldServerRef;
import com.bergerkiller.bukkit.common.utils.CommonUtil;
import com.bergerkiller.bukkit.common.utils.EntityUtil;
import com.bergerkiller.bukkit.common.utils.WorldUtil;
import com.bergerkiller.bukkit.common.wrappers.EntityTracker;
import com.bergerkiller.bukkit.common.wrappers.IntHashMap;

/**
* Wrapper class for additional methods Bukkit can't or doesn't provide.
*
* @param <T> - type of Entity
*/
public class CommonEntity<T extends org.bukkit.entity.Entity> extends ExtendedEntity<T> {
  public CommonEntity(T entity) {
    super(entity);
  }

  /**
   * Gets the Entity Network Controller currently assigned to this Entity.
   * If none is available, this method returns Null.
   * If no custom network controller is set, this method returns a new
   * {@link DefaultEntityNetworkController} instance.
   *
   * @return Entity Network Controller, or null if not available
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public EntityNetworkController<CommonEntity<T>> getNetworkController() {
    if (EntityRef.world.getInternal(getHandle()) == null) {
      return null;
    }
    final EntityNetworkController result;
    final Object entityTrackerEntry = WorldUtil.getTrackerEntry(entity);
    if (entityTrackerEntry == null) {
      return null;
    } else if (entityTrackerEntry instanceof NMSEntityTrackerEntry) {
      result = ((NMSEntityTrackerEntry) entityTrackerEntry).getController();
    } else if (EntityTrackerEntry.class.equals(entityTrackerEntry.getClass())) {
      result = new DefaultEntityNetworkController();
      result.bind(this, entityTrackerEntry);
    } else {
      result = new ExternalEntityNetworkController();
      result.bind(this, entityTrackerEntry);
    }
    return result;
  }

  /**
   * Sets an Entity Network Controller for this Entity.
   * To stop tracking this minecart, pass in Null.
   * To default back to the net.minecraft.server implementation, pass in a new
   * {@link DefaultEntityNetworkController} instance.<br>
   * <br>
   * This method only works if the Entity world has previously been set.
   *
   * @param controller to set to
   */
  @SuppressWarnings({"rawtypes", "unchecked"})
  public void setNetworkController(EntityNetworkController controller) {
    if (getWorld() == null) {
      throw new RuntimeException("Can not set the network controller when no world is known! (need to spawn it?)");
    }
    final EntityTracker tracker = WorldUtil.getTracker(getWorld());
    final Object storedEntry = tracker.getEntry(entity);

    // Properly handle a previously set controller
    if (storedEntry instanceof NMSEntityTrackerEntry) {
      final EntityNetworkController oldController = ((NMSEntityTrackerEntry) storedEntry).getController();
      if (oldController == controller) {
        return;
      } else if (oldController != null) {
        oldController.onDetached();
      }
    }

    // Take care of null controllers - stop tracking
    if (controller == null) {
      tracker.stopTracking(entity);
      return;
    }

    final Object newEntry;
    if (controller instanceof DefaultEntityNetworkController) {
      // Assign the default Entity Tracker Entry
      if (EntityTrackerEntryRef.TEMPLATE.isType(storedEntry)) {
        // Nothing to be done here
        newEntry = storedEntry;
      } else {
        // Create a new entry
        final CommonEntityType type = CommonEntityType.byEntity(entity);
        newEntry = new EntityTrackerEntry(getHandle(Entity.class), type.networkViewDistance, type.networkUpdateInterval, type.networkIsMobile);
        // Transfer data if needed
        if (storedEntry != null) {
          EntityTrackerEntryRef.TEMPLATE.transfer(storedEntry, newEntry);
        }
      }
    } else if (controller instanceof ExternalEntityNetworkController) {
      // Use the entry as stored by the external network controller
      newEntry = controller.getHandle();
      // Be sure to refresh stats using the old entry
      if (storedEntry != null && newEntry != null) {
        EntityTrackerEntryRef.TEMPLATE.transfer(storedEntry, newEntry);
      }
    } else {
      // Assign a new Entity Tracker Entry with controller capabilities
      if (storedEntry instanceof NMSEntityTrackerEntry) {
        // Use the previous entry - hotswap the controller
        newEntry = storedEntry;
        EntityTrackerEntryRef.viewers.get(newEntry).clear();
      } else {
        // Create a new entry from scratch
        newEntry = new NMSEntityTrackerEntry(this.getEntity());
        // Transfer possible information over
        if (storedEntry != null) {
          EntityTrackerEntryRef.TEMPLATE.transfer(storedEntry, newEntry);
        }
      }
    }

    // Attach the entry to the controller
    controller.bind(this, newEntry);

    // Attach (new?) entry to the world
    if (storedEntry != newEntry) {
      tracker.setEntry(entity, newEntry);

      // Make sure to update the viewers
      EntityTrackerEntryRef.scanPlayers(newEntry, getWorld().getPlayers());
    }
  }

  /**
   * Gets the Entity Controller currently assigned to this Entity.
   * If no custom controller is set, this method returns a new
   * {@link DefaultEntityController} instance.
   *
   * @return Entity Controller
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public EntityController<CommonEntity<T>> getController() {
    if (isHooked()) {
      return (EntityController<CommonEntity<T>>) getHandle(NMSEntityHook.class).getController();
    }
    final EntityController controller = new DefaultEntityController();
    controller.bind(this);
    return controller;
  }

  /**
   * Checks whether this particular Entity supports the use of Entity Controllers.
   * If this method returns True, {@link #setController(EntityController)} can be used.<br><br>
   *
   * Note that Entity Network Controllers are always supported.
   *
   * @return True if Entity Controllers are supported, False if not
   */
  public boolean hasControllerSupport() {
    // Check whether already hooked
    if (isHooked()) {
      return true;
    }
    final Object handle = getHandle();
    final CommonEntityType type = CommonEntityType.byNMSEntity(handle);
    // Check whether the handle is not of an external-plugin type
    if (handle == null || !type.nmsType.isType(handle)) {
      return false;
    }
    // Check whether the CommonEntityType supports hooking
    return type.hasNMSEntity();
  }

  /**
   * Checks whether the Entity is a BKCommonLib hook
   *
   * @return True if hooked, False if not
   */
  protected boolean isHooked() {
    return getHandle() instanceof NMSEntityHook;
  }

  /**
   * Replaces the current entity, if needed, with the BKCommonLib Hook entity type
   */
  protected void prepareHook() {
    final Entity oldInstance = getHandle(Entity.class);
    if (oldInstance instanceof NMSEntityHook) {
      // Already hooked
      return;
    }

    // Check whether conversion is allowed
    final String oldInstanceName = oldInstance.getClass().getName();
    final CommonEntityType type = CommonEntityType.byEntity(entity);
    if (!type.nmsType.isType(oldInstance)) {
      throw new RuntimeException("Can not assign controllers to a custom Entity Type (" + oldInstanceName + ")");
    }
    if (!type.hasNMSEntity()) {
      throw new RuntimeException("Entity of type '" + type.entityType + "' has no Controller support!");
    }
    // Respawn the entity and attach the controller
    try {
      // Create a new entity instance and perform data/property transfer
      Object newInstance = type.createNMSHookEntity(this);
      type.nmsType.transfer(oldInstance, newInstance);
      replaceEntity((Entity) newInstance);
    } catch (Throwable t) {
      throw new RuntimeException("Failed to set controller:", t);
    }
  }

  private void replaceEntity(final Entity newInstance) {
    final Entity oldInstance = getHandle(Entity.class);
    oldInstance.dead = true;
    newInstance.dead = false;
    oldInstance.valid = false;
    newInstance.valid = true;

    // *** Bukkit Entity ***
    ((CraftEntity) entity).setHandle(newInstance);
    if (entity instanceof InventoryHolder) {
      Inventory inv = ((InventoryHolder) entity).getInventory();
      if (inv instanceof CraftInventory && newInstance instanceof IInventory) {
        SafeField.set(inv, "inventory", newInstance);
      }
    }

    // *** Give the old entity a new Bukkit Entity ***
    EntityRef.bukkitEntity.set(oldInstance, EntityRef.createEntity(oldInstance));

    // *** Passenger/Vehicle ***
    if (newInstance.vehicle != null) {
      newInstance.vehicle.passenger = newInstance;
    }
    if (newInstance.passenger != null) {
      newInstance.passenger.vehicle = newInstance;
    }

    // Only do this replacement logic for Entities that are already spawned
    if (this.isSpawned()) {
      // Now proceed to replace this NMS Entity in all places imaginable.
      // First load the chunk so we can at least work on something
      Chunk chunk = CommonNMS.getNative(getWorld().getChunkAt(getChunkX(), getChunkZ()));

      // *** Entities By ID Map ***
      final IntHashMap<Object> entitiesById = WorldServerRef.entitiesById.get(oldInstance.world);
      if (entitiesById.remove(oldInstance.getId()) == null) {
        CommonUtil.nextTick(new Runnable() {
          public void run() {
            entitiesById.put(newInstance.getId(), newInstance);
          }
        });
      }
      entitiesById.put(newInstance.getId(), newInstance);

      // *** EntityTrackerEntry ***
      final EntityTracker tracker = WorldUtil.getTracker(getWorld());
      Object entry = tracker.getEntry(entity);
      if (entry != null) {
        EntityTrackerEntryRef.tracker.set(entry, entity);
      }
      if (hasPassenger()) {
        entry = tracker.getEntry(getPassenger());
        if (entry != null) {
          EntityTrackerEntryRef.vehicle.set(entry, entity);
        }
      }

      // *** World ***
      replaceInList(oldInstance.world.entityList, newInstance);
      replaceInList(WorldRef.entityRemovalList.get(oldInstance.world), newInstance);

      // *** Chunk ***
      final int chunkY = getChunkY();
      if (!replaceInChunk(chunk, chunkY, newInstance)) {
        for (int y = 0; y < chunk.entitySlices.length; y++) {
          if (y != chunkY && replaceInChunk(chunk, y, newInstance)) {
            break;
          }
        }
      }
    }
  }

  private static boolean replaceInChunk(Chunk chunk, int chunkY, Entity entity) {
    if (replaceInList(chunk.entitySlices[chunkY], entity)) {
      chunk.m = true;
      return true;
    } else {
      return false;
    }
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private static boolean replaceInList(List list, Entity entity) {
    ListIterator<Entity> iter = list.listIterator();
    while (iter.hasNext()) {
      if (iter.next().getId() == entity.getId())  {
        iter.set(entity);
        return true;
      }
    }
    return false;
  }

  /**
   * Sets an Entity Controller for this Entity.
   * This method throws an Exception if this kind of Entity is not supported.
   *
   * @param controller to set to
   */
  @SuppressWarnings({"rawtypes", "unchecked"})
  public void setController(EntityController controller) {
    // Prepare the hook
    this.prepareHook();

    // If null, resolve to the default type
    if (controller == null) {
      controller = new DefaultEntityController();
    }
    getController().bind(null);
    controller.bind(this);
  }

  /**
   * Performs all the logic normally performed after ticking a single Entity.
   * If onTick is managed externally, this method keeps it compatible.
   * Calling this method results in the entity properly moved between chunks, and other logic.
   */
  public void doPostTick() {
    final int oldcx = getChunkX();
    final int oldcy = getChunkY();
    final int oldcz = getChunkZ();
    final int newcx = loc.x.chunk();
    final int newcy = loc.y.chunk();
    final int newcz = loc.z.chunk();
    final org.bukkit.World world = getWorld();
    final boolean changedChunks = oldcx != newcx || oldcy != newcy || oldcz != newcz;
    boolean isLoaded = this.isInLoadedChunk();

    // Handle chunk/slice movement
    // Remove from the previous chunk
    if (isLoaded && changedChunks) {
      final org.bukkit.Chunk chunk = WorldUtil.getChunk(world, oldcx, oldcz);
      if (chunk != null) {
        WorldUtil.removeEntity(chunk, entity);
      }
    }
    // Add to the new chunk
    if (!isLoaded || changedChunks) {
      final org.bukkit.Chunk chunk = WorldUtil.getChunk(world, newcx, newcz);
      if (isLoaded = chunk != null) {
        WorldUtil.addEntity(chunk, entity);
      }
      EntityRef.isLoaded.set(getHandle(), isLoaded);
    }

    // Tick the passenger
    if (isLoaded && hasPassenger()) {
      final org.bukkit.entity.Entity passenger = getPassenger();
      if (!passenger.isDead() && passenger.getVehicle() == entity) {
        CommonEntity<?> commonPassenger = get(passenger);
        commonPassenger.getController().onTick();
        commonPassenger.doPostTick();
      } else {
        setPassengerSilent(null);
      }
    }
  }

  @Override
  public boolean teleport(Location location, TeleportCause cause) {
    if (isDead()) {
      return false;
    }
    // Preparations prior to teleportation
    final Entity entityHandle = CommonNMS.getNative(entity);
    final CommonEntity<?> passenger = get(getPassenger());
    final World newworld = CommonNMS.getNative(location.getWorld());
    final boolean isWorldChange = entityHandle.world != newworld;
    final EntityNetworkController<?> oldNetworkController = getNetworkController();
    final boolean hasNetworkController = !(oldNetworkController instanceof DefaultEntityNetworkController);
    WorldUtil.loadChunks(location, 3);

    // If in a vehicle, make sure we eject first
    if (isInsideVehicle()) {
      getVehicle().eject();
    }

    // If vehicle, eject the passenger first
    if (hasPassenger()) {
      setPassengerSilent(null);
    }

    // Perform actual teleportation
    final boolean succ;
    if (!isWorldChange || entity instanceof Player) {
      // First: stop tracking the entity
      final EntityTracker tracker = WorldUtil.getTracker(getWorld());
      tracker.stopTracking(entity);

      // Destroy packets are queued: Make sure to send them RIGHT NOW
      for (Player bukkitPlayer : WorldUtil.getPlayers(getWorld())) {
        CommonPlayer player = get(bukkitPlayer);
        if (player != null) {
          player.flushEntityRemoveQueue();
        }
      }

      // Teleport
      succ = entity.teleport(location, cause);

      // Start tracking the entity again
      if (!hasNetworkController && !isWorldChange) {
        tracker.startTracking(entity);
      }
    } else {
      // Remove from one world and add to the other
      entityHandle.world.removeEntity(entityHandle);
      entityHandle.dead = false;
      entityHandle.world = newworld;
      entityHandle.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
      entityHandle.world.addEntity(entityHandle);
      succ = true;
    }
    if (hasNetworkController) {
      this.setNetworkController(oldNetworkController);
    }
    if (!succ) {
      return false;
    }

    // If there was a passenger, teleport it and let passenger enter again
    if (passenger != null) {
      // Teleport the passenger, but ignore the chunk send check so vehicle is properly spawned to all players
      EntityRef.ignoreChunkCheck.set(entityHandle, true);
      final boolean passengerTeleported = passenger.teleport(location, cause);
      EntityRef.ignoreChunkCheck.set(entityHandle, false);
      if (passengerTeleported) {
        setPassengerSilent(passenger.getEntity());
        // For players, set checkMovement to True - some odd issue
        if (passenger instanceof CommonPlayer) {
          Object connection = EntityPlayerRef.playerConnection.get(passenger.getHandle());
          if (connection != null) {
            PlayerConnectionRef.checkMovement.set(connection, true);
          }
        }
      }
    }
    return true;
  }

  /**
   * Spawns this Entity at the Location and using the network controller specified.
   *
   * @param location to spawn at
   * @param networkController to assign to the Entity after spawning
   * @return True if spawning occurred, False if not
   * @see #spawn(Location)
   */
  @SuppressWarnings("rawtypes")
  public final boolean spawn(Location location, EntityNetworkController networkController) {
    final boolean spawned = spawn(location);
    this.setNetworkController(networkController);
    return spawned;
  }

  /**
   * Spawns this Entity at the Location specified.
   * Note that if important properties have to be set beforehand, this should be done first.
   * It is recommended to set Entity Controllers before spawning, not after.
   * This method will trigger Entity spawning events.
   * The network controller can ONLY be set after spawning.
   * To be on the safe side, use the Network Controller spawn alternative.
   *
   * @param location to spawn at
   * @return True if the Entity spawned, False if not (and just teleported)
   */
  public boolean spawn(Location location) {
    if (this.isSpawned()) {
      teleport(location);
      return false;
    }
   
    EntityNetworkController<?> controller = getNetworkController();
    if (controller != null) {
        controller.onAttached();
    }
   
    last.set(loc.set(location));
    EntityUtil.addEntity(entity);
    // Perform controller attaching
    getController().onAttached();
    getNetworkController().onAttached();
    return true;
  }

  /**
   * Obtains a (new) {@link CommonItem} instance providing additional methods for the Item specified.
   * This method never returns null, unless the input Entity is null.
   *
   * @param item to get a CommonItem for
   * @return a (new) CommonItem instance for the Item
   */
  public static CommonItem get(Item item) {
    return get(item, CommonItem.class);
  }

  /**
   * Obtains a (new) {@link CommonPlayer} instance providing additional methods for the Player specified.
   * This method never returns null, unless the input Entity is null.
   *
   * @param player to get a CommonPlayer for
   * @return a (new) CommonPlayer instance for the Player
   */
  public static CommonPlayer get(Player player) {
    return get(player, CommonPlayer.class);
  }

  /**
   * Obtains a (new) {@link CommonLivingEntity} instance providing additional methods for the Living Entity specified.
   * This method never returns null, unless the input Entity is null.
   *
   * @param livingEntity to get a CommonLivingEntity for
   * @return a (new) CommonLivingEntity instance for the Living Entity
   */
  @SuppressWarnings("unchecked")
  public static <T extends LivingEntity, C extends CommonLivingEntity<? extends T>> C get(T livingEntity) {
    return (C) get(livingEntity, CommonLivingEntity.class);
  }

  /**
   * Obtains a (new) {@link CommonEntity} instance providing additional methods for the Entity specified.
   * A specific CommonEntity extension that best fits the input Entity can be requested using this method.
   * This method never returns null, unless the input Entity is null.
   *
   * @param entity to get a CommonEntity for
   * @param type of CommonEntity to get
   * @return a (new) CommonEntity type requested, or null if casting failed/entity is invalid
   */
  public static <T extends org.bukkit.entity.Entity, C extends CommonEntity<? extends T>> C get(T entity, Class<C> type) {
    return CommonUtil.tryCast(get(entity), type);
  }

  /**
   * Obtains a (new) {@link CommonEntity} instance providing additional methods for the Entity specified.
   * This method never returns null, unless the input Entity is null.
   *
   * @param entity to get a CommonEntity for
   * @return a (new) CommonEntity instance for the Entity
   */
  @SuppressWarnings("unchecked")
  public static <T extends org.bukkit.entity.Entity> CommonEntity<T> get(T entity) {
    if (entity == null) {
      return null;
    }
    final Object handle = Conversion.toEntityHandle.convert(entity);
    if (handle == null) {
      return null;
    }
    if (handle instanceof NMSEntityHook) {
      EntityController<?> controller = ((NMSEntityHook) handle).getController();
      if (controller != null) {
        return (CommonEntity<T>) controller.getEntity();
      }
    }
    return CommonEntityType.byNMSEntity(handle).createCommonEntity(entity);
  }

  /**
   * Creates (but does not spawn) a new Common Entity backed by a proper Entity.
   *
   * @param entityType to create
   * @return a new CommonEntity type instance
   */
  public static CommonEntity<?> create(EntityType entityType) {
    CommonEntityType type = CommonEntityType.byEntityType(entityType);
    if (type == CommonEntityType.UNKNOWN) {
      throw new IllegalArgumentException("The Entity Type '" + entityType + "' is invalid!");
    }
    final CommonEntity<org.bukkit.entity.Entity> entity = type.createCommonEntity(null);
    // Spawn a new NMS Entity
    Entity handle;
    if (type.hasNMSEntity()) {
      handle = (Entity) type.createNMSHookEntity(entity);
    } else {
      throw new RuntimeException("The Entity Type '"  + entityType + "' has no suitable Entity constructor to use!");
    }
    entity.entity = Conversion.toEntity.convert(handle);
    // Create a new CommonEntity and done
    return entity;
  }

  /**
   * Clears possible network or Entity controllers from the Entity.
   * This should be called when a specific Entity should default back to all default behaviours.
   *
   * @param entity to clear the controllers of
   */
  public static void clearControllers(org.bukkit.entity.Entity entity) {
    CommonEntity<?> commonEntity = get(entity);
    Object oldInstance = commonEntity.getHandle();

    // Detach controller and undo hook Entity replacement
    if (oldInstance instanceof NMSEntityHook) {
      try {
        CommonEntityController<?> controller = ((NMSEntityHook) oldInstance).getController();
        if (controller != null) {
          controller.onDetached();
        }
      } catch (Throwable t) {
        CommonPlugin.LOGGER.log(Level.SEVERE, "Failed to handle controller detachment:");
        t.printStackTrace();
      }
      try {
        CommonEntityType type = CommonEntityType.byNMSEntity(oldInstance);
        // Transfer data and replace
        Object newInstance = type.createNMSEntity();
        type.nmsType.transfer(oldInstance, newInstance);
        commonEntity.replaceEntity((Entity) newInstance);
      } catch (Throwable t) {
        CommonPlugin.LOGGER.log(Level.SEVERE, "Failed to unhook Common Entity Controller:");
        t.printStackTrace();
      }
    }
    // Unhook network controller
    EntityNetworkController<?> controller = commonEntity.getNetworkController();
    if (controller != null && !(controller instanceof DefaultEntityNetworkController)) {
      commonEntity.setNetworkController(new DefaultEntityNetworkController());
    }
  }
}
TOP

Related Classes of com.bergerkiller.bukkit.common.entity.CommonEntity

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.