Package com.bergerkiller.bukkit.tc.controller

Source Code of com.bergerkiller.bukkit.tc.controller.MinecartMember

package com.bergerkiller.bukkit.tc.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import org.bukkit.Chunk;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.vehicle.VehicleDamageEvent;
import org.bukkit.event.vehicle.VehicleDestroyEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.event.vehicle.VehicleUpdateEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.material.Rails;
import org.bukkit.util.Vector;

import com.bergerkiller.bukkit.common.ToggledState;
import com.bergerkiller.bukkit.common.bases.IntVector2;
import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.bases.mutable.LocationAbstract;
import com.bergerkiller.bukkit.common.controller.EntityController;
import com.bergerkiller.bukkit.common.entity.CommonEntity;
import com.bergerkiller.bukkit.common.entity.type.CommonMinecart;
import com.bergerkiller.bukkit.common.utils.BlockUtil;
import com.bergerkiller.bukkit.common.utils.CommonUtil;
import com.bergerkiller.bukkit.common.utils.EntityUtil;
import com.bergerkiller.bukkit.common.utils.FaceUtil;
import com.bergerkiller.bukkit.common.utils.MathUtil;
import com.bergerkiller.bukkit.common.utils.WorldUtil;
import com.bergerkiller.bukkit.common.wrappers.BlockInfo;
import com.bergerkiller.bukkit.common.wrappers.DamageSource;
import com.bergerkiller.bukkit.tc.CollisionMode;
import com.bergerkiller.bukkit.tc.GroupUnloadedException;
import com.bergerkiller.bukkit.tc.MemberMissingException;
import com.bergerkiller.bukkit.tc.TCListener;
import com.bergerkiller.bukkit.tc.TrainCarts;
import com.bergerkiller.bukkit.tc.Util;
import com.bergerkiller.bukkit.tc.controller.components.ActionTrackerMember;
import com.bergerkiller.bukkit.tc.controller.components.BlockTrackerMember;
import com.bergerkiller.bukkit.tc.controller.components.RailTracker;
import com.bergerkiller.bukkit.tc.controller.components.SoundLoop;
import com.bergerkiller.bukkit.tc.events.SignActionEvent;
import com.bergerkiller.bukkit.tc.properties.CartProperties;
import com.bergerkiller.bukkit.tc.properties.CartPropertiesStore;
import com.bergerkiller.bukkit.tc.properties.IPropertiesHolder;
import com.bergerkiller.bukkit.tc.rails.logic.RailLogic;
import com.bergerkiller.bukkit.tc.rails.logic.RailLogicVertical;
import com.bergerkiller.bukkit.tc.rails.logic.RailLogicVerticalSlopeDown;
import com.bergerkiller.bukkit.tc.rails.type.RailType;
import com.bergerkiller.bukkit.tc.rails.type.RailTypeActivator;
import com.bergerkiller.bukkit.tc.signactions.SignAction;
import com.bergerkiller.bukkit.tc.signactions.SignActionType;
import com.bergerkiller.bukkit.tc.storage.OfflineGroupManager;
import com.bergerkiller.bukkit.tc.utils.ChunkArea;
import com.bergerkiller.bukkit.tc.utils.TrackIterator;
import com.bergerkiller.bukkit.tc.utils.TrackMap;

public abstract class MinecartMember<T extends CommonMinecart<?>> extends EntityController<T> implements IPropertiesHolder {
  public static final double GRAVITY_MULTIPLIER = 0.04;
  public static final double VERTRAIL_MULTIPLIER = 0.02;
  public static final double VERT_TO_SLOPE_MIN_VEL = 8.0 * VERTRAIL_MULTIPLIER;
  public static final double SLOPE_VELOCITY_MULTIPLIER = 0.0078125;
  public static final double MIN_VEL_FOR_SLOPE = 0.05;

  private BlockFace direction;
  private BlockFace directionTo;
  private BlockFace directionFrom = BlockFace.SELF;
  protected MinecartGroup group;
  protected boolean died = false;
  private final BlockTrackerMember blockTracker = new BlockTrackerMember(this);
  private final ActionTrackerMember actionTracker = new ActionTrackerMember(this);
  private final RailTracker railTracker = new RailTracker(this);
  protected final ToggledState forcedBlockUpdate = new ToggledState(true);
  private final ToggledState railActivated = new ToggledState(false);
  protected final ToggledState ignoreDie = new ToggledState(false);
  private boolean ignoreAllCollisions = false;
  private int collisionEnterTimer = 0;
  private CartProperties properties;
  private Map<UUID, AtomicInteger> collisionIgnoreTimes = new HashMap<UUID, AtomicInteger>();
  protected boolean unloaded = false;
  public boolean vertToSlope = false;
  protected SoundLoop<?> soundLoop;
  private ChunkArea lastChunks, currentChunks;

  @Override
  public void onAttached() {
    super.onAttached();
    this.railTracker.onAttached();
    this.soundLoop = new SoundLoop<MinecartMember<?>>(this);
    this.lastChunks = new ChunkArea(entity.loc.x.chunk(), entity.loc.z.chunk());
    this.currentChunks = new ChunkArea(lastChunks);
    this.updateDirection();
  }

  @Override
   public CartProperties getProperties() {
     if (this.properties == null) {
       this.properties = CartProperties.get(this);
     }
     return this.properties;
   }

