package mods.eln.gridnode;
import mods.eln.generic.GenericItemBlockUsingDamageDescriptor;
import mods.eln.misc.*;
import mods.eln.node.transparent.TransparentNode;
import mods.eln.node.transparent.TransparentNodeDescriptor;
import mods.eln.node.transparent.TransparentNodeElement;
import mods.eln.sim.ElectricalLoad;
import mods.eln.sim.ThermalLoad;
import mods.eln.sixnode.electricalcable.ElectricalCableDescriptor;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import org.apache.commons.lang3.tuple.Pair;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.UUID;
/**
* Created by svein on 22/08/15.
*/
abstract public class GridElement extends TransparentNodeElement {
/**
* The last place any given player tried to link two instance nodes.
*/
private static HashMap<UUID, Pair<Coordonate, Direction>> pending = new HashMap<UUID, Pair<Coordonate, Direction>>();
public HashSet<GridLink> gridLinkList = new HashSet<GridLink>();
public HashSet<GridLink> gridLinksBooting = new HashSet<GridLink>();
GridDescriptor desc;
int connectRange;
private float idealRenderingAngle;
public GridElement(TransparentNode transparentNode, TransparentNodeDescriptor descriptor, int connectRange) {
super(transparentNode, descriptor);
this.desc = (GridDescriptor) descriptor;
this.connectRange = connectRange;
}
/* Connect one GridNode to another. */
@Override
public boolean onBlockActivated(EntityPlayer entityPlayer, Direction side, float vx, float vy, float vz) {
// Check if user is holding an appropriate tool.
final ItemStack stack = entityPlayer.getCurrentEquippedItem();
final GenericItemBlockUsingDamageDescriptor itemDesc = GenericItemBlockUsingDamageDescriptor.getDescriptor(stack);
if (itemDesc instanceof ElectricalCableDescriptor) {
return onTryGridConnect(entityPlayer, stack, (ElectricalCableDescriptor) itemDesc, side);
}
// TODO: Scissors. Break the connection without breaking the pole.
return false;
}
private boolean onTryGridConnect(EntityPlayer entityPlayer, ItemStack stack, ElectricalCableDescriptor cable, Direction side) {
// First node, or second node?
UUID uuid = entityPlayer.getPersistentID();
Pair<Coordonate, Direction> p = pending.get(uuid);
GridElement other = null;
if (p != null) {
other = GridLink.getElementFromCoordinate(p.getLeft());
}
// Check if it's the *correct* cable descriptor.
if (!cable.equals(desc.cableDescriptor)) {
Utils.addChatMessage(entityPlayer, "Wrong cable, you need " + desc.cableDescriptor.name);
return true;
}
if (other == null || other == this) {
Utils.addChatMessage(entityPlayer, "Setting starting point");
pending.put(uuid, Pair.of(this.coordonate(), side));
} else {
final double distance = other.coordonate().trueDistanceTo(this.coordonate());
final int cableLength = (int) Math.ceil(distance);
final int range = Math.min(connectRange, other.connectRange);
if (stack.stackSize < distance) {
Utils.addChatMessage(entityPlayer, "You need " + cableLength + " units of cable");
} else if (distance > range) {
Utils.addChatMessage(entityPlayer, "Cannot connect, range " + Math.ceil(distance) + " and limit " + range + " blocks");
} else if (!this.canConnect(other)) {
Utils.addChatMessage(entityPlayer, "Cannot connect these two objects");
} else if (!this.validLOS(other)) {
Utils.addChatMessage(entityPlayer, "Cannot connect, no line of sight");
} else {
if (GridLink.addLink(this, other, side, p.getRight(), cable, cableLength)) {
Utils.addChatMessage(entityPlayer, "Added connection");
stack.splitStack(cableLength);
} else {
Utils.addChatMessage(entityPlayer, "Already connected");
}
}
pending.remove(uuid);
}
return true;
}
@Override
public void initialize() {
connect();
for (GridLink link : gridLinksBooting) {
link.connect();
}
gridLinksBooting.clear();
updateIdealRenderAngle();
}
@Override
public void connectJob() {
super.connectJob();
for (GridLink link : gridLinkList) {
link.connect();
}
}
@Override
public void disconnectJob() {
super.disconnectJob();
for (GridLink link : gridLinkList) {
link.disconnect();
}
}
@Override
public void onBreakElement() {
super.onBreakElement();
HashSet<GridLink> copy = new HashSet<GridLink>(gridLinkList);
for (GridLink link : copy) {
node.dropItem(link.onBreakElement());
}
}
@Override
public void selfDestroy() {
super.selfDestroy();
HashSet<GridLink> copy = new HashSet<GridLink>(gridLinkList);
for (GridLink link : copy) {
link.selfDestroy();
}
}
@Override
public void writeToNBT(NBTTagCompound nbt) {
super.writeToNBT(nbt);
Integer i = 0;
NBTTagCompound gridLinks = Utils.newNbtTagCompund(nbt, "gridLinks");
for (GridLink link : gridLinkList) {
link.writeToNBT(Utils.newNbtTagCompund(gridLinks, i.toString()), "");
i++;
}
}
@Override
public void readFromNBT(NBTTagCompound nbt) {
super.readFromNBT(nbt);
assert gridLinkList.isEmpty();
final NBTTagCompound gridLinks = nbt.getCompoundTag("gridLinks");
for (Integer i = 0; ; i++) {
final NBTTagCompound linkTag = gridLinks.getCompoundTag(i.toString());
if (linkTag.hasNoTags())
break;
gridLinksBooting.add(new GridLink(linkTag, ""));
}
}
abstract protected ElectricalLoad getGridElectricalLoad(Direction side);
// TODO: This should check if the wire isn't passing through blocks.
private boolean validLOS(GridElement other) {
return true;
}
// Return false if connecting grid elements that can't connect.
protected boolean canConnect(GridElement other) {
return true;
}
// TODO: One pole turns, all connected cables should be recalculated,
// not just the ones being rendered here.
/* Compute a rendering angle that minimizes any straight-on cables. */
public void updateIdealRenderAngle() {
if (desc.rotationIsFixed()) {
switch (front) {
case XN:
idealRenderingAngle = 0;
break;
case XP:
idealRenderingAngle = 180;
break;
case YN:
idealRenderingAngle = 90;
break;
case YP:
idealRenderingAngle = 90;
break;
case ZN:
idealRenderingAngle = 270;
break;
case ZP:
idealRenderingAngle = 90;
break;
}
//System.out.println(idealRenderingAngle);
} else if (gridLinkList.size() == 0) {
idealRenderingAngle = 0;
} else {
// Compute angles.
double angles[] = new double[gridLinkList.size()];
int i = 0;
for (GridLink link : gridLinkList) {
Coordonate vec = link.a.subtract(link.b);
// Angles 180 degrees apart are equivalent.
if (vec.z < 0)
vec = vec.negate();
double h = Math.sqrt(vec.x * vec.x + vec.z * vec.z);
angles[i++] = Math.acos(vec.x / h);
}
// This could probably be optimised with a bit of math, but w.e.
double optAngle = 0;
double optErr = Double.POSITIVE_INFINITY;
for (i = 0; i < 128; i++) {
// Check a half-circle.
double angle = Math.PI * i / 128.0;
double error = 0;
for (double a : angles) {
double err = Math.abs(Math.sin(angle - a));
error += err * err * err;
}
if (error < optErr) {
optAngle = angle;
optErr = error;
}
}
idealRenderingAngle = (float) Math.toDegrees(-optAngle);
}
}
@Override
public void networkSerialize(DataOutputStream stream) {
super.networkSerialize(stream);
try {
stream.writeFloat(idealRenderingAngle);
// Each wire pair should be drawn by exactly one node.
// Check for which ones it's this one.
ArrayList<GridLink> ourLinks = new ArrayList<GridLink>();
for (GridLink link : gridLinkList) {
if (link.a.equals(coordonate())/* && link.connected*/) {
ourLinks.add(link);
}
}
// The renderer needs to know, for each catenary:
// - Vec3 of the starting point.
// - Vec3 of the end point.
// There's a finite number of starting points, and a potentially unlimited number of endpoints...
// But until we get protocol buffers or something, simple remains good.
// So we'll just send pairs, even if there's some duplication.
stream.writeInt(ourLinks.size());
for (GridLink link : ourLinks) {
GridElement target = link.getOtherElement(this);
Direction ourSide = link.getSide(this);
Direction theirSide = link.getSide(target);
// It's always the "a" side doing this.
Coordonate offset = link.b.subtract(link.a);
for (int i = 0; i < 2; i++) {
final Vec3 start = getCablePoint(ourSide, i);
start.rotateAroundY((float) Math.toRadians(idealRenderingAngle));
Vec3 end = target.getCablePoint(theirSide, i);
end.rotateAroundY((float) Math.toRadians(target.idealRenderingAngle));
end = end.addVector(offset.x, offset.y, offset.z);
writeVec(stream, start);
writeVec(stream, end);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
protected Vec3 getCablePoint(Direction side, int i) {
if (i >= 2) throw new AssertionError("Invalid cable point index");
Obj3D.Obj3DPart part = (i == 0 ? desc.plus : desc.gnd).get(0);
BoundingBox bb = part.boundingBox();
return bb.centre();
}
private void writeVec(DataOutputStream stream, Vec3 sp) throws IOException {
stream.writeFloat((float) sp.xCoord);
stream.writeFloat((float) sp.yCoord);
stream.writeFloat((float) sp.zCoord);
}
@Override
public String multiMeterString(Direction side) {
ElectricalLoad electricalLoad = getGridElectricalLoad(side);
return Utils.plotUIP(electricalLoad.getU(), electricalLoad.getI());
}
@Override
public String thermoMeterString(Direction side) {
ThermalLoad thermalLoad = getThermalLoad(side, LRDU.Up);
return Utils.plotCelsius("T", thermalLoad.Tc);
}
}