package micdoodle8.mods.galacticraft.api.vector;
import micdoodle8.mods.galacticraft.core.GalacticraftCore;
import micdoodle8.mods.galacticraft.core.util.GCCoreUtil;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.entity.Entity;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ReportedException;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/* BlockVec3 is similar to galacticraft.api.vector.Vector3?
*
* But for speed it uses integer arithmetic not doubles, for block coordinates
* This reduces unnecessary type conversion between integers and doubles and back again.
* (Minecraft block coordinates are always integers, only entity coordinates are doubles.)
*
*/
public class BlockVec3Dim implements Cloneable
{
public int x;
public int y;
public int z;
public int dim;
private static Chunk chunkCached;
public static int chunkCacheDim = Integer.MAX_VALUE;
private static int chunkCacheX = 1876000; // outside the world edge
private static int chunkCacheZ = 1876000; // outside the world edge
// INVALID_VECTOR is used in cases where a null vector cannot be used
public static final BlockVec3Dim INVALID_VECTOR = new BlockVec3Dim(-1, -1, -1, -2);
public BlockVec3Dim()
{
this(0, 0, 0, 0);
}
public BlockVec3Dim(int x, int y, int z, int d)
{
this.x = x;
this.y = y;
this.z = z;
this.dim = d;
}
public BlockVec3Dim(Entity par1)
{
this.x = (int) Math.floor(par1.posX);
this.y = (int) Math.floor(par1.posY);
this.z = (int) Math.floor(par1.posZ);
this.dim = par1.dimension;
}
public BlockVec3Dim(TileEntity par1)
{
this.x = par1.getPos().getX();
this.y = par1.getPos().getY();
this.z = par1.getPos().getZ();
this.dim = par1.getWorld().provider.getDimensionId();
}
public BlockVec3Dim(BlockPos pos, int dimensionId)
{
this(pos.getX(), pos.getY(), pos.getZ(), dimensionId);
}
/**
* Makes a new copy of this Vector. Prevents variable referencing problems.
*/
@Override
public final BlockVec3Dim clone()
{
return new BlockVec3Dim(this.x, this.y, this.z, this.dim);
}
/**
* Get block ID at the BlockVec3Dim coordinates, with a forced chunk load if
* the coordinates are unloaded. Only works server-side.
*
* @return the block ID, or null if the y-coordinate is less than 0 or
* greater than 256 or the x or z is outside the Minecraft worldmap.
*/
public Block getBlockID()
{
if (this.y < 0 || this.y >= 256 || this.x < -30000000 || this.z < -30000000 || this.x >= 30000000 || this.z >= 30000000)
{
return null;
}
World world = getWorldForId(this.dim);
if (world == null) return null;
int chunkx = this.x >> 4;
int chunkz = this.z >> 4;
try
{
// In a typical inner loop, 80% of the time consecutive calls to
// this will be within the same chunk
if (BlockVec3Dim.chunkCacheX == chunkx && BlockVec3Dim.chunkCacheZ == chunkz && BlockVec3Dim.chunkCacheDim == world.provider.getDimensionId() && BlockVec3Dim.chunkCached.isLoaded())
{
return BlockVec3Dim.chunkCached.getBlock(this.x & 15, this.y, this.z & 15);
}
else
{
Chunk chunk = null;
chunk = world.getChunkFromChunkCoords(chunkx, chunkz);
BlockVec3Dim.chunkCached = chunk;
BlockVec3Dim.chunkCacheDim = world.provider.getDimensionId();
BlockVec3Dim.chunkCacheX = chunkx;
BlockVec3Dim.chunkCacheZ = chunkz;
return chunk.getBlock(this.x & 15, this.y, this.z & 15);
}
}
catch (Throwable throwable)
{
CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Oxygen Sealer thread: Exception getting block type in world");
CrashReportCategory crashreportcategory = crashreport.makeCategory("Requested block coordinates");
crashreportcategory.addCrashSection("Location", CrashReportCategory.getCoordinateInfo(new BlockPos(this.x, this.y, this.z)));
throw new ReportedException(crashreport);
}
}
/**
* Get block ID at the BlockVec3 coordinates without forcing a chunk load.
*
* @return the block ID, or null if the y-coordinate is less than 0 or
* greater than 256 or the x or z is outside the Minecraft worldmap.
* Returns Blocks.bedrock if the coordinates being checked are in an
* unloaded chunk
*/
public Block getBlockID_noChunkLoad()
{
if (this.y < 0 || this.y >= 256 || this.x < -30000000 || this.z < -30000000 || this.x >= 30000000 || this.z >= 30000000)
{
return null;
}
World world = getWorldForId(this.dim);
if (world == null) return null;
int chunkx = this.x >> 4;
int chunkz = this.z >> 4;
try
{
if (world.getChunkProvider().chunkExists(chunkx, chunkz))
{
// In a typical inner loop, 80% of the time consecutive calls to
// this will be within the same chunk
if (BlockVec3Dim.chunkCacheX == chunkx && BlockVec3Dim.chunkCacheZ == chunkz && BlockVec3Dim.chunkCacheDim == world.provider.getDimensionId() && BlockVec3Dim.chunkCached.isLoaded())
{
return BlockVec3Dim.chunkCached.getBlock(this.x & 15, this.y, this.z & 15);
}
else
{
Chunk chunk = null;
chunk = world.getChunkFromChunkCoords(chunkx, chunkz);
BlockVec3Dim.chunkCached = chunk;
BlockVec3Dim.chunkCacheDim = world.provider.getDimensionId();
BlockVec3Dim.chunkCacheX = chunkx;
BlockVec3Dim.chunkCacheZ = chunkz;
return chunk.getBlock(this.x & 15, this.y, this.z & 15);
}
}
//Chunk doesn't exist - meaning, it is not loaded
return Blocks.bedrock;
}
catch (Throwable throwable)
{
CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Oxygen Sealer thread: Exception getting block type in world");
CrashReportCategory crashreportcategory = crashreport.makeCategory("Requested block coordinates");
crashreportcategory.addCrashSection("Location", CrashReportCategory.getCoordinateInfo(new BlockPos(this.x, this.y, this.z)));
throw new ReportedException(crashreport);
}
}
public Block getBlock()
{
World world = GalacticraftCore.proxy.getWorldForID(this.dim);
if (world == null) return null;
return world.getBlockState(new BlockPos(this.x, this.y, this.z)).getBlock();
}
public BlockVec3Dim modifyPositionFromSide(EnumFacing side, int amount)
{
switch (side.ordinal())
{
case 0:
this.y -= amount;
break;
case 1:
this.y += amount;
break;
case 2:
this.z -= amount;
break;
case 3:
this.z += amount;
break;
case 4:
this.x -= amount;
break;
case 5:
this.x += amount;
break;
}
return this;
}
public BlockVec3Dim newVecSide(int side)
{
BlockVec3Dim vec = new BlockVec3Dim(this.x, this.y, this.z, this.dim);
switch (side)
{
case 0:
vec.y--;
return vec;
case 1:
vec.y++;
return vec;
case 2:
vec.z--;
return vec;
case 3:
vec.z++;
return vec;
case 4:
vec.x--;
return vec;
case 5:
vec.x++;
return vec;
}
return vec;
}
public BlockVec3Dim modifyPositionFromSide(EnumFacing side)
{
return this.modifyPositionFromSide(side, 1);
}
@Override
public int hashCode()
{
return (((this.z * 431 + this.x) * 379 + this.y) * 373 + this.dim) * 7;
}
@Override
public boolean equals(Object o)
{
if (o instanceof BlockVec3Dim)
{
BlockVec3Dim vector = (BlockVec3Dim) o;
return this.x == vector.x && this.y == vector.y && this.z == vector.z && this.dim == vector.dim;
}
return false;
}
@Override
public String toString()
{
return "BlockVec3 "+this.dim+":[" + this.x + "," + this.y + "," + this.z + "]";
}
/**
* This will load the chunk - use getTileEntityNoLoad() if just updating things if present.
*/
public TileEntity getTileEntity()
{
World world = GalacticraftCore.proxy.getWorldForID(this.dim);
if (world == null) return null;
return world.getTileEntity(new BlockPos(this.x, this.y, this.z));
}
public TileEntity getTileEntityNoLoad()
{
World world = getWorldForId(this.dim);
if (world == null) return null;
BlockPos pos = new BlockPos(this.x, this.y, this.z);
if (world.isBlockLoaded(pos, false))
return world.getTileEntity(pos);
return null;
}
public IBlockState getBlockMetadata()
{
World world = GalacticraftCore.proxy.getWorldForID(this.dim);
if (world == null) return null;
return world.getBlockState(new BlockPos(this.x, this.y, this.z));
}
public static BlockVec3Dim readFromNBT(NBTTagCompound nbtCompound)
{
BlockVec3Dim tempVector = new BlockVec3Dim();
tempVector.x = nbtCompound.getInteger("x");
tempVector.y = nbtCompound.getInteger("y");
tempVector.z = nbtCompound.getInteger("z");
tempVector.dim = nbtCompound.getInteger("dim");
return tempVector;
}
public NBTTagCompound writeToNBT(NBTTagCompound par1NBTTagCompound)
{
par1NBTTagCompound.setInteger("x", this.x);
par1NBTTagCompound.setInteger("y", this.y);
par1NBTTagCompound.setInteger("z", this.z);
par1NBTTagCompound.setInteger("dim", this.dim);
return par1NBTTagCompound;
}
public BlockVec3Dim(NBTTagCompound par1NBTTagCompound)
{
this.x = par1NBTTagCompound.getInteger("x");
this.y = par1NBTTagCompound.getInteger("y");
this.z = par1NBTTagCompound.getInteger("z");
this.dim = par1NBTTagCompound.getInteger("dim");
}
public double getMagnitude()
{
return Math.sqrt(this.getMagnitudeSquared());
}
public int getMagnitudeSquared()
{
return this.x * this.x + this.y * this.y + this.z * this.z;
}
public void setBlock(IBlockState block)
{
World world = GalacticraftCore.proxy.getWorldForID(this.dim);
if (world == null) return;
world.setBlockState(new BlockPos(this.x, this.y, this.z), block, 3);
}
public boolean blockExists()
{
World world = GalacticraftCore.proxy.getWorldForID(this.dim);
if (world == null) return false;
return world.isBlockLoaded(new BlockPos(this.x, this.y, this.z));
}
/**
* It is up to the calling method to check that the dimension matches
*
* @param vector
* @return
*/
public int distanceSquared(BlockVec3 vector)
{
int var2 = vector.x - this.x;
int var4 = vector.y - this.y;
int var6 = vector.z - this.z;
return var2 * var2 + var4 * var4 + var6 * var6;
}
public BlockPos toBlockPos()
{
return new BlockPos(x, y, z);
}
private World getWorldForId(int dimensionID)
{
if (GCCoreUtil.getEffectiveSide() == Side.SERVER)
{
MinecraftServer theServer = FMLCommonHandler.instance().getMinecraftServerInstance();
if (theServer == null) return null;
return theServer.worldServerForDimension(dimensionID);
}
else
{
return getWorldForIdClient(dimensionID);
}
}
@SideOnly(Side.CLIENT)
private World getWorldForIdClient(int dimensionID)
{
World world = FMLClientHandler.instance().getClient().theWorld;
if (world != null && world.provider.getDimensionId() == dimensionID)
{
return world;
}
return null;
}
}