package com.infinityraider.agricraft.tiles.irrigation;
import com.infinityraider.agricraft.api.irrigation.IConnectable;
import com.infinityraider.agricraft.api.irrigation.IIrrigationComponent;
import com.infinityraider.agricraft.reference.AgriCraftConfig;
import com.infinityraider.agricraft.network.MessageSyncFluidLevel;
import com.infinityraider.agricraft.reference.Constants;
import com.infinityraider.agricraft.tiles.TileEntityCustomWood;
import com.infinityraider.infinitylib.block.multiblock.IMultiBlockComponent;
import com.infinityraider.infinitylib.block.multiblock.IMultiBlockPartData;
import com.infinityraider.infinitylib.block.multiblock.MultiBlockManager;
import com.infinityraider.infinitylib.block.multiblock.MultiBlockPartData;
import com.infinityraider.infinitylib.utility.debug.IDebuggable;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import com.agricraft.agricore.core.AgriCore;
import com.infinityraider.agricraft.api.irrigation.IrrigationConnectionType;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.List;
import net.minecraft.util.ITickable;
import com.infinityraider.agricraft.reference.AgriNBT;
import com.infinityraider.infinitylib.utility.WorldHelper;
import java.util.function.Consumer;
public class TileEntityTank extends TileEntityCustomWood implements ITickable, IFluidHandler, IIrrigationComponent, IMultiBlockComponent<MultiBlockManager, MultiBlockPartData>, IDebuggable {
public static final int SYNC_DELTA = Constants.HALF_BUCKET_mB;
public static final int DISCRETE_MAX = Constants.WHOLE;
public static final int SINGLE_CAPACITY = 8 * Constants.BUCKET_mB;
/**
* Don't call this directly, use getFluidLevel() and setFluidLevel(int
* amount) because only the tank at position (0, 0, 0) in the multiblock
* holds the liquid.
* <p>
* Represents the amount of fluid the tank is holding.
* </p>
*/
private int fluidLevel = 0;
private int lastFluidLevel = 0;
private int lastDiscreteFluidLevel = 0;
private MultiBlockPartData multiBlockData;
/**
* Main component cache is only used in the server thread because it's
* accessed there very often
*/
private TileEntityTank mainComponent;
@Override
protected void writeNBT(NBTTagCompound tag) {
this.getMultiBlockData().writeToNBT(tag);
if (this.fluidLevel > 0) {
tag.setInteger(AgriNBT.LEVEL, this.fluidLevel);
}
}
@Override
protected void readNBT(NBTTagCompound tag) {
this.fluidLevel = tag.hasKey(AgriNBT.LEVEL) ? tag.getInteger(AgriNBT.LEVEL) : 0;
this.multiBlockData = new MultiBlockPartData(0, 0, 0, 1, 1, 1);
this.multiBlockData.readFromNBT(tag);
this.mainComponent = null;
}
//updates the tile entity every tick
@Override
public void update() {
if (!this.worldObj.isRemote) {
if (this.worldObj.canBlockSeeSky(getPos()) && this.worldObj.isRaining()) {
if (!this.hasNeighbour(EnumFacing.UP)) {
Biome biome = this.worldObj.getBiomeGenForCoords(getPos());
if (biome.getRainfall() > 0) {
this.setFluidLevel(this.getFluidAmount(0) + 1);
}
}
}
Block block = this.worldObj.getBlockState(pos.add(0, 1, 0)).getBlock();
if (AgriCraftConfig.fillFromFlowingWater && (block == Blocks.WATER || block == Blocks.FLOWING_WATER)) {
this.setFluidLevel(this.getFluidAmount(0) + 5);
}
}
}
@Override
public void syncFluidLevel() {
if (needsSync()) {
NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint(this.worldObj.provider.getDimension(), this.xCoord(), this.yCoord(), this.zCoord(), 64);
new MessageSyncFluidLevel(this.fluidLevel, this.getPos()).sendToAllAround(point);
}
}
private boolean needsSync() {
int newDiscreteLvl = getDiscreteFluidLevel();
//sync when the discrete fluid LEVEL has changed
if (newDiscreteLvl != lastDiscreteFluidLevel) {
lastDiscreteFluidLevel = newDiscreteLvl;
lastFluidLevel = fluidLevel;
return true;
}
//sync when the fluid LEVEL has changed too much (used for big tanks, where a change in discrete fluid level is very big)
if (SYNC_DELTA <= Math.abs(lastFluidLevel - fluidLevel)) {
lastDiscreteFluidLevel = newDiscreteLvl;
lastFluidLevel = fluidLevel;
return true;
}
return false;
}
public boolean isConnectedToChannel(EnumFacing direction) {
if ((this.worldObj != null) && (direction !=null) && (direction.getFrontOffsetY() == 0)) {
TileEntity tile = this.getWorld().getTileEntity(pos.offset(direction));
if (tile instanceof TileEntityChannel) {
return ((TileEntityChannel) tile).isSameMaterial(this);
}
}
return false;
}
//TANK METHODS
//------------
public FluidStack getContents() {
return new FluidStack(FluidRegistry.WATER, this.getFluidAmount(0));
}
@Override
public int getFluidAmount(int y) {
if (this.getMainComponent() == this) {
return this.fluidLevel;
}
TileEntityTank mainComponent = this.getMainComponent();
return mainComponent != null ? mainComponent.getFluidAmount(0) : 0;
}
/**
* TEMPORARY: Fix to correct build.
*
* @param lvl
* @return
*/
@Override
public float getFluidHeight(int lvl) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
public int getYPosition() {
return getMultiBlockData().posY();
}
/**
* Maps the current fluid LEVEL into the interval [0,
* {@value #DISCRETE_MAX}]
*
* @return The discrete fluid level.
*/
public int getDiscreteFluidLevel() {
IMultiBlockPartData data = getMultiBlockData();
float discreteFactor = DISCRETE_MAX / ((float) SINGLE_CAPACITY * data.sizeX() * data.sizeZ());
int discreteFluidLevel = Math.round(discreteFactor * getFluidAmount(0));
// This is so the fluid shows up over the bottom...
if (discreteFluidLevel < 2 && getFluidAmount(0) > 0) {
discreteFluidLevel = 2;
}
return discreteFluidLevel;
}
@Override
public int getFluidHeight() {
return this.getDiscreteFluidLevel();
}
@Override
public boolean canAcceptFluid(int y, int amount, boolean partial) {
return (partial && this.getFluidAmount(0) < this.getCapacity()) || (this.getFluidAmount(0) + amount <= this.getCapacity());
}
@Override
public int acceptFluid(int y, int amount, boolean partial) {
if (!worldObj.isRemote && this.canAcceptFluid(y, amount, partial) && amount >= 0) {
int room = this.getCapacity() - this.getFluidAmount(0);
if (room >= amount) {
this.setFluidLevel(this.getFluidAmount(0) + amount);
amount = 0;
} else if (room > 0) {
this.setFluidLevel(this.getCapacity());
amount = amount - room;
}
}
return amount;
}
@Override
public void setFluidLevel(int lvl) {
if (lvl != this.getFluidAmount(0)) {
TileEntityTank tank = this.getMainComponent();
lvl = lvl > tank.getCapacity() ? tank.getCapacity() : lvl;
tank.fluidLevel = lvl;
if (!tank.worldObj.isRemote) {
tank.syncFluidLevel();
}
}
}
@Override
public boolean canConnectTo(EnumFacing side, IConnectable component) {
return false;
}
@Override
public IrrigationConnectionType getConnectionType(EnumFacing facing) {
if(facing.getAxis() == EnumFacing.Axis.Y) {
return IrrigationConnectionType.NONE;
} else if (this.hasNeighbour(facing)) {
return IrrigationConnectionType.AUXILIARY;
}
TileEntity te = worldObj.getTileEntity(getPos().offset(facing));
if (te instanceof TileEntityChannel && ((TileEntityChannel) te).isSameMaterial(this)) {
return IrrigationConnectionType.PRIMARY;
}
return IrrigationConnectionType.NONE;
}
@Override
public int getCapacity() {
return SINGLE_CAPACITY * getMultiBlockData().size();
}
public boolean isEmpty() {
return this.getFluidAmount(0) == 0;
}
/*
* IFluidHandler methods
* ---------------------
*/
//try to fill the tank
@Override
public int fill(EnumFacing from, FluidStack resource, boolean doFill) {
if (resource == null || !this.canFill(from, resource.getFluid())) {
return 0;
}
int filled = Math.min(resource.amount, this.getCapacity() - this.getFluidAmount(0));
if (doFill && !worldObj.isRemote) {
this.setFluidLevel(this.getFluidAmount(0) + filled);
}
return filled;
}
//try to drain from the tank
@Override
public FluidStack drain(EnumFacing from, FluidStack resource, boolean doDrain) {
if (resource == null || !this.canDrain(from, resource.getFluid())) {
return null;
}
int drained = Math.min(resource.amount, this.getFluidAmount(0));
if (doDrain && !worldObj.isRemote) {
this.setFluidLevel(this.getFluidAmount(0) - drained);
}
return new FluidStack(FluidRegistry.WATER, drained);
}
//try to drain from the tank
@Override
public FluidStack drain(EnumFacing from, int maxDrain, boolean doDrain) {
return this.drain(from, new FluidStack(FluidRegistry.WATER, maxDrain), doDrain);
}
//check if the tank can be filled
@Override
public boolean canFill(EnumFacing from, Fluid fluid) {
return fluid == FluidRegistry.WATER && this.getFluidAmount(0) != this.getCapacity();
}
//check if the tank can be drained
@Override
public boolean canDrain(EnumFacing from, Fluid fluid) {
return fluid == FluidRegistry.WATER && this.getFluidAmount(0) != 0;
}
@Override
public FluidTankInfo[] getTankInfo(EnumFacing from) {
FluidTankInfo[] info = new FluidTankInfo[1];
info[0] = new FluidTankInfo(this.getContents(), this.getCapacity());
return info;
}
/*
* MultiBlock methods
* ------------------
*/
@Override
public TileEntityTank getMainComponent() {
if (this.mainComponent == null) {
IMultiBlockPartData data = this.getMultiBlockData();
this.mainComponent = WorldHelper
.getTile(worldObj, getPos().add(-data.posX(), -data.posY(), -data.posZ()), TileEntityTank.class)
.orElse(this);
}
return mainComponent;
}
// This is kinda an odd choice.
@Override
public MultiBlockManager getMultiBlockManager() {
return MultiBlockManager.INSTANCE;
}
@Override
public void setMultiBlockPartData(MultiBlockPartData data) {
this.multiBlockData = data;
this.mainComponent = null;
this.markForUpdate();
}
@Override
public MultiBlockPartData getMultiBlockData() {
if (this.multiBlockData == null) {
this.multiBlockData = new MultiBlockPartData(0, 0, 0, 1, 1, 1);
}
return this.multiBlockData;
}
@Override
public boolean hasNeighbour(EnumFacing dir) {
IMultiBlockPartData data = this.getMultiBlockData();
int x = data.posX() + dir.getFrontOffsetX();
int y = data.posY() + dir.getFrontOffsetY();
int z = data.posZ() + dir.getFrontOffsetZ();
return (x >= 0 && x < data.sizeX()) && (y >= 0 && y < data.sizeY()) && (z >= 0 && z < data.sizeZ());
}
@Override
public boolean isValidComponent(IMultiBlockComponent component) {
return component instanceof TileEntityTank && this.isSameMaterial((TileEntityTank) component);
}
@Override
public void preMultiBlockCreation(int sizeX, int sizeY, int sizeZ) {
int lvl = 0;
for (int x = 0; x < sizeX; x++) {
for (int y = 0; y < sizeY; y++) {
for (int z = 0; z < sizeZ; z++) {
TileEntityTank tank = (TileEntityTank) worldObj.getTileEntity(getPos().add(xCoord(), yCoord(), zCoord()));
if (tank == null) {
continue;
}
lvl = lvl + tank.fluidLevel;
tank.fluidLevel = 0;
}
}
}
this.fluidLevel = lvl;
}
@Override
public void postMultiBlockCreation() {
this.mainComponent = null;
}
@Override
public void preMultiBlockBreak() {
MultiBlockPartData data = this.getMultiBlockData();
int[] fluidLevelByLayer = new int[data.sizeY()];
int area = data.sizeX() * data.sizeZ();
int fluidContentByLayer = area * SINGLE_CAPACITY;
int layer = 0;
while (fluidLevel > 0 && layer < fluidLevelByLayer.length) {
fluidLevelByLayer[layer] = (fluidLevel >= fluidContentByLayer) ? (fluidContentByLayer / area) : (fluidLevel / area);
fluidLevel = (fluidLevel >= fluidContentByLayer) ? (fluidLevel - fluidContentByLayer) : 0;
layer++;
}
for (int x = 0; x < data.sizeX(); x++) {
for (int y = 0; y < fluidLevelByLayer.length; y++) {
for (int z = 0; z < data.sizeZ(); z++) {
TileEntityTank tank = (TileEntityTank) worldObj.getTileEntity(getPos().add(xCoord(), yCoord(), zCoord()));
if (tank != null) {
tank.fluidLevel = fluidLevelByLayer[y];
}
}
}
}
}
@Override
public void postMultiBlockBreak() {
this.mainComponent = null;
this.syncFluidLevel();
}
/*
* IDebuggable methods
* -------------------
*/
//debug info
@Override
public void addServerDebugInfo(Consumer<String> consumer) {
super.addServerDebugInfo(consumer);
IMultiBlockPartData data = this.getMultiBlockData();
TileEntityTank root = getMainComponent();
consumer.accept("TANK:");
consumer.accept("coordinates: (" + xCoord() + ", " + yCoord() + ", " + zCoord() + ")");
consumer.accept("root coords: (" + root.xCoord() + ", " + root.yCoord() + ", " + root.zCoord() + ")");
consumer.accept("Tank: (single capacity: " + SINGLE_CAPACITY + ")");
consumer.accept(" - FluidLevel: " + this.getFluidAmount(0) + "/" + this.getCapacity());
consumer.accept(" - Water level is on layer " + (int) Math.floor((this.getFluidAmount(0) - 0.1F) / (this.getCapacity() * data.sizeX() * data.sizeZ())) + ".");
consumer.accept(" - Water height is " + this.getFluidHeight());
StringBuilder neighbours = new StringBuilder();
for (EnumFacing dir : EnumFacing.values()) {
if (dir == null) {
continue;
}
if (this.hasNeighbour(dir)) {
neighbours.append(dir.name()).append(", ");
}
}
consumer.accept(" - Neighbours: " + neighbours.toString());
consumer.accept(" - MultiBlock data: " + data.toString());
consumer.accept(" - MultiBlock Size: " + data.sizeX() + "x" + data.sizeY() + "x" + data.sizeZ());
}
/*
* Waila methods
* -------------
*/
@Override
@SideOnly(Side.CLIENT)
public void addDisplayInfo(List information) {
super.addDisplayInfo(information);
information.add(AgriCore.getTranslator().translate("agricraft_tooltip.waterLevel") + ": " + this.getFluidAmount(0) + "/" + this.getCapacity());
}
}