Package com.bergerkiller.bukkit.tc.controller

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

package com.bergerkiller.bukkit.tc.controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.Inventory;

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.controller.EntityNetworkController;
import com.bergerkiller.bukkit.common.entity.type.CommonMinecart;
import com.bergerkiller.bukkit.common.inventory.ItemParser;
import com.bergerkiller.bukkit.common.inventory.MergedInventory;
import com.bergerkiller.bukkit.common.utils.LogicUtil;
import com.bergerkiller.bukkit.common.utils.MathUtil;
import com.bergerkiller.bukkit.tc.GroupUnloadedException;
import com.bergerkiller.bukkit.tc.MemberMissingException;
import com.bergerkiller.bukkit.tc.TrainCarts;
import com.bergerkiller.bukkit.tc.controller.components.ActionTrackerGroup;
import com.bergerkiller.bukkit.tc.controller.components.BlockTrackerGroup;
import com.bergerkiller.bukkit.tc.controller.type.MinecartMemberChest;
import com.bergerkiller.bukkit.tc.controller.type.MinecartMemberFurnace;
import com.bergerkiller.bukkit.tc.events.GroupCreateEvent;
import com.bergerkiller.bukkit.tc.events.GroupRemoveEvent;
import com.bergerkiller.bukkit.tc.events.GroupUnloadEvent;
import com.bergerkiller.bukkit.tc.events.MemberAddEvent;
import com.bergerkiller.bukkit.tc.events.MemberBlockChangeEvent;
import com.bergerkiller.bukkit.tc.events.MemberRemoveEvent;
import com.bergerkiller.bukkit.tc.properties.IPropertiesHolder;
import com.bergerkiller.bukkit.tc.properties.TrainProperties;
import com.bergerkiller.bukkit.tc.properties.TrainPropertiesStore;
import com.bergerkiller.bukkit.tc.storage.OfflineGroupManager;
import com.bergerkiller.bukkit.tc.utils.TrackWalkIterator;

public class MinecartGroup extends MinecartGroupStore implements IPropertiesHolder {
  private static final long serialVersionUID = 3;
  private static final HashSet<IntVector2> previousChunksBuffer = new HashSet<IntVector2>(50);
  private static final HashSet<IntVector2> newChunksBuffer = new HashSet<IntVector2>(50);

  private final BlockTrackerGroup blockTracker = new BlockTrackerGroup(this);
  private final ActionTrackerGroup actionTracker = new ActionTrackerGroup(this);
  private TrainProperties prop = null;
  private boolean breakPhysics = false;
  private int teleportImmunityTick = 0;
  protected long lastSync = Long.MIN_VALUE;
  protected final ToggledState networkInvalid = new ToggledState();
  protected final ToggledState ticked = new ToggledState();

  protected MinecartGroup() {}

  @Override
  public TrainProperties getProperties() {
    if (this.prop == null) {
      this.prop = TrainPropertiesStore.create();
      for (MinecartMember<?> member : this) {
        this.prop.add(member);
      }
    }
    return this.prop;
  }

  public void setProperties(TrainProperties properties) {
    if (properties == null) {
      throw new IllegalArgumentException("Can not set properties to null");
    }
    if (this.prop != null) {
      TrainPropertiesStore.remove(this.prop.getTrainName());
    }
    this.prop = properties;
  }

  public BlockTrackerGroup getBlockTracker() {
    return this.blockTracker;
  }

  /**
   * Gets the Action Tracker that keeps track of the actions of this Group
   *
   * @return action tracker
   */
  public ActionTrackerGroup getActions() {
    return this.actionTracker;
  }

  public MinecartMember<?> head(int index) {
    return this.get(index);
  }
  public MinecartMember<?> head() {
    return this.head(0);
  }
  public MinecartMember<?> tail(int index) {
    return this.get(this.size() - 1 - index);
  }
  public MinecartMember<?> tail() {
    return this.tail(0);
  }
  public MinecartMember<?> middle() {
    return this.get((int) Math.floor((double) size() / 2));
  }

