Added accelerator data structures

This commit is contained in:
LemADEC 2017-02-25 23:24:34 +01:00
parent e2d963c071
commit e269118b1e
2 changed files with 1220 additions and 0 deletions

View file

@ -0,0 +1,550 @@
package cr0s.warpdrive.data;
import com.google.common.collect.ImmutableSet;
import cr0s.warpdrive.Commons;
import cr0s.warpdrive.LocalProfiler;
import cr0s.warpdrive.WarpDrive;
import cr0s.warpdrive.block.atomic.BlockAcceleratorControlPoint;
import cr0s.warpdrive.block.atomic.BlockChiller;
import cr0s.warpdrive.block.atomic.BlockElectromagnetPlain;
import cr0s.warpdrive.block.atomic.BlockParticlesCollider;
import cr0s.warpdrive.block.atomic.BlockParticlesInjector;
import cr0s.warpdrive.block.atomic.BlockVoidShellPlain;
import cr0s.warpdrive.block.atomic.TileEntityParticlesInjector;
import cr0s.warpdrive.block.energy.BlockEnergyBank;
import cr0s.warpdrive.block.energy.TileEntityEnergyBank;
import cr0s.warpdrive.config.WarpDriveConfig;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
import net.minecraft.block.Block;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import net.minecraftforge.common.util.ForgeDirection;
public class AcceleratorSetup extends GlobalPosition {
private static final int ACCELERATOR_MAX_RANGE_SQUARED = 192 * 192;
// raw data
private HashMap<VectorI, TrajectoryPoint> trajectoryAccelerator;
private HashMap<VectorI, TrajectoryPoint> trajectoryTransfer;
// computed values
private int[] countMagnets = new int[3];
private int[] countChillers = new int[3];
private final HashMap<VectorI, Integer> controlPoints = new HashMap<>();
public final HashMap<VectorI, Byte> chillers = new HashMap<>();
public final Set<TileEntityEnergyBank> energyBanks = new HashSet<>();
public final int energy_maxStorage;
public final TreeMap<Integer, VectorI> mapInjectors = new TreeMap<>();
public final Integer[] keyInjectors;
public final ArrayList<TrajectoryPoint> listColliders = new ArrayList<>();
private VectorI vMin;
private VectorI vMax;
// Cooldown duration
// increase with number of electromagnets ^0.5
// increase with tier (0.75 + 0.25 * x)
// decrease with number of cooling vents ^0.6
// has a minimum duration function of number of electromagnets per cooling vent
// Cooldown energy
// increase with tier (0.25 > 1.0 > 4.0)
// increase with number of cooling vents
public double[] temperatures_cooling_K_perTick = new double[3];
public double temperature_coolingEnergyCost_perTick;
public double[] temperatures_sustainEnergyCost_perTick = new double[3];
public double particleEnergy_energyCost_perTick;
public AcceleratorSetup(final int dimensionId, final int x, final int y, final int z) {
super(dimensionId, x, y, z);
LocalProfiler.start(String.format("[AcceleratorSetup] Scanning @ DIM%d (%d %d %d)", dimensionId, x, y, z));
refresh();
// cache energy stats
if (energyBanks.isEmpty()) {
energy_maxStorage = 0;
} else {
int maxStorage = 0;
for(TileEntityEnergyBank tileEntityEnergyBank : energyBanks) {
maxStorage += tileEntityEnergyBank.energy_getMaxStorage();
}
energy_maxStorage = maxStorage;
}
// sort injectors by their video channels
if (mapInjectors.isEmpty()) {
keyInjectors = null;
} else {
keyInjectors = mapInjectors.keySet().toArray(new Integer[0]);
}
if (WarpDriveConfig.LOGGING_FORCEFIELD) {
WarpDrive.logger.info(String.format("Accelerator length: %d + %d including %d + %d + %d magnets, %d chillers and %d energy banks"
+ " cooldown: %.3f %.3f %.3f /t %.3f EU/t"
+ " sustain: %.3f %.3f %.3f EU/t"
+ " acceleration: %.3f /particle",
trajectoryAccelerator.size(), trajectoryTransfer.size(),
countMagnets[0], countMagnets[1], countMagnets[2], chillers.size(), energyBanks.size(),
temperatures_cooling_K_perTick[0], temperatures_cooling_K_perTick[1], temperatures_cooling_K_perTick[2],
temperature_coolingEnergyCost_perTick,
temperatures_sustainEnergyCost_perTick[0], temperatures_sustainEnergyCost_perTick[1], temperatures_sustainEnergyCost_perTick[2],
particleEnergy_energyCost_perTick));
}
LocalProfiler.stop();
}
// add the vector with it's surrounding block so we can catch 'added' blocks
private void addToBoundingBox(final VectorI vector) {
vMin.x = Math.min(vMin.x, vector.x - 1);
vMin.y = Math.min(vMin.y, vector.y - 1);
vMin.z = Math.min(vMin.z, vector.z - 1);
vMax.x = Math.max(vMax.x, vector.x + 1);
vMax.y = Math.max(vMax.y, vector.y + 1);
vMax.z = Math.max(vMax.z, vector.z + 1);
}
private void refresh() {
WorldServer world = getWorldServerIfLoaded();
if (world == null) {
if (WarpDriveConfig.LOGGING_ACCELERATOR) {
WarpDrive.logger.warn(String.format("Accelerator scan cancelled: Dimension %d isn't loaded", dimensionId));
}
return;
}
if (FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT) {
WarpDrive.logger.warn("Accelerator scan cancelled: client side");
return;
}
// get all accelerator and transfer trajectory points
fillTrajectoryPoints(world);
if (trajectoryAccelerator == null) {
return;
}
// scan the accelerators
computeCountsAndBoundingBox();
computeVectorArrays(world);
// compute values
double coolingFactor = 10.0 / (countMagnets[0] + countMagnets[1] + countMagnets[2]);
temperatures_cooling_K_perTick[0] = (countChillers[0] * 1.00 + countChillers[1] * 0.75 + countChillers[2] * 0.5) * coolingFactor;
temperatures_cooling_K_perTick[1] = (countChillers[1] * 1.00 + countChillers[2] * 0.75) * coolingFactor;
temperatures_cooling_K_perTick[2] = (countChillers[2] * 1.00) * coolingFactor;
temperature_coolingEnergyCost_perTick = countChillers[0] * 10.0 + countChillers[1] * 20.0 + countChillers[2] * 40.0;
temperatures_sustainEnergyCost_perTick[0] = countChillers[0] * 1.00 + countChillers[1] * 2.0 + countChillers[2] * 2.0;
temperatures_sustainEnergyCost_perTick[1] = countChillers[0] * 0.50 + countChillers[1] * 2.0 + countChillers[2] * 3.0;
temperatures_sustainEnergyCost_perTick[2] = countChillers[0] * 0.25 + countChillers[1] * 1.0 + countChillers[2] * 4.0;
particleEnergy_energyCost_perTick = countMagnets[0] * 1.00 + countMagnets[1] * 2.00 + countMagnets[2] * 3.00;
}
private void fillTrajectoryPoints(WorldServer world) {
// find closest connected VoidShell
Set<Block> whitelist = ImmutableSet.of(
WarpDrive.blockElectromagnetPlain[0],
WarpDrive.blockElectromagnetGlass[0],
WarpDrive.blockElectromagnetPlain[1],
WarpDrive.blockElectromagnetGlass[1],
WarpDrive.blockElectromagnetPlain[2],
WarpDrive.blockElectromagnetGlass[2],
WarpDrive.blockVoidShellPlain,
WarpDrive.blockVoidShellGlass);
Set<VectorI> connections = Commons.getConnectedBlocks(world, new VectorI(x, y, z), ForgeDirection.VALID_DIRECTIONS, whitelist, 3);
VectorI firstVoidShell = null;
for (VectorI connection : connections) {
Block block = connection.getBlock(world);
if (block instanceof BlockVoidShellPlain) {
firstVoidShell = connection.clone();
break;
}
}
WarpDrive.logger.info("First void shell is " + firstVoidShell);
if (firstVoidShell == null) {
WarpDrive.logger.warn("No void shell connection found");
return;
}
// find initial direction
whitelist = ImmutableSet.of(
WarpDrive.blockVoidShellPlain,
WarpDrive.blockVoidShellGlass);
TrajectoryPoint trajectoryPoint = null;
for(ForgeDirection direction : Commons.HORIZONTAL_DIRECTIONS) {
VectorI next = firstVoidShell.clone(direction);
if (whitelist.contains(next.getBlock_noChunkLoading(world))) {
trajectoryPoint = new TrajectoryPoint(world, firstVoidShell.translate(direction), direction);
break;
}
}
if (WarpDriveConfig.LOGGING_ACCELERATOR) {
WarpDrive.logger.info("First one is " + trajectoryPoint);
}
if (trajectoryPoint == null) {
return;
}
// scan all connected void shells
trajectoryAccelerator = new HashMap<>();
trajectoryTransfer = new HashMap<>();
HashSet<TrajectoryPoint> acceleratorToAdd = new HashSet<>();
acceleratorToAdd.add(trajectoryPoint.clone());
trajectoryPoint = new TrajectoryPoint(world, trajectoryPoint.translate(trajectoryPoint.directionBackward), trajectoryPoint.directionBackward);
acceleratorToAdd.add(trajectoryPoint);
HashSet<TrajectoryPoint> transferToAdd = new HashSet<>();
while(!acceleratorToAdd.isEmpty()) {
// add all accelerators found
for (TrajectoryPoint trajectoryToAdd : acceleratorToAdd) {
trajectoryPoint = trajectoryToAdd;
while ( trajectoryPoint.hasNoMissingVoidShells()
&& isInRange(trajectoryPoint)
&& !trajectoryAccelerator.containsKey(trajectoryPoint)) {
if (WarpDriveConfig.LOGGING_ACCELERATOR) {
WarpDrive.logger.info("Adding accelerator " + trajectoryPoint);
}
TrajectoryPoint trajectoryPointToAdd = trajectoryPoint.clone();
trajectoryAccelerator.put(trajectoryPointToAdd, trajectoryPointToAdd);
if (trajectoryPoint.vJunctionForward != null) {
transferToAdd.add(new TrajectoryPoint(world, trajectoryPoint, true));
}
if (trajectoryPoint.vJunctionBackward != null) {
transferToAdd.add(new TrajectoryPoint(world, trajectoryPoint, false));
}
trajectoryPoint = new TrajectoryPoint(world, trajectoryPoint.translate(trajectoryPoint.directionForward), trajectoryPoint.directionForward);
}
}
acceleratorToAdd.clear();
// add all transfer found
for (TrajectoryPoint trajectoryToAdd : transferToAdd) {
trajectoryPoint = trajectoryToAdd;
while ( trajectoryPoint.hasNoMissingVoidShells()
&& isInRange(trajectoryPoint)
&& !trajectoryTransfer.containsKey(trajectoryPoint)
&& !trajectoryPoint.needsReevaluation()) {
if (WarpDriveConfig.LOGGING_ACCELERATOR) {
WarpDrive.logger.info("Adding transfer " + trajectoryPoint);
}
TrajectoryPoint trajectoryPointToAdd = trajectoryPoint.clone();
trajectoryTransfer.put(trajectoryPointToAdd, trajectoryPointToAdd);
trajectoryPoint = new TrajectoryPoint(world, trajectoryPoint, true);
}
if (trajectoryPoint.needsReevaluation()) {
// rebuild as an accelerator point from the next one forward
trajectoryPoint = new TrajectoryPoint(world, trajectoryPoint.translate(trajectoryPoint.directionForward), trajectoryPoint.directionForward);
acceleratorToAdd.add(trajectoryPoint.clone());
// also go backward in case it's broken
trajectoryPoint = new TrajectoryPoint(world, trajectoryPoint.translate(trajectoryPoint.directionBackward), trajectoryPoint.directionBackward);
acceleratorToAdd.add(trajectoryPoint);
}
}
transferToAdd.clear();
}
}
private boolean isInRange(final TrajectoryPoint trajectoryPoint) {
double distanceSquared = trajectoryPoint.distance2To(new VectorI(x, y, z));
return distanceSquared <= ACCELERATOR_MAX_RANGE_SQUARED;
}
private void computeCountsAndBoundingBox() {
boolean isFirst = true;
for (TrajectoryPoint trajectoryPoint : trajectoryAccelerator.values()) {
// check bounding area
if (isFirst) {
vMin = trajectoryPoint.getVectorI();
vMax = trajectoryPoint.getVectorI();
isFirst = false;
}
addToBoundingBox(trajectoryPoint);
// count main magnets
int indexTier = trajectoryPoint.type & TrajectoryPoint.MASK_TIERS - 1;
if ((trajectoryPoint.type & TrajectoryPoint.MAGNETS_HORIZONTAL) != 0) {
countMagnets[indexTier] += 2;
}
if ((trajectoryPoint.type & TrajectoryPoint.MAGNETS_VERTICAL) != 0) {
countMagnets[indexTier] += 2;
}
// count input/output magnets
if ((trajectoryPoint.type & TrajectoryPoint.MASK_IS_INPUT) != 0 && indexTier > 0) {
countMagnets[indexTier - 1] += 12;
}
if ((trajectoryPoint.type & TrajectoryPoint.MASK_IS_OUTPUT) != 0 && indexTier < 2) {
countMagnets[indexTier + 1] += 12;
}
}
WarpDrive.logger.info("Bounding box is " + vMin + " to " + vMax);
}
private void computeVectorArrays(WorldServer world) {
// check for chillers, injectors and colliders blocks
for (TrajectoryPoint trajectoryPoint : trajectoryAccelerator.values()) {
// check for injectors
VectorI vectorToAdd = trajectoryPoint.clone(trajectoryPoint.directionForward.getOpposite());
Block blockForward = vectorToAdd.getBlock(world);
if (blockForward instanceof BlockParticlesInjector) {
final int controlChannel = ((TileEntityParticlesInjector) vectorToAdd.getTileEntity(world)).getControlChannel();
mapInjectors.put(controlChannel, vectorToAdd);
addToBoundingBox(vectorToAdd);
} else {
vectorToAdd = trajectoryPoint.clone(trajectoryPoint.directionBackward.getOpposite());
Block blockBackward = vectorToAdd.getBlock(world);
if (blockBackward instanceof BlockParticlesInjector) {
final int controlChannel = ((TileEntityParticlesInjector) vectorToAdd.getTileEntity(world)).getControlChannel();
mapInjectors.put(controlChannel, vectorToAdd);
addToBoundingBox(vectorToAdd);
}
}
// collect control points and colliders
if (trajectoryPoint.vControlPoint != null) {
controlPoints.put(trajectoryPoint.vControlPoint, trajectoryPoint.type);
addToBoundingBox(trajectoryPoint.vControlPoint);
if (trajectoryPoint.isCollider()) {
listColliders.add(trajectoryPoint);
}
}
// check corners when there's at least 1 set of main magnets and no control point
if (trajectoryPoint.vControlPoint == null && (trajectoryPoint.type & TrajectoryPoint.MASK_MAGNETS_BOTH) != 0) {
scanCorners(world, trajectoryPoint, trajectoryPoint.directionForward);
if (trajectoryPoint.directionForward != trajectoryPoint.directionBackward.getOpposite()) {
scanCorners(world, trajectoryPoint, trajectoryPoint.directionBackward);
}
}
}
}
private void scanCorners(WorldServer world, final VectorI vCenter, final ForgeDirection forgeDirection) {
ForgeDirection directionLeft = forgeDirection.getRotation(ForgeDirection.UP);
ForgeDirection directionRight = forgeDirection.getRotation(ForgeDirection.DOWN);
for (int indexCorner = 0; indexCorner < 4; indexCorner++) {
VectorI vector = new VectorI(
vCenter.x + ((indexCorner & 1) != 0 ? directionLeft.offsetX : directionRight.offsetX),
vCenter.y + ((indexCorner & 2) != 0 ? 1 : -1),
vCenter.z + ((indexCorner & 1) != 0 ? directionLeft.offsetZ : directionRight.offsetZ));
Block block = vector.getBlock(world);
if (block instanceof BlockChiller) {
chillers.put(vector, ((BlockChiller)block).tier);
countChillers[((BlockChiller)block).tier - 1]++;
} else if (block instanceof BlockEnergyBank) {
TileEntity tileEntity = vector.getTileEntity(world);
if (tileEntity instanceof TileEntityEnergyBank) {
energyBanks.add((TileEntityEnergyBank) tileEntity);
} else {
WarpDrive.logger.error("Invalid tile entity detected for energy bank at " + vector);
}
}
}
}
public int getMass() {
if (trajectoryAccelerator == null) {
return 0;
}
return trajectoryAccelerator.size() + trajectoryTransfer.size() + energyBanks.size() + controlPoints.size()
+ countMagnets[0] + countMagnets[1] + countMagnets[2]
+ countChillers[0] + countChillers[1] + countChillers[2];
}
public boolean isMajorChange(final AcceleratorSetup acceleratorSetup) {
return trajectoryAccelerator.size() != acceleratorSetup.trajectoryAccelerator.size()
|| trajectoryTransfer.size() != acceleratorSetup.trajectoryTransfer.size()
|| countMagnets[0] != acceleratorSetup.countMagnets[0]
|| countMagnets[1] != acceleratorSetup.countMagnets[1]
|| countMagnets[2] != acceleratorSetup.countMagnets[2]
|| countChillers[0] != acceleratorSetup.countChillers[0]
|| countChillers[1] != acceleratorSetup.countChillers[1]
|| countChillers[2] != acceleratorSetup.countChillers[2];
}
public boolean isBlockUpdated(World world, final VectorI vector, final Block block) {
boolean checkDirectConnection = false;
boolean checkRangedConnection = false;
boolean checkCornerConnection = false;
// check explicit inclusions
if (block instanceof BlockChiller) {
if (chillers.containsKey(vector)) {
return true;
}
checkCornerConnection = true;
} else if (block instanceof BlockVoidShellPlain) {
if (isTrajectoryPoint(vector)) {
return true;
}
checkDirectConnection = true;
} else if (block instanceof BlockParticlesInjector) {
checkDirectConnection = true;
} else if (block instanceof BlockElectromagnetPlain) {
checkDirectConnection = true;
checkCornerConnection = true;
} else if (block instanceof BlockParticlesCollider) {
checkCornerConnection = true;
} else if (block instanceof BlockAcceleratorControlPoint) {
checkRangedConnection = true;
} else if (block instanceof BlockEnergyBank) {
TileEntity tileEntity = vector.getTileEntity(world);
if (tileEntity instanceof TileEntityEnergyBank) {
if (energyBanks.contains(tileEntity)) {
return true;
}
}
checkCornerConnection = true;
}
// check connections
if (checkDirectConnection || checkCornerConnection) {
for (ForgeDirection forgeDirection : ForgeDirection.VALID_DIRECTIONS) {
Block blockConnected = vector.translate(forgeDirection).getBlock(world);
if (blockConnected instanceof BlockVoidShellPlain) {
if (isTrajectoryPoint(vector)) {
return true;
}
} else if (checkCornerConnection && blockConnected instanceof BlockElectromagnetPlain) {
for (ForgeDirection forgeDirection2 : ForgeDirection.VALID_DIRECTIONS) {
Block blockSubConnected = vector.translate(forgeDirection2).getBlock(world);
if (blockSubConnected instanceof BlockVoidShellPlain) {
if (isTrajectoryPoint(vector)) {
return true;
}
}
}
}
}
}
if (checkRangedConnection) {
for (ForgeDirection forgeDirection : ForgeDirection.VALID_DIRECTIONS) {
Block blockConnected = vector.translate(forgeDirection, 2).getBlock(world);
if (blockConnected instanceof BlockVoidShellPlain) {
if (isTrajectoryPoint(vector)) {
return true;
}
}
}
}
return false;
}
public boolean isTrajectoryPoint(final VectorI vectorI) {
return trajectoryAccelerator.containsKey(vectorI) || trajectoryTransfer.containsKey(vectorI);
}
public TrajectoryPoint getTrajectoryPoint(final VectorI vectorI) {
TrajectoryPoint trajectoryPoint = trajectoryAccelerator.get(vectorI);
if (trajectoryPoint != null) {
return trajectoryPoint;
}
return trajectoryTransfer.get(vectorI);
}
public AxisAlignedBB getBoundingBox() {
if (vMin == null || vMax == null) {
return null;
}
return AxisAlignedBB.getBoundingBox(
vMin.x, vMin.y, vMin.z,
vMax.x, vMax.y, vMax.z);
}
// sanity check
public boolean isValid() {
if (trajectoryAccelerator == null) {
return false;
}
for(TileEntityEnergyBank tileEntityEnergyBank : energyBanks) {
if (tileEntityEnergyBank.isInvalid()) {
return false;
}
}
return true;
}
// Pseudo-API for energy
public int energy_getEnergyStored() {
int energyStored = 0;
for(TileEntityEnergyBank tileEntityEnergyBank : energyBanks) {
energyStored += tileEntityEnergyBank.energy_getEnergyStored();
}
return energyStored;
}
public int energy_getPotentialOutput() {
long potentialOutput = 0;
for(TileEntityEnergyBank tileEntityEnergyBank : energyBanks) {
potentialOutput = Math.min(potentialOutput + tileEntityEnergyBank.energy_getPotentialOutput(), Integer.MAX_VALUE);
}
return (int) potentialOutput;
}
public int energy_getMaxStorage() {
return energy_maxStorage;
}
public void energy_consume(final int amount_internal) {
// first, draw average from all
final int energyMean = amount_internal / energyBanks.size();
int energyConsumed = 0;
int energyLeft = amount_internal - energyMean * energyBanks.size();
assert(energyConsumed + energyLeft == amount_internal);
for(TileEntityEnergyBank tileEntityEnergyBank : energyBanks) {
final int energyToConsume = Math.min(tileEntityEnergyBank.energy_getPotentialOutput(), energyMean + energyLeft);
tileEntityEnergyBank.energy_consume(energyToConsume);
energyConsumed += energyToConsume;
energyLeft += (energyMean - energyConsumed);
}
assert(energyConsumed + energyLeft == amount_internal);
// then, draw remaining in no special order
if (energyLeft > 0) {
for(TileEntityEnergyBank tileEntityEnergyBank : energyBanks) {
final int energyToConsume = Math.min(tileEntityEnergyBank.energy_getPotentialOutput(), energyLeft);
tileEntityEnergyBank.energy_consume(energyToConsume);
energyConsumed += energyToConsume;
energyLeft -= energyConsumed;
}
}
assert(energyConsumed == amount_internal);
assert(energyLeft == 0);
}
@Override
public String toString() {
if (vMin == null || vMax == null) {
return String.format("%s @ DIM%d (%d %d %d) (-null-) -> (-null)",
getClass().getSimpleName(), dimensionId,
x, y, z);
}
return String.format("%s @ DIM%d (%d %d %d) (%d %d %d) -> (%d %d %d)",
getClass().getSimpleName(), dimensionId,
x, y, z,
vMin.x, vMin.y, vMin.z,
vMax.x, vMax.y, vMax.z);
}
}

