package micdoodle8.mods.galacticraft.core.fluid; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import micdoodle8.mods.galacticraft.api.transmission.NetworkType; import micdoodle8.mods.galacticraft.api.transmission.grid.IGridNetwork; import micdoodle8.mods.galacticraft.api.transmission.grid.Pathfinder; import micdoodle8.mods.galacticraft.api.transmission.grid.PathfinderChecker; import micdoodle8.mods.galacticraft.api.transmission.tile.IBufferTransmitter; import micdoodle8.mods.galacticraft.api.transmission.tile.INetworkConnection; import micdoodle8.mods.galacticraft.api.transmission.tile.INetworkProvider; import micdoodle8.mods.galacticraft.api.transmission.tile.ITransmitter; import micdoodle8.mods.galacticraft.api.vector.BlockVec3; import micdoodle8.mods.galacticraft.core.GalacticraftCore; import micdoodle8.mods.galacticraft.core.network.IPacket; import micdoodle8.mods.galacticraft.core.network.PacketFluidNetworkUpdate; import micdoodle8.mods.galacticraft.core.util.GCCoreUtil; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraft.world.World; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidHandler; import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.fml.relauncher.Side; import org.apache.commons.lang3.tuple.Pair; import java.util.*; /** * Based heavily on Mekanism FluidNetwork * * @author aidancbrady */ public class FluidNetwork implements IGridNetwork<FluidNetwork, IBufferTransmitter<FluidStack>, TileEntity> { public Map<BlockPos, IFluidHandler> acceptors = Maps.newHashMap(); public Map<BlockPos, EnumSet<EnumFacing>> acceptorDirections = Maps.newHashMap(); public final Set<IBufferTransmitter<FluidStack>> pipes = Sets.newHashSet(); private Set<IBufferTransmitter<FluidStack>> pipesAdded = Sets.newHashSet(); private Set<DelayQueue> updateQueue = Sets.newLinkedHashSet(); public FluidStack buffer; private int capacity; private World worldObj; private int prevBufferAmount; private boolean needsUpdate; public float fluidScale; public Fluid refFluid; public boolean didTransfer; public boolean prevTransfer; public int transferDelay = 0; private int prevTransferAmount; private int updateDelay; private boolean firstUpdate = true; public FluidNetwork() { } public FluidNetwork(Collection<FluidNetwork> toMerge) { for (FluidNetwork network : toMerge) { if (network != null) { if (network.buffer != null) { if (this.buffer == null) { this.buffer = network.buffer.copy(); } else { if (buffer.getFluid() == network.buffer.getFluid()) { buffer.amount += network.buffer.amount; } else if (network.buffer.amount > buffer.amount) { this.buffer = network.buffer.copy(); } } network.buffer = null; } this.adoptNetwork(network); network.unregister(); } } this.register(); } public void adoptNetwork(FluidNetwork network) { for (IBufferTransmitter<FluidStack> transmitter : network.pipes) { transmitter.setNetwork(this); this.pipes.add(transmitter); this.pipesAdded.add(transmitter); this.updateDelay = this.firstUpdate ? 3 : 1; } this.acceptors.putAll(network.acceptors); for (Map.Entry<BlockPos, EnumSet<EnumFacing>> e : network.acceptorDirections.entrySet()) { BlockPos pos = e.getKey(); if (this.acceptorDirections.containsKey(pos)) { this.acceptorDirections.get(pos).addAll(e.getValue()); } else { this.acceptorDirections.put(pos, e.getValue()); } } } public void register() { GalacticraftCore.proxy.registerNetwork(this); } public void unregister() { GalacticraftCore.proxy.unregisterNetwork(this); } public void addTransmitter(IBufferTransmitter<FluidStack> transmitter) { this.pipes.add(transmitter); this.pipesAdded.add(transmitter); this.refresh(); this.updateDelay = this.firstUpdate ? 20 : 1; } public void removeTransmitter(IBufferTransmitter<FluidStack> transmitter) { this.pipes.remove(transmitter); this.updateCapacity(); } public void onTransmitterAdded(IBufferTransmitter<FluidStack> transmitter) { FluidStack stack = transmitter.getBuffer(); if (stack == null || stack.getFluid() == null || stack.amount == 0) { // Nothing to do return; } if (buffer == null || buffer.getFluid() == null || buffer.amount == 0) { // Set transmitter buffer to network buffer buffer = stack.copy(); stack.amount = 0; return; } if (buffer.isFluidEqual(stack)) { // Add transmitter fluid to network buffer buffer.amount += stack.amount; } stack.amount = 0; } public void clamp() { if (buffer != null && buffer.amount > getCapacity()) { buffer.amount = this.capacity; } } public void updateCapacity() { this.capacity = 0; for (IBufferTransmitter<FluidStack> transmitter : this.pipes) { this.capacity += transmitter.getCapacity(); } } public int getCapacity() { return capacity; } public int getRequest() { return getCapacity() - (buffer != null ? buffer.amount : 0); } private int emitToAcceptors(FluidStack toSend, boolean doTransfer) { List<Pair<BlockPos, IFluidHandler>> available = new ArrayList<>(); available.addAll(this.getAcceptors(toSend)); Collections.shuffle(available); int totalSend = 0; if (!available.isEmpty()) { int divider = available.size(); int remainder = toSend.amount % divider; int each = (toSend.amount - remainder) / divider; for (Pair<BlockPos, IFluidHandler> pair : available) { int currentSend = each; IFluidHandler acceptor = pair.getRight(); EnumSet<EnumFacing> sides = acceptorDirections.get(pair.getLeft()); if (remainder > 0) { currentSend++; remainder--; } for (EnumFacing side : sides) { int prev = totalSend; if (acceptor != null) { FluidStack copy = toSend.copy(); copy.amount = currentSend; totalSend += acceptor.fill(side, copy, doTransfer); } if (totalSend > prev) { // If fluid was sent to this handler, continue to next break; } } } } if (doTransfer && totalSend > 0 && GCCoreUtil.getEffectiveSide().isServer()) { this.didTransfer = true; this.transferDelay = 2; } return totalSend; } public int emitToBuffer(FluidStack toSend, boolean doTransfer) { if (toSend == null || (buffer != null && buffer.getFluid() != toSend.getFluid())) { return 0; } int toUse = Math.min(getRequest(), toSend.amount); if (doTransfer) { if (buffer == null) { // Copy buffer = toSend.copy(); buffer.amount = toUse; } else { // Add buffer.amount += toUse; } } return toUse; } public void tickEnd() { this.onUpdate(); } public void addUpdate(EntityPlayerMP player) { this.updateQueue.add(new DelayQueue(player)); } private IPacket getAddTransmitterUpdate() { BlockPos pos = ((TileEntity) this.pipes.iterator().next()).getPos(); return PacketFluidNetworkUpdate.getAddTransmitterUpdate(GCCoreUtil.getDimensionID(this.worldObj), pos, this.firstUpdate, this.pipesAdded); } public void onUpdate() { if (GCCoreUtil.getEffectiveSide().isServer()) { Iterator<DelayQueue> iterator = this.updateQueue.iterator(); try { while (iterator.hasNext()) { DelayQueue queue = iterator.next(); if (queue.delay > 0) { queue.delay--; } else { this.pipesAdded.addAll(this.pipes); GalacticraftCore.packetPipeline.sendTo(this.getAddTransmitterUpdate(), queue.player); this.pipesAdded.clear(); iterator.remove(); } } } catch (Exception e) { } if (this.updateDelay > 0) { this.updateDelay--; if (this.updateDelay == 0) { BlockPos pos = ((TileEntity) this.pipes.iterator().next()).getPos(); GalacticraftCore.packetPipeline.sendToAllAround(this.getAddTransmitterUpdate(), new NetworkRegistry.TargetPoint(GCCoreUtil.getDimensionID(this.worldObj), pos.getX(), pos.getY(), pos.getZ(), 30.0)); this.firstUpdate = false; this.pipesAdded.clear(); this.needsUpdate = true; } } this.prevTransferAmount = 0; if (this.transferDelay == 0) { this.didTransfer = false; } else { this.transferDelay--; } int stored = buffer != null ? buffer.amount : 0; if (stored != this.prevBufferAmount) { this.needsUpdate = true; } this.prevBufferAmount = stored; if (this.didTransfer != this.prevTransfer || this.needsUpdate) { BlockPos pos = ((TileEntity) this.pipes.iterator().next()).getPos(); GalacticraftCore.packetPipeline.sendToAllAround(PacketFluidNetworkUpdate.getFluidUpdate(GCCoreUtil.getDimensionID(this.worldObj), pos, this.buffer, this.didTransfer), new NetworkRegistry.TargetPoint(GCCoreUtil.getDimensionID(this.worldObj), pos.getX(), pos.getY(), pos.getZ(), 20.0)); this.needsUpdate = false; } this.prevTransfer = this.didTransfer; if (buffer != null) { this.prevTransferAmount = this.emitToAcceptors(buffer, true); if (buffer != null) { buffer.amount -= this.prevTransferAmount; if (buffer.amount <= 0) { this.buffer = null; } } } } } public void clientTick() { this.fluidScale = Math.max(this.fluidScale, this.getScale()); if (this.didTransfer && this.fluidScale < 1.0F) { this.fluidScale = Math.max(this.getScale(), Math.min(1, fluidScale + 0.02F)); } else if (!this.didTransfer && fluidScale > 0.0F) { this.fluidScale = this.getScale(); if (this.fluidScale == 0.0F) { this.buffer = null; } } } public float getScale() { if (this.buffer == null || this.getCapacity() == 0) { return 0.0F; } return Math.min(1.0F, this.buffer.amount / (float) this.getCapacity()); } public Set<Pair<BlockPos, IFluidHandler>> getAcceptors(FluidStack toSend) { Set<Pair<BlockPos, IFluidHandler>> toReturn = new HashSet<>(); if (GCCoreUtil.getEffectiveSide() == Side.CLIENT) { return toReturn; } if (this.acceptors == null || this.acceptors.isEmpty()) { this.refreshAcceptors(); } List<BlockPos> acceptorsCopy = new LinkedList(); acceptorsCopy.addAll(acceptors.keySet()); for (BlockPos coords : acceptorsCopy) { EnumSet<EnumFacing> sides = acceptorDirections.get(coords); if (sides == null || sides.isEmpty()) { continue; } TileEntity tile = this.worldObj.getTileEntity(coords); if (!(tile instanceof IFluidHandler)) { continue; } IFluidHandler acceptor = (IFluidHandler) tile; Fluid fluidToSend = toSend.getFluid(); for (EnumFacing side : sides) { if (acceptor.canFill(side, fluidToSend)) { toReturn.add(Pair.of(coords, acceptor)); } } } return toReturn; } @Override public void refresh() { if (this.acceptors != null) { this.acceptors.clear(); } try { Iterator<IBufferTransmitter<FluidStack>> it = this.pipes.iterator(); while (it.hasNext()) { ITransmitter transmitter = it.next(); TileEntity tileTransmitter = (TileEntity) transmitter; if (transmitter == null) { it.remove(); continue; } transmitter.onNetworkChanged(); if (tileTransmitter.isInvalid() || tileTransmitter.getWorld() == null) { it.remove(); continue; } else { if (this.worldObj == null) { this.worldObj = tileTransmitter.getWorld(); } transmitter.setNetwork(this); } } updateCapacity(); clamp(); } catch (Exception e) { FMLLog.severe("Failed to refresh liquid pipe network."); e.printStackTrace(); } } public void refreshAcceptors() { if (this.acceptors == null) { this.acceptors = Maps.newHashMap(); } else { this.acceptors.clear(); } try { Iterator<IBufferTransmitter<FluidStack>> it = this.pipes.iterator(); while (it.hasNext()) { IBufferTransmitter<FluidStack> transmitter = it.next(); TileEntity tile = (TileEntity) transmitter; if (transmitter == null || tile.isInvalid() || tile.getWorld() == null) { it.remove(); continue; } if (!transmitter.canTransmit()) { continue; } int i = 0; for (TileEntity acceptor : transmitter.getAdjacentConnections()) { if (!(acceptor instanceof IBufferTransmitter) && acceptor instanceof IFluidHandler) { EnumFacing facing = EnumFacing.getFront(i).getOpposite(); BlockPos acceptorPos = tile.getPos().offset(facing.getOpposite()); EnumSet<EnumFacing> facingSet = this.acceptorDirections.get(acceptorPos); if (facingSet != null) { facingSet.add(facing); } else { facingSet = EnumSet.of(facing); } this.acceptors.put(acceptorPos, (IFluidHandler) acceptor); this.acceptorDirections.put(acceptorPos, facingSet); } i++; } } } catch (Exception e) { FMLLog.severe("Failed to refresh liquid acceptors"); e.printStackTrace(); } } @Override public ImmutableSet<IBufferTransmitter<FluidStack>> getTransmitters() { return ImmutableSet.copyOf(this.pipes); } @Override public FluidNetwork merge(FluidNetwork network) { if (network != null && network != this) { FluidNetwork newNetwork = new FluidNetwork(Lists.newArrayList(this, network)); newNetwork.refresh(); return newNetwork; } return this; } @Override public void split(IBufferTransmitter<FluidStack> splitPoint) { if (splitPoint instanceof TileEntity) { this.pipes.remove(splitPoint); /** * Loop through the connected blocks and attempt to see if there are * connections between the two points elsewhere. */ TileEntity[] connectedBlocks = splitPoint.getAdjacentConnections(); for (TileEntity connectedBlockA : connectedBlocks) { if (connectedBlockA instanceof INetworkConnection) { for (final TileEntity connectedBlockB : connectedBlocks) { if (connectedBlockA != connectedBlockB && connectedBlockB instanceof INetworkConnection) { Pathfinder finder = new PathfinderChecker(((TileEntity) splitPoint).getWorld(), (INetworkConnection) connectedBlockB, NetworkType.FLUID, splitPoint); finder.init(new BlockVec3(connectedBlockA)); if (finder.results.size() > 0) { /** * The connections A and B are still intact * elsewhere. Set all references of wire * connection into one network. */ for (BlockVec3 node : finder.closedSet) { TileEntity nodeTile = node.getTileEntity(((TileEntity) splitPoint).getWorld()); if (nodeTile instanceof INetworkProvider) { if (nodeTile != splitPoint) { ((INetworkProvider) nodeTile).setNetwork(this); } } } } else { /** * The connections A and B are not connected * anymore. Give both of them a new network. */ FluidNetwork newNetwork = new FluidNetwork(); for (BlockVec3 node : finder.closedSet) { TileEntity nodeTile = node.getTileEntity(((TileEntity) splitPoint).getWorld()); if (nodeTile instanceof IBufferTransmitter) { if (nodeTile != splitPoint) { newNetwork.pipes.add((IBufferTransmitter<FluidStack>) nodeTile); newNetwork.pipesAdded.add((IBufferTransmitter<FluidStack>) nodeTile); newNetwork.onTransmitterAdded((IBufferTransmitter<FluidStack>) nodeTile); this.pipes.remove(nodeTile); } } } newNetwork.refresh(); newNetwork.register(); } } } } } if (this.pipes.isEmpty()) { this.unregister(); } else { this.updateCapacity(); } } } @Override public String toString() { return "FluidNetwork[" + this.hashCode() + "|Pipes:" + this.pipes.size() + "|Acceptors:" + (this.acceptors == null ? 0 : this.acceptors.size()) + "]"; } public static class DelayQueue { public EntityPlayerMP player; public int delay; public DelayQueue(EntityPlayerMP player) { this.player = player; this.delay = 5; } @Override public int hashCode() { return this.player.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof DelayQueue && ((DelayQueue) obj).player.equals(this.player); } } }