package com.hbm.entity.train; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.hbm.blocks.ILookOverlay; import com.hbm.blocks.rail.IRailNTM; import com.hbm.blocks.rail.IRailNTM.MoveContext; import com.hbm.blocks.rail.IRailNTM.RailCheckType; import com.hbm.blocks.rail.IRailNTM.RailContext; import com.hbm.blocks.rail.IRailNTM.TrackGauge; import com.hbm.items.ModItems; import com.hbm.packet.AuxParticlePacketNT; import com.hbm.packet.PacketDispatcher; import com.hbm.packet.PlayerInformPacket; import com.hbm.util.ChatBuilder; import com.hbm.util.fauxpointtwelve.BlockPos; import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ChatComponentText; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.MathHelper; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent.Pre; public abstract class EntityRailCarBase extends Entity implements ILookOverlay { public LogicalTrainUnit ltu; public int ltuIndex = 0; public boolean isOnRail = true; private int turnProgress; /* Clientside position that should be approached with smooth interpolation */ private double trainX; private double trainY; private double trainZ; private double trainYaw; private double trainPitch; private float movementYaw; @SideOnly(Side.CLIENT) private double velocityX; @SideOnly(Side.CLIENT) private double velocityY; @SideOnly(Side.CLIENT) private double velocityZ; /* "Actual" position with offset directly between the front and back pos, won't match the standard position on curves */ public double lastRenderX; public double lastRenderY; public double lastRenderZ; public double renderX; public double renderY; public double renderZ; public EntityRailCarBase coupledFront; public EntityRailCarBase coupledBack; public boolean initDummies = false; public BoundingBoxDummyEntity[] dummies = new BoundingBoxDummyEntity[0]; public EntityRailCarBase(World world) { super(world); } @Override protected void entityInit() { } @Override protected void readEntityFromNBT(NBTTagCompound nbt) { } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { } @Override public boolean interactFirst(EntityPlayer player) { if(player.getHeldItem() != null && player.getHeldItem().getItem() == ModItems.coupling_tool) { List intersecting = worldObj.getEntitiesWithinAABB(EntityRailCarBase.class, this.boundingBox.expand(2D, 0D, 2D)); for(EntityRailCarBase neighbor : intersecting) { if(neighbor == this) continue; if(neighbor.getGauge() != this.getGauge()) continue; TrainCoupling closestOwnCoupling = null; TrainCoupling closestNeighborCoupling = null; double closestDist = Double.POSITIVE_INFINITY; for(TrainCoupling ownCoupling : TrainCoupling.values()) { for(TrainCoupling neighborCoupling : TrainCoupling.values()) { Vec3 ownPos = this.getCouplingPos(ownCoupling); Vec3 neighborPos = neighbor.getCouplingPos(neighborCoupling); if(ownPos != null && neighborPos != null) { Vec3 delta = Vec3.createVectorHelper(ownPos.xCoord - neighborPos.xCoord, ownPos.yCoord - neighborPos.yCoord, ownPos.zCoord - neighborPos.zCoord); double length = delta.lengthVector(); if(length < 1 && length < closestDist) { closestDist = length; closestOwnCoupling = ownCoupling; closestNeighborCoupling = neighborCoupling; } } } } if(closestOwnCoupling != null && closestNeighborCoupling != null) { if(this.getCoupledTo(closestOwnCoupling) != null) continue; if(neighbor.getCoupledTo(closestNeighborCoupling) != null) continue; this.couple(closestOwnCoupling, neighbor); neighbor.couple(closestNeighborCoupling, this); if(this.ltu != null) this.ltu.dissolveTrain(); if(neighbor.ltu != null) neighbor.ltu.dissolveTrain(); player.swingItem(); player.addChatComponentMessage(new ChatComponentText("Coupled " + this.hashCode() + " (" + closestOwnCoupling.name() + ") to " + neighbor.hashCode() + " (" + closestNeighborCoupling.name() + ")")); return true; } } } //DEBUG if(this.ltu != null) { String id = Integer.toHexString(ltu.hashCode()); for(EntityRailCarBase train : ltu.trains) { NBTTagCompound data = new NBTTagCompound(); data.setString("type", "debug"); data.setInteger("color", 0x0000ff); data.setFloat("scale", 1.5F); data.setString("text", id); PacketDispatcher.wrapper.sendToAllAround(new AuxParticlePacketNT(data, train.posX, train.posY + 1, train.posZ), new TargetPoint(this.dimension, train.posX, train.posY + 1, train.posZ, 50)); } } return false; } @Override public void onUpdate() { if(this.worldObj.isRemote) { this.prevPosX = this.posX; this.prevPosY = this.posY; this.prevPosZ = this.posZ; if(this.turnProgress > 0) { this.prevRotationYaw = this.rotationYaw; double x = this.posX + (this.trainX - this.posX) / (double) this.turnProgress; double y = this.posY + (this.trainY - this.posY) / (double) this.turnProgress; double z = this.posZ + (this.trainZ - this.posZ) / (double) this.turnProgress; double yaw = MathHelper.wrapAngleTo180_double(this.trainYaw - (double) this.rotationYaw); this.rotationYaw = (float) ((double) this.rotationYaw + yaw / (double) this.turnProgress); this.rotationPitch = (float) ((double) this.rotationPitch + (this.trainPitch - (double) this.rotationPitch) / (double) this.turnProgress); --this.turnProgress; this.setPosition(x, y, z); this.setRotation(this.rotationYaw, this.rotationPitch); } else { this.setPosition(this.posX, this.posY, this.posZ); this.setRotation(this.rotationYaw, this.rotationPitch); } BlockPos anchor = this.getCurrentAnchorPos(); Vec3 frontPos = getRelPosAlongRail(anchor, this.getLengthSpan(), new MoveContext(RailCheckType.FRONT)); Vec3 backPos = getRelPosAlongRail(anchor, -this.getLengthSpan(), new MoveContext(RailCheckType.BACK)); this.lastRenderX = this.renderX; this.lastRenderY = this.renderY; this.lastRenderZ = this.renderZ; if(frontPos != null && backPos != null) { this.renderX = (frontPos.xCoord + backPos.xCoord) / 2D; this.renderY = (frontPos.yCoord + backPos.yCoord) / 2D; this.renderZ = (frontPos.zCoord + backPos.zCoord) / 2D; } } else { if(this.coupledFront != null && this.coupledFront.isDead) { this.coupledFront = null; if(this.ltu != null) this.ltu.dissolveTrain(); } if(this.coupledBack != null && this.coupledBack.isDead) { this.coupledBack = null; if(this.ltu != null) this.ltu.dissolveTrain(); } if(this.ltu == null && (this.coupledFront == null || this.coupledBack == null)) { LogicalTrainUnit.generateTrain(this); } DummyConfig[] definitions = this.getDummies(); if(!this.initDummies) { this.dummies = new BoundingBoxDummyEntity[definitions.length]; for(int i = 0; i < definitions.length; i++) { DummyConfig def = definitions[i]; BoundingBoxDummyEntity dummy = new BoundingBoxDummyEntity(worldObj, this, def.width, def.height); Vec3 rot = Vec3.createVectorHelper(def.offset.xCoord, def.offset.yCoord, def.offset.zCoord); rot.rotateAroundY((float) (-this.rotationYaw * Math.PI / 180)); double x = posX + rot.xCoord; double y = posY + rot.yCoord; double z = posZ + rot.zCoord; dummy.setPosition(x, y, z); dummy.setSize(def.width, def.height); dummy.velocityChanged = true; worldObj.spawnEntityInWorld(dummy); this.dummies[i] = dummy; } this.initDummies = true; } for(int i = 0; i < definitions.length; i++) { DummyConfig def = definitions[i]; BoundingBoxDummyEntity dummy = dummies[i]; Vec3 rot = Vec3.createVectorHelper(def.offset.xCoord, def.offset.yCoord, def.offset.zCoord); rot.rotateAroundY((float) (-this.rotationYaw * Math.PI / 180)); double x = renderX + rot.xCoord; double y = renderY + rot.yCoord; double z = renderZ + rot.zCoord; dummy.setPosition(x, y, z); } } } public Vec3 getRelPosAlongRail(BlockPos anchor, double distanceToCover, MoveContext context) { return getRelPosAlongRail(anchor, distanceToCover, this.getGauge(), this.worldObj, Vec3.createVectorHelper(posX, posY, posZ), this.rotationYaw, context); } public static Vec3 getRelPosAlongRail(BlockPos anchor, double distanceToCover, TrackGauge gauge, World worldObj, Vec3 next, float yaw, MoveContext context) { if(distanceToCover < 0) { distanceToCover *= -1; yaw += 180; } int it = 0; do { it++; if(it > 30) { return null; } int x = anchor.getX(); int y = anchor.getY(); int z = anchor.getZ(); Block block = worldObj.getBlock(x, y, z); Vec3 rot = Vec3.createVectorHelper(0, 0, 1); rot.rotateAroundY((float) (-yaw * Math.PI / 180D)); if(block instanceof IRailNTM) { IRailNTM rail = (IRailNTM) block; if(it == 1) { next = rail.getTravelLocation(worldObj, x, y, z, next.xCoord, next.yCoord, next.zCoord, rot.xCoord, rot.yCoord, rot.zCoord, 0, new RailContext(), context); } boolean flip = distanceToCover < 0; if(rail.getGauge(worldObj, x, y, z) == gauge) { RailContext info = new RailContext(); Vec3 prev = next; next = rail.getTravelLocation(worldObj, x, y, z, prev.xCoord, prev.yCoord, prev.zCoord, rot.xCoord, rot.yCoord, rot.zCoord, distanceToCover, info, context); distanceToCover = info.overshoot; anchor = info.pos; yaw = generateYaw(next, prev) * (flip ? -1 : 1); } else { return null; } } else { return null; } } while(distanceToCover != 0); //if there's still length to cover, keep going return next; } public static float generateYaw(Vec3 front, Vec3 back) { double deltaX = front.xCoord - back.xCoord; double deltaZ = front.zCoord - back.zCoord; double radians = -Math.atan2(deltaX, deltaZ); return (float) MathHelper.wrapAngleTo180_double(radians * 180D / Math.PI); } public static void updateMotion(World world) { Set ltus = new HashSet(); /* gather all LTUs */ for(Object o : world.loadedEntityList) { if(o instanceof EntityRailCarBase) { EntityRailCarBase train = (EntityRailCarBase) o; if(train.ltu != null) ltus.add(train.ltu); } } /* Move carts together with links */ //for(LogicalTrainUnit ltu : ltus) ltu.combineWagons(); /* Move carts with unified speed */ //for(LogicalTrainUnit ltu : ltus) ltu.moveTrain(); for(LogicalTrainUnit ltu : ltus) { double speed = ltu.getTotalSpeed(); if(Math.abs(speed) < 0.001) speed = 0; if(ltu.trains.length == 1) { EntityRailCarBase train = ltu.trains[0]; BlockPos anchor = new BlockPos(train.posX, train.posY, train.posZ); Vec3 newPos = train.getRelPosAlongRail(anchor, speed, new MoveContext(RailCheckType.CORE)); if(newPos == null) { train.derail(); ltu.dissolveTrain(); continue; } train.setPosition(newPos.xCoord, newPos.yCoord, newPos.zCoord); anchor = train.getCurrentAnchorPos(); Vec3 frontPos = train.getRelPosAlongRail(anchor, train.getLengthSpan(), new MoveContext(RailCheckType.FRONT)); Vec3 backPos = train.getRelPosAlongRail(anchor, -train.getLengthSpan(), new MoveContext(RailCheckType.BACK)); if(frontPos == null || backPos == null) { train.derail(); ltu.dissolveTrain(); continue; } else { ltu.setRenderPos(train, frontPos, backPos); } continue; } if(speed == 0) { ltu.combineWagons(); } else { ltu.moveTrainByApproach(speed); } } } /** Returns the amount of blocks that the train should move per tick */ public abstract double getCurrentSpeed(); public abstract double getMaxRailSpeed(); /** Returns the gauge of this train */ public abstract TrackGauge getGauge(); /** Returns the length between the core and one of the bogies */ public abstract double getLengthSpan(); /** Returns the length between the core and the collision points */ public abstract double getCollisionSpan(); /** Returns a collision box, usually smaller than the entity's AABB for rendering, which is used for colliding trains */ /*public AxisAlignedBB getCollisionBox() { return this.boundingBox; }*/ /** Returns a collision box used for block collisions when derailed */ /*@Override public AxisAlignedBB getBoundingBox() { return this.boundingBox; }*/ /** Returns the "true" position of the train, i.e. the block it wants to snap to */ public BlockPos getCurrentAnchorPos() { return new BlockPos(posX, posY + 0.25, posZ); } public void derail() { isOnRail = false; this.setDead(); } @SideOnly(Side.CLIENT) public void setPositionAndRotation2(double posX, double posY, double posZ, float yaw, float pitch, int turnProg) { this.trainX = posX; this.trainY = posY; this.trainZ = posZ; //this.trainYaw = (double) yaw; this.trainPitch = (double) pitch; this.turnProgress = turnProg + 2; this.motionX = this.velocityX; this.motionY = this.velocityY; this.motionZ = this.velocityZ; this.trainYaw = this.movementYaw; } @SideOnly(Side.CLIENT) public void setVelocity(double mX, double mY, double mZ) { this.movementYaw = (float) this.motionX * 360F; this.velocityX = this.motionX = mX; this.velocityY = this.motionY = mY; this.velocityZ = this.motionZ = mZ; } /** Invisible entities that make up the dynamic bounding structure of the train, moving as the train rotates. */ public static class BoundingBoxDummyEntity extends Entity implements ILookOverlay { private int turnProgress; private double trainX; private double trainY; private double trainZ; public EntityRailCarBase train; public BoundingBoxDummyEntity(World world) { this(world, null, 1F, 1F); } public BoundingBoxDummyEntity(World world, EntityRailCarBase train, float width, float height) { super(world); this.setSize(width, height); this.train = train; if(train != null) this.dataWatcher.updateObject(3, train.getEntityId()); } @Override protected void setSize(float width, float height) { super.setSize(width, height); this.dataWatcher.updateObject(4, width); this.dataWatcher.updateObject(5, height); } @Override protected void entityInit() { this.dataWatcher.addObject(3, new Integer(0)); this.dataWatcher.addObject(4, new Float(1F)); this.dataWatcher.addObject(5, new Float(1F)); } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { } @Override public boolean writeToNBTOptional(NBTTagCompound nbt) { return false; } @Override public void readEntityFromNBT(NBTTagCompound nbt) { this.setDead(); } @Override public boolean canBePushed() { return true; } @Override public boolean canBeCollidedWith() { return !this.isDead; } @Override public boolean attackEntityFrom(DamageSource source, float amount) { if(train != null) return train.attackEntityFrom(source, amount); return super.attackEntityFrom(source, amount); } @Override public boolean interactFirst(EntityPlayer player) { if(train != null) return train.interactFirst(player); return super.interactFirst(player); } @Override public void onUpdate() { if(!worldObj.isRemote) { if(this.train == null || this.train.isDead) { this.setDead(); } } else { if(this.turnProgress > 0) { this.prevRotationYaw = this.rotationYaw; double x = this.posX + (this.trainX - this.posX) / (double) this.turnProgress; double y = this.posY + (this.trainY - this.posY) / (double) this.turnProgress; double z = this.posZ + (this.trainZ - this.posZ) / (double) this.turnProgress; --this.turnProgress; this.setPosition(x, y, z); } else { this.setPosition(this.posX, this.posY, this.posZ); } this.setSize(this.dataWatcher.getWatchableObjectFloat(4), this.dataWatcher.getWatchableObjectFloat(5)); } } @Override @SideOnly(Side.CLIENT) public void setPositionAndRotation2(double posX, double posY, double posZ, float yaw, float pitch, int turnProg) { this.trainX = posX; this.trainY = posY; this.trainZ = posZ; this.turnProgress = turnProg + 2; } @Override public void printHook(Pre event, World world, int x, int y, int z) { Entity e = worldObj.getEntityByID(this.dataWatcher.getWatchableObjectInt(3)); if(e instanceof EntityRailCarBase) { ((EntityRailCarBase) e).printHook(event, world, x, y, z); } } } public DummyConfig[] getDummies() { return new DummyConfig[0]; } public static class DummyConfig { public Vec3 offset; public float width; public float height; public DummyConfig(float width, float height, Vec3 offset) { this.width = width; this.height = height; this.offset = offset; } } public static enum TrainCoupling { FRONT, BACK } public double getCouplingDist(TrainCoupling coupling) { return 0D; } public Vec3 getCouplingPos(TrainCoupling coupling) { double dist = this.getCouplingDist(coupling); if(dist <= 0) return null; if(coupling == TrainCoupling.BACK) dist *= -1; Vec3 rot = Vec3.createVectorHelper(0, 0, dist); rot.rotateAroundY((float) (-this.rotationYaw * Math.PI / 180D)); rot.xCoord += this.renderX; rot.yCoord += this.renderY; rot.zCoord += this.renderZ; return rot; } public EntityRailCarBase getCoupledTo(TrainCoupling coupling) { return coupling == TrainCoupling.FRONT ? this.coupledFront : coupling == TrainCoupling.BACK ? this.coupledBack : null; } public TrainCoupling getCouplingFrom(EntityRailCarBase coupledTo) { return coupledTo == this.coupledFront ? TrainCoupling.FRONT : coupledTo == this.coupledBack ? TrainCoupling.BACK : null; } public void couple(TrainCoupling coupling, EntityRailCarBase to) { if(coupling == TrainCoupling.FRONT) this.coupledFront = to; if(coupling == TrainCoupling.BACK) this.coupledBack = to; } public static class LogicalTrainUnit { protected EntityRailCarBase trains[]; /** Assumes that the train is an endpoint, i.e. that only one coupling is in use */ public static LogicalTrainUnit generateTrain(EntityRailCarBase train) { List links = new ArrayList(); Set brake = new HashSet(); LogicalTrainUnit ltu = new LogicalTrainUnit(); if(train.coupledFront == null && train.coupledBack == null) { ltu.trains = new EntityRailCarBase[] {train}; train.ltu = ltu; train.ltuIndex = 0; return ltu; } EntityRailCarBase current = train; EntityRailCarBase next = null; do { next = null; if(current.coupledFront != null && !brake.contains(current.coupledFront)) next = current.coupledFront; if(current.coupledBack != null && !brake.contains(current.coupledBack)) next = current.coupledBack; links.add(current); brake.add(current); current = next; } while(next != null); ltu.trains = new EntityRailCarBase[links.size()]; for(int i = 0; i < ltu.trains.length; i++) { ltu.trains[i] = links.get(i); ltu.trains[i].ltu = ltu; ltu.trains[i].ltuIndex = i; } return ltu; } /** Removes the LTU from all wagons */ public void dissolveTrain() { for(EntityRailCarBase train : trains) { train.ltu = null; train.ltuIndex = 0; } } /** Find the center fo the train, then moves all wagons towards that center until the coupling points roughly touch */ public void combineWagons() { if(trains.length <= 1) return; boolean odd = trains.length % 2 == 1; int centerIndex = odd ? trains.length / 2 : trains.length / 2 - 1; EntityRailCarBase center = trains[centerIndex]; EntityRailCarBase prev = center; for(int i = centerIndex - 1; i >= 0; i--) { EntityRailCarBase next = trains[i]; moveWagonTo(prev, next); prev = next; } prev = center; for(int i = centerIndex + 1; i < trains.length; i++) { EntityRailCarBase next = trains[i]; moveWagonTo(prev, next); prev = next; } } /** Moves one wagon to ne next until the coupling points roughly touch */ public void moveWagonTo(EntityRailCarBase moveTo, EntityRailCarBase moving) { TrainCoupling prevCouple = moveTo.getCouplingFrom(moving); TrainCoupling nextCouple = moving.getCouplingFrom(moveTo); Vec3 prevLoc = moveTo.getCouplingPos(prevCouple); Vec3 nextLoc = moving.getCouplingPos(nextCouple); Vec3 delta = Vec3.createVectorHelper(prevLoc.xCoord - nextLoc.xCoord, 0, prevLoc.zCoord - nextLoc.zCoord); double len = delta.lengthVector(); len *= 0.75; //suspension, causes movements to be less rigid BlockPos anchor = new BlockPos(moving.posX, moving.posY, moving.posZ); Vec3 trainPos = Vec3.createVectorHelper(moving.posX, moving.posY, moving.posZ); float yaw = EntityRailCarBase.generateYaw(prevLoc, nextLoc); Vec3 newPos = EntityRailCarBase.getRelPosAlongRail(anchor, len, moving.getGauge(), moving.worldObj, trainPos, yaw, new MoveContext(RailCheckType.CORE)); moving.setPosition(newPos.xCoord, newPos.yCoord, newPos.zCoord); anchor = moving.getCurrentAnchorPos(); //reset origin to new position Vec3 frontPos = moving.getRelPosAlongRail(anchor, moving.getLengthSpan(), new MoveContext(RailCheckType.FRONT)); Vec3 backPos = moving.getRelPosAlongRail(anchor, -moving.getLengthSpan(), new MoveContext(RailCheckType.BACK)); if(frontPos == null || backPos == null) { moving.derail(); this.dissolveTrain(); return; } else { setRenderPos(moving, frontPos, backPos); } } /** Generates the speed of the train, then moves the rain along the rail */ @Deprecated public void moveTrain() { EntityRailCarBase prev = trains[0]; TrainCoupling dir = prev.getCouplingFrom(null); double totalSpeed = 0; double maxSpeed = Double.POSITIVE_INFINITY; for(EntityRailCarBase train : this.trains) { boolean con = train.getCouplingFrom(prev) == dir; double speed = train.getCurrentSpeed(); if(!con) speed *= -1; totalSpeed += speed; maxSpeed = Math.min(maxSpeed, train.getMaxRailSpeed()); prev = train; } if(Math.abs(totalSpeed) > maxSpeed) { totalSpeed = maxSpeed * Math.signum(totalSpeed); } this.moveTrainBy(totalSpeed); } /** Moves the entire train along the rail by a certain speed */ @Deprecated public void moveTrainBy(double totalSpeed) { for(EntityRailCarBase train : this.trains) { BlockPos anchor = train.getCurrentAnchorPos(); Vec3 corePos = train.getRelPosAlongRail(anchor, totalSpeed, new MoveContext(RailCheckType.CORE)); if(corePos == null) { train.derail(); this.dissolveTrain(); return; } else { train.setPosition(corePos.xCoord, corePos.yCoord, corePos.zCoord); anchor = train.getCurrentAnchorPos(); //reset origin to new position Vec3 frontPos = train.getRelPosAlongRail(anchor, train.getLengthSpan(), new MoveContext(RailCheckType.FRONT)); Vec3 backPos = train.getRelPosAlongRail(anchor, -train.getLengthSpan(), new MoveContext(RailCheckType.BACK)); if(frontPos == null || backPos == null) { train.derail(); this.dissolveTrain(); return; } else { train.renderX = (frontPos.xCoord + backPos.xCoord) / 2D; train.renderY = (frontPos.yCoord + backPos.yCoord) / 2D; train.renderZ = (frontPos.zCoord + backPos.zCoord) / 2D; train.prevRotationYaw = train.rotationYaw; train.rotationYaw = train.movementYaw = generateYaw(frontPos, backPos); train.motionX = train.rotationYaw / 360D; // hijacking this crap for easy syncing train.velocityChanged = true; } } } } /** Returns the total speed of the LTU, negative if it is backwards compared to the arbitrary "front" wagon */ public double getTotalSpeed() { EntityRailCarBase prev = trains[0]; double totalSpeed = 0; double maxSpeed = Double.POSITIVE_INFINITY; //if the first car is in reverse, flip all subsequent cars as well boolean reverseTheReverse = prev.getCouplingFrom(null) == TrainCoupling.BACK; if(trains.length == 1) { return prev.getCurrentSpeed(); } for(EntityRailCarBase train : this.trains) { //if the car's linked indices are the wrong way, it is in reverse and speed applies negatively boolean reverse = false; EntityRailCarBase conFront = train.getCoupledTo(TrainCoupling.FRONT); EntityRailCarBase conBack = train.getCoupledTo(TrainCoupling.BACK); if(conFront != null && conFront.ltuIndex > train.ltuIndex) reverse = true; if(conBack != null && conBack.ltuIndex < train.ltuIndex) reverse = true; reverse ^= reverseTheReverse; double speed = train.getCurrentSpeed(); if(reverse) speed *= -1; totalSpeed += speed; maxSpeed = Math.min(maxSpeed, train.getMaxRailSpeed()); prev = train; } if(Math.abs(totalSpeed) > maxSpeed) { totalSpeed = maxSpeed * Math.signum(totalSpeed); } return totalSpeed; } /** Determines the "front" wagon based on the movement and moves it, then moves all other wagons towards that */ public void moveTrainByApproach(double speed) { boolean forward = speed < 0; double origSpeed = speed; speed = Math.abs(speed); EntityRailCarBase previous = null; EntityRailCarBase first = this.trains[0]; for(int i = forward ? 0 : this.trains.length - 1; forward ? i < this.trains.length : i >= 0; i += forward ? 1 : -1) { EntityRailCarBase current = this.trains[i]; if(previous == null) { PacketDispatcher.wrapper.sendToAllAround(new PlayerInformPacket(ChatBuilder.start("" + current.getClass() + " " + origSpeed).color(EnumChatFormatting.RED).flush(), 1), new TargetPoint(current.dimension, current.posX, current.posY + 1, current.posZ, 50)); boolean inReverse = first.getCouplingFrom(null) == current.getCouplingFrom(null); int sigNum = inReverse ? -1 : 1; BlockPos anchor = current.getCurrentAnchorPos(); Vec3 corePos = current.getRelPosAlongRail(anchor, speed * sigNum, new MoveContext(RailCheckType.CORE)); if(corePos == null) { current.derail(); this.dissolveTrain(); return; } else { current.setPosition(corePos.xCoord, corePos.yCoord, corePos.zCoord); anchor = current.getCurrentAnchorPos(); //reset origin to new position Vec3 frontPos = current.getRelPosAlongRail(anchor, current.getLengthSpan(), new MoveContext(RailCheckType.FRONT)); Vec3 backPos = current.getRelPosAlongRail(anchor, -current.getLengthSpan(), new MoveContext(RailCheckType.BACK)); if(frontPos == null || backPos == null) { current.derail(); this.dissolveTrain(); return; } else { setRenderPos(current, frontPos, backPos); } } } else { this.moveWagonTo(previous, current); } previous = current; } } /** Uses the front and back bogey positions to set the render pos and angles of a wagon */ public void setRenderPos(EntityRailCarBase current, Vec3 frontPos, Vec3 backPos) { current.renderX = (frontPos.xCoord + backPos.xCoord) / 2D; current.renderY = (frontPos.yCoord + backPos.yCoord) / 2D; current.renderZ = (frontPos.zCoord + backPos.zCoord) / 2D; current.prevRotationYaw = current.rotationYaw; current.rotationYaw = current.movementYaw = generateYaw(frontPos, backPos); current.motionX = current.rotationYaw / 360D; // hijacking this crap for easy syncing current.velocityChanged = true; } } @Override @SideOnly(Side.CLIENT) public void printHook(RenderGameOverlayEvent.Pre event, World world, int x, int y, int z) { List text = new ArrayList(); ILookOverlay.printGeneric(event, this.getClass().getSimpleName() + " " + this.hashCode(), 0xffff00, 0x404000, text); //none of this shit is going to work anyway } }