  public Iterator<MinecartMember<?>> iterator() {
    final Iterator<MinecartMember<?>> listIter = super.iterator();
    return new Iterator<MinecartMember<?>>() {
      @Override
      public boolean hasNext() {
        return listIter.hasNext();
      }

      @Override
      public MinecartMember<?> next() {
        try {
          return listIter.next();
        } catch (ConcurrentModificationException ex) {
          throw new MemberMissingException();
        }
      }

      @Override
      public void remove() {
        listIter.remove();
      }
    };
  }

  public MinecartMember<?>[] toArray() {
    return super.toArray(new MinecartMember<?>[0]);
  }

  public boolean connect(MinecartMember<?> contained, MinecartMember<?> with) {
    if (this.size() <= 1) {
      this.add(with);
    } else if (this.head() == contained && this.canConnect(with, 0)) {
      this.add(0, with);
    } else if (this.tail() == contained && this.canConnect(with, this.size() - 1)) {
      this.add(with);
    } else {
      return false;
    }
    return true;
  }

  @Override
  public int indexOf(Object object) {
    MinecartMember<?> mm = MinecartMemberStore.get(object);
    if (mm == null) {
      return -1;
    }
    return super.indexOf(mm);
  }

  private void addMember(MinecartMember<?> member) {
    member.setGroup(this);
    this.getBlockTracker().updatePosition();
    this.getProperties().add(member);
  }
  public void add(int index, MinecartMember<?> member) {
    if (member.isUnloaded()) {
      throw new IllegalArgumentException("Can not add unloaded members to groups");
    }
    super.add(index, member);
    MemberAddEvent.call(member, this);
    this.addMember(member);
  }
  public boolean add(MinecartMember<?> member) {
    if (member.isUnloaded()) {
      throw new IllegalArgumentException("Can not add unloaded members to groups");
    }
    super.add(member);
    MemberAddEvent.call(member, this);
    this.addMember(member);
    return true;
  }
  public boolean addAll(int index, Collection<? extends MinecartMember<?>> members) {
    super.addAll(index, members);
    MinecartMember<?>[] memberArr = members.toArray(new MinecartMember<?>[0]);
    for (MinecartMember<?> m : memberArr) {
      if (m.isUnloaded()) {
        throw new IllegalArgumentException("Can not add unloaded members to groups");
      }
      MemberAddEvent.call(m, this);
    }
    for (MinecartMember<?> member : memberArr) {
      this.addMember(member);
    }
    return true;
  }
  public boolean addAll(Collection<? extends MinecartMember<?>> members) {
    super.addAll(members);
    MinecartMember<?>[] memberArr = members.toArray(new MinecartMember<?>[0]);
    for (MinecartMember<?> m : memberArr) {
      if (m.isUnloaded()) {
        throw new IllegalArgumentException("Can not add unloaded members to groups");
      }
      MemberAddEvent.call(m, this);
    }
    for (MinecartMember<?> member : memberArr) {
      this.addMember(member);
    }
    return true;
  }

  public boolean contains(Object o) {
    return super.contains(MinecartMemberStore.get(o));
  }
  public boolean containsIndex(int index) {
    return this.isEmpty() ? false : index >= 0 && index < this.size();
  }

  public World getWorld() {
    return isEmpty() ? null : get(0).getEntity().getWorld();
  }

  public double length() {
    return TrainCarts.cartDistance * (this.size() - 1);
  }

  public int size(EntityType carttype) {
    int rval = 0;
    for (MinecartMember<?> mm : this) {
      if (mm.getEntity().getType() == carttype) {
        rval++;
      }
    }
    return rval;
  }

  public boolean isValid() {
    if (this.size() == 0) return false;
    if (this.size() == 1) return true;
    if (this.getProperties().requirePoweredMinecart) {
      return this.size(EntityType.MINECART_FURNACE) > 0;
    } else {
      return true;
    }
  }

  /**
   * Removes a member without splitting the train or causing link effects
   *
   * @param member to remove
   * @return True if removed, False if not
   */
  public boolean removeSilent(MinecartMember<?> member) {
    int index = this.indexOf(member);
    if (index == -1) {
      return false;
    }
    this.removeMember(index);
    if (this.isEmpty()) {
      this.remove();
    }
    return true;
  }
  public boolean remove(Object o) {
    int index = this.indexOf(o);
    if (index == -1) return false;
    return this.remove(index) != null;
  }
  private MinecartMember<?> removeMember(int index) {
    MinecartMember<?> member = super.get(index);
    MemberRemoveEvent.call(member);
    super.remove(index);
    this.getProperties().remove(member);
    this.getActions().removeActions(member);
    this.getBlockTracker().updatePosition();
    member.group = null;
    return member;
  }
  public MinecartMember<?> remove(int index) {
    MinecartMember<?> removed = this.removeMember(index);
    if (this.isEmpty()) {
      //Remove empty group as a result
      this.remove();
    } else {
      //Split the train at the index
      removed.playLinkEffect();
      this.split(index);
    }
    return removed;
  }

