Package mods.railcraft.common.carts

Source Code of mods.railcraft.common.carts.LinkageHandler

/*
* 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 cpw.mods.fml.common.eventhandler.SubscribeEvent;
import java.util.Map;
import net.minecraft.entity.item.EntityMinecart;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.event.entity.minecart.MinecartInteractEvent;
import net.minecraftforge.event.entity.minecart.MinecartUpdateEvent;
import mods.railcraft.api.carts.ILinkableCart;
import mods.railcraft.api.core.items.IToolCrowbar;
import mods.railcraft.api.tracks.RailTools;
import mods.railcraft.common.modules.ModuleManager;
import mods.railcraft.common.modules.ModuleManager.Module;
import mods.railcraft.common.util.collections.CircularVec3Queue;
import mods.railcraft.common.util.misc.Vec2D;
import net.minecraft.util.Vec3;

public class LinkageHandler {

    public static final String LINK_A_TIMER = "linkA_timer";
    public static final String LINK_B_TIMER = "linkB_timer";
    public static final double LINK_DRAG = 0.95;
    public static final float MAX_DISTANCE = 8f;
    private static final float STIFFNESS = 0.7f;
    private static final float HS_STIFFNESS = 0.7f;
//    private static final float TRANSFER = 0.15f;
    private static final float DAMPING = 0.4f;
    private static final float HS_DAMPING = 0.3f;
    private static final float FORCE_LIMITER = 6f;
    private static final int TICK_HISTORY = 200;
    private static LinkageHandler instance;
    private static Map<EntityMinecart, CircularVec3Queue> history = new MapMaker().weakKeys().makeMap();

    private LinkageHandler() {
    }

    public static LinkageHandler getInstance() {
        if (instance == null)
            instance = new LinkageHandler();
        return instance;
    }

    /**
     * Returns the optimal distance between two linked carts that the
     * LinkageHandler will attempt to maintain at all times.
     *
     * @param cart1
     * @param cart2
     * @return The optimal distance
     */
    private float getOptimalDistance(EntityMinecart cart1, EntityMinecart cart2) {
        float dist = 0;
        if (cart1 instanceof ILinkableCart)
            dist += ((ILinkableCart) cart1).getOptimalDistance(cart2);
        else
            dist += LinkageManager.OPTIMAL_DISTANCE;
        if (cart2 instanceof ILinkableCart)
            dist += ((ILinkableCart) cart2).getOptimalDistance(cart1);
        else
            dist += LinkageManager.OPTIMAL_DISTANCE;
        return dist;
    }

    private boolean canCartBeAdjustedBy(EntityMinecart cart1, EntityMinecart cart2) {
        if (cart1 == cart2)
            return false;
        if (cart1 instanceof ILinkableCart && !((ILinkableCart) cart1).canBeAdjusted(cart2))
            return false;
        if (RailTools.isCartLockedDown(cart1))
            return false;
        return true;
    }

    /**
     * This is where the physics magic actually gets performed. It uses Spring
     * Forces and Damping Forces to maintain a fixed distance between carts.
     *
     * @param cart1
     * @param cart2
     * @param link
     */
    protected void adjustVelocity(EntityMinecart cart1, EntityMinecart cart2, char link) {
        String timer = LINK_A_TIMER;
        if (link == 'B')
            timer = LINK_B_TIMER;
        if (cart1.worldObj.provider.dimensionId != cart2.worldObj.provider.dimensionId) {
            short count = cart1.getEntityData().getShort(timer);
            count++;
            if (count > 200) {
                LinkageManager.instance().breakLink(cart1, cart2);
                LinkageManager.printDebug("Reason For Broken Link: Carts in different dimensions.");
            }
            cart1.getEntityData().setShort(timer, count);
            return;
        }
        cart1.getEntityData().setShort(timer, (short) 0);

        double dist = cart1.getDistanceToEntity(cart2);
        if (dist > MAX_DISTANCE) {
            LinkageManager.instance().breakLink(cart1, cart2);
            LinkageManager.printDebug("Reason For Broken Link: Max distance exceeded.");
            return;
        }

        boolean adj1 = canCartBeAdjustedBy(cart1, cart2);
        boolean adj2 = canCartBeAdjustedBy(cart2, cart1);

        Vec2D cart1Pos = new Vec2D(cart1.posX, cart1.posZ);
        Vec2D cart2Pos = new Vec2D(cart2.posX, cart2.posZ);

        Vec2D unit = Vec2D.subtract(cart2Pos, cart1Pos);
        unit.normalize();

        // Energy transfer

//        double transX = TRANSFER * (cart2.motionX - cart1.motionX);
//        double transZ = TRANSFER * (cart2.motionZ - cart1.motionZ);
//
//        transX = limitForce(transX);
//        transZ = limitForce(transZ);
//
//        if(adj1) {
//            cart1.motionX += transX;
//            cart1.motionZ += transZ;
//        }
//
//        if(adj2) {
//            cart2.motionX -= transX;
//            cart2.motionZ -= transZ;
//        }

        // Spring force

        float optDist = getOptimalDistance(cart1, cart2);
        double stretch = dist - optDist;
//        if(Math.abs(stretch) > 0.5) {
//            stretch *= 2;
//        }

        boolean highSpeed = cart1.getEntityData().getBoolean("HighSpeed");

        double stiffness = highSpeed ? HS_STIFFNESS : STIFFNESS;
        double springX = stiffness * stretch * unit.getX();
        double springZ = stiffness * stretch * unit.getY();

        springX = limitForce(springX);
        springZ = limitForce(springZ);

        if (adj1) {
            cart1.motionX += springX;
            cart1.motionZ += springZ;
        }

        if (adj2) {
            cart2.motionX -= springX;
            cart2.motionZ -= springZ;
        }

        // Damping

        Vec2D cart1Vel = new Vec2D(cart1.motionX, cart1.motionZ);
        Vec2D cart2Vel = new Vec2D(cart2.motionX, cart2.motionZ);

        double dot = Vec2D.subtract(cart2Vel, cart1Vel).dotProduct(unit);

        double damping = highSpeed ? HS_DAMPING : DAMPING;
        double dampX = damping * dot * unit.getX();
        double dampZ = damping * dot * unit.getY();

        dampX = limitForce(dampX);
        dampZ = limitForce(dampZ);

        if (adj1) {
            cart1.motionX += dampX;
            cart1.motionZ += dampZ;
        }

        if (adj2) {
            cart2.motionX -= dampX;
            cart2.motionZ -= dampZ;
        }
    }

    private float getTrainMaxSpeed(EntityMinecart cart) {
        LinkageManager lm = LinkageManager.instance();
        EntityMinecart linkA = lm.getLinkedCartA(cart);
        EntityMinecart linkB = lm.getLinkedCartB(cart);

        float speed = cart.getMaxCartSpeedOnRail();

        float min1 = speed;
        min1 = Math.min(min1, getTrainMaxSpeedRecursive(linkA, cart));

        float min2 = speed;
        min2 = Math.min(min2, getTrainMaxSpeedRecursive(linkB, cart));


        return Math.min(min1, min2);
    }

    private float getTrainMaxSpeedRecursive(EntityMinecart cart, EntityMinecart prev) {
        if (cart == null)
            return Float.MAX_VALUE;
        LinkageManager lm = LinkageManager.instance();
        EntityMinecart linkA = lm.getLinkedCartA(cart);
        EntityMinecart linkB = lm.getLinkedCartB(cart);

        float speed = cart.getMaxCartSpeedOnRail();

        float min1 = speed;
        if (linkA != prev)
            min1 = Math.min(min1, getTrainMaxSpeedRecursive(linkA, cart));

        float min2 = speed;
        if (linkB != prev)
            min2 = Math.min(min2, getTrainMaxSpeedRecursive(linkB, cart));

        return Math.min(min1, min2);
    }

    private void setTrainMaxSpeed(EntityMinecart cart, float trainSpeed) {
        LinkageManager lm = LinkageManager.instance();
        EntityMinecart linkA = lm.getLinkedCartA(cart);
        EntityMinecart linkB = lm.getLinkedCartB(cart);

        setTrainMaxSpeedRecursive(linkA, cart, trainSpeed);

        setTrainMaxSpeedRecursive(linkB, cart, trainSpeed);

        cart.setCurrentCartSpeedCapOnRail(trainSpeed);
    }

    private void setTrainMaxSpeedRecursive(EntityMinecart cart, EntityMinecart prev, float trainSpeed) {
        if (cart == null)
            return;
        LinkageManager lm = LinkageManager.instance();
        EntityMinecart linkA = lm.getLinkedCartA(cart);
        EntityMinecart linkB = lm.getLinkedCartB(cart);

        if (linkA != prev)
            setTrainMaxSpeedRecursive(linkA, cart, trainSpeed);

        if (linkB != prev)
            setTrainMaxSpeedRecursive(linkB, cart, trainSpeed);

        cart.setCurrentCartSpeedCapOnRail(trainSpeed);
    }

    private double limitForce(double force) {
        return Math.copySign(Math.min(Math.abs(force), FORCE_LIMITER), force);
    }

    /**
     * This function inspects the links and determines if any physics
     * adjustments need to be made.
     *
     * @param cart
     * @param lm
     */
    private void adjustCart(EntityMinecart cart, LinkageManager lm) {
        int launched = cart.getEntityData().getInteger("Launched");
        if (launched > 0)
            return;

        if (isOnElevator(cart))
            return;

        boolean linked = false;

        EntityMinecart link_A = lm.getLinkedCartA(cart);
        if (link_A != null) {
            launched = link_A.getEntityData().getInteger("Launched");
            if (launched <= 0 && !isOnElevator(link_A)) {
                linked = true;
                adjustVelocity(cart, link_A, 'A');
                adjustCartFromHistory(cart, link_A);
            }
        }

        EntityMinecart link_B = lm.getLinkedCartB(cart);
        if (link_B != null) {
            launched = link_B.getEntityData().getInteger("Launched");
            if (launched <= 0 && !isOnElevator(link_B)) {
                linked = true;
                adjustVelocity(cart, link_B, 'B');
                adjustCartFromHistory(cart, link_B);
            }
        }

        if (linked && ModuleManager.isModuleLoaded(Module.LOCOMOTIVES)) {
            cart.motionX *= LINK_DRAG;
            cart.motionZ *= LINK_DRAG;
        }

        if (link_A == null && link_B != null || link_A != null && link_B == null) {
            float trainSpeed = getTrainMaxSpeed(cart);
            setTrainMaxSpeed(cart, trainSpeed);
        } else if (link_A == null && link_B == null)
            setTrainMaxSpeed(cart, 1.2f);
    }

    /**
     * Determines whether a cart is leading another.
     *
     * @param leader
     * @param follower
     * @return true if leader is leading follower
     */
    private boolean isCartLeading(EntityMinecart leader, EntityMinecart follower) {
        return true; // TODO: magic
    }

    /**
     * Adjust the current cart's position based on the linked cart its following
     * so that it follows the same path at a set distance.
     *
     * @param current
     * @param linked
     */
    private void adjustCartFromHistory(EntityMinecart current, EntityMinecart linked) {
        // If we are leading, we don't want to adjust anything
        if (isCartLeading(current, linked))
            return;

        CircularVec3Queue leaderHistory = history.get(linked);

        // Optimal distance is how far apart the carts should be
        double optimalDist = getOptimalDistance(current, linked);
        optimalDist *= optimalDist;

        double currentDistance = linked.getDistanceSqToEntity(current);

        // Search the history for the point closest to the optimal distance.
        // There may be some issues with it chosing the wrong side of the cart.
        // Probably needs some kind of logic to compare the distance from the
        // new position to the current position and determine if its a valid position.
        Vec3 closestPoint = null;
        Vec3 linkedVec = Vec3.createVectorHelper(linked.posX, linked.posY, linked.posZ);
        double distance = Math.abs(optimalDist - currentDistance);
        for (Vec3 pos : leaderHistory) {
            double historyDistance = linkedVec.squareDistanceTo(pos);
            double diff = Math.abs(optimalDist - historyDistance);
            if (diff < distance) {
                closestPoint = pos;
                distance = diff;
            }
        }

        // If we found a point closer to our desired distance, move us there
        if (closestPoint != null)
            current.setPosition(closestPoint.xCoord, closestPoint.yCoord, closestPoint.zCoord);
    }

    /**
     * Saved the position history of the cart every tick in a Circular Buffer.
     *
     * @param cart
     */
    private void savePosition(EntityMinecart cart) {
        CircularVec3Queue myHistory = history.get(cart);
        if (myHistory == null) {
            myHistory = new CircularVec3Queue(TICK_HISTORY);
            history.put(cart, myHistory);
        }
        myHistory.add(cart.posX, cart.posY, cart.posZ);
    }

    /**
     * This is our entry point, its triggered once per tick per cart.
     *
     * @param event
     */
    @SubscribeEvent
    public void onMinecartUpdate(MinecartUpdateEvent event) {
        EntityMinecart cart = event.minecart;

        LinkageManager lm = LinkageManager.instance();

        if (cart.isDead) {
            lm.removeLinkageId(cart);
            return;
        }

        // Causes a link id cache store
        lm.getLinkageId(cart);

        // Physics done here
        adjustCart(cart, lm);

        savePosition(cart);
    }

    @SubscribeEvent
    public void onMinecartInteract(MinecartInteractEvent event) {
        EntityPlayer player = event.player;
        if (player.getCurrentEquippedItem() != null && player.getCurrentEquippedItem().getItem() instanceof IToolCrowbar)
            event.setCanceled(true);
    }

    private boolean isOnElevator(EntityMinecart cart) {
        int elevator = cart.getEntityData().getByte("elevator");
        return elevator > 0;
    }

}
TOP

Related Classes of mods.railcraft.common.carts.LinkageHandler

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.