  /**
   * Sets the group of this Minecart, removing this member from the previous group<br>
   * Only called by internal methods (as it relies on group adding)
   *
   * @param group to set to
   */
  protected void setGroup(MinecartGroup group) {
    if (this.group != null && this.group != group) {
      this.group.removeSilent(this);
    }
    this.unloaded = false;
    this.group = group;
  }

  /**
   * Gets the Minecart Group of this Minecart<br>
   * If this Minecart is unloaded, a runtime exception is thrown<br>
   * If no group was previously set, a group is created
   *
   * @return group of this Minecart
   */
   public MinecartGroup getGroup() {
     if (this.isUnloaded()) {
       throw new RuntimeException("Unloaded members do not have groups!");
     }
     if (this.group == null) {
       MinecartGroup.create(this);
     }
     return this.group;
   }
   /**
    * Removes this Minecart from it's current group<br>
    * Upon the next call of getGroup() a new group is created
    */
   public void clearGroup() {
     this.setGroup(null);
   }
   public int getIndex() {
     if (this.group == null) {
       return this.entity.isDead() ? -1 : 0;
     } else {
       return this.group.indexOf(this);
     }
   }
   public MinecartMember<?> getNeighbour(int offset) {
     int index = this.getIndex();
     if (index == -1) {
       return null;
     }
     index += offset;
     if (this.getGroup().containsIndex(index)) {
       return this.getGroup().get(index);
     }
     return null;
   }
   public MinecartMember<?>[] getNeightbours() {
    if (this.getGroup() == null) return new MinecartMember<?>[0];
    int index = this.getIndex();
    if (index == -1) return new MinecartMember<?>[0];
    if (index > 0) {
      if (index < this.getGroup().size() - 1) {
        return new MinecartMember<?>[] {this.getGroup().get(index - 1), this.getGroup().get(index + 1)};
      } else {
        return new MinecartMember<?>[] {this.getGroup().get(index - 1)};
      }
    } else if (index < this.getGroup().size() - 1) {
      return new MinecartMember<?>[] {this.getGroup().get(index + 1)};
    } else {
      return new MinecartMember<?>[0];
    }
  }
   public BlockTrackerMember getBlockTracker() {
     return blockTracker;
   }

  /**
   * Gets whether this Minecart is unloaded
   *
   * @return True if it is unloaded, False if not
   */
  public boolean isUnloaded() {
    return this.unloaded;
  }

  /**
   * Gets whether this Minecart allows player and world interaction.
   * Unloaded or dead minecarts do not allow world interaction.
   *
   * @return True if interactable, False if not
   */
  public boolean isInteractable() {
    return !this.entity.isDead() && !this.isUnloaded();
  }

  public boolean isInChunk(Chunk chunk) {
    return this.isInChunk(chunk.getWorld(), chunk.getX(), chunk.getZ());
  }

  public boolean isInChunk(org.bukkit.World world, int cx, int cz) {
    if (world != entity.getWorld()) return false;
    if (Math.abs(cx - entity.loc.x.chunk()) > 2) return false;
    if (Math.abs(cz - entity.loc.z.chunk()) > 2) return false;
    return true;
  }

  protected void updateChunks(Set<IntVector2> previousChunks, Set<IntVector2> newChunks) {
    previousChunks.addAll(Arrays.asList(this.lastChunks.getChunks()));
    newChunks.addAll(Arrays.asList(this.currentChunks.getChunks()));
    this.lastChunks.update(this.currentChunks);
    this.currentChunks.update(entity.loc.x.chunk(), entity.loc.z.chunk());
  }

  public boolean isSingle() {
    return this.group == null || this.group.size() == 1;
  }

   /*
    * Block functions
    */
   public Block getBlock(int dx, int dy, int dz) {
     return entity.getWorld().getBlockAt(getBlockPos().x + dx, getBlockPos().y + dy, getBlockPos().z + dz);
   }
  public Block getBlock(BlockFace face) {
    return this.getBlock(face.getModX(), face.getModY(), face.getModZ());
  }
  public Block getBlockRelative(BlockFace direction) {
    return this.getBlock(FaceUtil.add(direction, this.getDirection()));
  }
   public Rails getRails() {
     return BlockUtil.getRails(this.getBlock());
   }
  public Block getGroundBlock() {
    return this.getBlock(0, -1, 0);
  }

  /*
   * Velocity functions
   */
  public double getForceSquared() {
    if (entity.isOnGround()) {
      return entity.vel.xz.lengthSquared();
    }
    return entity.vel.lengthSquared();
  }
  public double getForce() {
    return Math.sqrt(this.getForceSquared());
  }
  public double getForwardForce() {
    return this.getRailLogic().getForwardVelocity(this);
  }
  public void setForwardForce(double force) {
    this.getRailLogic().setForwardVelocity(this, force);
  }
  public void limitSpeed() {
    //Limits the velocity to the maximum
    final double currvel = getForce();
    if (currvel > entity.getMaxSpeed() && currvel > 0.01) {
      entity.vel.xz.multiply(entity.getMaxSpeed() / currvel);
    }
  }
  public Vector getLimitedVelocity() {
    double max;
    if (this.isUnloaded()) {
      max = entity.getMaxSpeed();
    } else {
      max = this.getGroup().getProperties().getSpeedLimit();
    }
    return new Vector(entity.vel.x.getClamped(max), entity.vel.y.getClamped(max), entity.vel.z.getClamped(max));
  }
  public TrackMap makeTrackMap(int size) {
    return new TrackMap(this.getBlock(), this.direction, size);
  }