  /**
   * Splits this train, the index is the first cart for the new group<br><br>
   *
   * For example, this Group has a total cart count of 5<br>
   * If you then split at index 2, it will result in:<br>
   * - This group becomes a group of 2 carts<br>
   * - A new group of 3 carts is created
   */
  public MinecartGroup split(int at) {
    if (at <= 0) return this;
    if (at >= this.size()) return null;
    //transfer the new removed carts
    MinecartGroup gnew = new MinecartGroup();
    int count = this.size();
    for (int i = at; i < count; i++) {
      gnew.add(this.removeMember(this.size() - 1));
    }
    //Remove this train if now empty
    if (!this.isValid()) {
      this.remove();
    }
    //Remove if empty or not allowed, else add
    if (gnew.isValid()) {
      //Add the group
      groups.add(gnew);

      //Set the new group properties
      gnew.getProperties().load(this.getProperties());

      GroupCreateEvent.call(gnew);
      return gnew;
    } else {
      gnew.clear();
      return null;
    }
  }

  @Override
  public void clear() {
    this.getBlockTracker().clear();
    this.getActions().clear();
    for (MinecartMember<?> mm : this.toArray()) {
      this.getProperties().remove(mm);
      if (mm.getEntity().isDead()) {
        mm.onDie();
      } else {
        mm.group = null;
        mm.getGroup().getProperties().load(this.getProperties());
      }
    }
    super.clear();
  }
  public void remove() {
    if (!groups.remove(this)) {
      return; // Already removed
    }
    GroupRemoveEvent.call(this);
    this.clear();
    if (this.prop != null) {
      TrainPropertiesStore.remove(this.prop.getTrainName());
      this.prop = null;
    }
  }
  public void destroy() {
    for (MinecartMember<?> mm : this) {
      mm.getEntity().remove();
    }
    this.remove();
  }
  public void unload() {
    // Undo partial-unloading before calling the event
    for (MinecartMember<?> member : this) {
      member.group = this;
      member.unloaded = false;
    }

    // Event
    GroupUnloadEvent.call(this);

    // Unload in detector regions
    getBlockTracker().unload();

    // Store the group offline
    OfflineGroupManager.storeGroup(this);

    // Unload
    this.stop(true);
    groups.remove(this);
    for (MinecartMember<?> member : this) {
      member.group = null;
      member.unloaded = true;
    }
  }

  /**
   * Visually respawns this minecart to avoid teleportation smoothing
   */
  public void respawn() {
    for (MinecartMember<?> mm : this) {
      mm.respawn();
    }
  }
  public void playLinkEffect() {
    for (MinecartMember<?> mm : this) {
      mm.playLinkEffect();
    }
  }
  public void stop() {
    this.stop(false);
  }
  public void stop(boolean cancelLocationChange) {
    for (MinecartMember<?> m : this) {
      m.stop(cancelLocationChange);
    }
  }
  public void limitSpeed() {
    for (MinecartMember<?> mm : this) {
      mm.limitSpeed();
    }
  }
  public void eject() {
    for (MinecartMember<?> mm : this) mm.eject();
  }

  /**
   * A simple version of teleport where the inertia of the train is maintained
   */
  public void teleportAndGo(Block start, BlockFace direction) {
    double force = this.getAverageForce();
    this.teleport(start, direction);
    this.stop();
    this.getActions().clear();
    if (Math.abs(force) > 0.01) {
      this.tail().getActions().addActionLaunch(direction, 1.0, force);
    }
  }

