/* * Copyright (c) CovertJaguar, 2014 http://railcraft.info * * This code is the property of CovertJaguar * and may only be used with explicit written * permission unless otherwise specified on the * license page at http://railcraft.info/wiki/info:license. */ package mods.railcraft.common.carts; import com.google.common.collect.MapMaker; import mods.railcraft.api.carts.CartTools; import mods.railcraft.api.carts.ILinkableCart; import mods.railcraft.api.carts.ILinkageManager; import mods.railcraft.common.core.RailcraftConfig; import mods.railcraft.common.util.misc.Game; import net.minecraft.entity.item.EntityMinecart; import org.apache.logging.log4j.Level; import java.util.Iterator; import java.util.Map; import java.util.UUID; /** * The LinkageManager contains all the functions needed to link and interacted * with linked carts. * <p/> * One concept if import is that of the Linkage Id. Every cart is given a unique * identifier by the LinkageManager the first time it encounters the cart. * <p/> * This identifier is stored in the entity's NBT data between world loads so * that links are persistent rather than transitory. * <p/> * Links are also stored in NBT data as an Integer value that contains the * Linkage Id of the cart it is linked to. * <p/> * Generally you can ignore most of this and use the functions that don't * require or return Linkage Ids. * * @author CovertJaguar <http://www.railcraft.info> */ public class LinkageManager implements ILinkageManager { public static final String AUTO_LINK = "rcAutoLink"; public static final String LINK_A_HIGH = "rcLinkAHigh"; public static final String LINK_A_LOW = "rcLinkALow"; public static final String LINK_B_HIGH = "rcLinkBHigh"; public static final String LINK_B_LOW = "rcLinkBLow"; private final Map<UUID, EntityMinecart> carts = new MapMaker().weakValues().makeMap(); private LinkageManager() { } /** * Return an instance of the LinkageManager * * @return LinkageManager */ public static LinkageManager instance() { return (LinkageManager) CartTools.linkageManager; } public static void printDebug(String msg, Object... args) { if (RailcraftConfig.printLinkingDebug()) Game.log(Level.DEBUG, msg, args); } public static void reset() { CartTools.linkageManager = new LinkageManager(); } /** * Removes a id:cart pairing from the linkage registry. * <p/> * You should not need to call this function ever, it is needed only by the * LinkageHandler (internal RailcraftProxy code) in order to clean up dead * links left by dead carts. * * @param cart The cart to remove */ public void removeLinkageId(EntityMinecart cart) { carts.remove(getLinkageId(cart)); } /** * Returns the linkage id of the cart and adds the cart the linkage cache. * * @param cart The EntityMinecart * @return The linkage id */ public UUID getLinkageId(EntityMinecart cart) { UUID id = cart.getPersistentID(); if (!cart.isDead) carts.put(id, cart); return id; } /** * Returns a minecart from a persistent UUID. * * @param id Cart's persistent UUID * @return EntityMinecart */ @Override public EntityMinecart getCartFromUUID(UUID id) { EntityMinecart cart = carts.get(id); if (cart != null && cart.isDead) { carts.remove(id); return null; } return carts.get(id); } /** * Returns the square of the max distance two carts can be and still be * linkable. * * @param cart1 First Cart * @param cart2 Second Cart * @return The square of the linkage distance */ private float getLinkageDistanceSq(EntityMinecart cart1, EntityMinecart cart2) { float dist = 0; if (cart1 instanceof ILinkableCart) dist += ((ILinkableCart) cart1).getLinkageDistance(cart2); else dist += LINKAGE_DISTANCE; if (cart2 instanceof ILinkableCart) dist += ((ILinkableCart) cart2).getLinkageDistance(cart1); else dist += LINKAGE_DISTANCE; return dist * dist; } @Override public boolean setAutoLink(EntityMinecart cart, boolean autoLink) { if (autoLink && hasFreeLink(cart)) { cart.getEntityData().setBoolean(AUTO_LINK, true); printDebug("Cart {0}({1}) Set To Auto Link With First Collision.", getLinkageId(cart), cart); return true; } if (!autoLink) { cart.getEntityData().removeTag(AUTO_LINK); return true; } return false; } @Override public boolean hasAutoLink(EntityMinecart cart) { if (!hasFreeLink(cart)) cart.getEntityData().removeTag(AUTO_LINK); return cart.getEntityData().getBoolean(AUTO_LINK); } @Override public boolean tryAutoLink(EntityMinecart cart1, EntityMinecart cart2) { if ((hasAutoLink(cart1) || hasAutoLink(cart2)) && createLink(cart1, cart2)) { cart1.getEntityData().removeTag(LinkageManager.AUTO_LINK); cart2.getEntityData().removeTag(LinkageManager.AUTO_LINK); printDebug("Automatically Linked Carts {0}({1}) and {2}({3}).", getLinkageId(cart1), cart1, getLinkageId(cart2), cart2); if (cart1 instanceof EntityLocomotive) { ((EntityLocomotive) cart1).setSpeed(EntityLocomotive.LocoSpeed.SLOWEST); } if (cart2 instanceof EntityLocomotive) { ((EntityLocomotive) cart2).setSpeed(EntityLocomotive.LocoSpeed.SLOWEST); } return true; } return false; } /** * Returns true if there is nothing preventing the two carts from being * linked. * * @param cart1 First Cart * @param cart2 Second Cart * @return True if can be linked */ private boolean canLinkCarts(EntityMinecart cart1, EntityMinecart cart2) { if (cart1 == null || cart2 == null) return false; if (cart1 == cart2) return false; if (cart1 instanceof ILinkableCart) { ILinkableCart link = (ILinkableCart) cart1; if (!link.isLinkable() || !link.canLinkWithCart(cart2)) return false; } if (cart2 instanceof ILinkableCart) { ILinkableCart link = (ILinkableCart) cart2; if (!link.isLinkable() || !link.canLinkWithCart(cart1)) return false; } if (areLinked(cart1, cart2)) return false; if (cart1.getDistanceSqToEntity(cart2) > getLinkageDistanceSq(cart1, cart2)) return false; if (Train.areInSameTrain(cart1, cart2)) return false; return hasFreeLink(cart1) && hasFreeLink(cart2); } /** * Creates a link between two carts, but only if there is nothing preventing * such a link. * * @param cart1 First Cart * @param cart2 Second Cart * @return True if the link succeeded. */ @Override public boolean createLink(EntityMinecart cart1, EntityMinecart cart2) { if (canLinkCarts(cart1, cart2)) { Train train = Train.getLongestTrain(cart1, cart2); setLink(cart1, cart2); setLink(cart2, cart1); train.addLink(cart1, cart2); if (cart1 instanceof ILinkableCart) ((ILinkableCart) cart1).onLinkCreated(cart2); if (cart2 instanceof ILinkableCart) ((ILinkableCart) cart2).onLinkCreated(cart1); // sendLinkInfo(cart1); // sendLinkInfo(cart2); return true; } return false; } @Override public boolean hasFreeLink(EntityMinecart cart) { return getLinkedCartA(cart) == null || (hasLink(cart, LinkType.LINK_B) && getLinkedCartB(cart) == null); } public boolean hasLink(EntityMinecart cart, LinkType linkType) { if (linkType == LinkType.LINK_B && cart instanceof ILinkableCart) return ((ILinkableCart) cart).hasTwoLinks(); return true; } private void setLink(EntityMinecart cart1, EntityMinecart cart2) { if (getLinkedCartA(cart1) == null) setLink(cart1, cart2, LinkType.LINK_A); else if (hasLink(cart1, LinkType.LINK_B) && getLinkedCartB(cart1) == null) setLink(cart1, cart2, LinkType.LINK_B); } public UUID getLink(EntityMinecart cart, LinkType linkType) { long high = cart.getEntityData().getLong(linkType.tagHigh); long low = cart.getEntityData().getLong(linkType.tagLow); return new UUID(high, low); } public UUID getLinkA(EntityMinecart cart) { return getLink(cart, LinkType.LINK_A); } public UUID getLinkB(EntityMinecart cart) { return getLink(cart, LinkType.LINK_B); } private void setLink(EntityMinecart cart1, EntityMinecart cart2, LinkType linkType) { if (!hasLink(cart1, linkType)) return; UUID id = getLinkageId(cart2); cart1.getEntityData().setLong(linkType.tagHigh, id.getMostSignificantBits()); cart1.getEntityData().setLong(linkType.tagLow, id.getLeastSignificantBits()); } /** * Returns the cart linked to LinkType A or null if nothing is currently * occupying LinkType A. * * @param cart The cart for which to get the link * @return The linked cart or null */ @Override public EntityMinecart getLinkedCartA(EntityMinecart cart) { return getLinkedCart(cart, LinkType.LINK_A); } /** * Returns the cart linked to LinkType B or null if nothing is currently * occupying LinkType B. * * @param cart The cart for which to get the link * @return The linked cart or null */ @Override public EntityMinecart getLinkedCartB(EntityMinecart cart) { return getLinkedCart(cart, LinkType.LINK_B); } public EntityMinecart getLinkedCart(EntityMinecart cart, LinkType type) { return getCartFromUUID(getLink(cart, type)); } @Deprecated public Train getTrain(EntityMinecart cart) { return Train.getTrain(cart); } @Deprecated public Train getTrain(UUID cartUUID) { if (cartUUID == null) return null; EntityMinecart cart = getCartFromUUID(cartUUID); if (cart == null) return null; return getTrain(cart); } @Deprecated public UUID getTrainUUID(EntityMinecart cart) { return Train.getTrainUUID(cart); } @Deprecated public void resetTrain(EntityMinecart cart) { Train.resetTrain(cart); } @Deprecated public boolean areInSameTrain(EntityMinecart cart1, EntityMinecart cart2) { return Train.areInSameTrain(cart1, cart2); } /** * Returns true if the two carts are linked directly to each other. * * @param cart1 First Cart * @param cart2 Second Cart * @return True if linked */ @Override public boolean areLinked(EntityMinecart cart1, EntityMinecart cart2) { return areLinked(cart1, cart2, true); } /** * Returns true if the two carts are linked directly to each other. * * @param cart1 First Cart * @param cart2 Second Cart * @param strict true if both carts should have linking data pointing to the other cart, * false if its ok if only one cart has the data (this is technically an invalid state, but its been known to happen) * @return True if linked */ public boolean areLinked(EntityMinecart cart1, EntityMinecart cart2, boolean strict) { if (cart1 == null || cart2 == null) return false; if (cart1 == cart2) return false; // System.out.println("cart2 id = " + getLinkageId(cart2)); // System.out.println("cart2 A = " + getLinkA(cart2)); // System.out.println("cart2 B = " + getLinkB(cart2)); // System.out.println("cart1 id = " + getLinkageId(cart1)); // System.out.println("cart1 A = " + getLinkA(cart1)); // System.out.println("cart1 B = " + getLinkB(cart1)); boolean cart1Linked = false; UUID id1 = getLinkageId(cart1); UUID id2 = getLinkageId(cart2); if (id2.equals(getLinkA(cart1)) || id2.equals(getLinkB(cart1))) cart1Linked = true; // System.out.println("cart1 linked"); boolean cart2Linked = false; if (id1.equals(getLinkA(cart2)) || id1.equals(getLinkB(cart2))) cart2Linked = true; // System.out.println("cart2 linked"); if (strict) return cart1Linked && cart2Linked; else return cart1Linked || cart2Linked; } /** * Breaks a link between two carts, if any link exists. * * @param cart1 First Cart * @param cart2 Second Cart */ @Override public void breakLink(EntityMinecart cart1, EntityMinecart cart2) { // if (!areLinked(cart1, cart2)) // return; UUID link = getLinkageId(cart2); if (link.equals(getLinkA(cart1))) breakLinkA(cart1); if (link.equals(getLinkB(cart1))) breakLinkB(cart1); // int numCarts1 = numLinkedCarts(null, cart1); // int numCarts2 = numLinkedCarts(null, cart2); // if (numCarts1 >= numCarts2) // Train.getTrain(cart1).removeAllLinkedCarts(cart2); // else // Train.getTrain(cart2).removeAllLinkedCarts(cart1); } /** * Breaks all links the passed cart has. * * @param cart Cart */ @Override public void breakLinks(EntityMinecart cart) { breakLink(cart, LinkType.LINK_A); breakLink(cart, LinkType.LINK_B); } /** * Break only link A. * * @param cart Cart */ @Override public void breakLinkA(EntityMinecart cart) { breakLink(cart, LinkType.LINK_A); } /** * Break only link B. * * @param cart Cart */ @Override public void breakLinkB(EntityMinecart cart) { breakLink(cart, LinkType.LINK_B); } private EntityMinecart breakLink(EntityMinecart cart, LinkType linkType) { Train.resetTrain(cart); UUID link = getLink(cart, linkType); cart.getEntityData().setLong(linkType.tagHigh, 0); cart.getEntityData().setLong(linkType.tagLow, 0); EntityMinecart other = getCartFromUUID(link); if (other != null) { breakLink(other, cart); } if (cart instanceof ILinkableCart) ((ILinkableCart) cart).onLinkBroken(other); printDebug("Carts {0}({1}) and {2}({3}) unlinked ({4}).", getLinkageId(cart), cart, link, other != null ? other : "null", linkType.name()); return other; } /** * Counts how many carts are in the train. * * @param cart Any cart in the train * @return The number of carts in the train */ @Override public int countCartsInTrain(EntityMinecart cart) { return Train.getTrain(cart).size(); } private int numLinkedCarts(EntityMinecart prev, EntityMinecart next) { int count = 1; EntityMinecart linkA = getLinkedCartA(next); EntityMinecart linkB = getLinkedCartB(next); if (linkA != null && linkA != prev) count += numLinkedCarts(next, linkA); if (linkB != null && linkB != prev) count += numLinkedCarts(next, linkB); return count; } @Override public Iterable<EntityMinecart> getCartsInTrain(EntityMinecart cart) { return Train.getTrain(cart); } public Iterable<EntityMinecart> getLinkedCarts(final EntityMinecart cart, final LinkType type) { return new Iterable<EntityMinecart>() { @Override public Iterator<EntityMinecart> iterator() { return new Iterator<EntityMinecart>() { private final LinkageManager lm = LinkageManager.instance(); private EntityMinecart last = null; private EntityMinecart current = cart; @Override public boolean hasNext() { if (last == null) { EntityMinecart next = lm.getLinkedCart(current, type); return next != null; } EntityMinecart next = lm.getLinkedCartA(current); if (next != null && next != last) return true; next = lm.getLinkedCartB(current); if (next != null && next != last) return true; return false; } @Override public EntityMinecart next() { if (last == null) { EntityMinecart next = lm.getLinkedCart(current, type); if (next == null) return null; last = current; current = next; return current; } EntityMinecart next = lm.getLinkedCartA(current); if (next != null && next != last) { last = current; current = next; return current; } next = lm.getLinkedCartB(current); if (next != null && next != last) { last = current; current = next; return current; } return null; } @Override public void remove() { throw new UnsupportedOperationException("Removing not supported."); } }; } }; } public static enum LinkType { LINK_A(LINK_A_HIGH, LINK_A_LOW), LINK_B(LINK_B_HIGH, LINK_B_LOW); public final String tagHigh, tagLow; private LinkType(String tagHigh, String tagLow) { this.tagHigh = tagHigh; this.tagLow = tagLow; } } }