Backwards for Progress

- Rebuilt the entire fluid propagation and transfer system with a new approach
This commit is contained in:
simibubi 2020-12-09 16:05:52 +01:00
parent 3301f8ff01
commit 9a7886f406
32 changed files with 1853 additions and 2066 deletions

View file

@ -9,7 +9,7 @@ import java.util.List;
import java.util.Map;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour.AttachmentTypes;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour.AttachmentTypes;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock.HeatLevel;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.Iterate;

View file

@ -0,0 +1,137 @@
package com.simibubi.create.content.contraptions.fluids;
import java.lang.ref.WeakReference;
import java.util.function.Predicate;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
public abstract class FlowSource {
private static final LazyOptional<IFluidHandler> EMPTY = LazyOptional.empty();
BlockFace location;
public FlowSource(BlockFace location) {
this.location = location;
}
public FluidStack provideFluid(Predicate<FluidStack> extractionPredicate) {
IFluidHandler tank = provideHandler().orElse(null);
if (tank == null)
return FluidStack.EMPTY;
FluidStack immediateFluid = tank.drain(1, FluidAction.SIMULATE);
if (extractionPredicate.test(immediateFluid))
return immediateFluid;
for (int i = 0; i < tank.getTanks(); i++) {
FluidStack contained = tank.getFluidInTank(i);
if (contained.isEmpty())
continue;
if (!extractionPredicate.test(contained))
continue;
FluidStack toExtract = contained.copy();
toExtract.setAmount(1);
return tank.drain(toExtract, FluidAction.SIMULATE);
}
return FluidStack.EMPTY;
}
// Layer III. PFIs need active attention to prevent them from disengaging early
public void keepAlive() {}
public abstract boolean isEndpoint();
public void manageSource(World world) {}
public void whileFlowPresent(World world, boolean pulling) {}
public LazyOptional<IFluidHandler> provideHandler() {
return EMPTY;
}
public static class FluidHandler extends FlowSource {
LazyOptional<IFluidHandler> fluidHandler;
public FluidHandler(BlockFace location) {
super(location);
fluidHandler = EMPTY;
}
public void manageSource(World world) {
if (fluidHandler.isPresent())
return;
TileEntity tileEntity = world.getTileEntity(location.getConnectedPos());
if (tileEntity != null)
fluidHandler = tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY,
location.getOppositeFace());
}
@Override
public LazyOptional<IFluidHandler> provideHandler() {
return fluidHandler;
}
@Override
public boolean isEndpoint() {
return true;
}
}
public static class OtherPipe extends FlowSource {
WeakReference<FluidTransportBehaviour> cached;
public OtherPipe(BlockFace location) {
super(location);
}
@Override
public void manageSource(World world) {
if (cached != null && cached.get() != null && !cached.get().tileEntity.isRemoved())
return;
cached = null;
FluidTransportBehaviour fluidTransportBehaviour =
TileEntityBehaviour.get(world, location.getConnectedPos(), FluidTransportBehaviour.TYPE);
if (fluidTransportBehaviour != null)
cached = new WeakReference<>(fluidTransportBehaviour);
}
@Override
public FluidStack provideFluid(Predicate<FluidStack> extractionPredicate) {
if (cached == null || cached.get() == null)
return FluidStack.EMPTY;
FluidTransportBehaviour behaviour = cached.get();
FluidStack providedOutwardFluid = behaviour.getProvidedOutwardFluid(location.getOppositeFace());
return extractionPredicate.test(providedOutwardFluid) ? providedOutwardFluid : FluidStack.EMPTY;
}
@Override
public boolean isEndpoint() {
return false;
}
}
public static class Blocked extends FlowSource {
public Blocked(BlockFace location) {
super(location);
}
@Override
public boolean isEndpoint() {
return false;
}
}
}

View file