  public void teleport(Block start, BlockFace direction) {
    this.teleport(start, direction, TrainCarts.cartDistance);
  }
  public void teleport(Block start, BlockFace direction, double stepsize) {
    this.teleport(TrackWalkIterator.walk(start, direction, this.size(), stepsize), true);
  }
  public void teleport(Location[] locations) {
    this.teleport(locations, false);
  }
  public void teleport(Location[] locations, boolean reversed) {
    if (LogicUtil.nullOrEmpty(locations) || locations.length != this.size()) {
      return;
    }
    this.teleportImmunityTick = 10;
    this.getBlockTracker().clear();
    this.getBlockTracker().updatePosition();
    this.breakPhysics();
    if (reversed) {
      for (int i = 0; i < locations.length; i++) {
        teleportMember(this.get(i), locations[locations.length - i - 1]);
      }
    } else {
      for (int i = 0; i < locations.length; i++) {
        teleportMember(this.get(i), locations[i]);
      }
    }
    this.getBlockTracker().updatePosition();
  }
  private void teleportMember(MinecartMember<?> member, Location location) {
    member.ignoreDie.set();
    member.getEntity().teleport(location);
    member.ignoreDie.clear();
    member.getRailTracker().refreshBlock();
  }
  /**
   * Gets whether this Minecart and the passenger has immunity as a result of teleportation
   *
   * @return True if it is immune, False if not
   */
  public boolean isTeleportImmune() {
    return this.teleportImmunityTick > 0;
  }

  public void shareForce() {
    double f = this.getAverageForce();
    for (MinecartMember<?> m : this) {
      m.setForwardForce(f);
    }
  }
  public void reverse() {
    for (MinecartMember<?> mm : this) {
      mm.reverse();
    }
    Collections.reverse(this);
  }
  public void setForwardForce(double force) {
    if (force == 0.0) {
      this.stop();
      return;
    }
    final double currvel = this.head().getForce();
    if (currvel <= 0.01 || Math.abs(force) < 0.01) {
      for (MinecartMember<?> mm : this) {
        mm.setForwardForce(force);
      }
    } else {
      final double f = force / currvel;
      for (MinecartMember<?> mm : this) {
        mm.getEntity().vel.multiply(f);
      }
    }

  }

  public boolean canConnect(MinecartMember<?> mm, int at) {
    if (this.size() == 1) return true;
    if (this.size() == 0) return false;
    CommonMinecart<?> connectedEnd;
    CommonMinecart<?> otherEnd;
    if (at == 0) {
      // Compare the head
      if (!this.head().isNearOf(mm)) {
        return false;
      }
      connectedEnd = this.head().getEntity();
      otherEnd = this.tail().getEntity();
    } else if (at == this.size() - 1) {
      //compare the tail
      if (!this.tail().isNearOf(mm)) {
        return false;
      }
      connectedEnd = this.tail().getEntity();
      otherEnd = this.head().getEntity();
    } else {
      return false;
    }
    // Verify connected end is closer than the opposite end of this Train
    // This ensures that no wrongful connections are made in curves
    return connectedEnd.loc.distanceSquared(mm.getEntity()) < otherEnd.loc.distanceSquared(mm.getEntity());
  }
  public void updateDirection() {
    if (this.size() == 1) {
      this.get(0).updateDirection();
    } else if (this.size() > 1) {
      // Update direction of individual carts
      tail().updateDirectionTo(tail(1));
      for (int i = size() - 2;i >= 0;i--) {
        get(i).updateDirectionFrom(get(i + 1));
      }

      // Check whether the train has reversed
      double fforce = 0;
      for (MinecartMember<?> m : this) {
        fforce += m.getForwardForce();
      }
      if (fforce < 0) {
        Collections.reverse(this);

        // Redo cart direction calculation with altered order
        tail().updateDirectionTo(tail(1));
        for (int i = size() - 2;i >= 0;i--) {
          get(i).updateDirectionFrom(get(i + 1));
        }
      }
    }
  }
  public double getAverageForce() {
    if (this.isEmpty()) {
      return 0;
    }
    if (this.size() == 1) {
      return this.get(0).getForce();
    }
    //Get the average forward force of all carts
    double force = 0;
    for (MinecartMember<?> m : this) {
      force += MathUtil.invert(m.getForce(), m.getForwardForce() < 0.0);
    }
    return force / (double) size();
  }
  public List<Material> getTypes() {
    ArrayList<Material> types = new ArrayList<Material>(this.size());
    for (MinecartMember<?> mm : this) {
      types.add(mm.getEntity().getCombinedItem());
    }
    return types;
  }

