package openblocks.common.tileentity; import com.google.common.collect.Lists; import java.util.List; import java.util.Set; import net.minecraft.block.Block; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidContainerRegistry; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidHandler; import net.minecraftforge.fluids.IFluidTank; import openblocks.Config; import openblocks.OpenBlocks; import openblocks.client.renderer.tileentity.tank.INeighbourMap; import openblocks.client.renderer.tileentity.tank.ITankConnections; import openblocks.client.renderer.tileentity.tank.ITankRenderFluidData; import openblocks.client.renderer.tileentity.tank.NeighbourMap; import openblocks.client.renderer.tileentity.tank.TankRenderLogic; import openblocks.common.LiquidXpUtils; import openblocks.common.item.ItemTankBlock; import openmods.api.IActivateAwareTile; import openmods.api.ICustomHarvestDrops; import openmods.api.INeighbourAwareTile; import openmods.api.IPlacerAwareTile; import openmods.include.IncludeInterface; import openmods.include.IncludeOverride; import openmods.liquids.GenericFluidHandler; import openmods.sync.ISyncListener; import openmods.sync.ISyncableObject; import openmods.sync.SyncableTank; import openmods.tileentity.SyncedTileEntity; import openmods.utils.EnchantmentUtils; import openmods.utils.ItemUtils; public class TileEntityTank extends SyncedTileEntity implements IActivateAwareTile, IPlacerAwareTile, INeighbourAwareTile, ICustomHarvestDrops { private class RenderUpdateListeners implements ISyncListener { private FluidStack prevFluidStack; private int prevLuminosity; private boolean isSameFluid(FluidStack currentFluid) { if (currentFluid == null) return prevFluidStack == null; return currentFluid.isFluidEqual(prevFluidStack); } @Override public void onSync(Set<ISyncableObject> changes) { if (changes.contains(tank)) { final FluidStack fluidStack = tank.getFluid(); if (!isSameFluid(fluidStack)) { worldObj.markBlockForUpdate(xCoord, yCoord, zCoord); prevFluidStack = fluidStack; int luminosity = fluidStack != null? fluidStack.getFluid().getLuminosity(fluidStack) : 0; if (luminosity != prevLuminosity) { worldObj.func_147451_t(xCoord, yCoord, zCoord); prevLuminosity = luminosity; } } renderLogic.updateFluid(fluidStack); } } } private final TankRenderLogic renderLogic; private boolean needsTankUpdate; @Override public void validate() { super.validate(); needsTankUpdate = true; if (worldObj.isRemote) renderLogic.initialize(worldObj, xCoord, yCoord, zCoord); } @Override public void invalidate() { super.invalidate(); if (worldObj.isRemote) renderLogic.invalidateConnections(); } protected TileEntityTank getNeighourTank(final int x, final int y, final int z) { if (!worldObj.blockExists(x, y, z)) return null; Chunk chunk = worldObj.getChunkFromBlockCoords(x, z); TileEntity te = chunk.getTileEntityUnsafe(x & 0xF, y, z & 0xF); return (te instanceof TileEntityTank)? (TileEntityTank)te : null; } private static final int SYNC_THRESHOLD = 8; private static final int UPDATE_THRESHOLD = 20; private SyncableTank tank; private boolean forceUpdate = true; private int ticksSinceLastSync = hashCode() % SYNC_THRESHOLD; private boolean needsSync; private int ticksSinceLastUpdate = hashCode() % UPDATE_THRESHOLD; private boolean needsUpdate; @IncludeInterface(IFluidHandler.class) private final GenericFluidHandler tankWrapper = new GenericFluidHandler(tank); public TileEntityTank() { syncMap.addSyncListener(new ISyncListener() { @Override public void onSync(Set<ISyncableObject> changes) { ticksSinceLastSync = 0; } }); syncMap.addUpdateListener(new RenderUpdateListeners()); renderLogic = new TankRenderLogic(tank); } @Override protected void createSyncedFields() { tank = new SyncableTank(getTankCapacity()); } public double getFluidRatio() { return (double)tank.getFluidAmount() / (double)tank.getCapacity(); } public static int getTankCapacity() { return FluidContainerRegistry.BUCKET_VOLUME * Config.bucketsPerTank; } public int getFluidLightLevel() { FluidStack stack = tank.getFluid(); if (stack != null) { Fluid fluid = stack.getFluid(); if (fluid != null) return fluid.getLuminosity(); } return 0; } public INeighbourMap getRenderNeigbourMap() { return new NeighbourMap(worldObj, xCoord, yCoord, zCoord, tank.getFluid()); } public ITankRenderFluidData getRenderFluidData() { return renderLogic.getTankRenderData(); } public ITankConnections getTankConnections() { return renderLogic.getTankConnections(); } public boolean accepts(FluidStack liquid) { if (liquid == null) return true; final FluidStack ownFluid = tank.getFluid(); return ownFluid == null || ownFluid.isFluidEqual(liquid); } private boolean containsFluid(FluidStack liquid) { if (liquid == null) return false; final FluidStack ownFluid = tank.getFluid(); return ownFluid != null && ownFluid.isFluidEqual(liquid); } public IFluidTank getTank() { return tank; } public NBTTagCompound getItemNBT() { NBTTagCompound nbt = new NBTTagCompound(); tank.writeToNBT(nbt); return nbt; } @Override public void onNeighbourChanged(Block block) { forceUpdate = true; needsTankUpdate = true; } @Override public void onBlockPlacedBy(EntityLivingBase placer, ItemStack stack) { NBTTagCompound itemTag = stack.getTagCompound(); if (itemTag != null && itemTag.hasKey(ItemTankBlock.TANK_TAG)) { tank.readFromNBT(itemTag.getCompoundTag(ItemTankBlock.TANK_TAG)); } } private static TileEntityTank getValidTank(final TileEntity neighbor) { return (neighbor instanceof TileEntityTank && !neighbor.isInvalid())? (TileEntityTank)neighbor : null; } private TileEntityTank getTankInDirection(ForgeDirection direction) { final TileEntity neighbor = getTileInDirection(direction); return getValidTank(neighbor); } public TileEntityTank getTankInDirection(int dx, int dy, int dz) { final TileEntity neighbor = getNeighbour(dx, dy, dz); return getValidTank(neighbor); } @Override public boolean onBlockActivated(EntityPlayer player, int side, float hitX, float hitY, float hitZ) { ForgeDirection direction = ForgeDirection.getOrientation(side); ItemStack usedItem = player.inventory.getCurrentItem(); if (usedItem != null) return tryEmptyItem(player, direction, usedItem); if (worldObj.isRemote) return false; return tryDrainXp(player, direction); } protected boolean tryDrainXp(EntityPlayer player, ForgeDirection direction) { final FluidStack fluid = tank.getFluid(); if (fluid != null && fluid.isFluidEqual(new FluidStack(OpenBlocks.Fluids.xpJuice, 0))) { int currentXP = EnchantmentUtils.getPlayerXP(player); int nextLevelXP = EnchantmentUtils.getExperienceForLevel(player.experienceLevel + 1); int requiredXP = nextLevelXP - currentXP; int requiredXPJuice = LiquidXpUtils.xpToLiquidRatio(requiredXP); FluidStack drained = drain(direction, requiredXPJuice, false); if (drained != null) { int xp = LiquidXpUtils.liquidToXpRatio(drained.amount); if (xp > 0) { int actualDrain = LiquidXpUtils.xpToLiquidRatio(xp); EnchantmentUtils.addPlayerXP(player, xp); drain(direction, actualDrain, true); return true; } } } return false; } protected boolean tryUseFluidContainer(EntityPlayer player, ForgeDirection direction, ItemStack current) { return tryEmptyItem(player, direction, current); } protected boolean tryEmptyItem(EntityPlayer player, ForgeDirection direction, ItemStack current) { FluidStack containedFluid = FluidContainerRegistry.getFluidForFilledItem(current); if (containedFluid != null) { int qty = fill(direction, containedFluid, true); if (qty != 0 && !player.capabilities.isCreativeMode) { player.inventory.setInventorySlotContents(player.inventory.currentItem, ItemUtils.consumeItem(current)); } return true; } return false; } @Override public void updateEntity() { super.updateEntity(); ticksSinceLastSync++; ticksSinceLastUpdate++; if (Config.shouldTanksUpdate && !worldObj.isRemote && forceUpdate) { if (needsTankUpdate) { tank.updateNeighbours(worldObj, getPosition()); needsTankUpdate = false; } forceUpdate = false; FluidStack contents = tank.getFluid(); if (contents != null && contents.amount > 0 && yCoord > 0) { tryFillBottomTank(contents); contents = tank.getFluid(); } if (contents != null && contents.amount > 0) { tryBalanceNeighbors(contents); } needsSync = true; markUpdated(); } if (needsSync && !worldObj.isRemote && ticksSinceLastSync > SYNC_THRESHOLD) { needsSync = false; sync(); } if (needsUpdate && ticksSinceLastUpdate > UPDATE_THRESHOLD) { needsUpdate = false; ticksSinceLastUpdate = 0; worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, getBlockType()); } if (worldObj.isRemote) renderLogic.validateConnections(worldObj, xCoord, yCoord, zCoord); } private void tryGetNeighbor(List<TileEntityTank> result, FluidStack fluid, ForgeDirection side) { TileEntityTank neighbor = getTankInDirection(side); if (neighbor != null && neighbor.accepts(fluid)) result.add(neighbor); } private void tryBalanceNeighbors(FluidStack contents) { List<TileEntityTank> neighbors = Lists.newArrayList(); tryGetNeighbor(neighbors, contents, ForgeDirection.NORTH); tryGetNeighbor(neighbors, contents, ForgeDirection.SOUTH); tryGetNeighbor(neighbors, contents, ForgeDirection.EAST); tryGetNeighbor(neighbors, contents, ForgeDirection.WEST); final int count = neighbors.size(); if (count == 0) return; int sum = contents.amount; for (TileEntityTank n : neighbors) sum += n.tank.getFluidAmount(); final int suggestedAmount = sum / (count + 1); if (Math.abs(suggestedAmount - contents.amount) < Config.tankFluidUpdateThreshold) return; // Don't balance small amounts to reduce server load FluidStack suggestedStack = contents.copy(); suggestedStack.amount = suggestedAmount; for (TileEntityTank n : neighbors) { int amount = n.tank.getFluidAmount(); int diff = amount - suggestedAmount; if (diff != 1 && diff != 0 && diff != -1) { n.tank.setFluid(suggestedStack.copy()); n.tankChanged(); sum -= suggestedAmount; n.forceUpdate = true; } else { sum -= amount; } } FluidStack s = tank.getFluid(); if (sum != s.amount) { s.amount = sum; tankChanged(); } } private void notifyNeigbours() { needsUpdate = true; } private void tankChanged() { notifyNeigbours(); tank.markDirty(); } private void markContentsUpdated() { notifyNeigbours(); forceUpdate = true; } private void tryFillBottomTank(FluidStack fluid) { TileEntity te = worldObj.getTileEntity(xCoord, yCoord - 1, zCoord); if (te instanceof TileEntityTank) { int amount = ((TileEntityTank)te).internalFill(fluid, true); if (amount > 0) internalDrain(amount, true); } } private FluidStack internalDrain(int amount, boolean doDrain) { FluidStack drained = tank.drain(amount, doDrain); if (drained != null && doDrain) markContentsUpdated(); return drained; } private void drainFromColumn(FluidStack needed, boolean doDrain) { if (!containsFluid(needed) || needed.amount <= 0) return; if (yCoord < 255) { TileEntity te = worldObj.getTileEntity(xCoord, yCoord + 1, zCoord); if (te instanceof TileEntityTank) ((TileEntityTank)te).drainFromColumn(needed, doDrain); } if (needed.amount <= 0) return; FluidStack drained = internalDrain(needed.amount, doDrain); if (drained == null) return; needed.amount -= drained.amount; } private int internalFill(FluidStack resource, boolean doFill) { int amount = tank.fill(resource, doFill); if (amount > 0 && doFill) markContentsUpdated(); return amount; } private void fillColumn(FluidStack resource, boolean doFill) { if (!accepts(resource) || resource.amount <= 0) return; int amount = internalFill(resource, doFill); resource.amount -= amount; if (resource.amount > 0 && yCoord < 255) { TileEntity te = worldObj.getTileEntity(xCoord, yCoord + 1, zCoord); if (te instanceof TileEntityTank) ((TileEntityTank)te).fillColumn(resource, doFill); } } @IncludeOverride public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain) { if (resource == null) return null; FluidStack needed = resource.copy(); drainFromColumn(needed, doDrain); needed.amount = resource.amount - needed.amount; return needed; } @IncludeOverride public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) { if (maxDrain <= 0) return null; FluidStack contents = tank.getFluid(); if (contents == null || contents.amount <= 0) return null; FluidStack needed = contents.copy(); needed.amount = maxDrain; drainFromColumn(needed, doDrain); needed.amount = maxDrain - needed.amount; return needed; } @IncludeOverride public int fill(ForgeDirection from, FluidStack resource, boolean doFill) { if (resource == null) return 0; FluidStack copy = resource.copy(); fillColumn(copy, doFill); return resource.amount - copy.amount; } @Override public boolean suppressNormalHarvestDrops() { return true; } @Override public void addHarvestDrops(EntityPlayer player, List<ItemStack> drops) { ItemStack stack = new ItemStack(OpenBlocks.Blocks.tank); if (tank.getFluidAmount() > 0) { NBTTagCompound tankTag = getItemNBT(); NBTTagCompound itemTag = ItemUtils.getItemTag(stack); itemTag.setTag("tank", tankTag); } drops.add(stack); } @Override public boolean shouldRenderInPass(int pass) { return pass == 0; } }