package blusunrize.immersiveengineering.common.blocks.metal;
import blusunrize.immersiveengineering.api.IEProperties;
import blusunrize.immersiveengineering.api.IEProperties.PropertyBoolInverted;
import blusunrize.immersiveengineering.api.Lib;
import blusunrize.immersiveengineering.api.energy.wires.IImmersiveConnectable;
import blusunrize.immersiveengineering.api.energy.wires.ImmersiveNetHandler.Connection;
import blusunrize.immersiveengineering.api.energy.wires.TileEntityImmersiveConnectable;
import blusunrize.immersiveengineering.client.ClientUtils;
import blusunrize.immersiveengineering.client.models.IOBJModelCallback;
import blusunrize.immersiveengineering.common.Config.IEConfig;
import blusunrize.immersiveengineering.common.EventHandler;
import blusunrize.immersiveengineering.common.IEContent;
import blusunrize.immersiveengineering.common.blocks.BlockFakeLight.TileEntityFakeLight;
import blusunrize.immersiveengineering.common.blocks.IEBlockInterfaces.*;
import blusunrize.immersiveengineering.common.util.ChatUtils;
import blusunrize.immersiveengineering.common.util.Utils;
import blusunrize.immersiveengineering.common.util.chickenbones.Matrix4;
import com.google.common.base.Optional;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumFacing.Axis;
import net.minecraft.util.ITickable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.EnumSkyBlock;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.common.model.TRSRTransformation;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
public class TileEntityFloodlight extends TileEntityImmersiveConnectable implements ITickable, IAdvancedDirectionalTile, IHammerInteraction, ISpawnInterdiction, IBlockBounds, IActiveState, ILightValue, IOBJModelCallback<IBlockState>
{
public int energyStorage = 0;
private int energyDraw = IEConfig.Machines.floodlight_energyDraw;
private int maximumStorage = IEConfig.Machines.floodlight_maximumStorage;
public boolean active = false;
public boolean redstoneControlInverted = false;
public EnumFacing facing = EnumFacing.NORTH;
public EnumFacing side = EnumFacing.UP;
public float rotY=0;
public float rotX=0;
public List<BlockPos> fakeLights = new ArrayList<>();
public List<BlockPos> lightsToBePlaced = new ArrayList<>();
public List<BlockPos> lightsToBeRemoved = new ArrayList<>();
final int timeBetweenSwitches = 20;
int switchCooldown = 0;
private boolean shouldUpdate = true;
public boolean computerOn = true;
public int controllingComputers = 0;
public int turnCooldown = 0;
@Override
public void update()
{
if(worldObj.isRemote)
return;
if(turnCooldown > 0)
turnCooldown--;
//ToDo: I don't even know what this is for. I need Malte to explain that .-.
// if(turnCooldown == 0)
// notifyAll();
boolean b = active;
boolean enabled;
if(shouldUpdate)
{
updateFakeLights(true, active);
markDirty();
this.markContainingBlockForUpdate(null);
shouldUpdate = false;
}
enabled = (controllingComputers > 0 && computerOn) || (worldObj.isBlockIndirectlyGettingPowered(getPos())>0^redstoneControlInverted);
if(energyStorage >= (!active ? energyDraw*10 : energyDraw) && enabled && switchCooldown <= 0)
{
energyStorage -= energyDraw;
if(!active)
active=true;
}
else if(active)
{
active=false;
switchCooldown = timeBetweenSwitches;
}
switchCooldown--;
if(active != b || worldObj.getTotalWorldTime() % 512 == ((getPos().getX() ^ getPos().getZ()) & 511))
{
this.markContainingBlockForUpdate(null);
updateFakeLights(true,active);
}
if(!active)
{
if(!lightsToBePlaced.isEmpty())
lightsToBePlaced.clear();
}
else if(!lightsToBePlaced.isEmpty()||!lightsToBeRemoved.isEmpty() && worldObj.getTotalWorldTime()%8==((getPos().getX()^getPos().getZ())&7))
{
Iterator<BlockPos> it = lightsToBePlaced.iterator();
int timeout = 0;
while(it.hasNext() && timeout++<16)
{
BlockPos cc = it.next();
// worldObj.setBlockState(cc, Blocks.glass.getDefaultState(), 2);
worldObj.setBlockState(cc, IEContent.blockFakeLight.getStateFromMeta(0), 2);
TileEntity te = worldObj.getTileEntity(cc);
if (te instanceof TileEntityFakeLight)
((TileEntityFakeLight)te).floodlightCoords = new int[]{getPos().getX(),getPos().getY(),getPos().getZ()};
fakeLights.add(cc);
it.remove();
}
it = lightsToBeRemoved.iterator();
timeout = 0;
while(it.hasNext() && timeout++<16)
{
BlockPos cc = it.next();
if(worldObj.getTileEntity(cc) instanceof TileEntityFakeLight)
worldObj.setBlockToAir(cc);
it.remove();
}
}
}
public void updateFakeLights(boolean deleteOld, boolean genNew)
{
Iterator<BlockPos> it = this.fakeLights.iterator();
ArrayList<BlockPos> tempRemove = new ArrayList<BlockPos>();
while(it.hasNext())
{
BlockPos cc = it.next();
TileEntity te = worldObj.getTileEntity(cc);
if(te instanceof TileEntityFakeLight)
{
if(deleteOld)
tempRemove.add(cc);
}
else
it.remove();
}
if(genNew)
{
float angle =(float)( facing==EnumFacing.NORTH?180: facing==EnumFacing.EAST?90: facing==EnumFacing.WEST?-90: 0);
float yRotation = rotY;
double angleX = Math.toRadians(rotX);
Vec3d[] rays = {
/*Straight*/new Vec3d(0,0,1),
/*U,D,L,R*/new Vec3d(0,0,1),new Vec3d(0,0,1),new Vec3d(0,0,1),new Vec3d(0,0,1),
/*Intermediate*/new Vec3d(0,0,1),new Vec3d(0,0,1),new Vec3d(0,0,1),new Vec3d(0,0,1),
/*Diagonal*/new Vec3d(0,0,1),new Vec3d(0,0,1),new Vec3d(0,0,1),new Vec3d(0,0,1)};
Matrix4 mat = new Matrix4();
if(side==EnumFacing.DOWN)
mat.scale(1, -1, 1);
else if(side!=EnumFacing.UP)
{
angle = facing==EnumFacing.DOWN?180: facing==EnumFacing.NORTH?-90: facing==EnumFacing.SOUTH?90: angle;
if(side.getAxis()==Axis.X)
{
mat.rotate(Math.PI/2,-1,0,0);
mat.rotate(Math.PI/2, 0,0,-side.getAxisDirection().getOffset());
}
else
{
mat.rotate(Math.PI/2,-1,0,0);
if(side==EnumFacing.SOUTH)//I dunno why south is giving me so much trouble, but this works, so who cares
{
mat.rotate(Math.PI, 0,0,1);
if(facing.getAxis()==Axis.X)
angle = -angle;
}
}
}
double angleY = Math.toRadians(angle+yRotation);
mat.rotate(angleY, 0,1,0);
mat.rotate(-angleX, 1,0,0);
rays[0] = mat.apply(rays[0]);
mat.rotate(Math.PI/8, 0,1,0);
rays[1] = mat.apply(rays[1]);
mat.rotate(-Math.PI/16, 0,1,0);
rays[5] = mat.apply(rays[5]);
mat.rotate(-Math.PI/8, 0,1,0);
rays[6] = mat.apply(rays[6]);
mat.rotate(-Math.PI/16, 0,1,0);
rays[2] = mat.apply(rays[2]);
mat.rotate(Math.PI/8, 0,1,0);
mat.rotate(Math.PI/8, 1,0,0);
rays[3] = mat.apply(rays[3]);
mat.rotate(-Math.PI/16, 1,0,0);
rays[7] = mat.apply(rays[7]);
mat.rotate(-Math.PI/8, 1,0,0);
rays[8] = mat.apply(rays[8]);
mat.rotate(-Math.PI/16, 1,0,0);
rays[4] = mat.apply(rays[4]);
mat.rotate(Math.PI/8, 1,0,0);
mat.rotate(Math.PI/16, 1,0,0);
mat.rotate(Math.PI/16, 0,1,0);
rays[9] = mat.apply(rays[9]);
mat.rotate(-Math.PI/8, 0,1,0);
rays[10] = mat.apply(rays[10]);
mat.rotate(-Math.PI/8, 1,0,0);
rays[11] = mat.apply(rays[11]);
mat.rotate(Math.PI/8, 0,1,0);
rays[12] = mat.apply(rays[12]);
for(int ray=0; ray<rays.length; ray++)
{
int offset = ray==0?0: ray<4?3: 1;
placeLightAlongVector(rays[ray], offset, tempRemove);
}
}
this.lightsToBeRemoved.addAll(tempRemove);
}
public void placeLightAlongVector(Vec3d vec, int offset, ArrayList<BlockPos> checklist)
{
Vec3d light = new Vec3d(getPos()).addVector(.5,.75,.5);
int range = 32;
HashSet<BlockPos> ignore = new HashSet<BlockPos>();
ignore.add(getPos());
BlockPos hit = Utils.rayTraceForFirst(Utils.addVectors(vec,light), light.addVector(vec.xCoord*range,vec.yCoord*range,vec.zCoord*range), worldObj, ignore);
double maxDistance = hit!=null?new Vec3d(hit).addVector(.5,.75,.5).squareDistanceTo(light):range*range;
for(int i=1+offset; i<=range; i++)
{
BlockPos target = getPos().add(Math.round(vec.xCoord*i), Math.round(vec.yCoord*i), Math.round(vec.zCoord*i));
double dist = (vec.xCoord*i*vec.xCoord*i)+(vec.yCoord*i*vec.yCoord*i)+(vec.zCoord*i*vec.zCoord*i);
if(dist>maxDistance)
break;
if(target.getY()>255||target.getY()<0)
continue;
//&&worldObj.getBlockLightValue(xx,yy,zz)<12 using this makes it not work in daylight .-.
if(!target.equals(getPos())&&worldObj.isAirBlock(target))
{
if(!checklist.remove(target))
lightsToBePlaced.add(target);
i+=2;
}
}
}
@Override
public double getInterdictionRangeSquared()
{
return active?1024:0;
}
@Override
public void invalidate()
{
synchronized (EventHandler.interdictionTiles) {
if (EventHandler.interdictionTiles.contains(this))
EventHandler.interdictionTiles.remove(this);
}
super.invalidate();
}
@Override
public void readCustomNBT(NBTTagCompound nbt, boolean descPacket)
{
super.readCustomNBT(nbt, descPacket);
active = nbt.getBoolean("active");
energyStorage = nbt.getInteger("energy");
redstoneControlInverted = nbt.getBoolean("redstoneControlInverted");
facing = EnumFacing.getFront(nbt.getInteger("facing"));
side = EnumFacing.getFront(nbt.getInteger("side"));
rotY = nbt.getFloat("rotY");
rotX = nbt.getFloat("rotX");
int lightAmount = nbt.getInteger("lightAmount");
fakeLights.clear();
for(int i=0; i<lightAmount; i++)
{
int[] icc = nbt.getIntArray("fakeLight_"+i);
fakeLights.add(new BlockPos(icc[0],icc[1],icc[2]));
}
if(FMLCommonHandler.instance().getEffectiveSide()==Side.CLIENT && worldObj!=null)
this.markContainingBlockForUpdate(null);
if(descPacket)
{
controllingComputers = nbt.getBoolean("computerControlled") ? 1 : 0;
computerOn = nbt.getBoolean("computerOn");
}
}
@Override
public void writeCustomNBT(NBTTagCompound nbt, boolean descPacket)
{
super.writeCustomNBT(nbt, descPacket);
nbt.setBoolean("active",active);
nbt.setInteger("energyStorage",energyStorage);
nbt.setBoolean("redstoneControlInverted",redstoneControlInverted);
nbt.setInteger("facing",facing.ordinal());
nbt.setInteger("side",side.ordinal());
nbt.setFloat("rotY",rotY);
nbt.setFloat("rotX",rotX);
nbt.setInteger("lightAmount",fakeLights.size());
for(int i=0; i<fakeLights.size(); i++)
{
BlockPos cc = fakeLights.get(i);
nbt.setIntArray("fakeLight_"+i, new int[]{cc.getX(),cc.getY(),cc.getZ()});
}
if(descPacket)
{
nbt.setBoolean("computerControlled", controllingComputers > 0);
nbt.setBoolean("computerOn", computerOn);
}
}
@Override
protected boolean canTakeLV()
{
return true;
}
@Override
public boolean isEnergyOutput()
{
return true;
}
@Override
protected boolean isRelay()
{
return true;
}
@Override
public int outputEnergy(int amount, boolean simulate, int energyType)
{
if(amount > 0 && energyStorage < maximumStorage)
{
if(!simulate)
{
int rec = Math.min(maximumStorage - energyStorage, amount);
energyStorage+=rec;
return rec;
}
return Math.min(maximumStorage - energyStorage, amount);
}
return 0;
}
@Override
public boolean receiveClientEvent(int id, int arg)
{
if(id==1)
{
this.markContainingBlockForUpdate(null);
worldObj.checkLightFor(EnumSkyBlock.BLOCK, getPos());
return true;
}
return super.receiveClientEvent(id, arg);
}
@Override
public Vec3d getRaytraceOffset(IImmersiveConnectable link)
{
int xDif = ((TileEntity)link).getPos().getX()-getPos().getX();
int yDif = ((TileEntity)link).getPos().getY()-getPos().getY();
int zDif = ((TileEntity)link).getPos().getZ()-getPos().getZ();
double x, y, z;
switch(side)
{
case DOWN:
case UP:
x = (Math.abs(xDif) >= Math.abs(zDif)) ? (xDif >= 0) ? .9375 : .0625 : .5;
y = (side == EnumFacing.DOWN) ? .9375 : .0625;
z = (Math.abs(zDif) > Math.abs(xDif)) ? (zDif >= 0) ? .9375 : .0625 : .5;
break;
case NORTH:
case SOUTH:
x = (Math.abs(xDif) >= Math.abs(yDif)) ? (xDif >= 0) ? .9375 : .0625 : .5;
y = (Math.abs(yDif) > Math.abs(xDif)) ? (yDif >= 0) ? .9375 : .0625 : .5;
z = (side == EnumFacing.NORTH) ? .9375 : .0625;
break;
case WEST:
case EAST:
default:
x = (side == EnumFacing.WEST) ? .9375 : .0625;
y = (Math.abs(yDif) >= Math.abs(zDif)) ? (yDif >= 0) ? .9375 : .0625 : .5;
z = (Math.abs(zDif) > Math.abs(yDif)) ? (zDif >= 0) ? .9375 : .0625 : .5;
break;
}
return new Vec3d(x,y,z);
}
@Override
public Vec3d getConnectionOffset(Connection con)
{
int xDif = (con==null||con.start==null||con.end==null)?0: (con.start.equals(Utils.toCC(this))&&con.end!=null)? con.end.getX()-getPos().getX(): (con.end.equals(Utils.toCC(this))&& con.start!=null)?con.start.getX()-getPos().getX(): 0;
int yDif = (con==null||con.start==null||con.end==null)?0: (con.start.equals(Utils.toCC(this))&&con.end!=null)? con.end.getY()-getPos().getY(): (con.end.equals(Utils.toCC(this))&& con.start!=null)?con.start.getY()-getPos().getY(): 0;
int zDif = (con==null||con.start==null||con.end==null)?0: (con.start.equals(Utils.toCC(this))&&con.end!=null)? con.end.getZ()-getPos().getZ(): (con.end.equals(Utils.toCC(this))&& con.start!=null)?con.start.getZ()-getPos().getZ(): 0;
double x, y, z;
switch(side)
{
case DOWN:
case UP:
x = (Math.abs(xDif) >= Math.abs(zDif)) ? (xDif >= 0) ? .9375 : .0625 : .5;
y = (side == EnumFacing.DOWN) ? .9375 : .0625;
z = (Math.abs(zDif) > Math.abs(xDif)) ? (zDif >= 0) ? .9375 : .0625 : .5;
break;
case NORTH:
case SOUTH:
x = (Math.abs(xDif) >= Math.abs(yDif)) ? (xDif >= 0) ? .9375 : .0625 : .5;
y = (Math.abs(yDif) > Math.abs(xDif)) ? (yDif >= 0) ? .9375 : .0625 : .5;
z = (side == EnumFacing.NORTH) ? .9375 : .0625;
break;
case WEST:
case EAST:
default:
x = (side == EnumFacing.WEST) ? .9375 : .0625;
y = (Math.abs(yDif) >= Math.abs(zDif)) ? (yDif >= 0) ? .9375 : .0625 : .5;
z = (Math.abs(zDif) > Math.abs(yDif)) ? (zDif >= 0) ? .9375 : .0625 : .5;
break;
}
return new Vec3d(x,y,z);
}
@Override
public float[] getBlockBounds()
{
return new float[] {
side.getAxis()==Axis.X?0:.0625f,
side.getAxis() == Axis.Y ? 0 : .0625f,
side.getAxis() == Axis.Z ? 0 : .0625f,
side.getAxis() == Axis.X ? 1 : .9375f,
side.getAxis() == Axis.Y ? 1 : .9375f,
side.getAxis() == Axis.Z ? 1 : .9375f
};
}
@Override
public PropertyBoolInverted getBoolProperty(Class<? extends IUsesBooleanProperty> inf)
{
return IEProperties.BOOLEANS[0];
}
@Override
public boolean getIsActive()
{
return active;
}
@Override
public int getLightValue()
{
return active?15:0;
}
@Override
public boolean hammerUseSide(EnumFacing side, EntityPlayer player, float hitX, float hitY, float hitZ)
{
if(player.isSneaking() && side!=this.side)
{
boolean base = this.side==EnumFacing.DOWN?hitY>=.8125: this.side==EnumFacing.UP?hitY<=.1875: this.side==EnumFacing.NORTH?hitZ>=.8125: this.side==EnumFacing.UP?hitZ<=.1875: this.side==EnumFacing.WEST?hitX>=.8125: hitX<=.1875;
if(base)
{
redstoneControlInverted = !redstoneControlInverted;
ChatUtils.sendServerNoSpamMessages(player, new TextComponentTranslation(Lib.CHAT_INFO+"rsControl."+(redstoneControlInverted?"invertedOn":"invertedOff")));
markDirty();
this.markContainingBlockForUpdate(null);
return true;
}
}
if(side.getAxis()==this.side.getAxis())
turnY(player.isSneaking(), false);
else
turnX(player.isSneaking(), false);
return true;
}
@Override
public EnumFacing getFacing()
{
return side;
}
@Override
public void setFacing(EnumFacing facing)
{
this.side=facing;
}
@Override
public int getFacingLimitation()
{
return 0;
}
@Override
public boolean mirrorFacingOnPlacement(EntityLivingBase placer)
{
return false;
}
@Override
public boolean canHammerRotate(EnumFacing side, float hitX, float hitY, float hitZ, EntityLivingBase entity)
{
return false;
}
@Override
public boolean canRotate(EnumFacing axis)
{
return false;
}
@Override
public void onDirectionalPlacement(EnumFacing side, float hitX, float hitY, float hitZ, EntityLivingBase placer)
{
EnumFacing f = EnumFacing.fromAngle(placer.rotationYaw);
if(f==side.getOpposite())
f = placer.rotationPitch>0?EnumFacing.DOWN:EnumFacing.UP;
facing = f;
}
@SideOnly(Side.CLIENT)
@Override
public TextureAtlasSprite getTextureReplacement(IBlockState object, String material)
{
return null;
}
@SideOnly(Side.CLIENT)
@Override
public boolean shouldRenderGroup(IBlockState object, String group)
{
if("glass".equals(group))
return MinecraftForgeClient.getRenderLayer()== BlockRenderLayer.TRANSLUCENT;
else
return MinecraftForgeClient.getRenderLayer()== BlockRenderLayer.SOLID;
}
@SideOnly(Side.CLIENT)
@Override
public Optional<TRSRTransformation> applyTransformations(IBlockState object, String group, Optional<TRSRTransformation> transform)
{
if(!transform.isPresent())
transform = Optional.of(new TRSRTransformation((Matrix4f)null));
Matrix4f mat = transform.get().getMatrix();
Vector3f transl = new Vector3f(.5f,.5f,.5f);
double yaw = 0;
double pitch = 0;
double roll = 0;
// pitch, yaw, roll
if(side.getAxis()==Axis.Y)
{
yaw = facing==EnumFacing.SOUTH?180:facing==EnumFacing.WEST?90:facing==EnumFacing.EAST?-90:0;
if(side==EnumFacing.DOWN)
roll = 180;
}
else //It's a mess, but it works!
{
if(side==EnumFacing.NORTH)
{
pitch = 90;
yaw = 180;
}
if(side==EnumFacing.SOUTH)
pitch = 90;
if(side==EnumFacing.WEST)
{
pitch = 90;
yaw = -90;
}
if(side==EnumFacing.EAST)
{
pitch = 90;
yaw = 90;
}
if(facing==EnumFacing.DOWN)
roll += 180;
else if(side.getAxis()==Axis.X && facing.getAxis()==Axis.Z)
roll += 90*facing.getAxisDirection().getOffset()*side.getAxisDirection().getOffset();
else if(side.getAxis()==Axis.Z && facing.getAxis()==Axis.X)
roll += -90*facing.getAxisDirection().getOffset()*side.getAxisDirection().getOffset();
}
transl.add(new Vector3f(side.getFrontOffsetX()*.125f,side.getFrontOffsetY()*.125f,side.getFrontOffsetZ()*.125f));
if("axis".equals(group)||"light".equals(group)||"off".equals(group)||"glass".equals(group))
{
if(side.getAxis()==Axis.Y)
yaw += rotY;
else
roll += rotY;
if("light".equals(group)||"off".equals(group)||"glass".equals(group))
pitch += rotX;
}
mat.setRotation(ClientUtils.degreeToQuaterion(pitch, yaw, roll));
mat.setTranslation(transl);
return Optional.of(new TRSRTransformation(mat));
}
@SideOnly(Side.CLIENT)
@Override
public Matrix4 handlePerspective(IBlockState Object, TransformType cameraTransformType, Matrix4 perspective)
{
return perspective;
}
@SideOnly(Side.CLIENT)
@Override
public String getCacheKey(IBlockState object) {
return rotX+":"+rotY+":"+active;
}
//computer stuff
public boolean canComputerTurn()
{
return turnCooldown <= 0 || !active;
}
public void turnX(boolean dir, boolean throwException)
{
if(!canComputerTurn())
{
if(throwException)
throw new IllegalArgumentException("The floodlight can't turn again yet.");
else
return;
}
this.rotX = Math.min(191.25f, Math.max(-11.25f, rotX + (dir ? -11.25f : 11.25f)));
markDirty();
this.markContainingBlockForUpdate(null);
worldObj.addBlockEvent(getPos(), getBlockType(), 255, 0);
turnCooldown = 20;
}
public void turnY(boolean dir, boolean throwException)
{
if(!canComputerTurn())
{
if(throwException)
throw new IllegalArgumentException("The floodlight can't turn again yet.");
else
return;
}
this.rotY += dir ? -11.25 : 11.25;
this.rotY %= 360;
markDirty();
this.markContainingBlockForUpdate(null);
worldObj.addBlockEvent(getPos(), getBlockType(), 255, 0);
turnCooldown = 20;
}
}