  public boolean hasPassenger() {
    for (MinecartMember<?> mm : this) {
      if (mm.getEntity().hasPassenger()) {
        return true;
      }
    }
    return false;
  }
  public boolean hasFuel() {
    for (MinecartMember<?> mm : this) {
      if (mm instanceof MinecartMemberFurnace && ((MinecartMemberFurnace) mm).getEntity().hasFuel()) {
        return true;
      }
    }
    return false;
  }
  public boolean hasItems() {
    for (MinecartMember<?> mm : this) {
      if (mm instanceof MinecartMemberChest && ((MinecartMemberChest) mm).hasItems()) {
        return true;
      }
    }
    return false;
  }
  public boolean hasItem(ItemParser item) {
    for (MinecartMember<?> mm : this) {
      if (mm instanceof MinecartMemberChest && ((MinecartMemberChest) mm).hasItem(item)) {
        return true;
      }
    }
    return false;
  }
  public boolean isMoving() {
    if (this.isEmpty()) {
      return false;
    } else {
      return this.head().isMoving();
    }
  }

  /**
   * Checks if this Minecart Group can unload, or if chunks are kept loaded instead<br>
   * The keepChunksLoaded property is read, as well the moving state if configured<br>
   * If a player is inside the train, it will keep the chunks loaded as well
   *
   * @return True if it can unload, False if it keeps chunks loaded
   */
  public boolean canUnload() {
    if (this.getProperties().isKeepingChunksLoaded()) {
      if (!TrainCarts.keepChunksLoadedOnlyWhenMoving || this.isMoving()) {
        return false;
      }
    }
    for (MinecartMember<?> member : this) {
      if (member.getEntity().hasPlayerPassenger()) {
        return false;
      }
    }
    if (this.isTeleportImmune()) {
      return false;
    }
    return true;
  }
  public boolean isRemoved() {
    return !groups.contains(this);
  }

  public Inventory getInventory() {
    //count amount of storage minecarts
    Inventory[] source = new Inventory[this.size(EntityType.MINECART_CHEST)];
    int i = 0;
    for (MinecartMember<?> mm : this) {
      if (mm instanceof MinecartMemberChest) {
        source[i] = ((MinecartMemberChest) mm).getEntity().getInventory();
        i++;
      }
    }
    if (source.length == 1) {
      return source[0];
    }
    return new MergedInventory(source);
  }
  public Inventory getPlayerInventory() {
    //count amount of player passengers
    int count = 0;
    for (MinecartMember<?> mm : this) {
      if (mm.getEntity().hasPlayerPassenger()) {
        count++;
      }
    }
    Inventory[] source = new Inventory[count];
    if (source.length == 1) {
      return source[0];
    }
    int i = 0;
    for (MinecartMember<?> mm : this) {
      if (mm.getEntity().hasPlayerPassenger()) {
        source[i] = mm.getPlayerInventory();
        i++;
      }
    }
    return new MergedInventory(source);
  }

  public void loadChunks() {
    for (MinecartMember<?> mm : this) mm.loadChunks();
  }

  public boolean isInChunk(Chunk chunk) {
    return this.isInChunk(chunk.getWorld(), chunk.getX(), chunk.getZ());
  }
  public boolean isInChunk(World world, int cx, int cz) {
    for (MinecartMember<?> mm : this) {
      if (mm.isInChunk(world, cx, cz)) return true;
    }
    return false;
  }

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

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

  /**
   * Gets the maximum amount of ticks a member of this group has lived
   *
   * @return maximum amount of lived ticks
   */
  public int getTicksLived() {
    int ticksLived = 0;
    for (MinecartMember<?> member : this) {
      ticksLived = Math.max(ticksLived, member.getEntity().getTicksLived());
    }
    return ticksLived;
  }

  /**
   * Aborts any physics routines going on in this tick
   */
  public void breakPhysics() {
    this.breakPhysics = true;
  }

  /*
   * These two overrides ensure that sets use this MinecartGroup properly
   * Without it, the AbstractList versions were used, which don't apply here
   */
  @Override
  public int hashCode() {
    return System.identityHashCode(this);
  }

