package eiteam.esteemedinnovation.transport.entity;
import eiteam.esteemedinnovation.api.steamnet.SteamNetwork;
import eiteam.esteemedinnovation.api.tile.SteamTransporterTileEntity;
import eiteam.esteemedinnovation.api.tile.ThumperAdjacentBehaviorModifier;
import eiteam.esteemedinnovation.api.wrench.WrenchDisplay;
import eiteam.esteemedinnovation.api.wrench.Wrenchable;
import eiteam.esteemedinnovation.commons.Config;
import eiteam.esteemedinnovation.commons.util.MathUtility;
import eiteam.esteemedinnovation.transport.TransportationModule;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SPacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityHopper;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderGameOverlayEvent.Post;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class TileEntityVacuum extends SteamTransporterTileEntity implements Wrenchable, WrenchDisplay, ThumperAdjacentBehaviorModifier {
private static final int VACUUM_STEAM_CONSUMPTION = Config.vacuumConsumption;
// half angle of cone
private static final float THETA = (float) Math.PI / 2.0F;
public boolean active;
public boolean powered = false;
public boolean lastSteam = false;
public int rotateTicks = 0;
public int range = 9;
public static boolean isLyingInCone(float[] x, float[] t, float[] b, float aperture) {
// This is for our convenience
float halfAperture = aperture / 2.F;
// Vector pointing to X point from apex
float[] apexToXVect = dif(t, x);
// Vector pointing from apex to circle-center point.
float[] axisVect = dif(t, b);
// X is lying in cone only if it's lying in
// infinite version of its cone -- that is,
// not limited by "round basement".
// We'll use dotProd() to
// determine angle between apexToXVect and axis.
boolean isInInfiniteCone = dotProd(apexToXVect, axisVect)
/ magn(apexToXVect) / magn(axisVect)
>
// We can safely compare cos() of angles
// between vectors instead of bare angles.
Math.cos(halfAperture);
if (!isInInfiniteCone) {
return false;
}
// X is contained in cone only if projection of apexToXVect to axis
// is shorter than axis.
// We'll use dotProd() to figure projection length.
return dotProd(apexToXVect, axisVect) / magn(axisVect) < magn(axisVect);
}
public static float dotProd(float[] a, float[] b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
public static float[] dif(float[] a, float[] b) {
return (new float[]{
a[0] - b[0],
a[1] - b[1],
a[2] - b[2]
});
}
public static float magn(float[] a) {
return (float) (Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]));
}
@Override
public boolean canUpdate(IBlockState target) {
return target.getBlock() == TransportationModule.VACUUM;
}
@Override
public void initialUpdate() {
super.initialUpdate();
powered = worldObj.isBlockPowered(pos);
EnumFacing myDir = worldObj.getBlockState(pos).getValue(BlockVacuum.FACING);
setValidDistributionDirectionsExcluding(myDir, myDir.getOpposite());
}
@Override
public void safeUpdate() {
if (lastSteam != getSteamShare() > VACUUM_STEAM_CONSUMPTION) {
markForResync();
}
lastSteam = getSteamShare() > VACUUM_STEAM_CONSUMPTION;
if (!worldObj.isRemote) {
if ((getSteamShare() < VACUUM_STEAM_CONSUMPTION) || powered) {
active = false;
} else {
active = true;
decrSteam(VACUUM_STEAM_CONSUMPTION);
}
}
if (active) {
if (worldObj.isRemote) {
rotateTicks++;
}
EnumFacing dir = worldObj.getBlockState(pos).getValue(BlockVacuum.FACING);
float[] M = {
pos.getX() + 0.5F,
pos.getY() + 0.5F,
pos.getZ() + 0.5F
};
float[] N = {
pos.getX() + 0.5F + range * dir.getFrontOffsetX(),
pos.getY() + 0.5F + range * dir.getFrontOffsetY(),
pos.getZ() + 0.5F + range * dir.getFrontOffsetZ()
};
//List entities = worldObj.getEntitiesWithinAABB(Entity.class, AxisAlignedBB.getBoundingBox(xCoord+(dir.offsetX < 0 ? dir.offsetX * blocksInFront : 0), yCoord+(dir.offsetY < 0 ? dir.offsetY * blocksInFront : 0), zCoord+(dir.offsetZ < 0 ? dir.offsetZ * blocksInFront : 0), xCoord+1+(dir.offsetX > 0 ? dir.offsetX * blocksInFront : 0), yCoord+1+(dir.offsetY > 0 ? dir.offsetY * blocksInFront : 0), zCoord+1+(dir.offsetZ > 0 ? dir.offsetZ * blocksInFront : 0)));
List<Entity> entities = worldObj.getEntitiesWithinAABB(Entity.class,
new AxisAlignedBB(pos.getX() - 20, pos.getY() - 20, pos.getZ() - 20, pos.getX() + 20, pos.getY() + 20, pos.getZ() + 20));
for (int i = 0; i < 200; i++) {
float[] X = {
(worldObj.rand.nextFloat() * 40.0F) - 20.0F + pos.getX(),
(worldObj.rand.nextFloat() * 40.0F) - 20.0F + pos.getY(),
(worldObj.rand.nextFloat() * 40.0F) - 20.0F + pos.getZ()
};
if (isLyingInCone(X, M, N, THETA) && worldObj.rayTraceBlocks(new Vec3d(X[0], X[1], X[2]),
new Vec3d(pos.getX() + 0.5F + dir.getFrontOffsetX(), pos.getY() + 0.5F + dir.getFrontOffsetY(),
pos.getZ() + 0.5F + dir.getFrontOffsetZ())) == null) {
Vec3d vec = new Vec3d(X[0] - M[0], X[1] - M[1], X[2] - M[2]);
vec = vec.normalize();
worldObj.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, X[0], X[1], X[2], -vec.xCoord * 0.5F,
-vec.yCoord * 0.5F, -vec.zCoord * 0.5F);
}
}
for (Entity entity : entities) {
float[] X = {
(float) entity.posX,
(float) entity.posY,
(float) entity.posZ
};
if (isLyingInCone(X, M, N, THETA) && worldObj.rayTraceBlocks(
new Vec3d(entity.posX, entity.posY, entity.posZ),
new Vec3d(pos.getX() + 0.5F + dir.getFrontOffsetX(), pos.getY() + 0.5F + dir.getFrontOffsetY(),
pos.getZ() + 0.5F + dir.getFrontOffsetZ())) == null) {
if (!(entity instanceof EntityPlayer) || !(((EntityPlayer) entity).capabilities.isFlying &&
((EntityPlayer) entity).capabilities.isCreativeMode)) {
Vec3d vec = new Vec3d(X[0] - M[0], X[1] - M[1], X[2] - M[2]);
vec = vec.normalize();
double y = vec.yCoord;
double x = vec.xCoord;
double z = vec.zCoord;
y *= 1;
if (entity.isSneaking()) {
x *= 0.25F;
y *= 0.25F;
z *= 0.25F;
}
entity.motionX -= x * 0.025F;
entity.motionY -= y * 0.05F;
entity.motionZ -= z * 0.025F;
entity.fallDistance = 0.0F;
}
}
}
List<EntityItem> list = worldObj.getEntitiesWithinAABB(EntityItem.class, new AxisAlignedBB(
pos.getX() + dir.getFrontOffsetX() * 0.25F, pos.getY() + dir.getFrontOffsetY() * 0.25F,
pos.getZ() + dir.getFrontOffsetZ() * 0.25F, pos.getX() + 1.0D + dir.getFrontOffsetX() * 0.25F,
pos.getY() + 1.0D + dir.getFrontOffsetY() * 0.25F, pos.getZ() + 1.0D + dir.getFrontOffsetZ() * 0.25F));
// TODO: This may be able to be optimized by iterating over the entire list. Test.
if (!list.isEmpty()) {
EntityItem item = list.get(0);
BlockPos offsetPos = pos.offset(dir, -1);
TileEntity tile = worldObj.getTileEntity(offsetPos);
if (tile instanceof ISidedInventory) {
ISidedInventory inv = (ISidedInventory) tile;
int[] access = inv.getSlotsForFace(dir.getOpposite());
for (int slot : access) {
if (putInInventory(item, slot, inv)) {
break;
}
}
} else if (tile instanceof IInventory) {
IInventory inv = (IInventory) tile;
for (int i = 0; i < inv.getSizeInventory(); i++) {
if (putInInventory(item, i, inv)) {
break;
}
}
}
}
}
super.safeUpdate();
}
/**
* @param item The item to put inside the inventory, held in an EntityItem.
* @param slot The slot in which to put the item in within the inventory.
* @param inv The inventory to put the item inside of.
* @return Whether it was added or not.
*/
private boolean putInInventory(EntityItem item, int slot, IInventory inv) {
ItemStack checkStack1 = null;
ItemStack checkStack2 = null;
ItemStack stackInSlot = inv.getStackInSlot(slot);
if (inv.getStackInSlot(slot) != null) {
checkStack1 = stackInSlot.copy();
checkStack1.stackSize = 1;
checkStack2 = item.getEntityItem().copy();
checkStack2.stackSize = 1;
}
if ((stackInSlot == null || (ItemStack.areItemStacksEqual(checkStack1, checkStack2) &&
stackInSlot.stackSize < stackInSlot.getMaxStackSize())) &&
inv.isItemValidForSlot(slot, item.getEntityItem())) {
ItemStack stack = item.getEntityItem().copy();
boolean setDead = true;
if (inv.getStackInSlot(slot) != null) {
if ((stackInSlot.stackSize + stack.stackSize) > stack.getMaxStackSize() &&
checkStack2 != null) {
setDead = false;
int total = stackInSlot.stackSize + stack.stackSize;
stack.stackSize = stack.getMaxStackSize();
total -= stack.getMaxStackSize();
checkStack2.stackSize = total;
item.setEntityItemStack(checkStack2);
//item.getEntityItem().stackSize = (inv.getStackInSlot(slot).stackSize + stack.stackSize - stack.getMaxStackSize());
} else {
stack.stackSize = stackInSlot.stackSize + item.getEntityItem().stackSize;
}
}
inv.setInventorySlotContents(slot, stack);
if (setDead) {
item.setDead();
}
return true;
}
return false;
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound access) {
super.writeToNBT(access);
access.setBoolean("powered", powered);
access.setShort("range", (short) range);
return access;
}
@Override
public void readFromNBT(NBTTagCompound access) {
super.readFromNBT(access);
powered = access.getBoolean("powered");
range = access.getShort("range");
}
@Override
public SPacketUpdateTileEntity getUpdatePacket() {
NBTTagCompound access = super.getUpdateTag();
access.setBoolean("active", getSteamShare() > VACUUM_STEAM_CONSUMPTION && !powered);
access.setShort("range", (short) range);
return new SPacketUpdateTileEntity(pos, 1, access);
}
@Override
public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
super.onDataPacket(net, pkt);
NBTTagCompound access = pkt.getNbtCompound();
active = access.getBoolean("active");
range = access.getShort("range");
markForResync();
}
public void updateRedstoneState(boolean flag) {
if (flag != powered) {
powered = flag;
markForResync();
}
}
@Override
public boolean onWrench(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, IBlockState state, float xO, float yO, float zO) {
if (player.isSneaking()) {
range = MathUtility.minWithDefault(19, range + 2, 5);
markForResync();
return true;
} else {
int steam = getSteamShare();
getNetwork().split(this, true);
EnumFacing myDir = world.getBlockState(pos).getValue(BlockVacuum.FACING);
setValidDistributionDirectionsExcluding(myDir, myDir.getOpposite());
SteamNetwork.newOrJoin(this);
getNetwork().addSteam(steam);
return false;
}
}
@SideOnly(Side.CLIENT)
@Override
public void displayWrench(Post event) {
TileEntityFan.rangeDisplay(event, range);
}
@Override
public void dropItems(SteamTransporterTileEntity thumper, List<ItemStack> drops, IBlockState state, Collection<ThumperAdjacentBehaviorModifier> allBehaviorModifiers, EnumFacing directionIn) {
BlockPos offsetPos = pos.offset(directionIn);
TileEntity invTile = worldObj.getTileEntity(offsetPos);
// This will never happen because of isValidBehaviorModifier, but it won't compile without it :(
if (!(invTile instanceof IInventory)) {
return;
}
Collection<ItemStack> newDrops = new ArrayList<>();
for (ItemStack drop : drops) {
ItemStack remaining = TileEntityHopper.putStackInInventoryAllSlots((IInventory) invTile, drop, directionIn);
if (remaining != null && remaining.stackSize > 0) {
newDrops.add(remaining);
}
}
drops.clear();
drops.addAll(newDrops);
}
@Override
public boolean isValidBehaviorModifier(SteamTransporterTileEntity thumper, EnumFacing directionIn) {
BlockPos offsetPos = pos.offset(directionIn);
TileEntity tile = worldObj.getTileEntity(offsetPos);
return tile instanceof IInventory;
}
}