  public void loadChunks() {
    WorldUtil.loadChunks(entity.getWorld(), entity.loc.x.chunk(), entity.loc.z.chunk(), 2);
  }

  public boolean isCollisionIgnored(org.bukkit.entity.Entity entity) {
    MinecartMember<?> member = MinecartMemberStore.get(entity);
    if (member != null) {
      return this.isCollisionIgnored(member);
    }
    if (this.ignoreAllCollisions) {
      return true;
    }
    return collisionIgnoreTimes.containsKey(entity.getUniqueId());
  }
  public boolean isCollisionIgnored(MinecartMember<?> member) {
    if (this.ignoreAllCollisions || member.ignoreAllCollisions) {
      return true;
    }
    return this.collisionIgnoreTimes.containsKey(member.entity.getUniqueId()) ||
        member.collisionIgnoreTimes.containsKey(this.entity.getUniqueId());
  }
  public void ignoreCollision(org.bukkit.entity.Entity entity, int ticktime) {
    collisionIgnoreTimes.put(entity.getUniqueId(), new AtomicInteger(ticktime));
  }

  /**
   * Checks whether mobs/players are allowed to automatically (by collision) enter this Minecart
   *
   * @return True if entities can enter, False if not
   */
  public boolean canCollisionEnter() {
    return collisionEnterTimer == 0;
  }

  /**
   * Resets the enter collision timer, waiting the tick time as configured before
   * taking in new entities when colliding with them.
   */
  public void resetCollisionEnter() {
    this.collisionEnterTimer = TrainCarts.collisionReEnterDelay;
  }

  /*
   * Actions
   */
  public void pushSideways(org.bukkit.entity.Entity entity) {
    this.pushSideways(entity, TrainCarts.pushAwayForce);
  }
  public void pushSideways(org.bukkit.entity.Entity entity, double force) {
    float yaw = FaceUtil.faceToYaw(this.direction);
    float lookat = MathUtil.getLookAtYaw(this.entity.getEntity(), entity) - yaw;
    lookat = MathUtil.wrapAngle(lookat);
    if (lookat > 0) {
      yaw -= 180;
    }
    Vector vel = MathUtil.getDirection(yaw, 0).multiply(force);
    entity.setVelocity(vel);
  }
  public void push(org.bukkit.entity.Entity entity, double force) {
    Vector offset = this.entity.loc.offsetTo(entity);
    MathUtil.setVectorLength(offset, force);
    entity.setVelocity(entity.getVelocity().add(offset));
  }
  public void playLinkEffect() {
    this.playLinkEffect(true);
  }
  public void playLinkEffect(boolean showSmoke) {
    Location loc = entity.getLocation();
    if (showSmoke) {
      loc.getWorld().playEffect(loc, Effect.SMOKE, 0);
    }
    loc.getWorld().playEffect(loc, Effect.EXTINGUISH, 0);
  }

  /**
   * Checks if this minecart is dead, and throws an exception if it is
   *
   * @throws MemberMissingException
   */
  public void checkMissing() throws MemberMissingException {
    if (entity.isDead()) {
      this.onDie();
      throw new MemberMissingException();
    } else if (this.isUnloaded()) {
      throw new MemberMissingException();
    }
  }

  /**
   * Obtains the Action Tracker that keeps track of actions for this Minecart
   *
   * @return action tracker
   */
  public ActionTrackerMember getActions() {
    return actionTracker;
  }

  /**
   * Gets the rail tracker that keeps track of the current Rail of this Minecart
   *
   * @return the Rail Tracker
   */
  public RailTracker getRailTracker() {
    return this.railTracker;
  }

  public IntVector3 getBlockPos() {
    return getRailTracker().blockPos;
  }

  /**
   * Gets the block this minecart was previously in, or driving on
   *
   * @return Last rail block or block at last minecart position
   */
  public Block getLastBlock() {
    return getRailTracker().getLastBlock();
  }

  /**
   * Gets the block this minecart is currently in, or driving on
   *
   * @return Rail block or block at minecart position
   */
  public Block getBlock() {
    return getRailTracker().getBlock();
  }