View file

@ -0,0 +1,670 @@
package cr0s.warpdrive.data;
import cr0s.warpdrive.block.atomic.BlockAcceleratorControlPoint;
import cr0s.warpdrive.block.atomic.BlockElectromagnetPlain;
import cr0s.warpdrive.block.atomic.BlockParticlesCollider;
import cr0s.warpdrive.block.atomic.BlockParticlesInjector;
import cr0s.warpdrive.block.atomic.BlockVoidShellPlain;
import cr0s.warpdrive.block.atomic.TileEntityAcceleratorControlPoint;
import net.minecraft.block.Block;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
/**
* @author LemADEC
*/
public class TrajectoryPoint extends VectorI {
// 'type' is a bit-mask to cache surrounding blocks
public static final int NO_TYPE = 0x00000000;
public static final int TIER_NORMAL = 0x00000001;
public static final int TIER_ADVANCED = 0x00000002;
public static final int TIER_SUPERIOR = 0x00000003;
public static final int MASK_TIERS = 0x00000003;
public static final int MAGNETS_HORIZONTAL = 0x00000004;
public static final int MAGNETS_VERTICAL = 0x00000008;
public static final int MASK_MAGNETS_BOTH = 0x0000000C;
public static final int IS_INPUT_FORWARD = 0x00000010;
public static final int IS_INPUT_BACKWARD = 0x00000020;
public static final int MASK_IS_INPUT = 0x00000030;
public static final int IS_OUTPUT_FORWARD = 0x00000040;
public static final int IS_OUTPUT_BACKWARD = 0x00000080;
public static final int MASK_IS_OUTPUT = 0x000000C0;
public static final int IS_COLLIDER = 0x00000100;
public static final int IS_TRANSFER_PIPE = 0x00000200;
public static final int NEEDS_REEVALUATION = 0x00000400;
// reserved 0x00000800
// reserved 0x00001000
// reserved 0x00002000
// reserved 0x00004000
// reserved 0x00008000
public static final int ERROR_NONE = 0x00000000;
public static final int ERROR_DOUBLE_JUNCTION = 0x00010000; // invalid void shell (double junction)
public static final int ERROR_VERTICAL_JUNCTION = 0x00020000; // invalid void shell (vertical movement)
public static final int ERROR_MISSING_TURNING_MAGNET = 0x00040000; // missing main magnets at turning point
public static final int ERROR_MISSING_MAIN_MAGNET = 0x00080000; // missing main magnets at control point
public static final int ERROR_MISSING_CORNER_MAGNET = 0x00100000; // missing corner magnets at control point
public static final int ERROR_MISSING_COLLIDER = 0x00200000;
public static final int ERROR_MISSING_VOID_SHELL = 0x00400000; // too many void shells
public static final int ERROR_TOO_MANY_VOID_SHELLS = 0x00800000; // not enough void shells
// public static final int ERROR_OUT_OF_RANGE = 0x01000000;
// public static final int ERROR_TBD2 = 0x02000000;
// public static final int ERROR_TBD4 = 0x04000000;
// public static final int ERROR_TBD8 = 0x08000000;
// public static final int ERROR_TBD10 = 0x10000000;
// public static final int ERROR_TBD20 = 0x20000000;
// public static final int ERROR_TBD40 = 0x40000000;
// public static final int ERROR_TBD80 = 0x80000000;
public static final int MASK_ERRORS = 0xFFFF0000;
public final int type;
public final VectorI vControlPoint;
public final int controlChannel;
// next block direction in positive movement
public final ForgeDirection directionForward;
public final ForgeDirection directionBackward;
public final VectorI vJunctionForward;
public final VectorI vJunctionBackward;
public TrajectoryPoint(World world, final VectorI vPosition, final ForgeDirection directionForward) {
this(world, vPosition.x, vPosition.y, vPosition.z, directionForward);
}
public TrajectoryPoint(final int x, final int y, final int z,
final int type,
final VectorI vControlPoint,
final int controlChannel,
final ForgeDirection directionForward,
final ForgeDirection directionBackward,
final VectorI vJunctionForward,
final VectorI vJunctionBackward) {
super(x, y, z);
this.type = type;
this.vControlPoint = vControlPoint;
this.controlChannel = controlChannel;
this.directionForward = directionForward;
this.directionBackward = directionBackward;
this.vJunctionForward = vJunctionForward;
this.vJunctionBackward = vJunctionBackward;
}
// get next point on an acceleration pipe
private TrajectoryPoint(World world, final int x, final int y, final int z, final ForgeDirection directionMain) {
super(x, y, z);
int typeNew = NO_TYPE;
// check the core
Block blockCore = world.getBlock(x, y , z);
if (!(blockCore instanceof BlockVoidShellPlain)) {
typeNew |= ERROR_MISSING_VOID_SHELL;
}
// get main blocks
ForgeDirection directionLeft = directionMain.getRotation(ForgeDirection.UP);
ForgeDirection directionRight = directionMain.getRotation(ForgeDirection.DOWN);
Block blockForward = world.getBlock(x + directionMain.offsetX, y, z + directionMain.offsetZ);
Block blockUp = world.getBlock(x, y + 1, z);
Block blockDown = world.getBlock(x, y - 1, z);
Block blockLeft = world.getBlock(x + directionLeft .offsetX, y, z + directionLeft .offsetZ);
Block blockRight = world.getBlock(x + directionRight.offsetX, y, z + directionRight.offsetZ);
int tier = 0;
// check main magnets
if (blockUp instanceof BlockElectromagnetPlain && blockDown instanceof BlockElectromagnetPlain) {
int tierUp = ((BlockElectromagnetPlain) blockUp).tier;
if (tierUp == ((BlockElectromagnetPlain) blockDown).tier) {
tier = tier == 0 || tier == tierUp ? tierUp : -1;
typeNew |= MAGNETS_VERTICAL;
}
}
if (blockLeft instanceof BlockElectromagnetPlain && blockRight instanceof BlockElectromagnetPlain) {
int tierLeft = ((BlockElectromagnetPlain) blockLeft).tier;
if (tierLeft == ((BlockElectromagnetPlain) blockRight).tier) {
tier = tier == 0 || tier == tierLeft ? tierLeft : -1;
typeNew |= MAGNETS_HORIZONTAL;
}
}
// checking turning
TurnEvaluator turnEvaluator = new TurnEvaluator(world, x, y, z, directionMain, typeNew,
directionLeft, directionRight,
blockForward, blockUp, blockDown, blockLeft, blockRight,
tier);
typeNew = turnEvaluator.typeNew;
boolean isShellValid = turnEvaluator.isShellValid;
boolean isTurning = turnEvaluator.isTurning;
boolean isForward = turnEvaluator.isForward;
directionForward = turnEvaluator.directionForward;
directionBackward = turnEvaluator.directionBackward;
// check control point
// (requires a valid shell and at least one set of main magnets)
VectorI new_vControlPoint = null;
int new_controlChannel = -1;
if (isShellValid) {
for (ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) {
Block block = world.getBlock(
x + 2 * direction.offsetX,
y + 2 * direction.offsetY,
z + 2 * direction.offsetZ);
if (block instanceof BlockAcceleratorControlPoint && !(block instanceof BlockParticlesInjector)) {
if ((typeNew & MASK_MAGNETS_BOTH) == 0) {
typeNew |= ERROR_MISSING_MAIN_MAGNET;
} else {
new_vControlPoint = new VectorI(
x + 2 * direction.offsetX,
y + 2 * direction.offsetY,
z + 2 * direction.offsetZ);
new_controlChannel = ((TileEntityAcceleratorControlPoint) new_vControlPoint.getTileEntity(world)).getControlChannel();
}
break;
}
}
}
vControlPoint = new_vControlPoint;
controlChannel = new_controlChannel;
// evaluate 3x3x3 blocks centered here
boolean isInput = false;
boolean isOutput = false;
if (isShellValid) {
NodeEvaluator nodeEvaluator = new NodeEvaluator(world, x, y, z, typeNew, tier, isTurning);
// report errors found
typeNew = nodeEvaluator.typeNew;
// require control node for collider and output
if (nodeEvaluator.isCollider && vControlPoint != null) {
typeNew |= IS_COLLIDER;
}
// require control node for output
isOutput = nodeEvaluator.isOutput && vControlPoint != null;
// no control node required for input
isInput = nodeEvaluator.isInput;
}
// compute junction vectors
if (!isInput && !isOutput) {
vJunctionBackward = null;
vJunctionForward = null;
} else {
if (isTurning) {
if (isForward) {
typeNew |= isInput ? IS_INPUT_FORWARD : IS_OUTPUT_FORWARD;
vJunctionBackward = null;
vJunctionForward = new VectorI(directionMain);
} else {
typeNew |= isInput ? IS_INPUT_BACKWARD : IS_OUTPUT_BACKWARD;
vJunctionForward = null;
if (directionForward == directionRight) {
vJunctionBackward = new VectorI(directionLeft);
} else {
vJunctionBackward = new VectorI(directionRight);
}
}
} else {
typeNew |= isInput ? IS_INPUT_FORWARD : IS_OUTPUT_FORWARD; // @TODO code review, probably inverted somewhere
Block blockForwardLeft = world.getBlock(
x + directionMain.offsetX + directionLeft.offsetX,
y,
z + directionMain.offsetZ + directionLeft.offsetZ);
Block blockForwardRight = world.getBlock(
x + directionMain.offsetX + directionRight.offsetX,
y,
z + directionMain.offsetZ + directionRight.offsetZ);
Block blockBackwardLeft = world.getBlock(
x - directionMain.offsetX + directionLeft.offsetX,
y,
z - directionMain.offsetZ + directionLeft.offsetZ);
Block blockBackwardRight = world.getBlock(
x - directionMain.offsetX + directionRight.offsetX,
y,
z - directionMain.offsetZ + directionRight.offsetZ);
if (blockForwardLeft instanceof BlockVoidShellPlain) {
typeNew |= isInput ? IS_INPUT_FORWARD : IS_OUTPUT_FORWARD;
vJunctionForward = new VectorI(directionLeft).translate(directionMain);
if (blockBackwardLeft instanceof BlockVoidShellPlain) {
typeNew |= isInput ? IS_INPUT_BACKWARD : IS_OUTPUT_BACKWARD;
vJunctionBackward = new VectorI(directionLeft).translate(directionMain, -1);
} else {
vJunctionBackward = null;
}
} else if (blockForwardRight instanceof BlockVoidShellPlain) {
typeNew |= isInput ? IS_INPUT_FORWARD : IS_OUTPUT_FORWARD;
vJunctionForward = new VectorI(directionRight).translate(directionMain);
if (blockBackwardRight instanceof BlockVoidShellPlain) {
typeNew |= isInput ? IS_INPUT_BACKWARD : IS_OUTPUT_BACKWARD;
vJunctionBackward = new VectorI(directionRight).translate(directionMain, -1);
} else {
vJunctionBackward = null;
}
} else {
vJunctionForward = null;
if (blockBackwardLeft instanceof BlockVoidShellPlain) {
typeNew |= isInput ? IS_INPUT_BACKWARD : IS_OUTPUT_BACKWARD;
vJunctionBackward = new VectorI(directionLeft).translate(directionMain, -1);
} else if (blockBackwardRight instanceof BlockVoidShellPlain) {
typeNew |= isInput ? IS_INPUT_BACKWARD : IS_OUTPUT_BACKWARD;
vJunctionBackward = new VectorI(directionRight).translate(directionMain, -1);
} else {
vJunctionBackward = null;
}
}
}
}
// save the results
if (tier == 1) {
typeNew |= TIER_NORMAL;
} else if (tier == 2) {
typeNew |= TIER_ADVANCED;
} else if (tier == 3) {
typeNew |= TIER_SUPERIOR;
}
type = typeNew;
}
// get next point on a transfer pipe
public TrajectoryPoint(World world, final TrajectoryPoint trajectoryPoint, final boolean isForward) {
int typeNew = IS_TRANSFER_PIPE;
// get first/next transfer pipe
this.x = trajectoryPoint.x + (isForward ? trajectoryPoint.vJunctionForward.x : trajectoryPoint.vJunctionBackward.x);
this.y = trajectoryPoint.y + (isForward ? trajectoryPoint.vJunctionForward.y : trajectoryPoint.vJunctionBackward.y);
this.z = trajectoryPoint.z + (isForward ? trajectoryPoint.vJunctionForward.z : trajectoryPoint.vJunctionBackward.z);
// adjust to forward orientation
this.directionForward = isForward ? trajectoryPoint.directionBackward.getOpposite() : trajectoryPoint.directionForward.getOpposite();
this.directionBackward = directionForward.getOpposite();
this.vJunctionForward = isForward ? trajectoryPoint.vJunctionForward : trajectoryPoint.vJunctionBackward;
this.vJunctionBackward = null;
// get expected tier
int tier = (trajectoryPoint.type & MASK_TIERS)
+ (((trajectoryPoint.type & MASK_IS_INPUT ) != 0) ? -1 : 0)
+ (((trajectoryPoint.type & MASK_IS_OUTPUT) != 0) ? 1 : 0);
if (tier <= 0 || tier > 3) {
tier = 0;
}
// check support shells
boolean isStraightLine = (vJunctionForward.x == -directionBackward.offsetX) && (vJunctionForward.z == -directionBackward.offsetZ);
Block blockForward = world.getBlock(x + vJunctionForward.x, y, z + vJunctionForward.z);
Block blockBack = world.getBlock(x + directionBackward.offsetX, y, z + directionBackward.offsetZ);
Block blockUp = world.getBlock(x, y + 1, z);
Block blockDown = world.getBlock(x, y - 1, z);
// check main magnets to trigger node evaluation
// (up and down magnets should have same tier, but different from current one)
boolean hasVerticalMagnets = false;
if (blockUp instanceof BlockElectromagnetPlain && blockDown instanceof BlockElectromagnetPlain) {
int tierUp = ((BlockElectromagnetPlain) blockUp).tier;
if (tierUp == ((BlockElectromagnetPlain) blockDown).tier) {
hasVerticalMagnets = tier == tierUp;
}
}
// check forward movement
boolean isForwardOk = blockForward instanceof BlockVoidShellPlain;
boolean isBackOk = blockBack instanceof BlockVoidShellPlain;
boolean isShellValid = !(blockUp instanceof BlockVoidShellPlain) // no vertical transfer
&& !(blockDown instanceof BlockVoidShellPlain) // no vertical transfer
&& isForwardOk && isBackOk && tier != 0;
if (blockUp instanceof BlockVoidShellPlain || blockDown instanceof BlockVoidShellPlain) {
typeNew |= ERROR_VERTICAL_JUNCTION;
}
if (!isForwardOk || !isBackOk) {
typeNew |= ERROR_MISSING_VOID_SHELL;
}
// assuming it's a transfer node, we need to check if the void shells are turning
boolean isTurning = false;
if (hasVerticalMagnets) {
// when transfer line is at 45deg, we can't input/output in a turning corner, so we skip that case
if (isStraightLine) {
// we just do a basic check of void shells, the full validation of magnets is done in the node evaluator
ForgeDirection directionMain = directionBackward.getOpposite();
ForgeDirection directionLeft = directionMain.getRotation(ForgeDirection.UP);
ForgeDirection directionRight = directionMain.getRotation(ForgeDirection.DOWN);
Block blockLeft = world.getBlock(x + directionLeft .offsetX, y, z + directionLeft .offsetZ);
Block blockRight = world.getBlock(x + directionRight.offsetX, y, z + directionRight.offsetZ);
isTurning = blockLeft instanceof BlockVoidShellPlain || blockRight instanceof BlockVoidShellPlain;
}
}
// ignore control point
vControlPoint = null;
controlChannel = -1;
// look for an input or output node
if (isShellValid && hasVerticalMagnets) {
NodeEvaluator nodeEvaluator = new NodeEvaluator(world, x, y, z, typeNew, tier, isTurning);
typeNew = nodeEvaluator.typeNew;
if (nodeEvaluator.isInput || nodeEvaluator.isOutput) {
// it's a transfer node, mark it for re-evaluation
typeNew |= NEEDS_REEVALUATION;
}
}
// save the results
if (tier == 1) {
typeNew |= TIER_NORMAL;
} else if (tier == 2) {
typeNew |= TIER_ADVANCED;
} else if (tier == 3) {
typeNew |= TIER_SUPERIOR;
}
type = typeNew;
}
public boolean needsReevaluation() { return (type & NEEDS_REEVALUATION) != 0; }
public boolean hasNoMissingVoidShells() {
return (type & ERROR_MISSING_VOID_SHELL) == 0;
}
public int getTier() {
switch (type & MASK_TIERS) {
case TIER_NORMAL: return 1;
case TIER_ADVANCED: return 2;
case TIER_SUPERIOR: return 3;
default: return 0;
}
}
public int getMagnetsCount() {
switch (type & (MASK_MAGNETS_BOTH | MASK_ERRORS)) {
case MAGNETS_HORIZONTAL: return 1;
case MAGNETS_VERTICAL: return 1;
case MASK_MAGNETS_BOTH: return 2;
default: return 0;
}
}
public boolean isTransferPipe() {
return (type & IS_TRANSFER_PIPE) != 0;
}
public boolean isCollider() {
return (type & IS_COLLIDER) != 0;
}
public Vector3 getJunctionOut(final ForgeDirection directionCurrent) {
// skip erroneous setup
if ((type & MASK_ERRORS) != ERROR_NONE) {
return null;
}
// output while moving forward (i.e. while coming opposite of backward)
if (((type & IS_OUTPUT_FORWARD) != 0) && directionCurrent.getOpposite().equals(directionBackward)) {
return new Vector3(vJunctionForward.x, vJunctionForward.y, vJunctionForward.z).normalize();
}
// output while moving backward (i.e. while coming opposite of forward)
if (((type & IS_OUTPUT_BACKWARD) != 0) && directionCurrent.getOpposite().equals(directionForward)) {
return new Vector3(vJunctionBackward.x, vJunctionBackward.y, vJunctionBackward.z).normalize();
}
return null;
}
public ForgeDirection getTurnedDirection(final ForgeDirection directionCurrent) {
// skip erroneous setup
if ((type & ERROR_MISSING_TURNING_MAGNET) != ERROR_NONE) {
return null;
}
// skip straight line
if (directionForward.getOpposite().equals(directionBackward)) {
return null;
}
// turn forward
if (directionCurrent.equals(directionForward.getOpposite())) {
return directionBackward;
}
// turn backward
if (directionCurrent.equals(directionBackward.getOpposite())) {
return directionForward;
}
return null;
}
public ForgeDirection getJunctionIn(final Vector3 vectorCurrent) {
// skip erroneous setup
if ((type & MASK_ERRORS) != ERROR_NONE) {
return null;
}
// skip non-junction
if ((type & MASK_IS_INPUT) == 0) {
return null;
}
VectorI vJunctionRequired = new VectorI(
(int) -Math.signum(vectorCurrent.x),
(int) -Math.signum(vectorCurrent.y),
(int) -Math.signum(vectorCurrent.z));
// input in forward motion
if ( ((type & IS_INPUT_FORWARD) != 0) && vJunctionRequired.equals(vJunctionBackward) ) {
return directionForward;
}
// input in backward motion
if (((type & IS_INPUT_BACKWARD) != 0) && vJunctionRequired.equals(vJunctionBackward)) {
return directionBackward;
}
return null;
}
@Override
public TrajectoryPoint clone() {
return new TrajectoryPoint(x, y, z, type, vControlPoint, controlChannel, directionForward, directionBackward, vJunctionForward, vJunctionBackward);
}
public VectorI getVectorI() {
return new VectorI(x, y, z);
}
@Override
public boolean equals(final Object object) {
if (object instanceof VectorI) {
VectorI vector = (VectorI) object;
return (x == vector.x) && (y == vector.y) && (z == vector.z);
}
return false;
}
@Override
public String toString() {
return String.format("TrajectoryPoint [%d %d %d] %8x %5s %5s %s %s %s tier%d %s",
x, y, z,
type,
directionForward == null ? "-null-" : directionForward.toString(),
directionBackward == null ? "-null-" : directionBackward.toString(),
vJunctionForward == null ? "-null-" : vJunctionForward.toString(),
vJunctionBackward == null ? "-null-" : vJunctionBackward.toString(),
vControlPoint == null ? "-null-" : vControlPoint.toString(),
type & MASK_TIERS,
hasNoMissingVoidShells());
}
// validate a accelerator node
private class NodeEvaluator {
public int typeNew;
public boolean isCollider;
public boolean isInput;
public boolean isOutput;
public NodeEvaluator(World world, final int x, final int y, final int z, final int typeOriginal, final int tierMain, final boolean isTurning) {
typeNew = typeOriginal;
isInput = false;
isOutput = false;
// all 8 corners are always present, but can be glass or plain, so we can't check strict block equality
int countVoidShell = 0;
int countMainMagnet = 0;
int countLowerMagnet = 0;
int countHigherMagnet = 0;
int countCollider = 0;
for (int offsetX = -1; offsetX < 2; offsetX++) {
for (int offsetY = -1; offsetY < 2; offsetY++) {
for (int offsetZ = -1; offsetZ < 2; offsetZ++) {
Block blockCheck = world.getBlock(
x + offsetX,
y + offsetY,
z + offsetZ);
if (blockCheck instanceof BlockElectromagnetPlain) {
int tierCheck = ((BlockElectromagnetPlain) blockCheck).tier;
if (tierCheck == tierMain) {
countMainMagnet++;
} else if (tierCheck > tierMain) {
countHigherMagnet++;
} else {
countLowerMagnet++;
}
} else if (blockCheck instanceof BlockVoidShellPlain) {
countVoidShell++;
} else if (blockCheck instanceof BlockParticlesCollider) {
countCollider++;
} else if (blockCheck instanceof BlockParticlesInjector) {
int tierCheck = ((BlockParticlesInjector) blockCheck).tier;
if (tierCheck == tierMain) {
countMainMagnet++;
}
}
}
}
}
// check collider
// (we can be in between 2 colliders, so we need at least 9 to report an error)
if (countCollider > 8) {
if (countCollider < 12) {// need at least 12 collider blocks
typeNew |= ERROR_MISSING_COLLIDER;
} else if (countVoidShell < 3) {// need exactly 3 void shells
typeNew |= ERROR_MISSING_VOID_SHELL;
} else if (countVoidShell > 3) {// need exactly 3 void shells
typeNew |= ERROR_TOO_MANY_VOID_SHELLS;
} else if (countMainMagnet < 12) {
typeNew |= ERROR_MISSING_MAIN_MAGNET;
} else {
isCollider = true;
}
}
if (countLowerMagnet > 8 || countHigherMagnet > 8) {
if (countLowerMagnet < 12 && countHigherMagnet < 12) {// need at least 12 corner magnets
typeNew |= ERROR_MISSING_CORNER_MAGNET;
} else if (countLowerMagnet != 0 && countHigherMagnet != 0) {// only one type of corner magnets
typeNew |= ERROR_MISSING_CORNER_MAGNET;
} else if (isTurning && countVoidShell < 4) {// a turning junction requires 4 void shells
typeNew |= ERROR_MISSING_VOID_SHELL;
} else if (isTurning && countVoidShell > 4) {// a turning junction requires 4 void shells
typeNew |= ERROR_TOO_MANY_VOID_SHELLS;
} else if ((!isTurning) && countVoidShell < 5) {// a straight junction requires at least 5 void shells
typeNew |= ERROR_MISSING_VOID_SHELL;
} else if ((!isTurning) && countVoidShell > 6) {// a straight junction requires at most 6 void shells
typeNew |= ERROR_TOO_MANY_VOID_SHELLS;
} else if (countMainMagnet < 9) {// at 9 main magnets
typeNew |= ERROR_MISSING_MAIN_MAGNET;
} else if (countVoidShell + countMainMagnet != 15) {// exactly 15 void shells + main magnets
typeNew |= ERROR_MISSING_MAIN_MAGNET;
} else {
isInput = countLowerMagnet > 0;
isOutput = countHigherMagnet > 0;
assert(isInput || isOutput);
}
}
}
}
private class TurnEvaluator {
public int typeNew;
public boolean isForward;
public boolean isShellValid;
public boolean isTurning;
public ForgeDirection directionForward;
public ForgeDirection directionBackward;
public TurnEvaluator(World world, final int x, final int y, final int z, final ForgeDirection directionMain, final int typeOriginal,
final ForgeDirection directionLeft, final ForgeDirection directionRight,
final Block blockForward, final Block blockUp, final Block blockDown, final Block blockLeft, final Block blockRight,
final int tier) {
this.typeNew = typeOriginal;
// check turning magnets
isForward = blockForward instanceof BlockVoidShellPlain;
boolean isLeftTurn = blockLeft instanceof BlockVoidShellPlain;
boolean isRightTurn = blockRight instanceof BlockVoidShellPlain;
// boolean isJunction = (isForward && (isLeftTurn || isRightTurn)) || (isLeftTurn && isRightTurn);
isShellValid = !(blockUp instanceof BlockVoidShellPlain) // no vertical accelerators
&& !(blockDown instanceof BlockVoidShellPlain) // no vertical accelerators
&& (!isForward || !isLeftTurn || !isRightTurn);
// boolean isShellEnding = (!isForward && !isLeftTurn && !isRightTurn); // dead end
if (blockUp instanceof BlockVoidShellPlain || blockDown instanceof BlockVoidShellPlain) {
typeNew |= ERROR_VERTICAL_JUNCTION;
}
if (isForward && isLeftTurn && isRightTurn) {
typeNew |= ERROR_DOUBLE_JUNCTION;
}
isTurning = false;
if (isShellValid && (isLeftTurn || isRightTurn)) {
// validate the turning magnets
Block blockForwardLeft = world.getBlock(
x + directionMain.offsetX + directionLeft.offsetX,
y,
z + directionMain.offsetZ + directionLeft.offsetZ);
Block blockForwardRight = world.getBlock(
x + directionMain.offsetX + directionRight.offsetX,
y,
z + directionMain.offsetZ + directionRight.offsetZ);
Block blockBackwardLeft = world.getBlock(
x - directionMain.offsetX + directionLeft.offsetX,
y,
z - directionMain.offsetZ + directionLeft.offsetZ);
Block blockBackwardRight = world.getBlock(
x - directionMain.offsetX + directionRight.offsetX,
y,
z - directionMain.offsetZ + directionRight.offsetZ);
if ( tier > 0
&& blockForwardLeft instanceof BlockElectromagnetPlain && tier == ((BlockElectromagnetPlain) blockForwardLeft ).tier
&& blockForwardRight instanceof BlockElectromagnetPlain && tier == ((BlockElectromagnetPlain) blockForwardRight ).tier
&& blockBackwardLeft instanceof BlockElectromagnetPlain && tier == ((BlockElectromagnetPlain) blockBackwardLeft ).tier
&& blockBackwardRight instanceof BlockElectromagnetPlain && tier == ((BlockElectromagnetPlain) blockBackwardRight).tier
&& ((typeNew & MAGNETS_VERTICAL) == MAGNETS_VERTICAL) ) {
// also validate the sided magnet
isTurning = (isForward || blockForward instanceof BlockElectromagnetPlain || blockForward instanceof BlockParticlesInjector)
&& (isLeftTurn || blockLeft instanceof BlockElectromagnetPlain || blockLeft instanceof BlockParticlesInjector)
&& (isRightTurn || blockRight instanceof BlockElectromagnetPlain || blockRight instanceof BlockParticlesInjector);
}
if (!isTurning) {
// check if another void shell is present on the turn side, in which case we assume it's an input/output area
// otherwise, it's a missing turning magnet
if (!( isLeftTurn && (blockBackwardLeft instanceof BlockVoidShellPlain || blockForwardLeft instanceof BlockVoidShellPlain)
|| isRightTurn && (blockBackwardRight instanceof BlockVoidShellPlain || blockForwardRight instanceof BlockVoidShellPlain) ) ) {
typeNew |= ERROR_MISSING_TURNING_MAGNET;
}
}
}
// compute forward vector
if (!isTurning && (typeNew & ERROR_MISSING_TURNING_MAGNET) == 0) {
directionForward = directionMain;
} else if (isLeftTurn && !isRightTurn) {
directionForward = directionLeft;
} else if (!isLeftTurn && isRightTurn) {
directionForward = directionRight;
} else {
assert(isLeftTurn && isRightTurn);
// it's probably an input/output, in that case, magnets are all around, just pick one side to detect the direction
Block blockUpRight = world.getBlock(x + directionRight.offsetX, y + 1, z + directionRight.offsetZ);
if (blockUpRight instanceof BlockElectromagnetPlain && tier != ((BlockElectromagnetPlain) blockUpRight).tier) {
directionForward = directionLeft;
} else {
directionForward = directionRight;
}
}
// backward vector is where we came from
directionBackward = directionMain.getOpposite();
}
}
}