  @Override
  public boolean equals(Object other) {
    return other == this;
  }

  /**
   * Gets the Member at the block position specified
   *
   * @param position to get the member at
   * @return member at the position, or null if not found
   */
  public MinecartMember<?> getAt(IntVector3 position) {
    return getBlockTracker().getMemberFromRails(position);
  }

  private boolean doConnectionCheck(int stepcount) {
    //Validate positions in the group
    for (int i = 0; i < this.size() - 1; i++) {
      if (!get(i + 1).isFollowingOnTrack(get(i))) {
        // Undo stepcount based velocity modifications
        for (int j = i + 1; j < this.size(); j++) {
          this.get(j).getEntity().vel.multiply(stepcount);
        }
        // Split
        MinecartGroup gnew = this.split(i + 1);
        if (gnew != null) {
          //what time do we want to prevent them from colliding too soon?
          //needs to travel 2 blocks in the meantime
          int time = (int) MathUtil.clamp(2 / gnew.head().getForce(), 20, 40);
          for (MinecartMember<?> mm1 : gnew) {
            for (MinecartMember<?> mm2: this) {
              mm1.ignoreCollision(mm2.getEntity().getEntity(), time);
            }
          }
        }
        return false;
      }
    }
    return true;
  }

  public void logCartInfo(String header) {
    StringBuilder msg = new StringBuilder(size() * 7 + 10);
    msg.append(header);
    for (MinecartMember<?> member : this) {
      msg.append(" [");
      msg.append(member.getDirection());
      msg.append(" - ").append(member.getEntity().vel);
      msg.append("]");
    }
    System.out.println(msg);
  }