  /*
   * States
   */
  public boolean isMoving() {
    return entity.isMoving();
  }
  public boolean isTurned() {
    return FaceUtil.isSubCardinal(this.direction);
  }
  public boolean isDerailed() {
    return getRailType() == RailType.NONE;
  }
  /**
   * Checks whether this minecart is currently traveling on a vertical rail
   *
   * @return True if traveling vertically, False if not
   */
  public boolean isOnVertical() {
    return this.getRailLogic() instanceof RailLogicVertical;
  }
  public RailLogic getLastRailLogic() {
    return getRailTracker().getLastLogic();
  }
  public RailLogic getRailLogic() {
    return getRailTracker().getRailLogic();
  }
  public RailType getRailType() {
    return getRailTracker().getRailType();
  }
  public boolean hasBlockChanged() {
    return getRailTracker().hasBlockChanged();
  }
  public boolean isOnSlope() {
    return this.getRailLogic().isSloped();
  }
  public boolean isFlying() {
    return isDerailed() && !entity.isOnGround();
  }
  public boolean isMovingHorizontally() {
    return entity.isMovingHorizontally();
  }
  public boolean isMovingVerticalOnly() {
    return this.isMovingVertically() && !this.isMovingHorizontally();
  }
  public boolean isMovingVertically() {
    if (entity.isOnGround()) {
      // On the ground, are we possibly moving upwards (away from ground)?
      return entity.vel.getY() > CommonEntity.MIN_MOVE_SPEED;
    } else {
      // Not on the ground, if derailed we are flying, otherwise check for vertical movement
      return isDerailed() || entity.isMovingVertically();
    }
  }
  public boolean isNearOf(MinecartMember<?> member) {
    double max = TrainCarts.maxCartDistance * TrainCarts.maxCartDistance;
    if (entity.loc.xz.distanceSquared(member.entity) > max) {
      return false;
    }
    if (this.isDerailed() || this.isOnVertical() || member.isDerailed() || member.isOnVertical()) {
      return Math.abs(entity.loc.getY() - member.entity.loc.getY()) <= max;
    }
    return true;
  }
  public boolean isHeadingTo(org.bukkit.entity.Entity entity) {
    return this.isHeadingTo(entity.getLocation());
  }
  public boolean isHeadingTo(IntVector3 location) {
    return MathUtil.isHeadingTo(this.entity.loc.offsetTo(location.x, location.y, location.z), entity.getVelocity());
  }
  public boolean isHeadingTo(Location target) {
    return MathUtil.isHeadingTo(entity.getLocation(), target, entity.getVelocity());
  }
  public boolean isHeadingTo(BlockFace direction) {
    return MathUtil.isHeadingTo(direction, entity.getVelocity());
  }
  public boolean isFollowingOnTrack(MinecartMember<?> member) {
    // Checks if this member is able to follow the specified member on the tracks
    if (!this.isNearOf(member)) {
      return false;
    }
    // If derailed keep train alive
    if (this.isDerailed() || member.isDerailed()) {
      return true;
    }

    // Same block?
    Block memberrail = member.getBlock();
    if (BlockUtil.equals(this.getBlock(), memberrail)) {
      return true;
    }

    // If moving, use current direction, otherwise be flexible and allow both directions
    if (this.isMoving()) {
      // Check if the current direction allows this minecart to reach the other rail
      if (TrackIterator.canReach(this.getBlock(), this.getDirectionTo(), memberrail)) {
        return true;
      }
      // Check both ways (just in case this direction is invalid)
      if (TrackIterator.isConnected(this.getBlock(), memberrail, true)) {
        return true;
      }
    } else {
      if (TrackIterator.isConnected(this.getBlock(), memberrail, false)) {
        return true;
      }
    }

    return false;
  }
  public static boolean isTrackConnected(MinecartMember<?> m1, MinecartMember<?> m2) {
    //Can the minecart reach the other?
    boolean m1moving = m1.isMoving();
    boolean m2moving = m2.isMoving();
    if (m1moving && m2moving) {
      if (!m1.isFollowingOnTrack(m2) && !m2.isFollowingOnTrack(m1)) return false;
    } else if (m1moving) {
      if (!m1.isFollowingOnTrack(m2)) return false;
    } else if (m2moving) {
      if (!m2.isFollowingOnTrack(m1)) return false;
    } else {
      if (!m1.isNearOf(m2)) return false;
      if (!TrackIterator.isConnected(m1.getBlock(), m2.getBlock(), false)) return false;
    }
    return true;
  }

  /**
   * Gets whether this Minecart Member is heading into the same direction as specified
   *
   * @param direction to test against
   * @return True if heading in the same direction, False if not
   */
  public boolean isDirectionTo(BlockFace direction) {
    return this.directionTo == direction || this.direction == direction;
  }