@ -1,359 +1,263 @@
package com.simibubi.create.content.contraptions.fluids;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nullable;
import com.simibubi.create.content.contraptions.fluids.PipeConnection.Flow;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
public class FluidNetwork {
BlockFace pumpLocation;
Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph;
List<FluidNetworkFlow> flows;
Set<FluidNetworkEndpoint> targets;
Set<BlockFace> rangeEndpoints;
Map<BlockFace, FluidStack> previousFlow;
private static int CYCLES_PER_TICK = 16;
boolean connectToPumps;
int waitForUnloadedNetwork;
World world;
BlockFace start;
public FluidNetwork() {
pipeGraph = new HashMap<>();
flows = new ArrayList<>();
targets = new HashSet<>();
rangeEndpoints = new HashSet<>();
previousFlow = new HashMap<>();
Supplier<LazyOptional<IFluidHandler>> sourceSupplier;
LazyOptional<IFluidHandler> source;
int transferSpeed;
int pauseBeforePropagation;
List<BlockFace> queued;
Set<Pair<BlockFace, PipeConnection>> frontier;
Set<BlockPos> visited;
List<Pair<BlockFace, LazyOptional<IFluidHandler>>> targets;
Map<BlockPos, WeakReference<FluidTransportBehaviour>> cache;
public FluidNetwork(World world, BlockFace location, Supplier<LazyOptional<IFluidHandler>> sourceSupplier) {
this.world = world;
this.start = location;
this.sourceSupplier = sourceSupplier;
this.source = LazyOptional.empty();
this.frontier = new HashSet<>();
this.visited = new HashSet<>();
this.targets = new ArrayList<>();
this.cache = new HashMap<>();
this.queued = new ArrayList<>();
reset();
}
public boolean hasEndpoints() {
for (FluidNetworkFlow pipeFlow : flows)
if (pipeFlow.hasValidTargets())
return true;
return false;
}
public Collection<FluidNetworkEndpoint> getEndpoints(boolean pulling) {
if (!pulling) {
for (FluidNetworkFlow pipeFlow : flows)
return pipeFlow.outputEndpoints;
return Collections.emptySet();
}
List<FluidNetworkEndpoint> list = new ArrayList<>();
for (FluidNetworkFlow pipeFlow : flows) {
if (!pipeFlow.hasValidTargets())
continue;
list.add(pipeFlow.source);
}
return list;
}
public void tick(IWorld world, PumpTileEntity pumpTE) {
if (connectToPumps) {
connectToOtherFNs(world, pumpTE);
connectToPumps = false;
}
}
public void tickFlows(IWorld world, PumpTileEntity pumpTE, boolean pulling, float speed) {
if (connectToPumps)
public void tick() {
if (pauseBeforePropagation > 0) {
pauseBeforePropagation--;
return;
initFlows(pumpTE, pulling);
previousFlow.clear();
flows.forEach(ep -> ep.tick(world, speed));
}
private void initFlows(PumpTileEntity pumpTE, boolean pulling) {
if (targets.isEmpty())
return;
if (!flows.isEmpty())
return;
World world = pumpTE.getWorld();
if (pulling) {
targets.forEach(ne -> flows.add(new FluidNetworkFlow(this, ne, world, pulling)));
} else {
PumpEndpoint pumpEndpoint = new PumpEndpoint(pumpLocation.getOpposite(), pumpTE);
flows.add(new FluidNetworkFlow(this, pumpEndpoint, world, pulling));
for (int cycle = 0; cycle < CYCLES_PER_TICK; cycle++) {
boolean shouldContinue = false;
for (Iterator<BlockFace> iterator = queued.iterator(); iterator.hasNext();) {
BlockFace blockFace = iterator.next();
if (!isPresent(blockFace))
continue;
PipeConnection pipeConnection = get(blockFace);
if (pipeConnection != null) {
if (blockFace.equals(start))
transferSpeed = (int) Math.max(1, pipeConnection.pressure.get(true) / 2f);
frontier.add(Pair.of(blockFace, pipeConnection));
}
iterator.remove();
}
public void connectToOtherFNs(IWorld world, PumpTileEntity pump) {
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
int maxDistance = FluidPropagator.getPumpRange() * 2;
frontier.add(Pair.of(-1, pumpLocation.getPos()));
// drawDebugOutlines();
while (!frontier.isEmpty()) {
Pair<Integer, BlockPos> entry = frontier.remove(0);
int distance = entry.getFirst();
BlockPos currentPos = entry.getSecond();
for (Iterator<Pair<BlockFace, PipeConnection>> iterator = frontier.iterator(); iterator.hasNext();) {
Pair<BlockFace, PipeConnection> pair = iterator.next();
BlockFace blockFace = pair.getFirst();
PipeConnection pipeConnection = pair.getSecond();
if (!world.isAreaLoaded(currentPos, 0))
if (!pipeConnection.hasFlow())
continue;
if (visited.contains(currentPos))
Flow flow = pipeConnection.flow.get();
if (!flow.inbound) {
if (pipeConnection.comparePressure() >= 0)
iterator.remove();
continue;
}
if (!flow.complete)
continue;
visited.add(currentPos);
List<Direction> connections;
if (currentPos.equals(pumpLocation.getPos())) {
connections = ImmutableList.of(pumpLocation.getFace());
} else {
BlockState currentState = world.getBlockState(currentPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, currentPos);
if (pipe == null)
boolean canRemove = true;
for (Direction side : Iterate.directions) {
if (side == blockFace.getFace())
continue;
BlockFace adjacentLocation = new BlockFace(blockFace.getPos(), side);
PipeConnection adjacent = get(adjacentLocation);
if (adjacent == null)
continue;
if (!adjacent.hasFlow()) {
// Branch could potentially still appear
if (adjacent.hasPressure() && adjacent.pressure.getSecond() > 0)
canRemove = false;
continue;
}
Flow outFlow = adjacent.flow.get();
if (outFlow.inbound) {
if (adjacent.comparePressure() > 0)
canRemove = false;
continue;
}
if (!outFlow.complete) {
canRemove = false;
continue;
connections = FluidPropagator.getPipeConnections(currentState, pipe);
}
for (Direction face : connections) {
BlockFace blockFace = new BlockFace(currentPos, face);
BlockPos connectedPos = blockFace.getConnectedPos();
BlockState connectedState = world.getBlockState(connectedPos);
if (adjacent.source.isPresent() && adjacent.source.get()
.isEndpoint()) {
targets.add(Pair.of(adjacentLocation, adjacent.source.get()
.provideHandler()));
continue;
}
if (connectedPos.equals(pumpLocation.getPos()))
continue;
if (!world.isAreaLoaded(connectedPos, 0))
continue;
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == face.getAxis()) {
TileEntity tileEntity = world.getTileEntity(connectedPos);
if (tileEntity instanceof PumpTileEntity) {
PumpTileEntity otherPump = (PumpTileEntity) tileEntity;
if (otherPump.networks == null)
continue;
otherPump.networks.forEach(fn -> {
int nearest = Integer.MAX_VALUE;
BlockFace argNearest = null;
for (BlockFace pumpEndpoint : fn.rangeEndpoints) {
if (pumpEndpoint.isEquivalent(pumpLocation)) {
argNearest = pumpEndpoint;
if (visited.add(adjacentLocation.getConnectedPos())) {
queued.add(adjacentLocation.getOpposite());
shouldContinue = true;
}
}
if (canRemove)
iterator.remove();
}
if (!shouldContinue)
break;
}
Pair<Integer, Map<Direction, Boolean>> pair =
pipeGraph.get(pumpEndpoint.getConnectedPos());
if (pair == null)
continue;
Integer distanceFromPump = pair.getFirst();
Map<Direction, Boolean> pipeConnections = pair.getSecond();
if (!pipeConnections.containsKey(pumpEndpoint.getOppositeFace()))
continue;
if (nearest <= distanceFromPump)
continue;
nearest = distanceFromPump;
argNearest = pumpEndpoint;
// drawDebugOutlines();
}
if (argNearest != null) {
InterPumpEndpoint endpoint = new InterPumpEndpoint(world, argNearest.getOpposite(),
pump, otherPump, pumpLocation, fn.pumpLocation);
targets.add(endpoint);
fn.targets.add(endpoint.opposite(world));
}
if (!source.isPresent())
source = sourceSupplier.get();
if (!source.isPresent())
return;
if (targets.isEmpty())
return;
for (Pair<BlockFace, LazyOptional<IFluidHandler>> pair : targets) {
if (pair.getSecond()
.isPresent())
continue;
PipeConnection pipeConnection = get(pair.getFirst());
if (pipeConnection == null)
continue;
pipeConnection.source.ifPresent(fs -> {
if (fs.isEndpoint())
pair.setSecond(fs.provideHandler());
});
}
continue;
}
if (visited.contains(connectedPos))
continue;
if (distance > maxDistance)
continue;
FluidPipeBehaviour targetPipe = FluidPropagator.getPipe(world, connectedPos);
if (targetPipe == null)
continue;
if (targetPipe.isConnectedTo(connectedState, face.getOpposite()))
frontier.add(Pair.of(distance + 1, connectedPos));
}
}
int flowSpeed = transferSpeed;
for (boolean simulate : Iterate.trueAndFalse) {
FluidAction action = simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE;
IFluidHandler handler = source.orElse(null);
if (handler == null)
return;
FluidStack transfer = handler.drain(flowSpeed, action);
if (transfer.isEmpty())
return;
List<Pair<BlockFace, LazyOptional<IFluidHandler>>> availableOutputs = new ArrayList<>(targets);
while (!availableOutputs.isEmpty() && transfer.getAmount() > 0) {
int dividedTransfer = transfer.getAmount() / availableOutputs.size();
int remainder = transfer.getAmount() % availableOutputs.size();
for (Iterator<Pair<BlockFace, LazyOptional<IFluidHandler>>> iterator =
availableOutputs.iterator(); iterator.hasNext();) {
Pair<BlockFace, LazyOptional<IFluidHandler>> pair = iterator.next();
int toTransfer = dividedTransfer;
if (remainder > 0) {
toTransfer++;
remainder--;
}
public void assemble(IWorld world, PumpTileEntity pumpTE, BlockFace pumpLocation) {
Map<BlockFace, OpenEndedPipe> openEnds = pumpTE.getOpenEnds(pumpLocation.getFace());
openEnds.values()
.forEach(OpenEndedPipe::markStale);
this.pumpLocation = pumpLocation;
if (!collectEndpoint(world, pumpLocation, openEnds, 0)) {
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
int maxDistance = FluidPropagator.getPumpRange();
frontier.add(Pair.of(0, pumpLocation.getConnectedPos()));
while (!frontier.isEmpty()) {
Pair<Integer, BlockPos> entry = frontier.remove(0);
int distance = entry.getFirst();
BlockPos currentPos = entry.getSecond();
if (!world.isAreaLoaded(currentPos, 0))
continue;
if (visited.contains(currentPos))
continue;
visited.add(currentPos);
BlockState currentState = world.getBlockState(currentPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, currentPos);
if (pipe == null)
continue;
for (Direction face : FluidPropagator.getPipeConnections(currentState, pipe)) {
if (!pipe.canTransferToward(FluidStack.EMPTY, world.getBlockState(currentPos), face, false))
continue;
BlockFace blockFace = new BlockFace(currentPos, face);
BlockPos connectedPos = blockFace.getConnectedPos();
if (connectedPos.equals(pumpLocation.getPos())) {
addEntry(blockFace.getPos(), blockFace.getFace(), true, distance);
continue;
}
if (!world.isAreaLoaded(connectedPos, 0))
continue;
if (collectEndpoint(world, blockFace, openEnds, distance))
continue;
FluidPipeBehaviour pipeBehaviour = FluidPropagator.getPipe(world, connectedPos);
if (pipeBehaviour == null)
continue;
if (visited.contains(connectedPos))
continue;
if (distance + 1 >= maxDistance) {
rangeEndpoints.add(blockFace);
addEntry(currentPos, face, false, distance);
FluidPropagator.showBlockFace(blockFace)
.lineWidth(1 / 8f)
.colored(0xff0000);
if (transfer.isEmpty())
break;
IFluidHandler targetHandler = pair.getSecond()
.orElse(null);
if (targetHandler == null) {
iterator.remove();
continue;
}
addConnection(connectedPos, currentPos, face.getOpposite(), distance);
frontier.add(Pair.of(distance + 1, connectedPos));
FluidStack divided = transfer.copy();
divided.setAmount(toTransfer);
int fill = targetHandler.fill(divided, action);
transfer.setAmount(transfer.getAmount() - fill);
if (fill < toTransfer)
iterator.remove();
}
}
}
Set<BlockFace> staleEnds = new HashSet<>();
openEnds.entrySet()
.forEach(e -> {
if (e.getValue()
.isStale())
staleEnds.add(e.getKey());
});
staleEnds.forEach(openEnds::remove);
connectToPumps = true;
}
private FluidNetworkEndpoint reuseOrCreateOpenEnd(IWorld world, Map<BlockFace, OpenEndedPipe> openEnds,
BlockFace toCreate) {
OpenEndedPipe openEndedPipe = null;
if (openEnds.containsKey(toCreate)) {
openEndedPipe = openEnds.get(toCreate);
openEndedPipe.unmarkStale();
} else {
openEndedPipe = new OpenEndedPipe(toCreate);
openEnds.put(toCreate, openEndedPipe);
}
return new FluidNetworkEndpoint(world, toCreate, openEndedPipe.getCapability());
}
private boolean collectEndpoint(IWorld world, BlockFace blockFace, Map<BlockFace, OpenEndedPipe> openEnds,
int distance) {
BlockPos connectedPos = blockFace.getConnectedPos();
BlockState connectedState = world.getBlockState(connectedPos);
// other pipe, no endpoint
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, connectedPos);
if (pipe != null && pipe.isConnectedTo(connectedState, blockFace.getOppositeFace()))
return false;
TileEntity tileEntity = world.getTileEntity(connectedPos);
// fluid handler endpoint
Direction face = blockFace.getFace();
if (tileEntity != null) {
LazyOptional<IFluidHandler> capability =
tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face.getOpposite());
if (capability.isPresent()) {
targets.add(new FluidNetworkEndpoint(world, blockFace, capability));
addEntry(blockFace.getPos(), face, false, distance);
FluidPropagator.showBlockFace(blockFace)
.colored(0x00b7c2)
.lineWidth(1 / 8f);
return true;
flowSpeed -= transfer.getAmount();
transfer = FluidStack.EMPTY;
}
}
// open endpoint
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == face.getAxis()) {
rangeEndpoints.add(blockFace);
addEntry(blockFace.getPos(), face, false, distance);
return true;
}
if (!FluidPropagator.isOpenEnd(world, blockFace.getPos(), face))
return false;
// private void drawDebugOutlines() {
// FluidPropagator.showBlockFace(start)
// .lineWidth(1 / 8f)
// .colored(0xff0000);
// for (Pair<BlockFace, LazyOptional<IFluidHandler>> pair : targets)
// FluidPropagator.showBlockFace(pair.getFirst())
// .lineWidth(1 / 8f)
// .colored(0x00ff00);
// for (Pair<BlockFace, PipeConnection> pair : frontier)
// FluidPropagator.showBlockFace(pair.getFirst())
// .lineWidth(1 / 4f)
// .colored(0xfaaa33);
// }
targets.add(reuseOrCreateOpenEnd(world, openEnds, blockFace));
addEntry(blockFace.getPos(), face, false, distance);
FluidPropagator.showBlockFace(blockFace)
.colored(0xb700c2)
.lineWidth(1 / 8f);
return true;
}
private void addConnection(BlockPos from, BlockPos to, Direction direction, int distance) {
addEntry(from, direction, true, distance);
addEntry(to, direction.getOpposite(), false, distance + 1);
}
private void addEntry(BlockPos pos, Direction direction, boolean outbound, int distance) {
if (!pipeGraph.containsKey(pos))
pipeGraph.put(pos, Pair.of(distance, new HashMap<>()));
pipeGraph.get(pos)
.getSecond()
.put(direction, outbound);
}
public void reAssemble(IWorld world, PumpTileEntity pumpTE, BlockFace pumpLocation) {
rangeEndpoints.clear();
public void reset() {
frontier.clear();
visited.clear();
targets.clear();
pipeGraph.clear();
assemble(world, pumpTE, pumpLocation);
queued.clear();
queued.add(start);
pauseBeforePropagation = 2;
}
public void remove(IWorld world) {
clearFlows(world, false);
@Nullable
private PipeConnection get(BlockFace location) {
BlockPos pos = location.getPos();
FluidTransportBehaviour fluidTransfer = getFluidTransfer(pos);
if (fluidTransfer == null)
return null;
return fluidTransfer.getConnection(location.getFace());
}
public void clearFlows(IWorld world, boolean saveState) {
for (FluidNetworkFlow networkFlow : flows) {
if (!networkFlow.getFluidStack()
.isEmpty())
networkFlow.addToSkippedConnections(world);
networkFlow.resetFlow(world);
private boolean isPresent(BlockFace location) {
return world.isAreaLoaded(location.getPos(), 0);
}
flows.clear();
@Nullable
private FluidTransportBehaviour getFluidTransfer(BlockPos pos) {
WeakReference<FluidTransportBehaviour> weakReference = cache.get(pos);
FluidTransportBehaviour behaviour = weakReference != null ? weakReference.get() : null;
if (behaviour != null && behaviour.tileEntity.isRemoved())
behaviour = null;
if (behaviour == null) {
behaviour = TileEntityBehaviour.get(world, pos, FluidTransportBehaviour.TYPE);
if (behaviour != null)
cache.put(pos, new WeakReference<>(behaviour));
}
return behaviour;
}
}

View file

@ -1,49 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.foundation.utility.BlockFace;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.IWorld;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
public class FluidNetworkEndpoint {
BlockFace location;
protected LazyOptional<IFluidHandler> handler;
public FluidNetworkEndpoint(IWorld world, BlockFace location, LazyOptional<IFluidHandler> handler) {
this.location = location;
this.handler = handler;
this.handler.addListener($ -> onHandlerInvalidated(world));
}
protected void onHandlerInvalidated(IWorld world) {
IFluidHandler tank = handler.orElse(null);
if (tank != null)
return;
TileEntity tileEntity = world.getTileEntity(location.getConnectedPos());
if (tileEntity == null)
return;
LazyOptional<IFluidHandler> capability =
tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, location.getOppositeFace());
if (capability.isPresent()) {
handler = capability;
handler.addListener($ -> onHandlerInvalidated(world));
}
}
public FluidStack provideFluid() {
IFluidHandler tank = provideHandler().orElse(null);
if (tank == null)
return FluidStack.EMPTY;
return tank.drain(1, FluidAction.SIMULATE);
}
public LazyOptional<IFluidHandler> provideHandler() {
return handler;
}
}

View file

@ -1,306 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraftforge.fluids.FluidStack;
class FluidNetworkFlow {
@FunctionalInterface
static interface PipeFlowConsumer {
void accept(FluidPipeBehaviour pipe, Direction face, boolean inbound);
}
/**
*
*/
private final FluidNetwork activePipeNetwork;
FluidNetworkEndpoint source;
FluidStack fluidStack;
Set<BlockFace> flowPointers;
Set<FluidNetworkEndpoint> outputEndpoints;
boolean pumpReached;
boolean pulling;
float speed;
public FluidNetworkFlow(FluidNetwork activePipeNetwork, FluidNetworkEndpoint source, IWorld world,
boolean pulling) {
this.activePipeNetwork = activePipeNetwork;
this.source = source;
this.pulling = pulling;
flowPointers = new HashSet<>();
outputEndpoints = new HashSet<>();
fluidStack = FluidStack.EMPTY;
tick(world, 0);
}
void resetFlow(IWorld world) {
fluidStack = FluidStack.EMPTY;
flowPointers.clear();
outputEndpoints.clear();
pumpReached = false;
forEachPipeFlow(world, (pipe, face, inbound) -> pipe.removeFlow(this, face, inbound));
}
void addToSkippedConnections(IWorld world) {
forEachPipeFlow(world, (pipe, face, inbound) -> {
if (!pipe.getFluid().isFluidEqual(fluidStack))
return;
BlockFace blockFace = new BlockFace(pipe.getPos(), face);
this.activePipeNetwork.previousFlow.put(blockFace, pipe.getFluid());
});
}
void forEachPipeFlow(IWorld world, FluidNetworkFlow.PipeFlowConsumer consumer) {
Set<BlockFace> flowPointers = new HashSet<>();
flowPointers.add(getSource());
// Update all branches of this flow, and create new ones if necessary
while (!flowPointers.isEmpty()) {
List<BlockFace> toAdd = new ArrayList<>();
for (Iterator<BlockFace> iterator = flowPointers.iterator(); iterator.hasNext();) {
BlockFace flowPointer = iterator.next();
BlockPos currentPos = flowPointer.getPos();
FluidPipeBehaviour pipe = getPipeInTree(world, currentPos);
if (pipe == null) {
iterator.remove();
continue;
}
Map<Direction, Boolean> directions = this.activePipeNetwork.pipeGraph.get(currentPos)
.getSecond();
for (Entry<Direction, Boolean> entry : directions.entrySet()) {
boolean inbound = entry.getValue() != pulling;
Direction face = entry.getKey();
if (inbound && face != flowPointer.getFace())
continue;
consumer.accept(pipe, face, inbound);
if (inbound)
continue;
toAdd.add(new BlockFace(currentPos.offset(face), face.getOpposite()));
}
iterator.remove();
}
flowPointers.addAll(toAdd);
}
}
void tick(IWorld world, float speed) {
boolean skipping = speed == 0;
Map<BlockFace, FluidStack> previousFlow = this.activePipeNetwork.previousFlow;
if (skipping && previousFlow.isEmpty())
return;
this.speed = speed;
FluidStack provideFluid = source.provideFluid();
if (!fluidStack.isEmpty() && !fluidStack.isFluidEqual(provideFluid)) {
resetFlow(world);
return;
}
fluidStack = provideFluid.copy();
// There is currently no unfinished flow being followed
if (flowPointers.isEmpty()) {
// The fluid source has run out -> reset
if (fluidStack.isEmpty()) {
if (hasValidTargets())
resetFlow(world);
return;
}
// Keep the flows if all is well
if (hasValidTargets())
return;
// Start a new flow from or towards the pump
BlockFace source = getSource();
if (tryConnectTo(world, source.getOpposite()))
return;
flowPointers.add(source);
}
boolean skipped = false;
Set<BlockFace> pausedPointers = new HashSet<>();
do {
skipped = false;
List<BlockFace> toAdd = null;
// Update all branches of this flow, and create new ones if necessary
for (Iterator<BlockFace> iterator = flowPointers.iterator(); iterator.hasNext();) {
BlockFace flowPointer = iterator.next();
BlockPos currentPos = flowPointer.getPos();
if (pausedPointers.contains(flowPointer))
continue;
FluidPipeBehaviour pipe = getPipeInTree(world, currentPos);
if (pipe == null) {
iterator.remove();
continue;
}
Map<Direction, Boolean> directions = this.activePipeNetwork.pipeGraph.get(currentPos)
.getSecond();
boolean inboundComplete = false;
boolean allFlowsComplete = true;
BlockState state = world.getBlockState(currentPos);
// First loop only inbound flows of a pipe to see if they have reached the
// center
for (boolean inboundPass : Iterate.trueAndFalse) {
if (!inboundPass && !inboundComplete)
break;
// For all connections of the pipe tree of the pump
for (Entry<Direction, Boolean> entry : directions.entrySet()) {
Boolean awayFromPump = entry.getValue();
Direction direction = entry.getKey();
boolean inbound = awayFromPump != pulling;
if (inboundPass && direction != flowPointer.getFace())
continue;
if (!inboundPass && inbound)
continue;
if (!pipe.canTransferToward(fluidStack, state, direction, inbound))
continue;
BlockFace blockface = new BlockFace(currentPos, direction);
if (!pipe.hasStartedFlow(this, direction, inbound))
pipe.addFlow(this, direction, inbound, false);
if (skipping && canSkip(previousFlow, blockface)) {
pipe.skipFlow(direction, inbound);
FluidPropagator.showBlockFace(blockface)
.colored(0x0)
.lineWidth(1 / 8f);
skipped = true;
}
if (!pipe.hasCompletedFlow(direction, inbound)) {
allFlowsComplete = false;
continue;
}
if (inboundPass) {
inboundComplete = true;
continue;
}
// Outward pass, check if any target was reached
tryConnectTo(world, blockface);
}
}
if (!allFlowsComplete && !skipping)
continue;
// Create a new flow branch at each outward pipe connection
for (Entry<Direction, Boolean> entry : directions.entrySet()) {
if (entry.getValue() != pulling)
continue;
Direction face = entry.getKey();
if (!pipe.canTransferToward(fluidStack, state, face, false))
continue;
BlockFace addedBlockFace = new BlockFace(currentPos.offset(face), face.getOpposite());
if (skipping && !canSkip(previousFlow, addedBlockFace)) {
allFlowsComplete = false;
continue;
}
if (toAdd == null)
toAdd = new ArrayList<>();
toAdd.add(addedBlockFace);
}
if (!allFlowsComplete && skipping) {
pausedPointers.add(flowPointer);
continue;
}
iterator.remove();
} // End of branch loop
if (toAdd != null)
flowPointers.addAll(toAdd);
} while (skipping && skipped);
}
private boolean canSkip(Map<BlockFace, FluidStack> previousFlow, BlockFace blockface) {
return previousFlow.containsKey(blockface) && previousFlow.get(blockface)
.isFluidEqual(fluidStack);
}
private boolean tryConnectTo(IWorld world, BlockFace blockface) {
// Pulling flow, target is the pump
if (pulling) {
if (!this.activePipeNetwork.pumpLocation.getOpposite()
.equals(blockface))
return false;
pumpReached = true;
TileEntity targetTE = world.getTileEntity(this.activePipeNetwork.pumpLocation.getPos());
if (targetTE instanceof PumpTileEntity)
((PumpTileEntity) targetTE).setProvidedFluid(fluidStack);
FluidPropagator.showBlockFace(this.activePipeNetwork.pumpLocation)
.colored(0x799351)
.lineWidth(1 / 8f);
return true;
}
// Pushing flow, targets are the endpoints
for (FluidNetworkEndpoint networkEndpoint : this.activePipeNetwork.targets) {
if (!networkEndpoint.location.isEquivalent(blockface))
continue;
outputEndpoints.add(networkEndpoint);
FluidPropagator.showBlockFace(blockface)
.colored(0x799351)
.lineWidth(1 / 8f);
return !(networkEndpoint instanceof InterPumpEndpoint);
}
return false;
}
private BlockFace getSource() {
return pulling ? source.location : this.activePipeNetwork.pumpLocation.getOpposite();
}
private FluidPipeBehaviour getPipeInTree(IWorld world, BlockPos currentPos) {
if (!world.isAreaLoaded(currentPos, 0))
return null;
if (!this.activePipeNetwork.pipeGraph.containsKey(currentPos))
return null;
return TileEntityBehaviour.get(world, currentPos, FluidPipeBehaviour.TYPE);
}
boolean hasValidTargets() {
return pumpReached || !outputEndpoints.isEmpty();
}
public float getSpeed() {
return speed;
}
public FluidStack getFluidStack() {
return fluidStack;
}
}

View file

@ -1,77 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.fluids.pipes.EncasedPipeBlock;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import net.minecraft.block.BlockState;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
public class FluidPipeAttachmentBehaviour extends BracketedTileEntityBehaviour {
public static BehaviourType<FluidPipeAttachmentBehaviour> TYPE = new BehaviourType<>();
public AttachmentTypes getAttachment(ILightReader world, BlockPos pos, BlockState state, Direction direction) {
if (!isPipeConnectedTowards(state, direction))
return AttachmentTypes.NONE;
BlockPos offsetPos = pos.offset(direction);
BlockState facingState = world.getBlockState(offsetPos);
if (facingState.getBlock() instanceof PumpBlock && facingState.get(PumpBlock.FACING)
.getAxis() == direction.getAxis())
return AttachmentTypes.NONE;
if (AllBlocks.ENCASED_FLUID_PIPE.has(facingState)
&& facingState.get(EncasedPipeBlock.FACING_TO_PROPERTY_MAP.get(direction.getOpposite())))
return AttachmentTypes.NONE;
if (FluidPropagator.hasFluidCapability(facingState, world, offsetPos, direction)
&& !AllBlocks.HOSE_PULLEY.has(facingState))
return AttachmentTypes.DRAIN;
return AttachmentTypes.RIM;
}
public boolean isPipeConnectedTowards(BlockState state, Direction direction) {
FluidPipeBehaviour fluidPipeBehaviour = tileEntity.getBehaviour(FluidPipeBehaviour.TYPE);
if (fluidPipeBehaviour == null)
return false;
// BlockState bracket = getBracket();
// if (bracket != Blocks.AIR.getDefaultState() && bracket.get(BracketBlock.FACING) == direction)
// return false;
return fluidPipeBehaviour.isConnectedTo(state, direction);
}
public static enum AttachmentTypes {
NONE, RIM, DRAIN;
public boolean hasModel() {
return this != NONE;
}
}
public FluidPipeAttachmentBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public BehaviourType<?> getType() {
return TYPE;
}
@Override
public boolean canHaveBracket() {
BlockState blockState = tileEntity.getBlockState();
if (blockState.getBlock() instanceof PumpBlock)
return false;
if (blockState.getBlock() instanceof EncasedPipeBlock)
return false;
return true;
}
}

View file

@ -1,484 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import javax.annotation.Nullable;
import com.simibubi.create.AllSpecialTextures;
import com.simibubi.create.CreateClient;
import com.simibubi.create.content.contraptions.KineticDebugger;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.LerpedFloat.Chaser;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.particles.IParticleData;
import net.minecraft.util.Direction;
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.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.DistExecutor;
public abstract class FluidPipeBehaviour extends TileEntityBehaviour {
public static BehaviourType<FluidPipeBehaviour> TYPE = new BehaviourType<>();
public static final int MAX_PARTICLE_RENDER_DISTANCE = 20;
public static final int SPLASH_PARTICLE_AMOUNT = 1;
public static final float IDLE_PARTICLE_SPAWN_CHANCE = 1 / 800f;
public static final Random r = new Random();
// Direction -> (inboundflows{}, outwardflows{})
Map<Direction, Couple<PipeFlows>> allFlows;
FluidStack fluid;
Couple<FluidStack> collision;
public FluidPipeBehaviour(SmartTileEntity te) {
super(te);
allFlows = new IdentityHashMap<>();
fluid = FluidStack.EMPTY;
}
@Override
public BehaviourType<?> getType() {
return TYPE;
}
public void notifyNetwork() {
FluidPropagator.propagateChangedPipe(this.getWorld(), tileEntity.getPos(), tileEntity.getBlockState());
}
public boolean canTransferToward(FluidStack fluid, BlockState state, Direction direction, boolean inbound) {
return isConnectedTo(state, direction);
}
public abstract boolean isConnectedTo(BlockState state, Direction direction);
public float getRimRadius(BlockState state, Direction direction) {
return 1 / 4f + 1 / 64f;
}
public boolean hasStartedFlow(FluidNetworkFlow flow, Direction face, boolean inbound) {
return allFlows.containsKey(face) && allFlows.get(face)
.get(inbound)
.hasFlow(flow);
}
public boolean hasCompletedFlow(Direction face, boolean inbound) {
return allFlows.containsKey(face) && allFlows.get(face)
.get(inbound)
.isCompleted();
}
@Override
public void write(CompoundNBT compound, boolean client) {
compound.put("Fluid", fluid.writeToNBT(new CompoundNBT()));
ListNBT flows = new ListNBT();
for (Direction face : Iterate.directions)
for (boolean inbound : Iterate.trueAndFalse) {
LerpedFloat flowProgress = getFlowProgress(face, inbound);
if (flowProgress == null)
continue;
CompoundNBT nbt = new CompoundNBT();
NBTHelper.writeEnum(nbt, "Face", face);
nbt.putBoolean("In", inbound);
PipeFlows pipeFlows = allFlows.get(face)
.get(inbound);
Set<FluidNetworkFlow> participants = pipeFlows.participants;
nbt.putBoolean("Silent", participants == null || participants.isEmpty());
nbt.put("Progress", flowProgress.writeNBT());
if (client)
nbt.putFloat("Strength", pipeFlows.bestFlowStrength);
flows.add(nbt);
}
compound.put("Flows", flows);
}
@Override
public void read(CompoundNBT compound, boolean client) {
fluid = FluidStack.loadFluidStackFromNBT(compound.getCompound("Fluid"));
if (client) {
for (Direction face : Iterate.directions)
if (allFlows.containsKey(face))
allFlows.get(face)
.forEach(pf -> pf.progress = null);
}
NBTHelper.iterateCompoundList(compound.getList("Flows", NBT.TAG_COMPOUND), nbt -> {
Direction face = NBTHelper.readEnum(nbt, "Face", Direction.class);
boolean inbound = nbt.getBoolean("In");
LerpedFloat progress = createFlowProgress(0);
progress.readNBT(nbt.getCompound("Progress"), false);
addFlow(null, face, inbound, nbt.getBoolean("Silent"));
setFlowProgress(face, inbound, progress);
if (client)
setVisualFlowStrength(face, inbound, nbt.getFloat("Strength"));
});
if (!client)
return;
for (Direction face : Iterate.directions) {
if (!allFlows.containsKey(face))
return;
Couple<PipeFlows> couple = allFlows.get(face);
if (couple.get(true).progress == null && couple.get(false).progress == null)
allFlows.remove(face);
if (allFlows.isEmpty())
clear();
}
}
public void addFlow(@Nullable FluidNetworkFlow flow, Direction face, boolean inbound, boolean silent) {
if (flow != null) {
FluidStack fluid = flow.getFluidStack();
if (!this.fluid.isEmpty() && !fluid.isFluidEqual(this.fluid)) {
collision = Couple.create(this.fluid, fluid);
return;
}
this.fluid = fluid;
}
if (!allFlows.containsKey(face)) {
allFlows.put(face, Couple.create(PipeFlows::new));
if (inbound && !silent)
spawnSplashOnRim(face);
}
if (flow != null) {
PipeFlows flows = allFlows.get(face)
.get(inbound);
flows.addFlow(flow);
contentsChanged();
}
}
public void removeFlow(FluidNetworkFlow flow, Direction face, boolean inbound) {
if (!allFlows.containsKey(face))
return;
Couple<PipeFlows> couple = allFlows.get(face);
couple.get(inbound)
.removeFlow(flow);
contentsChanged();
if (!couple.get(true)
.isActive()
&& !couple.get(false)
.isActive())
allFlows.remove(face);
if (allFlows.isEmpty())
clear();
}
public void setVisualFlowStrength(Direction face, boolean inbound, float strength) {
if (!allFlows.containsKey(face))
return;
allFlows.get(face)
.get(inbound).bestFlowStrength = strength;
}
public void setFlowProgress(Direction face, boolean inbound, LerpedFloat progress) {
if (!allFlows.containsKey(face))
return;
allFlows.get(face)
.get(inbound).progress = progress;
}
public LerpedFloat getFlowProgress(Direction face, boolean inbound) {
if (!allFlows.containsKey(face))
return null;
return allFlows.get(face)
.get(inbound).progress;
}
public void skipFlow(Direction face, boolean inbound) {
if (!allFlows.containsKey(face))
return;
Couple<PipeFlows> couple = allFlows.get(face);
couple.get(inbound)
.skip();
}
public void clear() {
allFlows.clear();
fluid = FluidStack.EMPTY;
contentsChanged();
}
public void spawnSplashOnRim(Direction face) {
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> spawnSplashOnRimInner(face));
}
public void spawnParticles() {
DistExecutor.runWhenOn(Dist.CLIENT, () -> this::spawnParticlesInner);
}
@OnlyIn(Dist.CLIENT)
private void spawnParticlesInner() {
if (!isRenderEntityWithinDistance(tileEntity.getPos()))
return;
if (fluid.isEmpty())
return;
World world = Minecraft.getInstance().world;
BlockPos pos = tileEntity.getPos();
BlockState state = world.getBlockState(pos);
for (Direction face : Iterate.directions) {
boolean open = FluidPropagator.isOpenEnd(world, pos, face);
if (isConnectedTo(state, face)) {
if (open) {
spawnPouringLiquid(world, state, fluid, face, 1);
continue;
}
if (r.nextFloat() < IDLE_PARTICLE_SPAWN_CHANCE)
spawnRimParticles(world, state, fluid, face, 1);
}
}
}
@OnlyIn(Dist.CLIENT)
private void spawnSplashOnRimInner(Direction face) {
if (!isRenderEntityWithinDistance(tileEntity.getPos()))
return;
if (fluid.isEmpty())
return;
World world = Minecraft.getInstance().world;
BlockPos pos = tileEntity.getPos();
BlockState state = world.getBlockState(pos);
spawnRimParticles(world, state, fluid, face, SPLASH_PARTICLE_AMOUNT);
}
@OnlyIn(Dist.CLIENT)
private void spawnRimParticles(World world, BlockState state, FluidStack fluid, Direction side, int amount) {
BlockPos pos = tileEntity.getPos();
if (FluidPropagator.isOpenEnd(world, pos, side)) {
spawnPouringLiquid(world, state, fluid, side, amount);
return;
}
IParticleData particle = FluidFX.getDrippingParticle(fluid);
float rimRadius = getRimRadius(state, side);
FluidFX.spawnRimParticles(world, pos, side, amount, particle, rimRadius);
}
@OnlyIn(Dist.CLIENT)
private void spawnPouringLiquid(World world, BlockState state, FluidStack fluid, Direction side, int amount) {
IParticleData particle = FluidFX.getFluidParticle(fluid);
float rimRadius = getRimRadius(state, side);
Vec3d directionVec = new Vec3d(side.getDirectionVec());
BlockPos pos = tileEntity.getPos();
Couple<PipeFlows> couple = allFlows.get(side);
if (couple == null)
return;
couple.forEachWithContext((flow, inbound) -> {
if (flow.progress == null)
return;
FluidFX.spawnPouringLiquid(world, pos, amount, particle, rimRadius, directionVec, inbound);
});
}
@OnlyIn(Dist.CLIENT)
public static boolean isRenderEntityWithinDistance(BlockPos pos) {
Entity renderViewEntity = Minecraft.getInstance()
.getRenderViewEntity();
if (renderViewEntity == null)
return false;
Vec3d center = VecHelper.getCenterOf(pos);
if (renderViewEntity.getPositionVec()
.distanceTo(center) > MAX_PARTICLE_RENDER_DISTANCE)
return false;
return true;
}
static AxisAlignedBB smallCenter = new AxisAlignedBB(BlockPos.ZERO).shrink(.25);
@Override
public void tick() {
super.tick();
boolean isRemote = getWorld().isRemote;
allFlows.values()
.forEach(c -> c.forEach(pf -> pf.tick(isRemote)));
if (isRemote) {
clientTick();
return;
}
if (collision != null) {
FluidReactions.handlePipeFlowCollision(getWorld(), tileEntity.getPos(), collision.getFirst(),
collision.getSecond());
collision = null;
return;
}
}
public Pair<Boolean, LerpedFloat> getStrogestFlow(Direction side) {
Couple<PipeFlows> couple = allFlows.get(side);
if (couple == null)
return null;
PipeFlows in = couple.get(true);
PipeFlows out = couple.get(false);
Couple<LerpedFloat> progress = couple.map(pf -> pf.progress);
boolean inboundStronger = false;
if (in.isCompleted() != out.isCompleted()) {
inboundStronger = in.isCompleted();
} else if ((progress.get(true) == null) != (progress.get(false) == null)) {
inboundStronger = progress.get(true) != null;
} else {
if (progress.get(true) != null)
inboundStronger = in.bestFlowStrength > out.bestFlowStrength;
}
return Pair.of(inboundStronger, progress.get(inboundStronger));
}
private void clientTick() {
spawnParticles();
if (!KineticDebugger.isActive())
return;
if (fluid.isEmpty())
return;
for (Entry<Direction, Couple<PipeFlows>> entry : allFlows.entrySet()) {
Direction face = entry.getKey();
Vec3d directionVec = new Vec3d(face.getDirectionVec());
float size = 1 / 4f;
boolean extended = !isConnectedTo(tileEntity.getBlockState(), face.getOpposite());
float length = extended ? .75f : .5f;
entry.getValue()
.forEachWithContext((flow, inbound) -> {
if (flow.progress == null)
return;
float value = flow.progress.getValue();
Vec3d start = directionVec.scale(inbound ? .5 : .5f - length);
Vec3d offset = directionVec.scale(length * (inbound ? -1 : 1))
.scale(value);
Vec3d scale = new Vec3d(1, 1, 1).subtract(directionVec.scale(face.getAxisDirection()
.getOffset()))
.scale(size);
AxisAlignedBB bb =
new AxisAlignedBB(start, start.add(offset)).offset(VecHelper.getCenterOf(tileEntity.getPos()))
.grow(scale.x, scale.y, scale.z);
int color = 0x7fdbda;
if (!fluid.isEmpty()) {
Fluid fluid2 = fluid.getFluid();
if (fluid2 == Fluids.WATER)
color = 0x1D4D9B;
if (fluid2 == Fluids.LAVA)
color = 0xFF773D;
}
CreateClient.outliner.chaseAABB(Pair.of(this, face), bb)
.withFaceTexture(AllSpecialTextures.CUTOUT_CHECKERED)
.colored(color)
.lineWidth(1 / 16f);
});
}
}
private void contentsChanged() {
tileEntity.markDirty();
tileEntity.sendData();
}
private LerpedFloat createFlowProgress(double speed) {
return LerpedFloat.linear()
.startWithValue(0)
.chase(1, speed, Chaser.LINEAR);
}
public FluidStack getFluid() {
return fluid;
}
class PipeFlows {
LerpedFloat progress;
Set<FluidNetworkFlow> participants;
float bestFlowStrength;
void addFlow(FluidNetworkFlow flow) {
if (participants == null)
participants = new HashSet<>();
participants.add(flow);
if (progress == null) {
progress = createFlowProgress(flow.getSpeed());
}
}
boolean hasFlow(FluidNetworkFlow flow) {
return participants != null && participants.contains(flow);
}
void tick(boolean onClient) {
if (progress == null)
return;
if (!onClient) {
if (participants == null)
return;
bestFlowStrength = 0;
for (FluidNetworkFlow networkFlow : participants)
bestFlowStrength = Math.max(bestFlowStrength, networkFlow.getSpeed());
if (isCompleted())
return;
if (progress.updateChaseSpeed(bestFlowStrength))
contentsChanged();
}
progress.tickChaser();
}
void skip() {
progress = LerpedFloat.linear()
.startWithValue(1);
}
void removeFlow(FluidNetworkFlow flow) {
if (participants == null)
return;
participants.remove(flow);
}
boolean isActive() {
return participants != null && !participants.isEmpty();
}
boolean isCompleted() {
return progress != null && progress.getValue() == 1;
}
}
}

View file

@ -7,16 +7,14 @@ import java.util.Set;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.fluids.PipeConnection.Flow;
import com.simibubi.create.content.contraptions.fluids.pipes.AxisPipeBlock;
import com.simibubi.create.content.contraptions.fluids.pipes.FluidPipeBlock;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.outliner.Outline.OutlineParams;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@ -26,7 +24,6 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
@ -35,6 +32,94 @@ import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
public class FluidPropagator {
public static void propagateChangedPipe(IWorld world, BlockPos pipePos, BlockState pipeState) {
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
Set<Pair<PumpTileEntity, Direction>> discoveredPumps = new HashSet<>();
frontier.add(Pair.of(0, pipePos));
// Visit all connected pumps to update their network
while (!frontier.isEmpty()) {
Pair<Integer, BlockPos> pair = frontier.remove(0);
BlockPos currentPos = pair.getSecond();
if (visited.contains(currentPos))
continue;
visited.add(currentPos);
BlockState currentState = currentPos.equals(pipePos) ? pipeState : world.getBlockState(currentPos);
FluidTransportBehaviour pipe = getPipe(world, currentPos);
if (pipe == null)
continue;
pipe.wipePressure();
for (Direction direction : getPipeConnections(currentState, pipe)) {
BlockPos target = currentPos.offset(direction);
if (!world.isAreaLoaded(target, 0))
continue;
TileEntity tileEntity = world.getTileEntity(target);
BlockState targetState = world.getBlockState(target);
if (tileEntity instanceof PumpTileEntity) {
if (!AllBlocks.MECHANICAL_PUMP.has(targetState) || targetState.get(PumpBlock.FACING)
.getAxis() != direction.getAxis())
continue;
discoveredPumps.add(Pair.of((PumpTileEntity) tileEntity, direction.getOpposite()));
continue;
}
if (visited.contains(target))
continue;
FluidTransportBehaviour targetPipe = getPipe(world, target);
if (targetPipe == null)
continue;
Integer distance = pair.getFirst();
if (distance >= getPumpRange() && !targetPipe.hasAnyPressure())
continue;
if (targetPipe.canHaveFlowToward(targetState, direction.getOpposite()))
frontier.add(Pair.of(distance + 1, target));
}
}
discoveredPumps.forEach(pair -> pair.getFirst()
.updatePipesOnSide(pair.getSecond()));
}
public static void resetAffectedFluidNetworks(World world, BlockPos start, Direction side) {
List<BlockPos> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
frontier.add(start);
while (!frontier.isEmpty()) {
BlockPos pos = frontier.remove(0);
if (visited.contains(pos))
continue;
visited.add(pos);
FluidTransportBehaviour pipe = getPipe(world, pos);
if (pipe == null)
continue;
for (Direction d : Iterate.directions) {
if (pos.equals(start) && d != side)
continue;
BlockPos target = pos.offset(d);
if (visited.contains(target))
continue;
PipeConnection connection = pipe.getConnection(d);
if (connection == null)
continue;
if (!connection.hasFlow())
continue;
Flow flow = connection.flow.get();
if (!flow.inbound)
continue;
connection.resetNetwork();
frontier.add(target);
}
}
}
public static Direction validateNeighbourChange(BlockState state, World world, BlockPos pos, Block otherBlock,
BlockPos neighborPos, boolean isMoving) {
if (world.isRemote)
@ -58,15 +143,15 @@ public class FluidPropagator {
return null;
}
public static FluidPipeBehaviour getPipe(IBlockReader reader, BlockPos pos) {
return TileEntityBehaviour.get(reader, pos, FluidPipeBehaviour.TYPE);
public static FluidTransportBehaviour getPipe(IBlockReader reader, BlockPos pos) {
return TileEntityBehaviour.get(reader, pos, FluidTransportBehaviour.TYPE);
}
public static boolean isOpenEnd(IBlockReader reader, BlockPos pos, Direction side) {
BlockPos connectedPos = pos.offset(side);
BlockState connectedState = reader.getBlockState(connectedPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(reader, connectedPos);
if (pipe != null && pipe.isConnectedTo(connectedState, side.getOpposite()))
FluidTransportBehaviour pipe = FluidPropagator.getPipe(reader, connectedPos);
if (pipe != null && pipe.canHaveFlowToward(connectedState, side.getOpposite()))
return false;
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == side.getAxis())
@ -80,52 +165,10 @@ public class FluidPropagator {
return true;
}
public static void propagateChangedPipe(IWorld world, BlockPos pipePos, BlockState pipeState) {
List<BlockPos> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
frontier.add(pipePos);
// Visit all connected pumps to update their network
while (!frontier.isEmpty()) {
BlockPos currentPos = frontier.remove(0);
if (visited.contains(currentPos))
continue;
visited.add(currentPos);
BlockState currentState = currentPos.equals(pipePos) ? pipeState : world.getBlockState(currentPos);
FluidPipeBehaviour pipe = getPipe(world, currentPos);
if (pipe == null)
continue;
for (Direction direction : getPipeConnections(currentState, pipe)) {
BlockPos target = currentPos.offset(direction);
if (!world.isAreaLoaded(target, 0))
continue;
TileEntity tileEntity = world.getTileEntity(target);
BlockState targetState = world.getBlockState(target);
if (tileEntity instanceof PumpTileEntity) {
if (!AllBlocks.MECHANICAL_PUMP.has(targetState) || targetState.get(PumpBlock.FACING)
.getAxis() != direction.getAxis())
continue;
PumpTileEntity pump = (PumpTileEntity) tileEntity;
pump.updatePipesOnSide(direction.getOpposite());
continue;
}
if (visited.contains(target))
continue;
FluidPipeBehaviour targetPipe = getPipe(world, target);
if (targetPipe == null)
continue;
if (targetPipe.isConnectedTo(targetState, direction.getOpposite()))
frontier.add(target);
}
}
}
public static List<Direction> getPipeConnections(BlockState state, FluidPipeBehaviour pipe) {
public static List<Direction> getPipeConnections(BlockState state, FluidTransportBehaviour pipe) {
List<Direction> list = new ArrayList<>();
for (Direction d : Iterate.directions)
if (pipe.isConnectedTo(state, d))
if (pipe.canHaveFlowToward(state, d))
list.add(d);
return list;
}
@ -134,37 +177,38 @@ public class FluidPropagator {
return AllConfigs.SERVER.fluids.mechanicalPumpRange.get();
}
@Deprecated // Remove after pipes are fixed; comment out for production
public static OutlineParams showBlockFace(BlockFace face) {
MutableObject<OutlineParams> params = new MutableObject<>(new OutlineParams());
// static AxisAlignedBB smallCenter = new AxisAlignedBB(BlockPos.ZERO).shrink(.25);
//
// @Deprecated
// public static OutlineParams showBlockFace(BlockFace face) {
// MutableObject<OutlineParams> params = new MutableObject<>(new OutlineParams());
// DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> {
// Vec3d directionVec = new Vec3d(face.getFace()
// .getDirectionVec());
// Vec3d scaleVec = directionVec.scale(-.25f * face.getFace()
// .getAxisDirection()
// .getOffset());
// directionVec = directionVec.scale(.5f);
// directionVec = directionVec.scale(.45f);
// params.setValue(CreateClient.outliner.showAABB(face,
// FluidPropagator.smallCenter.offset(directionVec.add(new Vec3d(face.getPos())))
// .grow(scaleVec.x, scaleVec.y, scaleVec.z)
// .grow(1 / 16f)));
// });
return params.getValue();
}
// return params.getValue()
// .lineWidth(1 / 16f);
// }
static AxisAlignedBB smallCenter = new AxisAlignedBB(BlockPos.ZERO).shrink(.25);
public static boolean hasFluidCapability(BlockState state, IBlockReader world, BlockPos pos, Direction blockFace) {
if (!state.hasTileEntity())
return false;
public static boolean hasFluidCapability(IBlockReader world, BlockPos pos, Direction side) {
TileEntity tileEntity = world.getTileEntity(pos);
return tileEntity != null
&& tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, blockFace.getOpposite())
return tileEntity != null && tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side)
.isPresent();
}
@Nullable
public static Axis getStraightPipeAxis(BlockState state) {
if (state.getBlock() instanceof PumpBlock)
return state.get(PumpBlock.FACING)
.getAxis();
if (state.getBlock() instanceof AxisPipeBlock)
return state.get(AxisPipeBlock.AXIS);
if (!FluidPipeBlock.isPipe(state))

View file

@ -1,12 +1,15 @@
package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.AllFluids;
import com.simibubi.create.foundation.fluid.FluidHelper;
import com.simibubi.create.foundation.utility.BlockHelper;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack;
@ -19,19 +22,41 @@ public class FluidReactions {
BlockHelper.destroyBlock(world, pos, 1);
if (f1 == Fluids.WATER && f2 == Fluids.LAVA || f2 == Fluids.WATER && f1 == Fluids.LAVA)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
else if (f1 == Fluids.LAVA && FluidHelper.hasBlockState(f2)) {
BlockState lavaInteraction = AllFluids.getLavaInteraction(FluidHelper.convertToFlowing(f2)
.getDefaultState());
if (lavaInteraction != null)
world.setBlockState(pos, lavaInteraction);
} else if (f2 == Fluids.LAVA && FluidHelper.hasBlockState(f1)) {
BlockState lavaInteraction = AllFluids.getLavaInteraction(FluidHelper.convertToFlowing(f1)
.getDefaultState());
if (lavaInteraction != null)
world.setBlockState(pos, lavaInteraction);
}
}
public static void handlePipeSpillCollision(World world, BlockPos pos, Fluid pipeFluid, IFluidState worldFluid) {
Fluid pf = FluidHelper.convertToStill(pipeFluid);
Fluid wf = worldFluid.getFluid();
if (pf == Fluids.WATER && wf == Fluids.LAVA)
if (pf.isIn(FluidTags.WATER) && wf == Fluids.LAVA)
world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState());
if (pf == Fluids.WATER && wf == Fluids.FLOWING_LAVA)
else if (pf == Fluids.WATER && wf == Fluids.FLOWING_LAVA)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
else if (pf == Fluids.LAVA && wf == Fluids.WATER)
world.setBlockState(pos, Blocks.STONE.getDefaultState());
else if (pf == Fluids.LAVA && wf == Fluids.FLOWING_WATER)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
if (pf == Fluids.LAVA) {
BlockState lavaInteraction = AllFluids.getLavaInteraction(worldFluid);
if (lavaInteraction != null)
world.setBlockState(pos, lavaInteraction);
} else if (wf == Fluids.FLOWING_LAVA && FluidHelper.hasBlockState(pf)) {
BlockState lavaInteraction = AllFluids.getLavaInteraction(FluidHelper.convertToFlowing(pf)
.getDefaultState());
if (lavaInteraction != null)
world.setBlockState(pos, lavaInteraction);
}
}
}

View file

@ -0,0 +1,266 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.fluids.pipes.EncasedPipeBlock;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack;
public abstract class FluidTransportBehaviour extends TileEntityBehaviour {
public static BehaviourType<FluidTransportBehaviour> TYPE = new BehaviourType<>();
enum UpdatePhase {
WAIT_FOR_PUMPS, // Do not run Layer II logic while pumps could still be distributing pressure
FLIP_FLOWS, // Do not cut any flows until all pipes had a chance to reverse them
IDLE; // Operate normally
}
Map<Direction, PipeConnection> interfaces;
UpdatePhase phase;
public FluidTransportBehaviour(SmartTileEntity te) {
super(te);
phase = UpdatePhase.WAIT_FOR_PUMPS;
}
public boolean canPullFluidFrom(FluidStack fluid, BlockState state, Direction direction) {
return true;
}
public abstract boolean canHaveFlowToward(BlockState state, Direction direction);
@Override
public void initialize() {
super.initialize();
createConnectionData();
}
@Override
public void tick() {
super.tick();
World world = getWorld();
BlockPos pos = getPos();
boolean onClient = world.isRemote;
Collection<PipeConnection> connections = interfaces.values();
// Do not provide a lone pipe connection with its own flow input
PipeConnection singleSource = null;
// if (onClient) {
// connections.forEach(connection -> {
// connection.visualizeFlow(pos);
// connection.visualizePressure(pos);
// });
// }
if (phase == UpdatePhase.WAIT_FOR_PUMPS) {
phase = UpdatePhase.FLIP_FLOWS;
return;
}
if (!onClient) {
boolean sendUpdate = false;
for (PipeConnection connection : connections) {
sendUpdate |= connection.flipFlowsIfPressureReversed();
connection.manageSource(world, pos);
}
if (sendUpdate)
tileEntity.notifyUpdate();
}
if (phase == UpdatePhase.FLIP_FLOWS) {
phase = UpdatePhase.IDLE;
return;
}
if (!onClient) {
FluidStack availableFlow = FluidStack.EMPTY;
FluidStack collidingFlow = FluidStack.EMPTY;
for (PipeConnection connection : connections) {
FluidStack fluidInFlow = connection.getProvidedFluid();
if (fluidInFlow.isEmpty())
continue;
if (availableFlow.isEmpty()) {
singleSource = connection;
availableFlow = fluidInFlow;
continue;
}
if (availableFlow.isFluidEqual(fluidInFlow)) {
singleSource = null;
availableFlow = fluidInFlow;
continue;
}
collidingFlow = fluidInFlow;
break;
}
if (!collidingFlow.isEmpty()) {
FluidReactions.handlePipeFlowCollision(world, pos, availableFlow, collidingFlow);
return;
}
boolean sendUpdate = false;
for (PipeConnection connection : connections) {
FluidStack internalFluid = singleSource != connection ? availableFlow : FluidStack.EMPTY;
Predicate<FluidStack> extractionPredicate =
extracted -> canPullFluidFrom(extracted, tileEntity.getBlockState(), connection.side);
sendUpdate |= connection.manageFlows(world, pos, internalFluid, extractionPredicate);
}
if (sendUpdate)
tileEntity.notifyUpdate();
}
for (PipeConnection connection : connections)
connection.tickFlowProgress(world, pos);
}
@Override
public void read(CompoundNBT nbt, boolean clientPacket) {
super.read(nbt, clientPacket);
if (interfaces == null)
interfaces = new IdentityHashMap<>();
for (Direction face : Iterate.directions)
if (nbt.contains(face.getName()))
interfaces.computeIfAbsent(face, d -> new PipeConnection(d));
// Invalid data (missing/outdated). Defer init to runtime
if (interfaces.isEmpty()) {
interfaces = null;
return;
}
interfaces.values()
.forEach(connection -> connection.deserializeNBT(nbt, clientPacket));
}
@Override
public void write(CompoundNBT nbt, boolean clientPacket) {
super.write(nbt, clientPacket);
if (clientPacket)
createConnectionData();
if (interfaces == null)
return;
interfaces.values()
.forEach(connection -> connection.serializeNBT(nbt, clientPacket));
}
public FluidStack getProvidedOutwardFluid(Direction side) {
createConnectionData();
if (!interfaces.containsKey(side))
return FluidStack.EMPTY;
return interfaces.get(side)
.provideOutboundFlow();
}
@Nullable
public PipeConnection getConnection(Direction side) {
createConnectionData();
return interfaces.get(side);
}
public boolean hasAnyPressure() {
createConnectionData();
for (PipeConnection pipeConnection : interfaces.values())
if (pipeConnection.hasPressure())
return true;
return false;
}
@Nullable
public PipeConnection.Flow getFlow(Direction side) {
createConnectionData();
if (!interfaces.containsKey(side))
return null;
return interfaces.get(side).flow.orElse(null);
}
public void addPressure(Direction side, boolean inbound, float pressure) {
createConnectionData();
if (!interfaces.containsKey(side))
return;
interfaces.get(side)
.addPressure(inbound, pressure);
tileEntity.sendData();
}
public void wipePressure() {
if (interfaces != null)
for (Direction d : Iterate.directions) {
if (!canHaveFlowToward(tileEntity.getBlockState(), d))
interfaces.remove(d);
else
interfaces.computeIfAbsent(d, PipeConnection::new);
}
phase = UpdatePhase.WAIT_FOR_PUMPS;
createConnectionData();
interfaces.values()
.forEach(PipeConnection::wipePressure);
tileEntity.sendData();
}
private void createConnectionData() {
if (interfaces != null)
return;
interfaces = new IdentityHashMap<>();
for (Direction d : Iterate.directions)
if (canHaveFlowToward(tileEntity.getBlockState(), d))
interfaces.put(d, new PipeConnection(d));
}
public AttachmentTypes getRenderedRimAttachment(ILightReader world, BlockPos pos, BlockState state,
Direction direction) {
if (!canHaveFlowToward(state, direction))
return AttachmentTypes.NONE;
BlockPos offsetPos = pos.offset(direction);
BlockState facingState = world.getBlockState(offsetPos);
if (facingState.getBlock() instanceof PumpBlock && facingState.get(PumpBlock.FACING)
.getAxis() == direction.getAxis())
return AttachmentTypes.NONE;
if (AllBlocks.ENCASED_FLUID_PIPE.has(facingState)
&& facingState.get(EncasedPipeBlock.FACING_TO_PROPERTY_MAP.get(direction.getOpposite())))
return AttachmentTypes.NONE;
if (FluidPropagator.hasFluidCapability(world, offsetPos, direction.getOpposite())
&& !AllBlocks.HOSE_PULLEY.has(facingState))
return AttachmentTypes.DRAIN;
return AttachmentTypes.RIM;
}
public static enum AttachmentTypes {
NONE, RIM, DRAIN;
public boolean hasModel() {
return this != NONE;
}
}
@Override
public BehaviourType<?> getType() {
return TYPE;
}
}

View file

@ -1,108 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import java.lang.ref.WeakReference;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.world.IWorld;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
public class InterPumpEndpoint extends FluidNetworkEndpoint {
Couple<Pair<BlockFace, WeakReference<PumpTileEntity>>> pumps;
private InterPumpEndpoint(IWorld world, BlockFace location, LazyOptional<IFluidHandler> handler) {
super(world, location, handler);
}
public InterPumpEndpoint(IWorld world, BlockFace location, PumpTileEntity source, PumpTileEntity interfaced,
BlockFace sourcePos, BlockFace interfacedPos) {
this(world, location, LazyOptional.empty());
handler = LazyOptional.of(() -> new InterPumpFluidHandler(this));
pumps = Couple.create(Pair.of(sourcePos, new WeakReference<>(source)),
Pair.of(interfacedPos, new WeakReference<>(interfaced)));
}
public InterPumpEndpoint opposite(IWorld world) {
InterPumpEndpoint interPumpEndpoint = new InterPumpEndpoint(world, this.location.getOpposite(), handler);
interPumpEndpoint.pumps = pumps.copy();
return interPumpEndpoint;
}
public Couple<Pair<BlockFace, WeakReference<PumpTileEntity>>> getPumps() {
return pumps;
}
public boolean isPulling(boolean first) {
Pair<BlockFace, WeakReference<PumpTileEntity>> pair = getPumps().get(first);
PumpTileEntity pumpTileEntity = pair.getSecond()
.get();
if (pumpTileEntity == null || pumpTileEntity.isRemoved())
return false;
return pumpTileEntity.isPullingOnSide(pumpTileEntity.isFront(pair.getFirst()
.getFace()));
}
public int getTransferSpeed(boolean first) {
PumpTileEntity pumpTileEntity = getPumps().get(first)
.getSecond()
.get();
if (pumpTileEntity == null || pumpTileEntity.isRemoved())
return 0;
return pumpTileEntity.getFluidTransferSpeed();
}
@Override
public LazyOptional<IFluidHandler> provideHandler() {
if (isPulling(true) == isPulling(false))
return LazyOptional.empty();
if (getTransferSpeed(true) > getTransferSpeed(false))
return LazyOptional.empty();
return super.provideHandler();
}
@Override
public FluidStack provideFluid() {
if (!provideHandler().isPresent())
return FluidStack.EMPTY;
Couple<Pair<BlockFace, WeakReference<PumpTileEntity>>> pumps = getPumps();
for (boolean current : Iterate.trueAndFalse) {
if (isPulling(current))
continue;
Pair<BlockFace, WeakReference<PumpTileEntity>> pair = pumps.get(current);
BlockFace blockFace = pair.getFirst();
PumpTileEntity pumpTileEntity = pair.getSecond()
.get();
if (pumpTileEntity == null)
continue;
if (pumpTileEntity.networks == null)
continue;
FluidNetwork fluidNetwork = pumpTileEntity.networks.get(pumpTileEntity.isFront(blockFace.getFace()));
for (FluidNetworkFlow fluidNetworkFlow : fluidNetwork.flows) {
for (FluidNetworkEndpoint fne : fluidNetworkFlow.outputEndpoints) {
if (!(fne instanceof InterPumpEndpoint))
continue;
InterPumpEndpoint ipe = (InterPumpEndpoint) fne;
if (!ipe.location.isEquivalent(location))
continue;
FluidStack heldFluid = fluidNetworkFlow.fluidStack;
if (heldFluid.isEmpty())
return heldFluid;
FluidStack copy = heldFluid.copy();
copy.setAmount(1);
return heldFluid;
}
}
}
return FluidStack.EMPTY;
}
}

View file

@ -1,44 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class InterPumpFluidHandler extends FluidTank {
InterPumpEndpoint endpoint;
public InterPumpFluidHandler(InterPumpEndpoint endpoint) {
super(Integer.MAX_VALUE);
this.endpoint = endpoint;
}
@Override
public int fill(FluidStack resource, FluidAction action) {
if (resource.isEmpty())
return 0;
int maxInput = Math.min(resource.getAmount(), Math.max(getTransferCapacity() - getFluidAmount(), 0));
FluidStack toInsert = resource.copy();
toInsert.setAmount(maxInput);
FluidPropagator.showBlockFace(endpoint.location).colored(0x77d196).lineWidth(1/4f);
return super.fill(toInsert, action);
}
@Override
public FluidStack drain(int maxDrain, FluidAction action) {
return super.drain(maxDrain, action);
}
public FluidStack provide() {
FluidStack heldFluid = getFluid();
if (heldFluid.isEmpty())
return heldFluid;
FluidStack copy = heldFluid.copy();
copy.setAmount(1);
return copy;
}
private int getTransferCapacity() {
return Math.min(endpoint.getTransferSpeed(true), endpoint.getTransferSpeed(false));
}
}

View file

@ -8,12 +8,10 @@ import com.simibubi.create.AllFluids;
import com.simibubi.create.content.contraptions.fluids.potion.PotionFluidHandler;
import com.simibubi.create.foundation.fluid.FluidHelper;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.BlockState;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.entity.LivingEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.item.ItemStack;
@ -24,7 +22,6 @@ import net.minecraft.potion.EffectInstance;
import net.minecraft.potion.PotionUtils;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tags.FluidTags;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvents;
@ -34,10 +31,9 @@ import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class OpenEndedPipe {
public class OpenEndedPipe extends FlowSource {
World world;
BlockPos pos;
@ -46,12 +42,12 @@ public class OpenEndedPipe {
private OpenEndFluidHandler fluidHandler;
private BlockPos outputPos;
private boolean wasPulling;
private boolean stale;
private FluidStack cachedFluid;
private List<EffectInstance> cachedEffects;
public OpenEndedPipe(BlockFace face) {
super(face);
fluidHandler = new OpenEndFluidHandler();
outputPos = face.getConnectedPos();
pos = face.getPos();
@ -60,87 +56,106 @@ public class OpenEndedPipe {
aoe = aoe.expand(0, -1, 0);
}
public void tick(World world, boolean pulling) {
@Override
public void manageSource(World world) {
this.world = world;
if (!world.isAreaLoaded(outputPos, 0))
return;
if (pulling != wasPulling) {
if (pulling)
fluidHandler.clear();
wasPulling = pulling;
}
private FluidStack removeFluidFromSpace(boolean simulate) {
FluidStack empty = FluidStack.EMPTY;
if (world == null)
return empty;
if (!world.isAreaLoaded(outputPos, 0))
return empty;
BlockState state = world.getBlockState(outputPos);
IFluidState fluidState = state.getFluidState();
boolean waterlog = state.has(BlockStateProperties.WATERLOGGED);
if (!waterlog && !state.getMaterial()
.isReplaceable())
return;
if (pulling) {
return empty;
if (fluidState.isEmpty() || !fluidState.isSource())
return;
if (!fluidHandler.tryCollectFluid(fluidState.getFluid()))
return;
return empty;
FluidStack stack = new FluidStack(fluidState.getFluid(), 1000);
if (simulate)
return stack;
if (waterlog) {
world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, false), 3);
world.getPendingFluidTicks()
.scheduleTick(outputPos, Fluids.WATER, 1);
return;
return stack;
}
world.setBlockState(outputPos, fluidState.getBlockState()
.with(FlowingFluidBlock.LEVEL, 14), 3);
return;
return stack;
}
FluidStack fluid = fluidHandler.getFluid();
private boolean provideFluidToSpace(FluidStack fluid, boolean simulate) {
if (world == null)
return false;
if (!world.isAreaLoaded(outputPos, 0))
return false;
BlockState state = world.getBlockState(outputPos);
IFluidState fluidState = state.getFluidState();
boolean waterlog = state.has(BlockStateProperties.WATERLOGGED);
if (!waterlog && !state.getMaterial()
.isReplaceable())
return false;
if (fluid.isEmpty())
return;
return false;
if (!FluidHelper.hasBlockState(fluid.getFluid())) {
fluidHandler.drain(fluid.getAmount() > 1 ? fluid.getAmount() - 1 : 1, FluidAction.EXECUTE);
if (fluidHandler.isEmpty())
updatePumpIfNecessary();
if (!fluid.getFluid()
.isEquivalentTo(AllFluids.POTION.get()))
return;
applyPotionEffects(world, fluid);
return;
if (!simulate)
applyEffects(world, fluid);
return true;
}
Fluid providedFluid = fluidHandler.tryProvidingFluid();
if (providedFluid == null)
return;
if (!fluidState.isEmpty() && fluidState.getFluid() != providedFluid) {
FluidReactions.handlePipeSpillCollision(world, outputPos, providedFluid, fluidState);
return;
if (!fluidState.isEmpty() && fluidState.getFluid() != fluid.getFluid()) {
FluidReactions.handlePipeSpillCollision(world, outputPos, fluid.getFluid(), fluidState);
return false;
}
if (fluidState.isSource())
return;
return false;
if (waterlog && fluid.getFluid() != Fluids.WATER)
return false;
if (simulate)
return true;
if (world.dimension.doesWaterVaporize() && providedFluid.getFluid()
if (world.dimension.doesWaterVaporize() && fluid.getFluid()
.isIn(FluidTags.WATER)) {
int i = outputPos.getX();
int j = outputPos.getY();
int k = outputPos.getZ();
world.playSound(null, i, j, k, SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F,
2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
return;
return true;
}
if (waterlog) {
if (providedFluid.getFluid() != Fluids.WATER)
return;
world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, true), 3);
world.getPendingFluidTicks()
.scheduleTick(outputPos, Fluids.WATER, 1);
return;
return true;
}
world.setBlockState(outputPos, providedFluid.getDefaultState()
world.setBlockState(outputPos, fluid.getFluid()
.getDefaultState()
.getBlockState(), 3);
return true;
}
private void applyEffects(World world, FluidStack fluid) {
if (!fluid.getFluid()
.isEquivalentTo(AllFluids.POTION.get())) {
// other fx
return;
}
private void applyPotionEffects(World world, FluidStack fluid) {
if (cachedFluid == null || cachedEffects == null || !fluid.isFluidEqual(cachedFluid)) {
FluidStack copy = fluid.copy();
copy.setAmount(250);
@ -166,47 +181,30 @@ public class OpenEndedPipe {
}
public LazyOptional<IFluidHandler> getCapability() {
@Override
public LazyOptional<IFluidHandler> provideHandler() {
return LazyOptional.of(() -> fluidHandler);
}
public CompoundNBT writeToNBT(CompoundNBT compound) {
public CompoundNBT serializeNBT() {
CompoundNBT compound = new CompoundNBT();
fluidHandler.writeToNBT(compound);
compound.putBoolean("Pulling", wasPulling);
compound.put("Location", location.serializeNBT());
return compound;
}
public void readNBT(CompoundNBT compound) {
fluidHandler.readFromNBT(compound);
wasPulling = compound.getBoolean("Pulling");
}
public void markStale() {
stale = true;
}
public void unmarkStale() {
stale = false;
}
public boolean isStale() {
return stale;
}
private void updatePumpIfNecessary() {
if (world == null)
return;
if (!PumpBlock.isPump(world.getBlockState(pos)))
return;
TileEntity tileEntity = world.getTileEntity(pos);
if (tileEntity instanceof PumpTileEntity)
((PumpTileEntity) tileEntity).sendData();
public static OpenEndedPipe fromNBT(CompoundNBT compound) {
OpenEndedPipe oep = new OpenEndedPipe(BlockFace.fromNBT(compound.getCompound("Location")));
oep.fluidHandler.readFromNBT(compound);
oep.wasPulling = compound.getBoolean("Pulling");
return oep;
}
private class OpenEndFluidHandler extends FluidTank {
public OpenEndFluidHandler() {
super(1500);
super(1000);
}
@Override
@ -218,76 +216,76 @@ public class OpenEndedPipe {
return 0;
if (resource.isEmpty())
return 0;
FluidStack prevFluid = getFluid();
BlockState state = world.getBlockState(outputPos);
IFluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty() && fluidState.getFluid() != resource.getFluid()) {
FluidReactions.handlePipeSpillCollision(world, outputPos, resource.getFluid(), fluidState);
return 0;
}
if (fluidState.isSource())
return 0;
if (!(state.has(BlockStateProperties.WATERLOGGED) && resource.getFluid() == Fluids.WATER)
&& !state.getMaterial()
.isReplaceable())
if (!provideFluidToSpace(resource, true))
return 0;
// Never allow being filled above 1000
FluidStack insertable = resource.copy();
insertable.setAmount(Math.min(insertable.getAmount(), Math.max(1000 - getFluidAmount(), 0)));
int fill = super.fill(insertable, action);
if (!getFluid().isFluidEqual(prevFluid))
updatePumpIfNecessary();
if (!getFluid().isEmpty() && !getFluid().isFluidEqual(resource))
setFluid(FluidStack.EMPTY);
if (wasPulling)
wasPulling = false;
int fill = super.fill(resource, action);
if (action.execute() && (getFluidAmount() == 1000 || !FluidHelper.hasBlockState(getFluid().getFluid()))
&& provideFluidToSpace(getFluid(), false))
setFluid(FluidStack.EMPTY);
return fill;
}
@Override
public FluidStack drain(FluidStack resource, FluidAction action) {
boolean wasEmpty = isEmpty();
FluidStack drain = super.drain(resource, action);
if (action.execute() && !wasEmpty && isEmpty())
updatePumpIfNecessary();
return drain;
return drainInner(resource.getAmount(), resource, action);
}
@Override
public FluidStack drain(int maxDrain, FluidAction action) {
boolean wasEmpty = isEmpty();
FluidStack drain = super.drain(maxDrain, action);
if (action.execute() && !wasEmpty && isEmpty())
updatePumpIfNecessary();
return drain;
return drainInner(maxDrain, null, action);
}
public boolean tryCollectFluid(Fluid fluid) {
for (boolean simulate : Iterate.trueAndFalse)
if (super.fill(new FluidStack(fluid, 1000),
simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE) != 1000)
return false;
updatePumpIfNecessary();
private FluidStack drainInner(int amount, @Nullable FluidStack filter, FluidAction action) {
FluidStack empty = FluidStack.EMPTY;
boolean filterPresent = filter != null;
if (world == null)
return empty;
if (!world.isAreaLoaded(outputPos, 0))
return empty;
if (amount == 0)
return empty;
if (amount > 1000) {
amount = 1000;
if (filterPresent)
filter = FluidHelper.copyStackWithAmount(filter, amount);
}
if (!wasPulling)
wasPulling = true;
FluidStack drainedFromInternal = filterPresent ? super.drain(filter, action) : super.drain(amount, action);
if (!drainedFromInternal.isEmpty())
return drainedFromInternal;
FluidStack drainedFromWorld = removeFluidFromSpace(action.simulate());
if (drainedFromWorld.isEmpty())
return FluidStack.EMPTY;
if (filterPresent && !drainedFromWorld.isFluidEqual(filter))
return FluidStack.EMPTY;
int remainder = drainedFromWorld.getAmount() - amount;
drainedFromWorld.setAmount(amount);
if (!action.simulate() && remainder > 0) {
if (!getFluid().isEmpty() && !getFluid().isFluidEqual(drainedFromWorld))
setFluid(FluidStack.EMPTY);
super.fill(FluidHelper.copyStackWithAmount(drainedFromWorld, remainder), FluidAction.EXECUTE);
}
return drainedFromWorld;
}
}
@Override
public boolean isEndpoint() {
return true;
}
@Nullable
public Fluid tryProvidingFluid() {
Fluid fluid = getFluid().getFluid();
for (boolean simulate : Iterate.trueAndFalse)
if (drain(1000, simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE).getAmount() != 1000)
return null;
updatePumpIfNecessary();
return fluid;
}
public void clear() {
boolean wasEmpty = isEmpty();
setFluid(FluidStack.EMPTY);
if (!wasEmpty)
updatePumpIfNecessary();
}
}
}

View file

@ -6,8 +6,9 @@ import java.util.List;
import java.util.Random;
import com.simibubi.create.AllBlockPartials;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour.AttachmentTypes;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour.AttachmentTypes;
import com.simibubi.create.content.contraptions.fluids.pipes.FluidPipeBlock;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.foundation.block.connected.BakedModelWrapperWithData;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Iterate;
@ -35,16 +36,16 @@ public class PipeAttachmentModel extends BakedModelWrapperWithData {
@Override
protected Builder gatherModelData(Builder builder, ILightReader world, BlockPos pos, BlockState state) {
PipeModelData data = new PipeModelData();
FluidPipeAttachmentBehaviour attachmentBehaviour =
TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE);
FluidTransportBehaviour transport = TileEntityBehaviour.get(world, pos, FluidTransportBehaviour.TYPE);
BracketedTileEntityBehaviour bracket = TileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
if (attachmentBehaviour != null) {
if (transport != null)
for (Direction d : Iterate.directions)
data.putRim(d, attachmentBehaviour.getAttachment(world, pos, state, d));
data.putBracket(attachmentBehaviour.getBracket());
}
data.setEncased(FluidPipeBlock.shouldDrawCasing(world, pos, state));
data.putRim(d, transport.getRenderedRimAttachment(world, pos, state, d));
if (bracket != null)
data.putBracket(bracket.getBracket());
data.setEncased(FluidPipeBlock.shouldDrawCasing(world, pos, state));
return builder.withInitial(PIPE_PROPERTY, data);
}

View file

@ -0,0 +1,476 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.Optional;
import java.util.Random;
import java.util.function.Predicate;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.FloatNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.particles.IParticleData;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.DistExecutor;
public class PipeConnection {
Direction side;
// Layer I
Couple<Float> pressure; // [inbound, outward]
Optional<FlowSource> source;
Optional<FlowSource> previousSource;
// Layer II
Optional<Flow> flow;
boolean particleSplashNextTick;
// Layer III
Optional<FluidNetwork> network; // not serialized
public PipeConnection(Direction side) {
this.side = side;
pressure = Couple.create(() -> 0f);
flow = Optional.empty();
previousSource = Optional.empty();
source = Optional.empty();
network = Optional.empty();
particleSplashNextTick = false;
}
public FluidStack getProvidedFluid() {
FluidStack empty = FluidStack.EMPTY;
if (!hasFlow())
return empty;
Flow flow = this.flow.get();
if (!flow.inbound)
return empty;
if (!flow.complete)
return empty;
return flow.fluid;
}
public boolean flipFlowsIfPressureReversed() {
if (!hasFlow())
return false;
boolean singlePressure = comparePressure() != 0 && (getInboundPressure() == 0 || getOutwardPressure() == 0);
Flow flow = this.flow.get();
if (!singlePressure || comparePressure() < 0 == flow.inbound)
return false;
flow.inbound = !flow.inbound;
if (!flow.complete)
this.flow = Optional.empty();
return true;
}
public void manageSource(World world, BlockPos pos) {
if (!source.isPresent() && !determineSource(world, pos))
return;
FlowSource flowSource = source.get();
flowSource.manageSource(world);
}
public boolean manageFlows(World world, BlockPos pos, FluidStack internalFluid,
Predicate<FluidStack> extractionPredicate) {
// Only keep network if still valid
Optional<FluidNetwork> retainedNetwork = network;
network = Optional.empty();
// chunk border
if (!source.isPresent() && !determineSource(world, pos))
return false;
FlowSource flowSource = source.get();
if (!hasFlow()) {
if (!hasPressure())
return false;
// Try starting a new flow
boolean prioritizeInbound = comparePressure() < 0;
for (boolean trueFalse : Iterate.trueAndFalse) {
boolean inbound = prioritizeInbound == trueFalse;
if (pressure.get(inbound) == 0)
continue;
if (tryStartingNewFlow(inbound, inbound ? flowSource.provideFluid(extractionPredicate) : internalFluid))
return true;
}
return false;
}
// Manage existing flow
Flow flow = this.flow.get();
FluidStack provided = flow.inbound ? flowSource.provideFluid(extractionPredicate) : internalFluid;
if (!hasPressure() || provided.isEmpty() || !provided.isFluidEqual(flow.fluid)) {
this.flow = Optional.empty();
return true;
}
// Overwrite existing flow
if (flow.inbound != comparePressure() < 0) {
boolean inbound = !flow.inbound;
if (inbound && !provided.isEmpty() || !inbound && !internalFluid.isEmpty()) {
FluidPropagator.resetAffectedFluidNetworks(world, pos, side);
tryStartingNewFlow(inbound, inbound ? flowSource.provideFluid(extractionPredicate) : internalFluid);
return true;
}
}
flowSource.whileFlowPresent(world, flow.inbound);
if (!flowSource.isEndpoint())
return false;
if (!flow.inbound)
return false;
// Layer III
network = retainedNetwork;
if (!hasNetwork())
network = Optional.of(new FluidNetwork(world, new BlockFace(pos, side), flowSource::provideHandler));
network.get()
.tick();
return false;
}
private boolean tryStartingNewFlow(boolean inbound, FluidStack providedFluid) {
if (providedFluid.isEmpty())
return false;
Flow flow = new Flow(inbound, providedFluid);
this.flow = Optional.of(flow);
return true;
}
private boolean determineSource(World world, BlockPos pos) {
if (!world.isAreaLoaded(pos, 1))
return false;
BlockFace location = new BlockFace(pos, side);
if (FluidPropagator.isOpenEnd(world, pos, side)) {
if (previousSource.orElse(null) instanceof OpenEndedPipe)
source = previousSource;
else
source = Optional.of(new OpenEndedPipe(location));
return true;
}
if (FluidPropagator.hasFluidCapability(world, location.getConnectedPos(), side.getOpposite())) {
source = Optional.of(new FlowSource.FluidHandler(location));
return true;
}
FluidTransportBehaviour behaviour =
TileEntityBehaviour.get(world, pos.offset(side), FluidTransportBehaviour.TYPE);
source = Optional.of(behaviour == null ? new FlowSource.Blocked(location) : new FlowSource.OtherPipe(location));
return true;
}
public void tickFlowProgress(World world, BlockPos pos) {
if (!hasFlow())
return;
Flow flow = this.flow.get();
if (flow.fluid.isEmpty())
return;
if (world.isRemote) {
if (!source.isPresent())
determineSource(world, pos);
spawnParticles(world, pos, flow.fluid);
if (particleSplashNextTick)
spawnSplashOnRim(world, pos, flow.fluid);
particleSplashNextTick = false;
}
float flowSpeed = 1 / 32f + MathHelper.clamp(pressure.get(flow.inbound) / 512f, 0, 1) * 31 / 32f;
flow.progress.setValue(Math.min(flow.progress.getValue() + flowSpeed, 1));
if (flow.progress.getValue() >= 1)
flow.complete = true;
}
public void serializeNBT(CompoundNBT tag, boolean clientPacket) {
CompoundNBT connectionData = new CompoundNBT();
tag.put(side.getName(), connectionData);
if (hasPressure()) {
ListNBT pressureData = new ListNBT();
pressureData.add(FloatNBT.of(getInboundPressure()));
pressureData.add(FloatNBT.of(getOutwardPressure()));
connectionData.put("Pressure", pressureData);
}
if (hasOpenEnd())
connectionData.put("OpenEnd", ((OpenEndedPipe) source.get()).serializeNBT());
if (hasFlow()) {
CompoundNBT flowData = new CompoundNBT();
Flow flow = this.flow.get();
flow.fluid.writeToNBT(flowData);
flowData.putBoolean("In", flow.inbound);
if (!flow.complete)
flowData.put("Progress", flow.progress.writeNBT());
connectionData.put("Flow", flowData);
}
}
private boolean hasOpenEnd() {
return source.orElse(null) instanceof OpenEndedPipe;
}
public void deserializeNBT(CompoundNBT tag, boolean clientPacket) {
CompoundNBT connectionData = tag.getCompound(side.getName());
if (connectionData.contains("Pressure")) {
ListNBT pressureData = connectionData.getList("Pressure", NBT.TAG_FLOAT);
pressure = Couple.create(pressureData.getFloat(0), pressureData.getFloat(1));
} else
pressure.replace(f -> 0f);
source = Optional.empty();
if (connectionData.contains("OpenEnd"))
source = Optional.of(OpenEndedPipe.fromNBT(connectionData.getCompound("OpenEnd")));
if (connectionData.contains("Flow")) {
CompoundNBT flowData = connectionData.getCompound("Flow");
FluidStack fluid = FluidStack.loadFluidStackFromNBT(flowData);
boolean inbound = flowData.getBoolean("In");
if (!flow.isPresent()) {
flow = Optional.of(new Flow(inbound, fluid));
if (clientPacket)
particleSplashNextTick = true;
}
Flow flow = this.flow.get();
flow.fluid = fluid;
flow.inbound = inbound;
flow.complete = !flowData.contains("Progress");
if (!flow.complete)
flow.progress.readNBT(flowData.getCompound("Progress"), clientPacket);
else {
if (flow.progress.getValue() == 0)
flow.progress.startWithValue(1);
flow.progress.setValue(1);
}
} else
flow = Optional.empty();
}
/**
* @return zero if outward == inbound <br>
* positive if outward > inbound <br>
* negative if outward < inbound
*/
public float comparePressure() {
return getOutwardPressure() - getInboundPressure();
}
public void wipePressure() {
this.pressure.replace(f -> 0f);
if (this.source.isPresent())
this.previousSource = this.source;
this.source = Optional.empty();
resetNetwork();
}
public FluidStack provideOutboundFlow() {
if (!hasFlow())
return FluidStack.EMPTY;
Flow flow = this.flow.get();
if (!flow.complete || flow.inbound)
return FluidStack.EMPTY;
return flow.fluid;
}
public void addPressure(boolean inbound, float pressure) {
this.pressure = this.pressure.mapWithContext((f, in) -> in == inbound ? f + pressure : f);
}
public boolean hasPressure() {
return getInboundPressure() != 0 || getOutwardPressure() != 0;
}
private float getOutwardPressure() {
return pressure.getSecond();
}
private float getInboundPressure() {
return pressure.getFirst();
}
public boolean hasFlow() {
return flow.isPresent();
}
public boolean hasNetwork() {
return network.isPresent();
}
public void resetNetwork() {
network.ifPresent(FluidNetwork::reset);
}
public class Flow {
public boolean complete;
public boolean inbound;
public LerpedFloat progress;
public FluidStack fluid;
public Flow(boolean inbound, FluidStack fluid) {
this.inbound = inbound;
this.fluid = fluid;
this.progress = LerpedFloat.linear()
.startWithValue(0);
this.complete = false;
}
}
public static final int MAX_PARTICLE_RENDER_DISTANCE = 20;
public static final int SPLASH_PARTICLE_AMOUNT = 1;
public static final float IDLE_PARTICLE_SPAWN_CHANCE = 1 / 1000f;
public static final float RIM_RADIUS = 1 / 4f + 1 / 64f;
public static final Random r = new Random();
public void spawnSplashOnRim(World world, BlockPos pos, FluidStack fluid) {
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> spawnSplashOnRimInner(world, pos, fluid));
}
public void spawnParticles(World world, BlockPos pos, FluidStack fluid) {
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> spawnParticlesInner(world, pos, fluid));
}
@OnlyIn(Dist.CLIENT)
private void spawnParticlesInner(World world, BlockPos pos, FluidStack fluid) {
if (!isRenderEntityWithinDistance(pos))
return;
if (hasOpenEnd())
spawnPouringLiquid(world, pos, fluid, 1);
else if (r.nextFloat() < IDLE_PARTICLE_SPAWN_CHANCE)
spawnRimParticles(world, pos, fluid, 1);
}
@OnlyIn(Dist.CLIENT)
private void spawnSplashOnRimInner(World world, BlockPos pos, FluidStack fluid) {
if (!isRenderEntityWithinDistance(pos))
return;
spawnRimParticles(world, pos, fluid, SPLASH_PARTICLE_AMOUNT);
}
@OnlyIn(Dist.CLIENT)
private void spawnRimParticles(World world, BlockPos pos, FluidStack fluid, int amount) {
if (hasOpenEnd()) {
spawnPouringLiquid(world, pos, fluid, amount);
return;
}
IParticleData particle = FluidFX.getDrippingParticle(fluid);
FluidFX.spawnRimParticles(world, pos, side, amount, particle, RIM_RADIUS);
}
@OnlyIn(Dist.CLIENT)
private void spawnPouringLiquid(World world, BlockPos pos, FluidStack fluid, int amount) {
IParticleData particle = FluidFX.getFluidParticle(fluid);
Vec3d directionVec = new Vec3d(side.getDirectionVec());
if (!hasFlow())
return;
Flow flow = this.flow.get();
FluidFX.spawnPouringLiquid(world, pos, amount, particle, RIM_RADIUS, directionVec, flow.inbound);
}
@OnlyIn(Dist.CLIENT)
public static boolean isRenderEntityWithinDistance(BlockPos pos) {
Entity renderViewEntity = Minecraft.getInstance()
.getRenderViewEntity();
if (renderViewEntity == null)
return false;
Vec3d center = VecHelper.getCenterOf(pos);
if (renderViewEntity.getPositionVec()
.distanceTo(center) > MAX_PARTICLE_RENDER_DISTANCE)
return false;
return true;
}
// void visualizePressure(BlockPos pos) {
// if (!hasPressure())
// return;
//
// pressure.forEachWithContext((pressure, inbound) -> {
// if (inbound)
// return;
//
// Vec3d directionVec = new Vec3d(side.getDirectionVec());
// Vec3d scaleVec = directionVec.scale(-.25f * side.getAxisDirection()
// .getOffset());
// directionVec = directionVec.scale(inbound ? .35f : .45f);
// CreateClient.outliner.chaseAABB("pressure" + pos.toShortString() + side.getName() + String.valueOf(inbound),
// FluidPropagator.smallCenter.offset(directionVec.add(new Vec3d(pos)))
// .grow(scaleVec.x, scaleVec.y, scaleVec.z)
// .expand(0, pressure / 64f, 0)
// .grow(1 / 64f));
// });
// }
//
// void visualizeFlow(BlockPos pos) {
// if (!hasFlow())
// return;
//
// Vec3d directionVec = new Vec3d(side.getDirectionVec());
// float size = 1 / 4f;
// float length = .5f;
// Flow flow = this.flow.get();
// boolean inbound = flow.inbound;
// FluidStack fluid = flow.fluid;
//
// if (flow.progress == null)
// return;
// float value = flow.progress.getValue();
// Vec3d start = directionVec.scale(inbound ? .5 : .5f - length);
// Vec3d offset = directionVec.scale(length * (inbound ? -1 : 1))
// .scale(value);
//
// Vec3d scale = new Vec3d(1, 1, 1).subtract(directionVec.scale(side.getAxisDirection()
// .getOffset()))
// .scale(size);
// AxisAlignedBB bb = new AxisAlignedBB(start, start.add(offset)).offset(VecHelper.getCenterOf(pos))
// .grow(scale.x, scale.y, scale.z);
//
// int color = 0x7fdbda;
// if (!fluid.isEmpty()) {
// Fluid fluid2 = fluid.getFluid();
// if (fluid2 == Fluids.WATER)
// color = 0x1D4D9B;
// else if (fluid2 == Fluids.LAVA)
// color = 0xFF773D;
// else
// color = fluid2.getAttributes()
// .getColor(fluid);
// }
//
// CreateClient.outliner.chaseAABB(this, bb)
// .withFaceTexture(AllSpecialTextures.SELECTION)
// .colored(color)
// .lineWidth(0);
// }
}

View file

@ -1,15 +1,12 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.Map;
import java.util.Random;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.AllShapes;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.base.DirectionalKineticBlock;
import com.simibubi.create.content.contraptions.fluids.pipes.FluidPipeBlock;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@ -30,8 +27,9 @@ import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.TickPriority;
import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack;
import net.minecraft.world.server.ServerWorld;
public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable {
@ -67,26 +65,8 @@ public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable
if (!(tileEntity instanceof PumpTileEntity))
return state;
PumpTileEntity pump = (PumpTileEntity) tileEntity;
if (pump.networks == null)
return state;
FluidNetwork apn1 = pump.networks.get(true);
FluidNetwork apn2 = pump.networks.get(false);
// Collect pipes that can be skipped
apn1.clearFlows(world, true);
apn2.clearFlows(world, true);
// Swap skipsets as the networks change sides
Map<BlockFace, FluidStack> skippedConnections = apn1.previousFlow;
apn1.previousFlow = apn2.previousFlow;
apn2.previousFlow = skippedConnections;
// Init networks next tick
pump.networksToUpdate.forEach(MutableBoolean::setTrue);
pump.networks.swap();
pump.sidesToUpdate.forEach(MutableBoolean::setTrue);
pump.reversed = !pump.reversed;
return state;
}
@ -111,22 +91,29 @@ public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable
public void neighborChanged(BlockState state, World world, BlockPos pos, Block otherBlock, BlockPos neighborPos,
boolean isMoving) {
DebugPacketSender.func_218806_a(world, pos);
if (world.isRemote)
Direction d = FluidPropagator.validateNeighbourChange(state, world, pos, otherBlock, neighborPos, isMoving);
if (d == null)
return;
if (otherBlock instanceof FluidPipeBlock)
if (!isOpenAt(state, d))
return;
TileEntity tileEntity = world.getTileEntity(pos);
if (!(tileEntity instanceof PumpTileEntity))
return;
PumpTileEntity pump = (PumpTileEntity) tileEntity;
Direction facing = state.get(FACING);
for (boolean front : Iterate.trueAndFalse) {
Direction side = front ? facing : facing.getOpposite();
if (!pos.offset(side)
.equals(neighborPos))
continue;
pump.updatePipesOnSide(side);
}
world.getPendingBlockTicks()
.scheduleTick(pos, this, 1, TickPriority.HIGH);
// if (world.isRemote)
// return;
// if (otherBlock instanceof FluidPipeBlock)
// return;
// TileEntity tileEntity = world.getTileEntity(pos);
// if (!(tileEntity instanceof PumpTileEntity))
// return;
// PumpTileEntity pump = (PumpTileEntity) tileEntity;
// Direction facing = state.get(FACING);
// for (boolean front : Iterate.trueAndFalse) {
// Direction side = front ? facing : facing.getOpposite();
// if (!pos.offset(side)
// .equals(neighborPos))
// continue;
// pump.updatePipesOnSide(side);
// }
}
@Override
@ -163,4 +150,32 @@ public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable
return state.getBlock() instanceof PumpBlock;
}
@Override
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean isMoving) {
if (world.isRemote)
return;
if (state != oldState)
world.getPendingBlockTicks()
.scheduleTick(pos, this, 1, TickPriority.HIGH);
}
public static boolean isOpenAt(BlockState state, Direction d) {
return d.getAxis() == state.get(FACING)
.getAxis();
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random r) {
FluidPropagator.propagateChangedPipe(world, pos, state);
}
@Override
public void onReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean isMoving) {
boolean blockTypeChanged = state.getBlock() != newState.getBlock();
if (blockTypeChanged && !world.isRemote)
FluidPropagator.propagateChangedPipe(world, pos, state);
if (state.hasTileEntity() && (blockTypeChanged || !newState.hasTileEntity()))
world.removeTileEntity(pos);
}
}

View file

@ -1,26 +0,0 @@
package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.foundation.utility.BlockFace;
import net.minecraft.world.IWorld;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
public class PumpEndpoint extends FluidNetworkEndpoint {
PumpTileEntity pumpTE;
public PumpEndpoint(BlockFace location, PumpTileEntity pumpTE) {
super(pumpTE.getWorld(), location, LazyOptional.empty());
this.pumpTE = pumpTE;
}
@Override
protected void onHandlerInvalidated(IWorld world) {}
@Override
public FluidStack provideFluid() {
return pumpTE.providedFluid;
}
}

View file

@ -1,11 +1,13 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
@ -19,48 +21,37 @@ import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.LerpedFloat.Chaser;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.particles.IParticleData;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.ILightReader;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fluids.FluidStack;
import net.minecraft.world.IWorld;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fml.DistExecutor;
public class PumpTileEntity extends KineticTileEntity {
LerpedFloat arrowDirection;
Couple<FluidNetwork> networks;
Couple<Map<BlockFace, OpenEndedPipe>> openEnds;
Couple<MutableBoolean> networksToUpdate;
Couple<MutableBoolean> sidesToUpdate;
boolean reversed;
FluidStack providedFluid;
public PumpTileEntity(TileEntityType<?> typeIn) {
super(typeIn);
arrowDirection = LerpedFloat.linear()
.startWithValue(1);
networksToUpdate = Couple.create(MutableBoolean::new);
openEnds = Couple.create(HashMap::new);
setProvidedFluid(FluidStack.EMPTY);
sidesToUpdate = Couple.create(MutableBoolean::new);
}
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
super.addBehaviours(behaviours);
behaviours.add(new PumpAttachmentBehaviour(this));
behaviours.add(new PumpFluidTransferBehaviour(this));
}
@Override
@ -77,196 +68,235 @@ public class PumpTileEntity extends KineticTileEntity {
if (world.isRemote) {
if (speed == 0)
return;
spawnParticles();
arrowDirection.chase(speed >= 0 ? 1 : -1, .5f, Chaser.EXP);
arrowDirection.tickChaser();
return;
}
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof PumpBlock))
return;
Direction face = blockState.get(PumpBlock.FACING);
MutableBoolean networkUpdated = new MutableBoolean(false);
if (networks == null) {
networks = Couple.create(new FluidNetwork(), new FluidNetwork());
networks.forEachWithContext((fn, front) -> {
BlockFace blockFace = new BlockFace(pos, front ? face : face.getOpposite());
fn.assemble(world, this, blockFace);
FluidPropagator.showBlockFace(blockFace)
.lineWidth(1 / 8f);
});
networkUpdated.setTrue();
}
networksToUpdate.forEachWithContext((update, front) -> {
sidesToUpdate.forEachWithContext((update, isFront) -> {
if (update.isFalse())
return;
FluidNetwork activePipeNetwork = networks.get(front);
if (activePipeNetwork == null)
return;
BlockFace blockFace = new BlockFace(pos, front ? face : face.getOpposite());
activePipeNetwork.reAssemble(world, this, blockFace);
FluidPropagator.showBlockFace(blockFace)
.lineWidth(1 / 8f);
update.setFalse();
networkUpdated.setTrue();
distributePressureTo(isFront ? getFront() : getFront().getOpposite());
});
if (networkUpdated.isTrue())
return;
networks.forEach(fn -> fn.tick(world, this));
if (speed == 0)
return;
if (speed < 0 != reversed) {
networks.forEachWithContext((fn, current) -> fn.clearFlows(world, true));
reversed = speed < 0;
return;
}
boolean pullingSide = isPullingOnSide(true);
float flowSpeed = Math.abs(speed) / 256f;
networks.forEachWithContext((fn, front) -> {
boolean pulling = isPullingOnSide(front);
fn.tickFlows(world, this, pulling, flowSpeed);
openEnds.get(front)
.values()
.forEach(oep -> oep.tick(world, pulling));
});
if (!networks.get(pullingSide)
.hasEndpoints()) {
setProvidedFluid(FluidStack.EMPTY);
return;
}
if (networks.getFirst()
.hasEndpoints()
&& networks.getSecond()
.hasEndpoints()) {
performTransfer();
}
}
@Override
public void remove() {
super.remove();
if (networks != null)
networks.forEachWithContext((fn, current) -> fn.clearFlows(world, false));
public void onSpeedChanged(float previousSpeed) {
super.onSpeedChanged(previousSpeed);
if (previousSpeed == getSpeed())
return;
if (speed != 0)
reversed = speed < 0;
if (world.isRemote)
return;
BlockPos frontPos = pos.offset(getFront());
BlockPos backPos = pos.offset(getFront().getOpposite());
FluidPropagator.propagateChangedPipe(world, frontPos, world.getBlockState(frontPos));
FluidPropagator.propagateChangedPipe(world, backPos, world.getBlockState(backPos));
}
private void performTransfer() {
boolean input = isPullingOnSide(true);
Collection<FluidNetworkEndpoint> inputs = networks.get(input)
.getEndpoints(true);
Collection<FluidNetworkEndpoint> outputs = networks.get(!input)
.getEndpoints(false);
protected void distributePressureTo(Direction side) {
if (getSpeed() == 0)
return;
int flowSpeed = getFluidTransferSpeed();
FluidStack transfer = FluidStack.EMPTY;
for (boolean simulate : Iterate.trueAndFalse) {
FluidAction action = simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE;
BlockFace start = new BlockFace(pos, side);
boolean pull = isPullingOnSide(isFront(side));
Set<BlockFace> targets = new HashSet<>();
Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph = new HashMap<>();
List<FluidNetworkEndpoint> availableInputs = new ArrayList<>(inputs);
while (!availableInputs.isEmpty() && transfer.getAmount() < flowSpeed) {
int diff = flowSpeed - transfer.getAmount();
int dividedTransfer = diff / availableInputs.size();
int remainder = diff % availableInputs.size();
if (!pull)
FluidPropagator.resetAffectedFluidNetworks(world, pos, side.getOpposite());
for (Iterator<FluidNetworkEndpoint> iterator = availableInputs.iterator(); iterator.hasNext();) {
int toTransfer = dividedTransfer;
if (remainder > 0) {
toTransfer++;
remainder--;
}
if (!hasReachedValidEndpoint(world, start, pull)) {
FluidNetworkEndpoint ne = iterator.next();
IFluidHandler handler = ne.provideHandler()
.orElse(null);
if (handler == null) {
iterator.remove();
pipeGraph.computeIfAbsent(pos, $ -> Pair.of(0, new IdentityHashMap<>()))
.getSecond()
.put(side, pull);
pipeGraph.computeIfAbsent(start.getConnectedPos(), $ -> Pair.of(1, new IdentityHashMap<>()))
.getSecond()
.put(side.getOpposite(), !pull);
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
int maxDistance = FluidPropagator.getPumpRange();
frontier.add(Pair.of(1, start.getConnectedPos()));
while (!frontier.isEmpty()) {
Pair<Integer, BlockPos> entry = frontier.remove(0);
int distance = entry.getFirst();
BlockPos currentPos = entry.getSecond();
if (!world.isAreaLoaded(currentPos, 0))
continue;
}
FluidStack drained = handler.drain(toTransfer, action);
if (drained.isEmpty()) {
iterator.remove();
if (visited.contains(currentPos))
continue;
}
if (transfer.isFluidEqual(drained) || transfer.isEmpty()) {
if (drained.getAmount() < toTransfer)
iterator.remove();
FluidStack copy = drained.copy();
copy.setAmount(drained.getAmount() + transfer.getAmount());
transfer = copy;
visited.add(currentPos);
BlockState currentState = world.getBlockState(currentPos);
FluidTransportBehaviour pipe = FluidPropagator.getPipe(world, currentPos);
if (pipe == null)
continue;
}
iterator.remove();
for (Direction face : FluidPropagator.getPipeConnections(currentState, pipe)) {
BlockFace blockFace = new BlockFace(currentPos, face);
BlockPos connectedPos = blockFace.getConnectedPos();
if (!world.isAreaLoaded(connectedPos, 0))
continue;
if (blockFace.isEquivalent(start))
continue;
if (hasReachedValidEndpoint(world, blockFace, pull)) {
pipeGraph.computeIfAbsent(currentPos, $ -> Pair.of(distance, new IdentityHashMap<>()))
.getSecond()
.put(face, pull);
targets.add(blockFace);
continue;
}
}
List<FluidNetworkEndpoint> availableOutputs = new ArrayList<>(outputs);
while (!availableOutputs.isEmpty() && transfer.getAmount() > 0) {
int dividedTransfer = transfer.getAmount() / availableOutputs.size();
int remainder = transfer.getAmount() % availableOutputs.size();
for (Iterator<FluidNetworkEndpoint> iterator = availableOutputs.iterator(); iterator.hasNext();) {
FluidNetworkEndpoint ne = iterator.next();
int toTransfer = dividedTransfer;
if (remainder > 0) {
toTransfer++;
remainder--;
}
if (transfer.isEmpty())
break;
IFluidHandler handler = ne.provideHandler()
.orElse(null);
if (handler == null) {
iterator.remove();
FluidTransportBehaviour pipeBehaviour = FluidPropagator.getPipe(world, connectedPos);
if (pipeBehaviour == null)
continue;
if (pipeBehaviour instanceof PumpFluidTransferBehaviour)
continue;
if (visited.contains(connectedPos))
continue;
if (distance + 1 >= maxDistance) {
pipeGraph.computeIfAbsent(currentPos, $ -> Pair.of(distance, new IdentityHashMap<>()))
.getSecond()
.put(face, pull);
targets.add(blockFace);
continue;
}
FluidStack divided = transfer.copy();
divided.setAmount(toTransfer);
int fill = handler.fill(divided, action);
transfer.setAmount(transfer.getAmount() - fill);
if (fill < toTransfer)
iterator.remove();
pipeGraph.computeIfAbsent(currentPos, $ -> Pair.of(distance, new IdentityHashMap<>()))
.getSecond()
.put(face, pull);
pipeGraph.computeIfAbsent(connectedPos, $ -> Pair.of(distance + 1, new IdentityHashMap<>()))
.getSecond()
.put(face.getOpposite(), !pull);
frontier.add(Pair.of(distance + 1, connectedPos));
}
}
}
// DFS
Map<Integer, Set<BlockFace>> validFaces = new HashMap<>();
searchForEndpointRecursively(pipeGraph, targets, validFaces,
new BlockFace(start.getPos(), start.getOppositeFace()), pull);
float pressure = Math.abs(getSpeed());
for (Set<BlockFace> set : validFaces.values()) {
int parallelBranches = set.size();
for (BlockFace face : set) {
BlockPos pipePos = face.getPos();
Direction pipeSide = face.getFace();
if (pipePos.equals(pos))
continue;
boolean inbound = pipeGraph.get(pipePos)
.getSecond()
.get(pipeSide);
FluidTransportBehaviour pipeBehaviour = FluidPropagator.getPipe(world, pipePos);
if (pipeBehaviour == null)
continue;
pipeBehaviour.addPressure(pipeSide, inbound, pressure / parallelBranches);
}
}
}
flowSpeed -= transfer.getAmount();
transfer = FluidStack.EMPTY;
}
protected boolean searchForEndpointRecursively(Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph,
Set<BlockFace> targets, Map<Integer, Set<BlockFace>> validFaces, BlockFace currentFace, boolean pull) {
BlockPos currentPos = currentFace.getPos();
if (!pipeGraph.containsKey(currentPos))
return false;
Pair<Integer, Map<Direction, Boolean>> pair = pipeGraph.get(currentPos);
int distance = pair.getFirst();
boolean atLeastOneBranchSuccessful = false;
for (Direction nextFacing : Iterate.directions) {
if (nextFacing == currentFace.getFace())
continue;
Map<Direction, Boolean> map = pair.getSecond();
if (!map.containsKey(nextFacing))
continue;
BlockFace localTarget = new BlockFace(currentPos, nextFacing);
if (targets.contains(localTarget)) {
validFaces.computeIfAbsent(distance, $ -> new HashSet<>())
.add(localTarget);
atLeastOneBranchSuccessful = true;
continue;
}
public int getFluidTransferSpeed() {
float rotationSpeed = Math.abs(getSpeed());
int flowSpeed = (int) (rotationSpeed / 2f);
if (rotationSpeed != 0 && flowSpeed == 0)
flowSpeed = 1;
return flowSpeed;
if (map.get(nextFacing) != pull)
continue;
if (!searchForEndpointRecursively(pipeGraph, targets, validFaces,
new BlockFace(currentPos.offset(nextFacing), nextFacing.getOpposite()), pull))
continue;
validFaces.computeIfAbsent(distance, $ -> new HashSet<>())
.add(localTarget);
atLeastOneBranchSuccessful = true;
}
if (atLeastOneBranchSuccessful)
validFaces.computeIfAbsent(distance, $ -> new HashSet<>())
.add(currentFace);
return atLeastOneBranchSuccessful;
}
private boolean hasReachedValidEndpoint(IWorld world, BlockFace blockFace, boolean pull) {
BlockPos connectedPos = blockFace.getConnectedPos();
BlockState connectedState = world.getBlockState(connectedPos);
TileEntity tileEntity = world.getTileEntity(connectedPos);
Direction face = blockFace.getFace();
// facing a pump
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == face.getAxis() && tileEntity instanceof PumpTileEntity) {
PumpTileEntity pumpTE = (PumpTileEntity) tileEntity;
return pumpTE.isPullingOnSide(pumpTE.isFront(blockFace.getOppositeFace())) != pull;
}
// other pipe, no endpoint
FluidTransportBehaviour pipe = FluidPropagator.getPipe(world, connectedPos);
if (pipe != null && pipe.canHaveFlowToward(connectedState, blockFace.getOppositeFace()))
return false;
// fluid handler endpoint
if (tileEntity != null) {
LazyOptional<IFluidHandler> capability =
tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face.getOpposite());
if (capability.isPresent())
return true;
}
// open endpoint
return FluidPropagator.isOpenEnd(world, blockFace.getPos(), face);
}
@Override
public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Reversed", reversed);
serializeOpenEnds(compound);
super.write(compound, clientPacket);
}
@Override
protected void read(CompoundNBT compound, boolean clientPacket) {
reversed = compound.getBoolean("Reversed");
deserializeOpenEnds(compound);
super.read(compound, clientPacket);
}
@ -274,11 +304,10 @@ public class PumpTileEntity extends KineticTileEntity {
if (!isSideAccessible(side))
return;
updatePipeNetwork(isFront(side));
getBehaviour(FluidTransportBehaviour.TYPE).wipePressure();
}
protected boolean isFront(Direction side) {
if (networks == null)
return false;
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof PumpBlock))
return false;
@ -296,13 +325,8 @@ public class PumpTileEntity extends KineticTileEntity {
}
protected void updatePipeNetwork(boolean front) {
if (networks != null)
networks.get(front)
.clearFlows(world, true);
networksToUpdate.get(front)
sidesToUpdate.get(front)
.setTrue();
if (getSpeed() == 0 || (isPullingOnSide(front)) && networks != null)
setProvidedFluid(FluidStack.EMPTY);
}
public boolean isSideAccessible(Direction side) {
@ -317,113 +341,32 @@ public class PumpTileEntity extends KineticTileEntity {
return front == reversed;
}
public void spawnParticles() {
DistExecutor.runWhenOn(Dist.CLIENT, () -> this::spawnParticlesInner);
}
class PumpFluidTransferBehaviour extends FluidTransportBehaviour {
@OnlyIn(Dist.CLIENT)
private void spawnParticlesInner() {
if (!FluidPipeBehaviour.isRenderEntityWithinDistance(pos))
return;
for (boolean front : Iterate.trueAndFalse) {
Direction side = getFront();
if (side == null)
return;
if (!front)
side = side.getOpposite();
if (!FluidPropagator.isOpenEnd(world, pos, side))
continue;
BlockFace key = new BlockFace(pos, side);
Map<BlockFace, OpenEndedPipe> map = openEnds.get(front);
if (map.containsKey(key)) {
FluidStack fluidStack = map.get(key)
.getCapability()
.map(fh -> fh.getFluidInTank(0))
.orElse(FluidStack.EMPTY);
if (!fluidStack.isEmpty())
spawnPouringLiquid(fluidStack, side, 1);
}
}
}
@OnlyIn(Dist.CLIENT)
private void spawnPouringLiquid(FluidStack fluid, Direction side, int amount) {
IParticleData particle = FluidFX.getFluidParticle(fluid);
float rimRadius = 1 / 4f + 1 / 64f;
boolean inbound = isPullingOnSide(getFront() == side);
Vec3d directionVec = new Vec3d(side.getDirectionVec());
FluidFX.spawnPouringLiquid(world, pos, amount, particle, rimRadius, directionVec, inbound);
}
public Map<BlockFace, OpenEndedPipe> getOpenEnds(Direction side) {
return openEnds.get(isFront(side));
}
private void serializeOpenEnds(CompoundNBT compound) {
compound.put("OpenEnds", openEnds.serializeEach(m -> {
CompoundNBT compoundNBT = new CompoundNBT();
ListNBT entries = new ListNBT();
m.entrySet()
.forEach(e -> {
CompoundNBT innerCompound = new CompoundNBT();
innerCompound.put("Pos", e.getKey()
.serializeNBT());
e.getValue()
.writeToNBT(innerCompound);
entries.add(innerCompound);
});
compoundNBT.put("Entries", entries);
return compoundNBT;
}));
}
private void deserializeOpenEnds(CompoundNBT compound) {
openEnds = Couple.deserializeEach(compound.getList("OpenEnds", NBT.TAG_COMPOUND), c -> {
Map<BlockFace, OpenEndedPipe> map = new HashMap<>();
NBTHelper.iterateCompoundList(c.getList("Entries", NBT.TAG_COMPOUND), innerCompound -> {
BlockFace key = BlockFace.fromNBT(innerCompound.getCompound("Pos"));
OpenEndedPipe value = new OpenEndedPipe(key);
value.readNBT(innerCompound);
map.put(key, value);
});
return map;
});
compound.put("OpenEnds", openEnds.serializeEach(m -> {
CompoundNBT compoundNBT = new CompoundNBT();
ListNBT entries = new ListNBT();
m.entrySet()
.forEach(e -> {
CompoundNBT innerCompound = new CompoundNBT();
innerCompound.put("Pos", e.getKey()
.serializeNBT());
e.getValue()
.writeToNBT(innerCompound);
entries.add(innerCompound);
});
compoundNBT.put("Entries", entries);
return compoundNBT;
}));
}
public void setProvidedFluid(FluidStack providedFluid) {
this.providedFluid = providedFluid;
}
class PumpAttachmentBehaviour extends FluidPipeAttachmentBehaviour {
public PumpAttachmentBehaviour(SmartTileEntity te) {
public PumpFluidTransferBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean isPipeConnectedTowards(BlockState state, Direction direction) {
public void tick() {
super.tick();
for (Entry<Direction, PipeConnection> entry : interfaces.entrySet()) {
boolean pull = isPullingOnSide(isFront(entry.getKey()));
Couple<Float> pressure = entry.getValue().pressure;
pressure.set(pull, Math.abs(getSpeed()));
pressure.set(!pull, 0f);
}
}
@Override
public boolean canHaveFlowToward(BlockState state, Direction direction) {
return isSideAccessible(direction);
}
@Override
public AttachmentTypes getAttachment(ILightReader world, BlockPos pos, BlockState state, Direction direction) {
AttachmentTypes attachment = super.getAttachment(world, pos, state, direction);
public AttachmentTypes getRenderedRimAttachment(ILightReader world, BlockPos pos, BlockState state,
Direction direction) {
AttachmentTypes attachment = super.getRenderedRimAttachment(world, pos, state, direction);
if (attachment == AttachmentTypes.RIM)
return AttachmentTypes.NONE;
return attachment;

View file

@ -6,8 +6,8 @@ import java.util.Random;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllShapes;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.content.contraptions.wrench.IWrenchableWithBracket;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Iterate;
@ -22,9 +22,9 @@ import net.minecraft.network.DebugPacketSender;
import net.minecraft.state.BooleanProperty;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceResult;
@ -126,7 +126,7 @@ public class AxisPipeBlock extends RotatedPillarBlock implements IWrenchableWith
@Override
public Optional<ItemStack> removeBracket(IBlockReader world, BlockPos pos) {
FluidPipeAttachmentBehaviour behaviour = TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE);
BracketedTileEntityBehaviour behaviour = TileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
if (behaviour == null)
return Optional.empty();
BlockState bracket = behaviour.getBracket();

View file

@ -2,7 +2,6 @@ package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.Optional;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
@ -32,11 +31,12 @@ public class BracketBlockItem extends BlockItem {
BracketBlock bracketBlock = getBracketBlock();
PlayerEntity player = context.getPlayer();
BracketedTileEntityBehaviour behaviour = TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE);
if (behaviour == null)
behaviour = TileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
BracketedTileEntityBehaviour behaviour = TileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
if (behaviour != null && behaviour.canHaveBracket()) {
if (behaviour == null)
return ActionResultType.FAIL;
if (!behaviour.canHaveBracket())
return ActionResultType.FAIL;
if (world.isRemote)
return ActionResultType.SUCCESS;
@ -63,9 +63,6 @@ public class BracketBlockItem extends BlockItem {
return ActionResultType.SUCCESS;
}
return ActionResultType.FAIL;
}
private BracketBlock getBracketBlock() {
return (BracketBlock) getBlock();
}

View file

@ -7,8 +7,9 @@ import javax.annotation.Nullable;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.content.contraptions.wrench.IWrenchableWithBracket;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Iterate;
@ -136,17 +137,17 @@ public class FluidPipeBlock extends SixWayBlock implements IWaterLoggable, IWren
return state.getBlock() instanceof FluidPipeBlock;
}
public static boolean canConnectTo(ILightReader world, BlockPos pos, BlockState neighbour, Direction blockFace) {
if (FluidPropagator.hasFluidCapability(neighbour, world, pos, blockFace))
public static boolean canConnectTo(ILightReader world, BlockPos neighbourPos, BlockState neighbour, Direction direction) {
if (FluidPropagator.hasFluidCapability(world, neighbourPos, direction.getOpposite()))
return true;
FluidPipeAttachmentBehaviour attachmentBehaviour =
TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE);
FluidTransportBehaviour transport = TileEntityBehaviour.get(world, neighbourPos, FluidTransportBehaviour.TYPE);
BracketedTileEntityBehaviour bracket = TileEntityBehaviour.get(world, neighbourPos, BracketedTileEntityBehaviour.TYPE);
if (isPipe(neighbour))
return attachmentBehaviour == null || attachmentBehaviour.getBracket() == Blocks.AIR.getDefaultState()
|| FluidPropagator.getStraightPipeAxis(neighbour) == blockFace.getAxis();
if (attachmentBehaviour == null)
return bracket == null || !bracket.isBacketPresent()
|| FluidPropagator.getStraightPipeAxis(neighbour) == direction.getAxis();
if (transport == null)
return false;
return attachmentBehaviour.isPipeConnectedTowards(neighbour, blockFace.getOpposite());
return transport.canHaveFlowToward(neighbour, direction.getOpposite());
}
public static boolean shouldDrawRim(ILightReader world, BlockPos pos, BlockState state, Direction direction) {
@ -220,8 +221,8 @@ public class FluidPipeBlock extends SixWayBlock implements IWaterLoggable, IWren
public BlockState updateBlockState(BlockState state, Direction preferredDirection, @Nullable Direction ignore,
ILightReader world, BlockPos pos) {
FluidPipeAttachmentBehaviour behaviour = TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE);
if (behaviour != null && behaviour.getBracket() != Blocks.AIR.getDefaultState())
BracketedTileEntityBehaviour bracket = TileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
if (bracket != null && bracket.isBacketPresent())
return state;
// Update sides that are not ignored
@ -258,7 +259,8 @@ public class FluidPipeBlock extends SixWayBlock implements IWaterLoggable, IWren
@Override
public Optional<ItemStack> removeBracket(IBlockReader world, BlockPos pos) {
FluidPipeAttachmentBehaviour behaviour = TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE);
BracketedTileEntityBehaviour behaviour =
BracketedTileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
if (behaviour == null)
return Optional.empty();
BlockState bracket = behaviour.getBracket();

View file

@ -3,8 +3,8 @@ package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.List;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidPipeBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
@ -22,43 +22,40 @@ public class FluidPipeTileEntity extends SmartTileEntity {
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviours.add(new StandardPipeBehaviour(this));
behaviours.add(new StandardPipeAttachmentBehaviour(this));
behaviours.add(new StandardPipeFluidTransportBehaviour(this));
behaviours.add(new BracketedTileEntityBehaviour(this, this::canHaveBracket));
}
class StandardPipeBehaviour extends FluidPipeBehaviour {
private boolean canHaveBracket(BlockState state) {
return !(state.getBlock() instanceof EncasedPipeBlock);
}
public StandardPipeBehaviour(SmartTileEntity te) {
class StandardPipeFluidTransportBehaviour extends FluidTransportBehaviour {
public StandardPipeFluidTransportBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean isConnectedTo(BlockState state, Direction direction) {
public boolean canHaveFlowToward(BlockState state, Direction direction) {
return (FluidPipeBlock.isPipe(state) || state.getBlock() instanceof EncasedPipeBlock)
&& state.get(FluidPipeBlock.FACING_TO_PROPERTY_MAP.get(direction));
}
}
class StandardPipeAttachmentBehaviour extends FluidPipeAttachmentBehaviour {
public StandardPipeAttachmentBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public AttachmentTypes getAttachment(ILightReader world, BlockPos pos, BlockState state, Direction direction) {
AttachmentTypes attachment = super.getAttachment(world, pos, state, direction);
public AttachmentTypes getRenderedRimAttachment(ILightReader world, BlockPos pos, BlockState state,
Direction direction) {
AttachmentTypes attachment = super.getRenderedRimAttachment(world, pos, state, direction);
if (attachment == AttachmentTypes.RIM && AllBlocks.ENCASED_FLUID_PIPE.has(state))
return AttachmentTypes.RIM;
BlockPos offsetPos = pos.offset(direction);
if (!FluidPipeBlock.isPipe(world.getBlockState(offsetPos))) {
FluidPipeAttachmentBehaviour attachmentBehaviour =
TileEntityBehaviour.get(world, offsetPos, FluidPipeAttachmentBehaviour.TYPE);
if (attachmentBehaviour != null && attachmentBehaviour
.isPipeConnectedTowards(world.getBlockState(offsetPos), direction.getOpposite()))
FluidTransportBehaviour pipeBehaviour =
TileEntityBehaviour.get(world, offsetPos, FluidTransportBehaviour.TYPE);
if (pipeBehaviour != null
&& pipeBehaviour.canHaveFlowToward(world.getBlockState(offsetPos), direction.getOpposite()))
return AttachmentTypes.NONE;
}

View file

@ -1,12 +1,16 @@
package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.Random;
import com.simibubi.create.AllShapes;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.base.DirectionalAxisKineticBlock;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.network.DebugPacketSender;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer.Builder;
import net.minecraft.tileentity.TileEntity;
@ -17,6 +21,9 @@ import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.TickPriority;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
public class FluidValveBlock extends DirectionalAxisKineticBlock implements IAxisPipe {
@ -75,4 +82,49 @@ public class FluidValveBlock extends DirectionalAxisKineticBlock implements IAxi
return getPipeAxis(state);
}
@Override
public void onReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean isMoving) {
boolean blockTypeChanged = state.getBlock() != newState.getBlock();
if (blockTypeChanged && !world.isRemote)
FluidPropagator.propagateChangedPipe(world, pos, state);
if (state.hasTileEntity() && (blockTypeChanged || !newState.hasTileEntity()))
world.removeTileEntity(pos);
}
@Override
public boolean isValidPosition(BlockState p_196260_1_, IWorldReader p_196260_2_, BlockPos p_196260_3_) {
return true;
}
@Override
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean isMoving) {
if (world.isRemote)
return;
if (state != oldState)
world.getPendingBlockTicks()
.scheduleTick(pos, this, 1, TickPriority.HIGH);
}
@Override
public void neighborChanged(BlockState state, World world, BlockPos pos, Block otherBlock, BlockPos neighborPos,
boolean isMoving) {
DebugPacketSender.func_218806_a(world, pos);
Direction d = FluidPropagator.validateNeighbourChange(state, world, pos, otherBlock, neighborPos, isMoving);
if (d == null)
return;
if (!isOpenAt(state, d))
return;
world.getPendingBlockTicks()
.scheduleTick(pos, this, 1, TickPriority.HIGH);
}
public static boolean isOpenAt(BlockState state, Direction d) {
return d.getAxis() == getPipeAxis(state);
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random r) {
FluidPropagator.propagateChangedPipe(world, pos, state);
}
}

View file

@ -3,8 +3,7 @@ package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.List;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.content.contraptions.fluids.FluidPipeBehaviour;
import com.simibubi.create.content.contraptions.fluids.pipes.StraightPipeTileEntity.StraightPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.fluids.pipes.StraightPipeTileEntity.StraightPipeFluidTransportBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.LerpedFloat;
@ -24,7 +23,8 @@ public class FluidValveTileEntity extends KineticTileEntity {
public FluidValveTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn);
pointer = LerpedFloat.linear()
.startWithValue(0).chase(0, 0, Chaser.LINEAR);
.startWithValue(0)
.chase(0, 0, Chaser.LINEAR);
}
@Override
@ -77,24 +77,23 @@ public class FluidValveTileEntity extends KineticTileEntity {
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviours.add(new ValvePipeBehaviour(this));
behaviours.add(new StraightPipeAttachmentBehaviour(this));
}
class ValvePipeBehaviour extends FluidPipeBehaviour {
class ValvePipeBehaviour extends StraightPipeFluidTransportBehaviour {
public ValvePipeBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean isConnectedTo(BlockState state, Direction direction) {
public boolean canHaveFlowToward(BlockState state, Direction direction) {
return FluidValveBlock.getPipeAxis(state) == direction.getAxis();
}
@Override
public boolean canTransferToward(FluidStack fluid, BlockState state, Direction direction, boolean inbound) {
public boolean canPullFluidFrom(FluidStack fluid, BlockState state, Direction direction) {
if (state.has(FluidValveBlock.ENABLED) && state.get(FluidValveBlock.ENABLED))
return super.canTransferToward(fluid, state, direction, inbound);
return super.canPullFluidFrom(fluid, state, direction);
return false;
}

View file

@ -118,6 +118,11 @@ public class SmartFluidPipeBlock extends HorizontalFaceBlock implements IAxisPip
return d.getAxis() == getPipeAxis(state);
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random r) {
FluidPropagator.propagateChangedPipe(world, pos, state);
}
protected static Axis getPipeAxis(BlockState state) {
return state.get(FACE) == AttachFace.WALL ? Axis.Y
: state.get(HORIZONTAL_FACING)
@ -134,11 +139,6 @@ public class SmartFluidPipeBlock extends HorizontalFaceBlock implements IAxisPip
return AllTileEntities.SMART_FLUID_PIPE.create();
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random r) {
FluidPropagator.propagateChangedPipe(world, pos, state);
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader p_220053_2_, BlockPos p_220053_3_,
ISelectionContext p_220053_4_) {

View file

@ -3,9 +3,8 @@ package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.List;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.content.contraptions.fluids.FluidPipeBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.content.contraptions.fluids.pipes.StraightPipeTileEntity.StraightPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.fluids.pipes.StraightPipeTileEntity.StraightPipeFluidTransportBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform;
@ -34,7 +33,6 @@ public class SmartFluidPipeTileEntity extends SmartTileEntity {
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviours.add(new SmartPipeBehaviour(this));
behaviours.add(new StraightPipeAttachmentBehaviour(this));
behaviours.add(filter = new FilteringBehaviour(this, new SmartPipeFilterSlot()).forFluids()
.withCallback(this::onFilterChanged));
}
@ -45,21 +43,21 @@ public class SmartFluidPipeTileEntity extends SmartTileEntity {
FluidPropagator.propagateChangedPipe(world, pos, getBlockState());
}
class SmartPipeBehaviour extends FluidPipeBehaviour {
class SmartPipeBehaviour extends StraightPipeFluidTransportBehaviour {
public SmartPipeBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean canTransferToward(FluidStack fluid, BlockState state, Direction direction, boolean inbound) {
public boolean canPullFluidFrom(FluidStack fluid, BlockState state, Direction direction) {
if (fluid.isEmpty() || filter != null && filter.test(fluid))
return super.canTransferToward(fluid, state, direction, inbound);
return super.canPullFluidFrom(fluid, state, direction);
return false;
}
@Override
public boolean isConnectedTo(BlockState state, Direction direction) {
public boolean canHaveFlowToward(BlockState state, Direction direction) {
return state.getBlock() instanceof SmartFluidPipeBlock
&& SmartFluidPipeBlock.getPipeAxis(state) == direction.getAxis();
}

View file

@ -2,8 +2,8 @@ package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.List;
import com.simibubi.create.content.contraptions.fluids.FluidPipeAttachmentBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidPipeBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
@ -23,32 +23,25 @@ public class StraightPipeTileEntity extends SmartTileEntity {
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviours.add(new StraightPipeBehaviour(this));
behaviours.add(new StraightPipeAttachmentBehaviour(this));
behaviours.add(new StraightPipeFluidTransportBehaviour(this));
behaviours.add(new BracketedTileEntityBehaviour(this));
}
class StraightPipeBehaviour extends FluidPipeBehaviour {
static class StraightPipeFluidTransportBehaviour extends FluidTransportBehaviour {
public StraightPipeBehaviour(SmartTileEntity te) {
public StraightPipeFluidTransportBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean isConnectedTo(BlockState state, Direction direction) {
public boolean canHaveFlowToward(BlockState state, Direction direction) {
return state.get(AxisPipeBlock.AXIS) == direction.getAxis();
}
}
static class StraightPipeAttachmentBehaviour extends FluidPipeAttachmentBehaviour {
public StraightPipeAttachmentBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public AttachmentTypes getAttachment(ILightReader world, BlockPos pos, BlockState state, Direction direction) {
AttachmentTypes attachment = super.getAttachment(world, pos, state, direction);
public AttachmentTypes getRenderedRimAttachment(ILightReader world, BlockPos pos, BlockState state,
Direction direction) {
AttachmentTypes attachment = super.getRenderedRimAttachment(world, pos, state, direction);
BlockState otherState = world.getBlockState(pos.offset(direction));
Axis axis = IAxisPipe.getAxisOf(state);

View file

@ -1,13 +1,13 @@
package com.simibubi.create.content.contraptions.fluids.pipes;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.content.contraptions.fluids.FluidPipeBehaviour;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour;
import com.simibubi.create.content.contraptions.fluids.PipeConnection.Flow;
import com.simibubi.create.foundation.fluid.FluidRenderer;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.renderer.SafeTileEntityRenderer;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
@ -23,32 +23,40 @@ public class TransparentStraightPipeRenderer extends SafeTileEntityRenderer<Stra
@Override
protected void renderSafe(StraightPipeTileEntity te, float partialTicks, MatrixStack ms, IRenderTypeBuffer buffer,
int light, int overlay) {
FluidPipeBehaviour pipe = te.getBehaviour(FluidPipeBehaviour.TYPE);
FluidTransportBehaviour pipe = te.getBehaviour(FluidTransportBehaviour.TYPE);
if (pipe == null)
return;
FluidStack fluidStack = pipe.getFluid();
if (fluidStack.isEmpty())
return;
for (Direction side : Iterate.directions) {
if (!pipe.isConnectedTo(te.getBlockState(), side))
Flow flow = pipe.getFlow(side);
if (flow == null)
continue;
Pair<Boolean, LerpedFloat> strogestFlow = pipe.getStrogestFlow(side);
if (strogestFlow == null)
FluidStack fluidStack = flow.fluid;
if (fluidStack.isEmpty())
continue;
LerpedFloat second = strogestFlow.getSecond();
if (second == null)
LerpedFloat progress = flow.progress;
if (progress == null)
continue;
float value = second.getValue(partialTicks);
Boolean inbound = strogestFlow.getFirst();
if (value == 1 && !inbound) {
FluidPipeBehaviour adjacent = TileEntityBehaviour.get(te.getWorld(), te.getPos()
.offset(side), FluidPipeBehaviour.TYPE);
if (adjacent != null && adjacent.getFluid()
.isEmpty())
float value = progress.getValue(partialTicks);
boolean inbound = flow.inbound;
if (value == 1) {
if (inbound) {
Flow opposite = pipe.getFlow(side.getOpposite());
if (opposite == null)
value -= 1e-6f;
} else {
FluidTransportBehaviour adjacent = TileEntityBehaviour.get(te.getWorld(), te.getPos()
.offset(side), FluidTransportBehaviour.TYPE);
if (adjacent == null)
value -= 1e-6f;
else {
Flow other = adjacent.getFlow(side.getOpposite());
if (other == null || !other.inbound && !other.complete)
value -= 1e-6f;
}
}
}
FluidRenderer.renderFluidStream(fluidStack, side, 3 / 16f, value, inbound, buffer, ms, light);

View file

@ -1,16 +1,20 @@
package com.simibubi.create.content.contraptions.relays.elementary;
import java.util.Optional;
import java.util.function.Predicate;
import com.google.common.base.Predicates;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.world.World;
public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
@ -19,8 +23,15 @@ public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
private Optional<BlockState> bracket;
private boolean reRender;
private Predicate<BlockState> pred;
public BracketedTileEntityBehaviour(SmartTileEntity te) {
this(te, Predicates.alwaysTrue());
}
public BracketedTileEntityBehaviour(SmartTileEntity te, Predicate<BlockState> pred) {
super(te);
this.pred = pred;
bracket = Optional.empty();
}
@ -36,11 +47,18 @@ public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
}
public void removeBracket() {
World world = getWorld();
if (!world.isRemote)
world.playEvent(2001, getPos(), Block.getStateId(getBracket()));
this.bracket = Optional.empty();
reRender = true;
tileEntity.notifyUpdate();
}
public boolean isBacketPresent() {
return getBracket() != Blocks.AIR.getDefaultState();
}
public BlockState getBracket() {
return bracket.orElse(Blocks.AIR.getDefaultState());
}
@ -66,10 +84,7 @@ public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
}
public boolean canHaveBracket() {
BlockState blockState = tileEntity.getBlockState();
if (blockState.getBlock() instanceof AbstractShaftBlock)
return true;
return false;
return pred.test(tileEntity.getBlockState());
}
}

View file

@ -16,7 +16,7 @@ public class SimpleKineticTileEntity extends KineticTileEntity {
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviours.add(new BracketedTileEntityBehaviour(this));
behaviours.add(new BracketedTileEntityBehaviour(this, state -> state.getBlock() instanceof AbstractShaftBlock));
super.addBehaviours(behaviours);
}

View file

@ -51,6 +51,14 @@ public class FluidHelper {
return blockState != null && blockState != Blocks.AIR.getDefaultState();
}
public static FluidStack copyStackWithAmount(FluidStack fs, int amount) {
if (fs.isEmpty())
return FluidStack.EMPTY;
FluidStack copy = fs.copy();
copy.setAmount(amount);
return copy;
}
public static Fluid convertToFlowing(Fluid fluid) {
if (fluid == Fluids.WATER)
return Fluids.FLOWING_WATER;

View file

@ -48,6 +48,10 @@ public abstract class Outline {
}
public void renderAACuboidLine(MatrixStack ms, SuperRenderTypeBuffer buffer, Vec3d start, Vec3d end) {
float lineWidth = params.getLineWidth();
if (lineWidth == 0)
return;
IVertexBuilder builder = buffer.getBuffer(RenderTypes.getOutlineSolid());
Vec3d diff = end.subtract(start);
@ -58,7 +62,6 @@ public abstract class Outline {
diff = diff.scale(-1);
}
float lineWidth = params.getLineWidth();
Vec3d extension = diff.normalize()
.scale(lineWidth / 2);
Vec3d plane = VecHelper.axisAlingedPlaneOf(diff);