  public void doPhysics() {
    for (MinecartMember<?> m : this) {
      if (m.isUnloaded()) {
        this.unload();
        return;
      }
    }
    try {
      double totalforce = this.getAverageForce();
      double speedlimit = this.getProperties().getSpeedLimit();
      if (totalforce > 0.4 && speedlimit > 0.4) {
        final int bits = (int) Math.ceil(speedlimit / 0.4);
        final double mult = (double) bits;
        for (MinecartMember<?> mm : this) {
          mm.getEntity().vel.divide(mult);
        }
        for (int i = 0; i < bits; i++) {
          while (!this.doPhysics(bits));
        }
        for (MinecartMember<?> mm : this) {
          mm.getEntity().vel.multiply(mult);
          mm.getEntity().setMaxSpeed(this.getProperties().getSpeedLimit());
        }
      } else {
        this.doPhysics(1);
      }
    } catch (GroupUnloadedException ex) {
      //this group is gone
    } catch (Throwable t) {
      final TrainProperties p = getProperties();
      TrainCarts.plugin.log(Level.SEVERE, "Failed to perform physics on train '" + p.getTrainName() + "' at " + p.getLocation() + ":");
      TrainCarts.plugin.handle(t);
    }
  }
  private boolean doPhysics(int stepcount) throws GroupUnloadedException {
    this.breakPhysics = false;
    try {
      // Prevent index exceptions: remove if not a train
      if (this.isEmpty()) {
        this.remove();
        throw new GroupUnloadedException();
      }

      // Validate members and set max speed
      for (MinecartMember<?> mm : this) {
        mm.checkMissing();
        mm.getEntity().setMaxSpeed(this.getProperties().getSpeedLimit() / (double) stepcount);
      }

      // Set up a valid network controller if needed
      if (networkInvalid.clear()) {
        for (MinecartMember<?> m : this) {
          EntityNetworkController<?> controller = m.getEntity().getNetworkController();
          if (!(controller instanceof MinecartMemberNetwork)) {
            m.getEntity().setNetworkController(new MinecartMemberNetwork());
          }
        }
      }

      // Update some per-tick stuff
      if (this.teleportImmunityTick > 0) {
        this.teleportImmunityTick--;
      }

      // Update direction and executed actions prior to updates
      this.updateDirection();
      this.getActions().doTick();
      for (MinecartMember<?> member : this) {
        member.getActions().doTick();
      }

      // Perform block updates prior to doing the movement calculations
      // First initialize all blocks and handle block change event
      for (MinecartMember<?> member : this) {
        member.onPhysicsStart();
      }
      this.getBlockTracker().refresh();

      // Perform block change Minecart logic, also take care of potential new block changes
      for (MinecartMember<?> member : this) {
        member.checkMissing();
        if (member.hasBlockChanged() | member.forcedBlockUpdate.clear()) {
          // Perform events and logic - validate along the way
          MemberBlockChangeEvent.call(member, member.getLastBlock(), member.getBlock());
          member.checkMissing();
          member.onBlockChange(member.getLastBlock(), member.getBlock());
          this.getBlockTracker().updatePosition();
          member.checkMissing();
        }
      }
      this.getBlockTracker().refresh();

      if (!this.doConnectionCheck(stepcount)) {
        return false;
      }
      this.updateDirection();
     
      // Perform velocity updates
      for (MinecartMember<?> m : this) {
        m.onPhysicsPreMove();
      }

      // Direction can change as a result of gravity
      this.updateDirection();

      if (this.size() == 1) {
        //Simplified calculation for single carts
        this.head().onPhysicsPostMove(1);
      } else {
        //Get the average forwarding force of all carts
        double force = this.getAverageForce();

        //Perform forward force or not? First check if we are not messing up...
        boolean performUpdate = true;
        for (int i = 0; i < this.size() - 1; i++) {
          if (!head(i + 1).isFollowingOnTrack(head(i))) {
            performUpdate = false;
            break;
          }
        }

        if (performUpdate) {
          //update force
          for (MinecartMember<?> m : this) {
            m.setForwardForce(force);
          }
        }

        //Apply force factors to carts from last cart and perform post positional updates
        if (this.size() < 2) return false;
        int i = 1;
        double distance, threshold, forcer;
        MinecartMember<?> after;
        for (MinecartMember<?> member : this) {
          after = this.get(i);
          distance = member.getEntity().loc.distance(after.getEntity());
          if (member.getDirectionDifference(after) >= 45 || member.getEntity().loc.getPitchDifference(after.getEntity()) > 10) {
            threshold = TrainCarts.turnedCartDistance;
            forcer = TrainCarts.turnedCartDistanceForcer;
          } else {
            threshold = TrainCarts.cartDistance;
            forcer = TrainCarts.cartDistanceForcer;
          }
          if (distance < threshold) {
            forcer *= TrainCarts.nearCartDistanceFactor;
          }
          member.onPhysicsPostMove(1 + (forcer * (threshold - distance)));
          if (this.breakPhysics) return true;
          if (i++ == this.size() - 1) {
            this.tail().onPhysicsPostMove(1);
            if (this.breakPhysics) return true;
            break;
          }
        }
      }

      // Update directions and perform connection checks after the position changes
      this.updateDirection();
      if (!this.doConnectionCheck(stepcount)) {
        return false;
      }
 
      // Check whether chunks are loaded, and load them if needed
      // If chunks are not kept loaded, the member will unload the entire train
      previousChunksBuffer.clear();
      newChunksBuffer.clear();
      for (MinecartMember<?> mm : this) {
        mm.updateChunks(previousChunksBuffer, newChunksBuffer);
      }
      int cx, cz;
      IntVector2 chunk;
      final World world = getWorld();
      Iterator<IntVector2> iter;
      if (this.canUnload()) {
        // Check whether the new chunks are unloaded
        iter = newChunksBuffer.iterator();
        while (iter.hasNext()) {
          chunk = iter.next();
          cx = chunk.x;
          cz = chunk.z;
          if (!world.isChunkLoaded(cx, cz)) {
            this.unload();
            throw new GroupUnloadedException();
          }
        }
      } else {
        // Mark previous chunks for unload
        iter = previousChunksBuffer.iterator();
        while (iter.hasNext()) {
          chunk = iter.next();
          if (!newChunksBuffer.contains(chunk)) {
            cx = chunk.x;
            cz = chunk.z;
            world.unloadChunkRequest(cx, cz);
          }
        }
        // Load the new chunks
        iter = newChunksBuffer.iterator();
        while (iter.hasNext()) {
          chunk = iter.next();
          if (!previousChunksBuffer.contains(chunk)) {
            cx = chunk.x;
            cz = chunk.z;
            world.getChunkAt(cx, cz);
          }
        }
      }
      return true;
    } catch (MemberMissingException ex) {
      return false;
    }
  }
}
TOP

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

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.