  /*
   * Directional functions
   */
  public BlockFace getDirection() {
    return this.direction;
  }
  public BlockFace getDirectionFrom() {
    return this.directionFrom;
  }
  public BlockFace getDirectionTo() {
    return this.directionTo;
  }
  public BlockFace getRailDirection() {
    return this.getRailLogic().getDirection();
  }
  public void invalidateDirection() {
    this.direction = this.directionTo = null;
    this.directionFrom = BlockFace.SELF;
  }
  public int getDirectionDifference(BlockFace dircomparer) {
    return FaceUtil.getFaceYawDifference(this.getDirection(), dircomparer);
  }
  public int getDirectionDifference(MinecartMember<?> comparer) {
    return this.getDirectionDifference(comparer.getDirection());
  }
  public void updateDirection(Vector movement) {
    // Take care of invalid directions before continuing
    if (this.direction == null) {
      this.direction = FaceUtil.getDirection(movement);
    }
    if (this.directionTo == null) {
      this.directionTo = FaceUtil.getDirection(movement, false);
    }
    // Obtain logic and the associated direction
    this.updateDirection(this.getRailLogic().getMovementDirection(this, movement));
  }
  public void updateDirection(BlockFace movement) {
    // Take care of invalid directions before continuing
    if (this.direction == null) {
      this.direction = movement;
    }
    if (this.directionTo == null) {
      if (FaceUtil.isSubCardinal(movement)) {
        this.directionTo = FaceUtil.getDirection(this.getEntity().getVelocity(), false);
      } else {
        this.directionTo = movement;
      }
    }
    final boolean fromInvalid = this.directionFrom == BlockFace.SELF;
    if (fromInvalid) {
      this.directionFrom = this.directionTo;
    }
    // Obtain logic and the associated direction
    this.direction = movement;

    // Calculate the to direction
    if (FaceUtil.isSubCardinal(this.direction)) {
      // Compare with the rail direction for curved rails
      // TODO: Turn this into an understandable transformation
      final BlockFace raildirection = this.getRailDirection();
      if (this.direction == BlockFace.NORTH_EAST) {
        this.directionTo = raildirection == BlockFace.NORTH_WEST ? BlockFace.EAST : BlockFace.NORTH;
      } else if (this.direction == BlockFace.SOUTH_EAST) {
        this.directionTo = raildirection == BlockFace.NORTH_EAST ? BlockFace.SOUTH : BlockFace.EAST;
      } else if (this.direction == BlockFace.SOUTH_WEST) {
        this.directionTo = raildirection == BlockFace.NORTH_WEST ? BlockFace.SOUTH : BlockFace.WEST;
      } else if (this.direction == BlockFace.NORTH_WEST) {
        this.directionTo = raildirection == BlockFace.NORTH_EAST ? BlockFace.WEST : BlockFace.NORTH;
      }
    } else {
      // Simply set it for other types of rails
      this.directionTo = this.direction;
    }

    // Force-update the from direction if it was invalidated
    if (fromInvalid) {
      this.directionFrom = this.directionTo;
    }
  }
  public void updateDirection() {
    this.updateDirection(this.entity.getVelocity());
  }
  public void updateDirectionFromTo(LocationAbstract from, LocationAbstract to) {
    this.updateDirection(from.offsetTo(to.getX(), to.getY(), to.getZ()));
  }
  public void updateDirectionTo(MinecartMember<?> member) {
    this.updateDirectionFromTo(this.entity.last, member.entity.last);
  }
  public void updateDirectionFrom(MinecartMember<?> member) {
    this.updateDirectionFromTo(member.entity.last, this.entity.last);
  }

  @Override
  public void onDamage(DamageSource damagesource, double damage) {
    if (this.entity.isDead()) {
      return;
    }
    final Entity damager = damagesource.getEntity();
    try {
      // Call CraftBukkit event
      VehicleDamageEvent event = new VehicleDamageEvent(this.entity.getEntity(), damager, damage);
      if (CommonUtil.callEvent(event).isCancelled()) {
        return;
      }
      damage = event.getDamage();
      // Play shaking animation and logic
      this.entity.setShakingDirection(-this.entity.getShakingDirection());
      this.entity.setShakingFactor(10);
      this.entity.setVelocityChanged(true);
      this.entity.setDamage(this.entity.getDamage() + damage * 10);
      // Check whether the entity is a creative (insta-build) entity
      if (TrainCarts.instantCreativeDestroy && Util.canInstantlyBuild(damager)) {
        this.entity.setDamage(100);
      }
      if (this.entity.getDamage() > 40) {
        // Send an event, pass in the drops to drop
        List<ItemStack> drops = new ArrayList<ItemStack>(2);
        if (getProperties().getSpawnItemDrops()) {
          if (TrainCarts.breakCombinedCarts) {
            drops.addAll(this.entity.getBrokenDrops());
          } else {
            drops.add(new ItemStack(this.entity.getCombinedItem()));
          }
        }
        VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(this.entity.getEntity(), damager);
        if (CommonUtil.callEvent(destroyEvent).isCancelled()) {
          this.entity.setDamage(40);
          return;
        }

        // Spawn drops and die
        for (ItemStack stack : drops) {
          this.entity.spawnItemDrop(stack, 0.0F);
        }
        this.onDie();
      }
    } catch (Throwable t) {
      TrainCarts.plugin.handle(t);
    }
  }

  /**
   * Tells the Minecart to ignore the very next call to {@link onDie()}
   * This is needed to avoid passengers removing their Minecarts.
   */
  public void ignoreNextDie() {
    ignoreDie.set();
  }

  @Override
  public void onDie() {
    try {
      // Die ignored?
      if (this.ignoreDie.clear()) {
        return;
      }
      if (!entity.isDead() || !this.died) {
        super.onDie();
        this.died = true;
        if (!this.isUnloaded()) {
          // Note: No getGroup() calls are allowed here!
          // They may create new groups!
          if (entity.hasPassenger()) {
            this.eject();
          }
          if (this.group != null) {
            entity.setDead(false);
            this.getBlockTracker().clear();
            entity.setDead(true);
          }
          if (entity.hasPassenger()) {
            entity.setPassenger(null);
          }
          if (this.group != null) {
            this.group.remove(this);
          }
          CartPropertiesStore.remove(entity.getUniqueId());
        }
      }
    } catch (Throwable t) {
      TrainCarts.plugin.handle(t);
    }
  }

  @Override
  public boolean onEntityCollision(Entity e) {
    if (!this.isInteractable()) {
      return false;
    }
    CollisionMode mode = this.getGroup().getProperties().getCollisionMode(e);
    if (!mode.execute(this, e)) {
      return false;
    }
    // Collision occurred, collided head-on? Stop the entire train
    if (this.isHeadingTo(e)) {
      this.getGroup().stop();
    }
    return true;
  }

