package openmods.liquids; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.annotation.Nullable; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTank; import net.minecraftforge.fluids.FluidTankInfo; import net.minecraftforge.fluids.IFluidHandler; import openmods.utils.BlockUtils; import openmods.utils.CollectionUtils; import openmods.utils.Coord; public class GenericTank extends FluidTank { private List<ForgeDirection> surroundingTanks = Lists.newArrayList(); private final IFluidFilter filter; public interface IFluidFilter { public boolean canAcceptFluid(FluidStack stack); } private static final IFluidFilter NO_RESTRICTIONS = new IFluidFilter() { @Override public boolean canAcceptFluid(FluidStack stack) { return true; } }; private static final Function<Fluid, FluidStack> FLUID_CONVERTER = new Function<Fluid, FluidStack>() { @Override @Nullable public FluidStack apply(@Nullable Fluid input) { return new FluidStack(input, 0); } }; private static IFluidFilter filter(final FluidStack... acceptableFluids) { if (acceptableFluids.length == 0) return NO_RESTRICTIONS; return new IFluidFilter() { @Override public boolean canAcceptFluid(FluidStack stack) { for (FluidStack acceptableFluid : acceptableFluids) if (acceptableFluid.isFluidEqual(stack)) return true; return false; } }; } public GenericTank(int capacity) { super(capacity); this.filter = NO_RESTRICTIONS; } public GenericTank(int capacity, FluidStack... acceptableFluids) { super(capacity); this.filter = filter(acceptableFluids); } public GenericTank(int capacity, Fluid... acceptableFluids) { super(capacity); this.filter = filter(CollectionUtils.transform(acceptableFluids, FLUID_CONVERTER)); } private static boolean isNeighbourTank(World world, Coord coord, ForgeDirection dir) { TileEntity tile = BlockUtils.getTileInDirectionSafe(world, coord, dir); return tile instanceof IFluidHandler; } private static Set<ForgeDirection> getSurroundingTanks(World world, Coord coord) { final Set<ForgeDirection> result = EnumSet.noneOf(ForgeDirection.class); for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) if (isNeighbourTank(world, coord, dir)) result.add(dir); return result; } public FluidStack drain(FluidStack resource, boolean doDrain) { if (resource == null || fluid == null || fluid.isFluidEqual(resource)) return null; return drain(resource.amount, doDrain); } public int getSpace() { return getCapacity() - getFluidAmount(); } @Override public int fill(FluidStack resource, boolean doFill) { if (resource == null || !filter.canAcceptFluid(resource)) return 0; return super.fill(resource, doFill); } public void updateNeighbours(World world, Coord coord, Set<ForgeDirection> sides) { this.surroundingTanks = Lists.newArrayList(Sets.difference(getSurroundingTanks(world, coord), sides)); } public void updateNeighbours(World world, Coord coord) { this.surroundingTanks = Lists.newArrayList(getSurroundingTanks(world, coord)); } private static int tryFillNeighbour(FluidStack drainedFluid, ForgeDirection side, TileEntity otherTank) { final FluidStack toFill = drainedFluid.copy(); final ForgeDirection fillSide = side.getOpposite(); if (otherTank instanceof IFluidHandler) return ((IFluidHandler)otherTank).fill(fillSide, toFill, true); return 0; } public void distributeToSides(int amountPerTick, World world, Coord coord, Set<ForgeDirection> allowedSides) { if (world == null) return; if (getFluidAmount() <= 0) return; if (surroundingTanks.isEmpty()) return; final List<ForgeDirection> sides = Lists.newArrayList(surroundingTanks); if (allowedSides != null) { sides.retainAll(allowedSides); if (sides.isEmpty()) return; } FluidStack drainedFluid = drain(amountPerTick, false); if (drainedFluid != null && drainedFluid.amount > 0) { int startingAmount = drainedFluid.amount; Collections.shuffle(sides); for (ForgeDirection side : sides) { if (drainedFluid.amount <= 0) break; TileEntity otherTank = BlockUtils.getTileInDirection(world, coord, side); if (otherTank != null) drainedFluid.amount -= tryFillNeighbour(drainedFluid, side, otherTank); } // return any remainder int distributed = startingAmount - drainedFluid.amount; if (distributed > 0) drain(distributed, true); } } public void fillFromSides(int maxAmount, World world, Coord coord) { fillFromSides(maxAmount, world, coord, null); } public void fillFromSides(int maxAmount, World world, Coord coord, Set<ForgeDirection> allowedSides) { if (world == null) return; int toDrain = Math.min(maxAmount, getSpace()); if (toDrain <= 0) return; if (surroundingTanks.isEmpty()) return; final List<ForgeDirection> sides = Lists.newArrayList(surroundingTanks); if (allowedSides != null) { sides.retainAll(allowedSides); if (sides.isEmpty()) return; } Collections.shuffle(sides); for (ForgeDirection side : sides) { if (toDrain <= 0) break; toDrain -= fillInternal(world, coord, side, toDrain); } } public int fillFromSide(World world, Coord coord, ForgeDirection side) { int maxDrain = getSpace(); if (maxDrain <= 0) return 0; return fillInternal(world, coord, side, maxDrain); } public int fillFromSide(int maxDrain, World world, Coord coord, ForgeDirection side) { maxDrain = Math.max(maxDrain, getSpace()); if (maxDrain <= 0) return 0; return fillInternal(world, coord, side, maxDrain); } private int fillInternal(World world, Coord coord, ForgeDirection side, int maxDrain) { int drain = 0; final TileEntity otherTank = BlockUtils.getTileInDirection(world, coord, side); if (otherTank instanceof IFluidHandler) { final ForgeDirection drainSide = side.getOpposite(); final IFluidHandler handler = (IFluidHandler)otherTank; final FluidTankInfo[] infos = handler.getTankInfo(drainSide); if (infos == null) return 0; for (FluidTankInfo info : infos) { if (filter.canAcceptFluid(info.fluid)) { final FluidStack drained = handler.drain(drainSide, maxDrain, true); if (drained != null) { fill(drained, true); drain += drained.amount; maxDrain -= drained.amount; if (maxDrain <= 0) break; } } } } return drain; } }