  @Override
  public boolean onBlockCollision(org.bukkit.block.Block hitBlock, BlockFace hitFace) {
    if (!RailType.getType(hitBlock).onCollide(this, hitBlock, hitFace)) {
      return false;
    }
    if (!getRailType().onBlockCollision(this, getBlock(), hitBlock, hitFace)) {
      return false;
    }
    // Stop the entire Group if hitting head-on
    final boolean hitHeadOn;
    IntVector3 delta = new IntVector3(hitBlock).subtract(this.getEntity().loc.block());
    if (FaceUtil.isVertical(this.getDirectionTo())) {
      hitHeadOn = delta.x == 0 && delta.z == 0 && delta.y == this.getDirectionTo().getModY();
    } else {
      hitHeadOn = delta.x == this.getDirectionTo().getModX() && delta.z == this.getDirectionTo().getModZ();
    }
    if (hitHeadOn) {
      this.getGroup().stop();
    }
    return true;
  }

  /**
   * Gets the inventory of a potential Player passenger
   *
   * @return the passenger Player inventory, or null if there is no player
   */
  public PlayerInventory getPlayerInventory() {
    Entity passenger = entity.getPassenger();
    if (passenger instanceof Player) {
      return ((Player) passenger).getInventory();
    } else {
      return null;
    }
  }

  /**
   * Ejects the passenger of this Minecart
   */
  public void eject() {
    this.getEntity().eject();
    this.resetCollisionEnter();
  }

  /**
   * Ejects the passenger with the offset, yaw and pitch as specified in the properties.
   * The passenger is ejected relative to the train.
   */
  public void ejectWithOffset() {
    Location loc = entity.getLocation();
    loc.setYaw(FaceUtil.faceToYaw(getDirection()));
    loc.setPitch(0.0f);
    loc = MathUtil.move(loc, getProperties().exitOffset);
    loc.setYaw(loc.getYaw() + getProperties().exitYaw + 90.0f);
    loc.setPitch(loc.getPitch() + getProperties().exitPitch);
    eject(loc);
  }

  /**
   * Ejects the passenger of this Minecart and teleports him to the offset and rotation specified
   *
   * @param offset to teleport to
   * @param yaw rotation
   * @param pitch rotation
   */
  public void eject(Vector offset, float yaw, float pitch) {
    eject(new Location(entity.getWorld(), entity.loc.getX() + offset.getX(), entity.loc.getY() + offset.getY(), entity.loc.getZ() + offset.getZ(), yaw, pitch));
  }

  /**
   * Ejects the passenger of this Minecart and teleports him to the location specified
   *
   * @param to location to eject/teleport to
   */
  public void eject(final Location to) {
    if (entity.hasPassenger()) {
      TCListener.ignoreNextEject = true;
      final Entity passenger = this.entity.getPassenger();
      this.eject();
      EntityUtil.teleportNextTick(passenger, to);
      TCListener.ignoreNextEject = false;
    }
  }

  public boolean connect(MinecartMember<?> with) {
    return this.getGroup().connect(this, with);
  }

  @Override
  public void onPropertiesChanged() {
    this.getBlockTracker().update();
  }

  /**
   * Checks whether this Minecart Member is being controlled externally by an action.
   * If this is True, the default physics such as gravity and slowing-down factors are not applied.
   *
   * @return True if movement is controlled, False if not
   */
  public boolean isMovementControlled() {
    return getActions().isMovementControlled() || getGroup().getActions().isMovementControlled();
  }

  public boolean isIgnoringCollisions() {
    return this.ignoreAllCollisions;
  }
  public void setIgnoreCollisions(boolean ignoreAll) {
    this.ignoreAllCollisions = ignoreAll;
  }

  public void stop() {
    this.stop(false);
  }
  public void stop(boolean cancelLocationChange) {
    entity.vel.setZero();
    if (cancelLocationChange) {
      entity.loc.set(entity.last);
    }
  }
  public void reverse() {
    reverse(true);
  }
  public void reverse(boolean reverseVelocity) {
    if (reverseVelocity) {
      entity.vel.multiply(-1.0);
    }
    this.updateDirection(this.getDirection().getOppositeFace());
  }

  public void updateUnloaded() {
    unloaded = OfflineGroupManager.containsMinecart(entity.getUniqueId());
    if (!unloaded) {
      // Check a 5x5 chunk area around this Minecart to see if it is loaded
      World world = entity.getWorld();
      int midX = entity.loc.x.chunk();
      int midZ = entity.loc.z.chunk();
      int cx, cz;
      for (cx = -2; cx <= 2; cx++) {
        for (cz = -2; cz <= 2; cz++) {
          if (!WorldUtil.isLoaded(world, cx + midX, cz + midZ)) {
            unloaded = true;
            return;
          }
        }
      }
    }
  }

  /**
   * Respawns the entity to the client (used to avoid teleport smoothing)
   */
  public void respawn() {
    entity.getNetworkController().syncRespawn();
  }

  /**
   * Called when the blocks below this minecart change block coordinates
   *
   * @param from block - the old block
   * @param to block - the new block
   */
  public void onBlockChange(Block from, Block to) {
    // Update from direction
    if (BlockUtil.getManhattanDistance(from, to, true) > 3) {
      this.directionFrom = BlockFace.SELF;
    } else {
      this.directionFrom = this.directionTo;
    }

    // Destroy blocks
    if (!this.isDerailed() && this.getProperties().hasBlockBreakTypes()) {
      Block left = this.getBlockRelative(BlockFace.WEST);
      Block right = this.getBlockRelative(BlockFace.EAST);
      if (this.getProperties().canBreak(left)) {
        BlockInfo.get(left).destroy(left, 20.0f);
      }
      if (this.getProperties().canBreak(right)) {
        BlockInfo.get(right).destroy(right, 20.0f);
      }
    }
  }

  /**
   * Executes the block and pre-movement calculations, which handles rail information updates<br>
   * Physics stage: <b>1</b>
   */
  public void onPhysicsStart() {
    //subtract times
    Iterator<AtomicInteger> times = collisionIgnoreTimes.values().iterator();
    while (times.hasNext()) {     
      if (times.next().decrementAndGet() <= 0) times.remove();
    }
    if (this.collisionEnterTimer > 0) {
      this.collisionEnterTimer--;
    }

    // Prepare
    entity.vel.fixNaN();
    entity.last.set(entity.loc);
    getRailTracker().refreshBlock();
  }

  /**
   * Executes the velocity and pre-movement calculations, which handles logic prior to actual movement occurs<br>
   * Physics stage: <b>3</b>
   */
  public void onPhysicsPreMove() {
    // At this point it's safe to say that the Rail Logic will not change
    getRailTracker().snapshotRailLogic();

    // Reduce shaking over time
    if (entity.getShakingFactor() > 0) {
      entity.setShakingFactor(entity.getShakingFactor() - 1);
    }

    // Health regenerate
    if (entity.getDamage() > 0) {
      entity.setDamage(entity.getDamage() - 1);
    }

    // Kill entity if falling into the void
    if (entity.loc.getY() < -64.0D) {
      this.onDie();
      throw new MemberMissingException();
    }

    // Perform gravity
    if (!isMovementControlled()) {
      entity.vel.y.subtract(getRailLogic().getGravityMultiplier(this));
    }

    // reset fall distance
    if (!this.isDerailed()) {
      entity.setFallDistance(0.0f);
    }

    // Perform rails logic
    getRailLogic().onPreMove(this);

    // Update the entity shape
    entity.setPosition(entity.loc.getX(), entity.loc.getY(), entity.loc.getZ());

    if (getGroup().getProperties().isManualMovementAllowed() && entity.hasPassenger()) {
      Vector vel = entity.getPassenger().getVelocity();
      vel.setY(0.0);
      if (vel.lengthSquared() > 1.0E-4 && entity.vel.xz.lengthSquared() < 0.01) {
        entity.vel.xz.add(vel.multiply(0.1));
      }
    }

    // Perform any pre-movement rail updates
    getRailType().onPreMove(this);
  }

  /**
   * Performs all logic right after movement has occurred
   */
  public void doPostMoveLogic() {
  }

  /**
   * Performs the move logic for when the Minecart travels on top of an Activator rail.
   *
   * @param activated state of the Activator rail
   */
  public void onActivatorUpdate(boolean activated) {
  }

  /**
   * Called when activated goes from FALSE to TRUE
   */
  public void onActivate() {
  }

  /**
   * Moves the minecart and performs post-movement logic such as events, onBlockChanged and other (rail) logic
   * Physics stage: <b>4</b>
   *
   * @param speedFactor to apply when moving
   * @throws MemberMissingException - thrown when the minecart is dead or dies
   * @throws GroupUnloadedException - thrown when the group is no longer loaded
   */
  public void onPhysicsPostMove(double speedFactor) throws MemberMissingException, GroupUnloadedException {
    this.checkMissing();

    // Modify speed factor to stay within bounds
    speedFactor = MathUtil.clamp(MathUtil.fixNaN(speedFactor, 1), 0.1, 10);

    // Apply speed factor to maxed and not-a-number-fixed values
    double motX = speedFactor * entity.vel.x.fixNaN().getClamped(entity.getMaxSpeed());
    double motY = speedFactor * entity.vel.y.fixNaN().getClamped(entity.getMaxSpeed());
    double motZ = speedFactor * entity.vel.z.fixNaN().getClamped(entity.getMaxSpeed());

    // No vertical motion if stuck to the rails that way
    if (!getRailLogic().hasVerticalMovement()) {
      motY = 0.0;
    }

    // Move using set motion, and perform post-move rail logic
    this.onMove(motX, motY, motZ);
    this.checkMissing();
    this.getRailLogic().onPostMove(this);

    // Post-move logic
    this.doPostMoveLogic();
    if (!this.isDerailed()) {
      // Slowing down of minecarts
      if (this.getGroup().getProperties().isSlowingDown()) {
        if (entity.hasPassenger() || !entity.isSlowWhenEmpty() || !TrainCarts.slowDownEmptyCarts) {
          entity.vel.multiply(TrainCarts.slowDownMultiplierNormal);
        } else {
          entity.vel.multiply(TrainCarts.slowDownMultiplierSlow);
        }
      }
    }

    // Activator rail logic here - we can't do it in the rail properly
    if (this.getRailType() instanceof RailTypeActivator) {
      final boolean powered = ((RailTypeActivator) this.getRailType()).isPowered();
      this.onActivatorUpdate(powered);
      if (powered && this.railActivated.set()) {
        this.onActivate();
      } else {
        this.railActivated.clear();
      }
    } else {
      this.railActivated.clear();
    }

    // Perform post-movement rail logic
    getRailType().onPostMove(this);

    // Update rotation
    this.onRotationUpdate();

    // Invalidate volatile information
    getRailTracker().setLiveRailLogic();

    // Perform some (CraftBukkit) events
    Location from = entity.getLastLocation();
    Location to = entity.getLocation();
    Vehicle vehicle = entity.getEntity();
    CommonUtil.callEvent(new VehicleUpdateEvent(vehicle));
    if (!from.equals(to)) {
      // Execute move events
      CommonUtil.callEvent(new VehicleMoveEvent(vehicle, from, to));
      for (org.bukkit.block.Block sign : this.getBlockTracker().getActiveSigns()) {
        SignAction.executeAll(new SignActionEvent(sign, this), SignActionType.MEMBER_MOVE);
      }
    }

    // Minecart collisions
    for (Entity near : entity.getNearbyEntities(0.2, 0, 0.2)) {
      if (near instanceof Minecart && near != this.entity.getPassenger()) {
        EntityUtil.doCollision(near, this.entity.getEntity());
      }
    }

    // Ensure that dead passengers are cleared
    if (entity.hasPassenger() && entity.getPassenger().isDead()) {
      entity.setPassenger(null);
    }

    // Final logic
    this.checkMissing();

    // Play additional sound effects
    this.soundLoop.onTick();
  }

  @Override
  public void onTick() {
    if (this.isUnloaded()) {
      return;
    }
    MinecartGroup g = this.getGroup();
    if (g == null) {
      return;
    }
    if (entity.isDead()) {
      // remove self
      g.remove(this);
    } else if (g.isEmpty()) {
      g.remove();
      super.onTick();
    } else if (g.ticked.set()) {
      g.doPhysics();
    }
  }

  /**
   * Performs rotation updates for yaw and pitch
   */
  public void onRotationUpdate() {
    //Update yaw and pitch based on motion
    final double movedX = entity.getMovedX();
    final double movedY = entity.getMovedY();
    final double movedZ = entity.getMovedZ();
    final boolean movedXZ = Math.abs(movedX) > 0.001 || Math.abs(movedZ) > 0.001;
    final float oldyaw = entity.loc.getYaw();
    float newyaw = oldyaw;
    float newpitch = entity.loc.getPitch();
    boolean orientPitch = false;
    if (isDerailed()) {
      // Update yaw
      if (Math.abs(movedX) > 0.01 || Math.abs(movedZ) > 0.01) {
        newyaw = MathUtil.getLookAtYaw(movedX, movedZ);
      }
      // Update pitch
      if (entity.isOnGround()) {
        // Reduce pitch over time
        if (Math.abs(newpitch) > 0.1) {
          newpitch *= 0.1;
        } else {
          newpitch = 0;
        }
        orientPitch = true;
      } else if (movedXZ && Math.abs(movedY) > 0.001) {
        // Use movement for pitch (but only when moving horizontally)
        newpitch = MathUtil.clamp(-0.7f * MathUtil.getLookAtPitch(-movedX, -movedY, -movedZ), 60.0f);
        orientPitch = true;
      }
    } else {
      // Update yaw
      BlockFace dir = this.getDirection();
      if (FaceUtil.isVertical(dir)) {
        newyaw = FaceUtil.faceToYaw(this.getRailDirection());
      } else {
        newyaw = FaceUtil.faceToYaw(this.getDirection());
      }
      // Update pitch
      if (getRailLogic() instanceof RailLogicVertical) {
        newpitch = TrainCarts.allowVerticalPitch ? -90.0f : 0.0f;
        orientPitch = true;
      } else if (getRailLogic() instanceof RailLogicVerticalSlopeDown) {
        newpitch = -45.0f;
        orientPitch = true;
      } else if (getRailLogic().isSloped()) {
        if (movedXZ && Math.abs(movedY) > 0.001) {
          // Use movement for pitch (but only when moving horizontally)
          newpitch = MathUtil.clamp(-0.7f * MathUtil.getLookAtPitch(-movedX, -movedY, -movedZ), 60.0f);
          orientPitch = true;
        }
        // This was the old method, where pitch is inverted to make players look up/down properly
        /*
        newpitch = 0.8f * MathUtil.getLookAtPitch(movedX, movedY, movedZ);
        orientPitch = false;
        */
      } else {
        newpitch = 0.0f;
        orientPitch = true;
      }
    }

    // Fix yaw based on the previous yaw angle
    if (MathUtil.getAngleDifference(oldyaw, newyaw) > 90.0f) {
      while ((newyaw - oldyaw) >= 90.0f) {
        newyaw -= 180.0f;
      }
      while ((newyaw - oldyaw) < -90.0f) {
        newyaw += 180.0f;
      }
      if (orientPitch) {
        newpitch = -newpitch;
      }
    }

    // Fix up wrap-around angles
    while ((newyaw - oldyaw) <= -180.0f) {
      newyaw += 360.0f;
    }
    while ((newyaw - oldyaw) > 180.0f) {
      newyaw -= 360.0f;
    }

    entity.setRotation(newyaw, newpitch);
  }

  @Override
  public String getLocalizedName() {
    return isSingle() ? "Minecart" : "Train";
  }

  @Override
  public boolean isPlayerTakable() {
    return this.isSingle() && (this.isUnloaded() || this.getGroup().getProperties().isPlayerTakeable());
  }

  @Override
  public boolean parseSet(String key, String args) {
    return false;
  }
}
TOP

Related Classes of com.bergerkiller.bukkit.tc.controller.MinecartMember

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.