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 java.util.Map;
import com.mojang.blaze3d.matrix.MatrixStack; 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.content.contraptions.processing.burner.BlazeBurnerBlock.HeatLevel;
import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.Iterate; 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; package com.simibubi.create.content.contraptions.fluids;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair; 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.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
public class FluidNetwork { public class FluidNetwork {
BlockFace pumpLocation; private static int CYCLES_PER_TICK = 16;
Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph;
List<FluidNetworkFlow> flows;
Set<FluidNetworkEndpoint> targets;
Set<BlockFace> rangeEndpoints;
Map<BlockFace, FluidStack> previousFlow;
boolean connectToPumps; World world;
int waitForUnloadedNetwork; BlockFace start;
public FluidNetwork() { Supplier<LazyOptional<IFluidHandler>> sourceSupplier;
pipeGraph = new HashMap<>(); LazyOptional<IFluidHandler> source;
flows = new ArrayList<>(); int transferSpeed;
targets = new HashSet<>();
rangeEndpoints = new HashSet<>(); int pauseBeforePropagation;
previousFlow = new HashMap<>(); 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() { public void tick() {
for (FluidNetworkFlow pipeFlow : flows) if (pauseBeforePropagation > 0) {
if (pipeFlow.hasValidTargets()) pauseBeforePropagation--;
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)
return; return;
initFlows(pumpTE, pulling);
previousFlow.clear();
flows.forEach(ep -> ep.tick(world, speed));
} }
private void initFlows(PumpTileEntity pumpTE, boolean pulling) { for (int cycle = 0; cycle < CYCLES_PER_TICK; cycle++) {
if (targets.isEmpty()) boolean shouldContinue = false;
return; for (Iterator<BlockFace> iterator = queued.iterator(); iterator.hasNext();) {
if (!flows.isEmpty()) BlockFace blockFace = iterator.next();
return; if (!isPresent(blockFace))
World world = pumpTE.getWorld(); continue;
if (pulling) { PipeConnection pipeConnection = get(blockFace);
targets.forEach(ne -> flows.add(new FluidNetworkFlow(this, ne, world, pulling))); if (pipeConnection != null) {
} else { if (blockFace.equals(start))
PumpEndpoint pumpEndpoint = new PumpEndpoint(pumpLocation.getOpposite(), pumpTE); transferSpeed = (int) Math.max(1, pipeConnection.pressure.get(true) / 2f);
flows.add(new FluidNetworkFlow(this, pumpEndpoint, world, pulling)); frontier.add(Pair.of(blockFace, pipeConnection));
} }
iterator.remove();
} }
public void connectToOtherFNs(IWorld world, PumpTileEntity pump) { // drawDebugOutlines();
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
int maxDistance = FluidPropagator.getPumpRange() * 2;
frontier.add(Pair.of(-1, pumpLocation.getPos()));
while (!frontier.isEmpty()) { for (Iterator<Pair<BlockFace, PipeConnection>> iterator = frontier.iterator(); iterator.hasNext();) {
Pair<Integer, BlockPos> entry = frontier.remove(0); Pair<BlockFace, PipeConnection> pair = iterator.next();
int distance = entry.getFirst(); BlockFace blockFace = pair.getFirst();
BlockPos currentPos = entry.getSecond(); PipeConnection pipeConnection = pair.getSecond();
if (!world.isAreaLoaded(currentPos, 0)) if (!pipeConnection.hasFlow())
continue; continue;
if (visited.contains(currentPos)) Flow flow = pipeConnection.flow.get();
if (!flow.inbound) {
if (pipeConnection.comparePressure() >= 0)
iterator.remove();
continue;
}
if (!flow.complete)
continue; continue;
visited.add(currentPos);
List<Direction> connections; boolean canRemove = true;
if (currentPos.equals(pumpLocation.getPos())) { for (Direction side : Iterate.directions) {
connections = ImmutableList.of(pumpLocation.getFace()); if (side == blockFace.getFace())
} else { continue;
BlockState currentState = world.getBlockState(currentPos); BlockFace adjacentLocation = new BlockFace(blockFace.getPos(), side);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, currentPos); PipeConnection adjacent = get(adjacentLocation);
if (pipe == null) 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; continue;
connections = FluidPropagator.getPipeConnections(currentState, pipe);
} }
for (Direction face : connections) { if (adjacent.source.isPresent() && adjacent.source.get()
BlockFace blockFace = new BlockFace(currentPos, face); .isEndpoint()) {
BlockPos connectedPos = blockFace.getConnectedPos(); targets.add(Pair.of(adjacentLocation, adjacent.source.get()
BlockState connectedState = world.getBlockState(connectedPos); .provideHandler()));
continue;
}
if (connectedPos.equals(pumpLocation.getPos())) if (visited.add(adjacentLocation.getConnectedPos())) {
continue; queued.add(adjacentLocation.getOpposite());
if (!world.isAreaLoaded(connectedPos, 0)) shouldContinue = true;
continue; }
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING) }
.getAxis() == face.getAxis()) { if (canRemove)
TileEntity tileEntity = world.getTileEntity(connectedPos); iterator.remove();
if (tileEntity instanceof PumpTileEntity) { }
PumpTileEntity otherPump = (PumpTileEntity) tileEntity; if (!shouldContinue)
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;
break; 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())) // drawDebugOutlines();
continue;
if (nearest <= distanceFromPump)
continue;
nearest = distanceFromPump;
argNearest = pumpEndpoint;
} if (!source.isPresent())
if (argNearest != null) { source = sourceSupplier.get();
InterPumpEndpoint endpoint = new InterPumpEndpoint(world, argNearest.getOpposite(), if (!source.isPresent())
pump, otherPump, pumpLocation, fn.pumpLocation); return;
targets.add(endpoint); if (targets.isEmpty())
fn.targets.add(endpoint.opposite(world)); 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) { if (transfer.isEmpty())
Map<BlockFace, OpenEndedPipe> openEnds = pumpTE.getOpenEnds(pumpLocation.getFace()); break;
openEnds.values() IFluidHandler targetHandler = pair.getSecond()
.forEach(OpenEndedPipe::markStale); .orElse(null);
if (targetHandler == null) {
this.pumpLocation = pumpLocation; iterator.remove();
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);
continue; continue;
} }
addConnection(connectedPos, currentPos, face.getOpposite(), distance); FluidStack divided = transfer.copy();
frontier.add(Pair.of(distance + 1, connectedPos)); 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, flowSpeed -= transfer.getAmount();
int distance) { transfer = FluidStack.EMPTY;
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;
} }
} }
// open endpoint // private void drawDebugOutlines() {
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING) // FluidPropagator.showBlockFace(start)
.getAxis() == face.getAxis()) { // .lineWidth(1 / 8f)
rangeEndpoints.add(blockFace); // .colored(0xff0000);
addEntry(blockFace.getPos(), face, false, distance); // for (Pair<BlockFace, LazyOptional<IFluidHandler>> pair : targets)
return true; // FluidPropagator.showBlockFace(pair.getFirst())
} // .lineWidth(1 / 8f)
if (!FluidPropagator.isOpenEnd(world, blockFace.getPos(), face)) // .colored(0x00ff00);
return false; // for (Pair<BlockFace, PipeConnection> pair : frontier)
// FluidPropagator.showBlockFace(pair.getFirst())
// .lineWidth(1 / 4f)
// .colored(0xfaaa33);
// }
targets.add(reuseOrCreateOpenEnd(world, openEnds, blockFace)); public void reset() {
addEntry(blockFace.getPos(), face, false, distance); frontier.clear();
FluidPropagator.showBlockFace(blockFace) visited.clear();
.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();
targets.clear(); targets.clear();
pipeGraph.clear(); queued.clear();
assemble(world, pumpTE, pumpLocation); queued.add(start);
pauseBeforePropagation = 2;
} }
public void remove(IWorld world) { @Nullable
clearFlows(world, false); 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) { private boolean isPresent(BlockFace location) {
for (FluidNetworkFlow networkFlow : flows) { return world.isAreaLoaded(location.getPos(), 0);
if (!networkFlow.getFluidStack()
.isEmpty())
networkFlow.addToSkippedConnections(world);
networkFlow.resetFlow(world);
} }
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 javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.AllBlocks; 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.AxisPipeBlock;
import com.simibubi.create.content.contraptions.fluids.pipes.FluidPipeBlock; import com.simibubi.create.content.contraptions.fluids.pipes.FluidPipeBlock;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; 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.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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -26,7 +24,6 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis; import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection; import net.minecraft.util.Direction.AxisDirection;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld; import net.minecraft.world.IWorld;
@ -35,6 +32,94 @@ import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
public class FluidPropagator { 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, public static Direction validateNeighbourChange(BlockState state, World world, BlockPos pos, Block otherBlock,
BlockPos neighborPos, boolean isMoving) { BlockPos neighborPos, boolean isMoving) {
if (world.isRemote) if (world.isRemote)
@ -58,15 +143,15 @@ public class FluidPropagator {
return null; return null;
} }
public static FluidPipeBehaviour getPipe(IBlockReader reader, BlockPos pos) { public static FluidTransportBehaviour getPipe(IBlockReader reader, BlockPos pos) {
return TileEntityBehaviour.get(reader, pos, FluidPipeBehaviour.TYPE); return TileEntityBehaviour.get(reader, pos, FluidTransportBehaviour.TYPE);
} }
public static boolean isOpenEnd(IBlockReader reader, BlockPos pos, Direction side) { public static boolean isOpenEnd(IBlockReader reader, BlockPos pos, Direction side) {
BlockPos connectedPos = pos.offset(side); BlockPos connectedPos = pos.offset(side);
BlockState connectedState = reader.getBlockState(connectedPos); BlockState connectedState = reader.getBlockState(connectedPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(reader, connectedPos); FluidTransportBehaviour pipe = FluidPropagator.getPipe(reader, connectedPos);
if (pipe != null && pipe.isConnectedTo(connectedState, side.getOpposite())) if (pipe != null && pipe.canHaveFlowToward(connectedState, side.getOpposite()))
return false; return false;
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING) if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == side.getAxis()) .getAxis() == side.getAxis())
@ -80,52 +165,10 @@ public class FluidPropagator {
return true; return true;
} }
public static void propagateChangedPipe(IWorld world, BlockPos pipePos, BlockState pipeState) { public static List<Direction> getPipeConnections(BlockState state, FluidTransportBehaviour pipe) {
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) {
List<Direction> list = new ArrayList<>(); List<Direction> list = new ArrayList<>();
for (Direction d : Iterate.directions) for (Direction d : Iterate.directions)
if (pipe.isConnectedTo(state, d)) if (pipe.canHaveFlowToward(state, d))
list.add(d); list.add(d);
return list; return list;
} }
@ -134,37 +177,38 @@ public class FluidPropagator {
return AllConfigs.SERVER.fluids.mechanicalPumpRange.get(); return AllConfigs.SERVER.fluids.mechanicalPumpRange.get();
} }
@Deprecated // Remove after pipes are fixed; comment out for production // static AxisAlignedBB smallCenter = new AxisAlignedBB(BlockPos.ZERO).shrink(.25);
public static OutlineParams showBlockFace(BlockFace face) { //
MutableObject<OutlineParams> params = new MutableObject<>(new OutlineParams()); // @Deprecated
// public static OutlineParams showBlockFace(BlockFace face) {
// MutableObject<OutlineParams> params = new MutableObject<>(new OutlineParams());
// DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> { // DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> {
// Vec3d directionVec = new Vec3d(face.getFace() // Vec3d directionVec = new Vec3d(face.getFace()
// .getDirectionVec()); // .getDirectionVec());
// Vec3d scaleVec = directionVec.scale(-.25f * face.getFace() // Vec3d scaleVec = directionVec.scale(-.25f * face.getFace()
// .getAxisDirection() // .getAxisDirection()
// .getOffset()); // .getOffset());
// directionVec = directionVec.scale(.5f); // directionVec = directionVec.scale(.45f);
// params.setValue(CreateClient.outliner.showAABB(face, // params.setValue(CreateClient.outliner.showAABB(face,
// FluidPropagator.smallCenter.offset(directionVec.add(new Vec3d(face.getPos()))) // FluidPropagator.smallCenter.offset(directionVec.add(new Vec3d(face.getPos())))
// .grow(scaleVec.x, scaleVec.y, scaleVec.z) // .grow(scaleVec.x, scaleVec.y, scaleVec.z)
// .grow(1 / 16f))); // .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(IBlockReader world, BlockPos pos, Direction side) {
public static boolean hasFluidCapability(BlockState state, IBlockReader world, BlockPos pos, Direction blockFace) {
if (!state.hasTileEntity())
return false;
TileEntity tileEntity = world.getTileEntity(pos); TileEntity tileEntity = world.getTileEntity(pos);
return tileEntity != null return tileEntity != null && tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side)
&& tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, blockFace.getOpposite())
.isPresent(); .isPresent();
} }
@Nullable @Nullable
public static Axis getStraightPipeAxis(BlockState state) { public static Axis getStraightPipeAxis(BlockState state) {
if (state.getBlock() instanceof PumpBlock)
return state.get(PumpBlock.FACING)
.getAxis();
if (state.getBlock() instanceof AxisPipeBlock) if (state.getBlock() instanceof AxisPipeBlock)
return state.get(AxisPipeBlock.AXIS); return state.get(AxisPipeBlock.AXIS);
if (!FluidPipeBlock.isPipe(state)) if (!FluidPipeBlock.isPipe(state))

View File

@ -1,12 +1,15 @@
package com.simibubi.create.content.contraptions.fluids; package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.AllFluids;
import com.simibubi.create.foundation.fluid.FluidHelper; import com.simibubi.create.foundation.fluid.FluidHelper;
import com.simibubi.create.foundation.utility.BlockHelper; import com.simibubi.create.foundation.utility.BlockHelper;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.fluid.Fluid; import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids; import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState; import net.minecraft.fluid.IFluidState;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
@ -19,19 +22,41 @@ public class FluidReactions {
BlockHelper.destroyBlock(world, pos, 1); BlockHelper.destroyBlock(world, pos, 1);
if (f1 == Fluids.WATER && f2 == Fluids.LAVA || f2 == Fluids.WATER && f1 == Fluids.LAVA) if (f1 == Fluids.WATER && f2 == Fluids.LAVA || f2 == Fluids.WATER && f1 == Fluids.LAVA)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState()); 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) { public static void handlePipeSpillCollision(World world, BlockPos pos, Fluid pipeFluid, IFluidState worldFluid) {
Fluid pf = FluidHelper.convertToStill(pipeFluid); Fluid pf = FluidHelper.convertToStill(pipeFluid);
Fluid wf = worldFluid.getFluid(); 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()); 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()); world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
else if (pf == Fluids.LAVA && wf == Fluids.WATER) else if (pf == Fluids.LAVA && wf == Fluids.WATER)
world.setBlockState(pos, Blocks.STONE.getDefaultState()); world.setBlockState(pos, Blocks.STONE.getDefaultState());
else if (pf == Fluids.LAVA && wf == Fluids.FLOWING_WATER) else if (pf == Fluids.LAVA && wf == Fluids.FLOWING_WATER)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState()); 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.content.contraptions.fluids.potion.PotionFluidHandler;
import com.simibubi.create.foundation.fluid.FluidHelper; import com.simibubi.create.foundation.fluid.FluidHelper;
import com.simibubi.create.foundation.utility.BlockFace; import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.FlowingFluidBlock; import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids; import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState; import net.minecraft.fluid.IFluidState;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -24,7 +22,6 @@ import net.minecraft.potion.EffectInstance;
import net.minecraft.potion.PotionUtils; import net.minecraft.potion.PotionUtils;
import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tags.FluidTags; import net.minecraft.tags.FluidTags;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvents; import net.minecraft.util.SoundEvents;
@ -34,10 +31,9 @@ import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.templates.FluidTank; import net.minecraftforge.fluids.capability.templates.FluidTank;
public class OpenEndedPipe { public class OpenEndedPipe extends FlowSource {
World world; World world;
BlockPos pos; BlockPos pos;
@ -46,12 +42,12 @@ public class OpenEndedPipe {
private OpenEndFluidHandler fluidHandler; private OpenEndFluidHandler fluidHandler;
private BlockPos outputPos; private BlockPos outputPos;
private boolean wasPulling; private boolean wasPulling;
private boolean stale;
private FluidStack cachedFluid; private FluidStack cachedFluid;
private List<EffectInstance> cachedEffects; private List<EffectInstance> cachedEffects;
public OpenEndedPipe(BlockFace face) { public OpenEndedPipe(BlockFace face) {
super(face);
fluidHandler = new OpenEndFluidHandler(); fluidHandler = new OpenEndFluidHandler();
outputPos = face.getConnectedPos(); outputPos = face.getConnectedPos();
pos = face.getPos(); pos = face.getPos();
@ -60,87 +56,106 @@ public class OpenEndedPipe {
aoe = aoe.expand(0, -1, 0); aoe = aoe.expand(0, -1, 0);
} }
public void tick(World world, boolean pulling) { @Override
public void manageSource(World world) {
this.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); BlockState state = world.getBlockState(outputPos);
IFluidState fluidState = state.getFluidState(); IFluidState fluidState = state.getFluidState();
boolean waterlog = state.has(BlockStateProperties.WATERLOGGED); boolean waterlog = state.has(BlockStateProperties.WATERLOGGED);
if (!waterlog && !state.getMaterial() if (!waterlog && !state.getMaterial()
.isReplaceable()) .isReplaceable())
return; return empty;
if (pulling) {
if (fluidState.isEmpty() || !fluidState.isSource()) if (fluidState.isEmpty() || !fluidState.isSource())
return; return empty;
if (!fluidHandler.tryCollectFluid(fluidState.getFluid()))
return; FluidStack stack = new FluidStack(fluidState.getFluid(), 1000);
if (simulate)
return stack;
if (waterlog) { if (waterlog) {
world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, false), 3); world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, false), 3);
world.getPendingFluidTicks() world.getPendingFluidTicks()
.scheduleTick(outputPos, Fluids.WATER, 1); .scheduleTick(outputPos, Fluids.WATER, 1);
return; return stack;
} }
world.setBlockState(outputPos, fluidState.getBlockState() world.setBlockState(outputPos, fluidState.getBlockState()
.with(FlowingFluidBlock.LEVEL, 14), 3); .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()) if (fluid.isEmpty())
return; return false;
if (!FluidHelper.hasBlockState(fluid.getFluid())) { if (!FluidHelper.hasBlockState(fluid.getFluid())) {
fluidHandler.drain(fluid.getAmount() > 1 ? fluid.getAmount() - 1 : 1, FluidAction.EXECUTE); if (!simulate)
if (fluidHandler.isEmpty()) applyEffects(world, fluid);
updatePumpIfNecessary(); return true;
if (!fluid.getFluid()
.isEquivalentTo(AllFluids.POTION.get()))
return;
applyPotionEffects(world, fluid);
return;
} }
Fluid providedFluid = fluidHandler.tryProvidingFluid(); if (!fluidState.isEmpty() && fluidState.getFluid() != fluid.getFluid()) {
if (providedFluid == null) FluidReactions.handlePipeSpillCollision(world, outputPos, fluid.getFluid(), fluidState);
return; return false;
if (!fluidState.isEmpty() && fluidState.getFluid() != providedFluid) {
FluidReactions.handlePipeSpillCollision(world, outputPos, providedFluid, fluidState);
return;
} }
if (fluidState.isSource()) 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)) { .isIn(FluidTags.WATER)) {
int i = outputPos.getX(); int i = outputPos.getX();
int j = outputPos.getY(); int j = outputPos.getY();
int k = outputPos.getZ(); int k = outputPos.getZ();
world.playSound(null, i, j, k, SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F, world.playSound(null, i, j, k, SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F,
2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F); 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
return; return true;
} }
if (waterlog) { if (waterlog) {
if (providedFluid.getFluid() != Fluids.WATER)
return;
world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, true), 3); world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, true), 3);
world.getPendingFluidTicks() world.getPendingFluidTicks()
.scheduleTick(outputPos, Fluids.WATER, 1); .scheduleTick(outputPos, Fluids.WATER, 1);
return; return true;
} }
world.setBlockState(outputPos, providedFluid.getDefaultState() world.setBlockState(outputPos, fluid.getFluid()
.getDefaultState()
.getBlockState(), 3); .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)) { if (cachedFluid == null || cachedEffects == null || !fluid.isFluidEqual(cachedFluid)) {
FluidStack copy = fluid.copy(); FluidStack copy = fluid.copy();
copy.setAmount(250); copy.setAmount(250);
@ -166,47 +181,30 @@ public class OpenEndedPipe {
} }
public LazyOptional<IFluidHandler> getCapability() { @Override
public LazyOptional<IFluidHandler> provideHandler() {
return LazyOptional.of(() -> fluidHandler); return LazyOptional.of(() -> fluidHandler);
} }
public CompoundNBT writeToNBT(CompoundNBT compound) { public CompoundNBT serializeNBT() {
CompoundNBT compound = new CompoundNBT();
fluidHandler.writeToNBT(compound); fluidHandler.writeToNBT(compound);
compound.putBoolean("Pulling", wasPulling); compound.putBoolean("Pulling", wasPulling);
compound.put("Location", location.serializeNBT());
return compound; return compound;
} }
public void readNBT(CompoundNBT compound) { public static OpenEndedPipe fromNBT(CompoundNBT compound) {
fluidHandler.readFromNBT(compound); OpenEndedPipe oep = new OpenEndedPipe(BlockFace.fromNBT(compound.getCompound("Location")));
wasPulling = compound.getBoolean("Pulling"); oep.fluidHandler.readFromNBT(compound);
} oep.wasPulling = compound.getBoolean("Pulling");
return oep;
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();
} }
private class OpenEndFluidHandler extends FluidTank { private class OpenEndFluidHandler extends FluidTank {
public OpenEndFluidHandler() { public OpenEndFluidHandler() {
super(1500); super(1000);
} }
@Override @Override
@ -218,76 +216,76 @@ public class OpenEndedPipe {
return 0; return 0;
if (resource.isEmpty()) if (resource.isEmpty())
return 0; return 0;
if (!provideFluidToSpace(resource, true))
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())
return 0; return 0;
// Never allow being filled above 1000 if (!getFluid().isEmpty() && !getFluid().isFluidEqual(resource))
FluidStack insertable = resource.copy(); setFluid(FluidStack.EMPTY);
insertable.setAmount(Math.min(insertable.getAmount(), Math.max(1000 - getFluidAmount(), 0))); if (wasPulling)
int fill = super.fill(insertable, action); wasPulling = false;
if (!getFluid().isFluidEqual(prevFluid))
updatePumpIfNecessary();
int fill = super.fill(resource, action);
if (action.execute() && (getFluidAmount() == 1000 || !FluidHelper.hasBlockState(getFluid().getFluid()))
&& provideFluidToSpace(getFluid(), false))
setFluid(FluidStack.EMPTY);
return fill; return fill;
} }
@Override @Override
public FluidStack drain(FluidStack resource, FluidAction action) { public FluidStack drain(FluidStack resource, FluidAction action) {
boolean wasEmpty = isEmpty(); return drainInner(resource.getAmount(), resource, action);
FluidStack drain = super.drain(resource, action);
if (action.execute() && !wasEmpty && isEmpty())
updatePumpIfNecessary();
return drain;
} }
@Override @Override
public FluidStack drain(int maxDrain, FluidAction action) { public FluidStack drain(int maxDrain, FluidAction action) {
boolean wasEmpty = isEmpty(); return drainInner(maxDrain, null, action);
FluidStack drain = super.drain(maxDrain, action);
if (action.execute() && !wasEmpty && isEmpty())
updatePumpIfNecessary();
return drain;
} }
public boolean tryCollectFluid(Fluid fluid) { private FluidStack drainInner(int amount, @Nullable FluidStack filter, FluidAction action) {
for (boolean simulate : Iterate.trueAndFalse) FluidStack empty = FluidStack.EMPTY;
if (super.fill(new FluidStack(fluid, 1000), boolean filterPresent = filter != null;
simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE) != 1000)
return false; if (world == null)
updatePumpIfNecessary(); 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; 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 java.util.Random;
import com.simibubi.create.AllBlockPartials; 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.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.block.connected.BakedModelWrapperWithData;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
@ -35,16 +36,16 @@ public class PipeAttachmentModel extends BakedModelWrapperWithData {
@Override @Override
protected Builder gatherModelData(Builder builder, ILightReader world, BlockPos pos, BlockState state) { protected Builder gatherModelData(Builder builder, ILightReader world, BlockPos pos, BlockState state) {
PipeModelData data = new PipeModelData(); PipeModelData data = new PipeModelData();
FluidPipeAttachmentBehaviour attachmentBehaviour = FluidTransportBehaviour transport = TileEntityBehaviour.get(world, pos, FluidTransportBehaviour.TYPE);
TileEntityBehaviour.get(world, pos, FluidPipeAttachmentBehaviour.TYPE); BracketedTileEntityBehaviour bracket = TileEntityBehaviour.get(world, pos, BracketedTileEntityBehaviour.TYPE);
if (attachmentBehaviour != null) { if (transport != null)
for (Direction d : Iterate.directions) for (Direction d : Iterate.directions)
data.putRim(d, attachmentBehaviour.getAttachment(world, pos, state, d)); data.putRim(d, transport.getRenderedRimAttachment(world, pos, state, d));
data.putBracket(attachmentBehaviour.getBracket()); if (bracket != null)
} data.putBracket(bracket.getBracket());
data.setEncased(FluidPipeBlock.shouldDrawCasing(world, pos, state));
data.setEncased(FluidPipeBlock.shouldDrawCasing(world, pos, state));
return builder.withInitial(PIPE_PROPERTY, data); 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; package com.simibubi.create.content.contraptions.fluids;
import java.util.Map; import java.util.Random;
import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.AllShapes; import com.simibubi.create.AllShapes;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.base.DirectionalKineticBlock; 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.Block;
import net.minecraft.block.BlockState; 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.IBlockReader;
import net.minecraft.world.IWorld; import net.minecraft.world.IWorld;
import net.minecraft.world.IWorldReader; import net.minecraft.world.IWorldReader;
import net.minecraft.world.TickPriority;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack; import net.minecraft.world.server.ServerWorld;
public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable { public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable {
@ -67,26 +65,8 @@ public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable
if (!(tileEntity instanceof PumpTileEntity)) if (!(tileEntity instanceof PumpTileEntity))
return state; return state;
PumpTileEntity pump = (PumpTileEntity) tileEntity; PumpTileEntity pump = (PumpTileEntity) tileEntity;
if (pump.networks == null) pump.sidesToUpdate.forEach(MutableBoolean::setTrue);
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.reversed = !pump.reversed; pump.reversed = !pump.reversed;
return state; 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, public void neighborChanged(BlockState state, World world, BlockPos pos, Block otherBlock, BlockPos neighborPos,
boolean isMoving) { boolean isMoving) {
DebugPacketSender.func_218806_a(world, pos); DebugPacketSender.func_218806_a(world, pos);
if (world.isRemote) Direction d = FluidPropagator.validateNeighbourChange(state, world, pos, otherBlock, neighborPos, isMoving);
if (d == null)
return; return;
if (otherBlock instanceof FluidPipeBlock) if (!isOpenAt(state, d))
return; return;
TileEntity tileEntity = world.getTileEntity(pos); world.getPendingBlockTicks()
if (!(tileEntity instanceof PumpTileEntity)) .scheduleTick(pos, this, 1, TickPriority.HIGH);
return; // if (world.isRemote)
PumpTileEntity pump = (PumpTileEntity) tileEntity; // return;
Direction facing = state.get(FACING); // if (otherBlock instanceof FluidPipeBlock)
for (boolean front : Iterate.trueAndFalse) { // return;
Direction side = front ? facing : facing.getOpposite(); // TileEntity tileEntity = world.getTileEntity(pos);
if (!pos.offset(side) // if (!(tileEntity instanceof PumpTileEntity))
.equals(neighborPos)) // return;
continue; // PumpTileEntity pump = (PumpTileEntity) tileEntity;
pump.updatePipesOnSide(side); // 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 @Override
@ -163,4 +150,32 @@ public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable
return state.getBlock() instanceof PumpBlock; 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; package com.simibubi.create.content.contraptions.fluids;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable; 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.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat; import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.LerpedFloat.Chaser; 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.block.BlockState;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT; import net.minecraft.tileentity.TileEntity;
import net.minecraft.particles.IParticleData;
import net.minecraft.tileentity.TileEntityType; import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.ILightReader; import net.minecraft.world.ILightReader;
import net.minecraftforge.api.distmarker.Dist; import net.minecraft.world.IWorld;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fml.DistExecutor;
public class PumpTileEntity extends KineticTileEntity { public class PumpTileEntity extends KineticTileEntity {
LerpedFloat arrowDirection; LerpedFloat arrowDirection;
Couple<FluidNetwork> networks; Couple<MutableBoolean> sidesToUpdate;
Couple<Map<BlockFace, OpenEndedPipe>> openEnds;
Couple<MutableBoolean> networksToUpdate;
boolean reversed; boolean reversed;
FluidStack providedFluid;
public PumpTileEntity(TileEntityType<?> typeIn) { public PumpTileEntity(TileEntityType<?> typeIn) {
super(typeIn); super(typeIn);
arrowDirection = LerpedFloat.linear() arrowDirection = LerpedFloat.linear()
.startWithValue(1); .startWithValue(1);
networksToUpdate = Couple.create(MutableBoolean::new); sidesToUpdate = Couple.create(MutableBoolean::new);
openEnds = Couple.create(HashMap::new);
setProvidedFluid(FluidStack.EMPTY);
} }
@Override @Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) { public void addBehaviours(List<TileEntityBehaviour> behaviours) {
super.addBehaviours(behaviours); super.addBehaviours(behaviours);
behaviours.add(new PumpAttachmentBehaviour(this)); behaviours.add(new PumpFluidTransferBehaviour(this));
} }
@Override @Override
@ -77,196 +68,235 @@ public class PumpTileEntity extends KineticTileEntity {
if (world.isRemote) { if (world.isRemote) {
if (speed == 0) if (speed == 0)
return; return;
spawnParticles();
arrowDirection.chase(speed >= 0 ? 1 : -1, .5f, Chaser.EXP); arrowDirection.chase(speed >= 0 ? 1 : -1, .5f, Chaser.EXP);
arrowDirection.tickChaser(); arrowDirection.tickChaser();
return; return;
} }
BlockState blockState = getBlockState(); sidesToUpdate.forEachWithContext((update, isFront) -> {
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) -> {
if (update.isFalse()) if (update.isFalse())
return; 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(); update.setFalse();
networkUpdated.setTrue(); distributePressureTo(isFront ? getFront() : getFront().getOpposite());
}); });
if (networkUpdated.isTrue())
return;
networks.forEach(fn -> fn.tick(world, this));
if (speed == 0) if (speed == 0)
return; return;
if (speed < 0 != reversed) { if (speed < 0 != reversed) {
networks.forEachWithContext((fn, current) -> fn.clearFlows(world, true));
reversed = speed < 0; reversed = speed < 0;
return; 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 @Override
public void remove() { public void onSpeedChanged(float previousSpeed) {
super.remove(); super.onSpeedChanged(previousSpeed);
if (networks != null)
networks.forEachWithContext((fn, current) -> fn.clearFlows(world, false)); 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() { protected void distributePressureTo(Direction side) {
boolean input = isPullingOnSide(true); if (getSpeed() == 0)
Collection<FluidNetworkEndpoint> inputs = networks.get(input) return;
.getEndpoints(true);
Collection<FluidNetworkEndpoint> outputs = networks.get(!input)
.getEndpoints(false);
int flowSpeed = getFluidTransferSpeed(); BlockFace start = new BlockFace(pos, side);
FluidStack transfer = FluidStack.EMPTY; boolean pull = isPullingOnSide(isFront(side));
for (boolean simulate : Iterate.trueAndFalse) { Set<BlockFace> targets = new HashSet<>();
FluidAction action = simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE; Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph = new HashMap<>();
List<FluidNetworkEndpoint> availableInputs = new ArrayList<>(inputs); if (!pull)
while (!availableInputs.isEmpty() && transfer.getAmount() < flowSpeed) { FluidPropagator.resetAffectedFluidNetworks(world, pos, side.getOpposite());
int diff = flowSpeed - transfer.getAmount();
int dividedTransfer = diff / availableInputs.size();
int remainder = diff % availableInputs.size();
for (Iterator<FluidNetworkEndpoint> iterator = availableInputs.iterator(); iterator.hasNext();) { if (!hasReachedValidEndpoint(world, start, pull)) {
int toTransfer = dividedTransfer;
if (remainder > 0) {
toTransfer++;
remainder--;
}
FluidNetworkEndpoint ne = iterator.next(); pipeGraph.computeIfAbsent(pos, $ -> Pair.of(0, new IdentityHashMap<>()))
IFluidHandler handler = ne.provideHandler() .getSecond()
.orElse(null); .put(side, pull);
if (handler == null) { pipeGraph.computeIfAbsent(start.getConnectedPos(), $ -> Pair.of(1, new IdentityHashMap<>()))
iterator.remove(); .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; continue;
} if (visited.contains(currentPos))
FluidStack drained = handler.drain(toTransfer, action);
if (drained.isEmpty()) {
iterator.remove();
continue; continue;
} visited.add(currentPos);
if (transfer.isFluidEqual(drained) || transfer.isEmpty()) { BlockState currentState = world.getBlockState(currentPos);
if (drained.getAmount() < toTransfer) FluidTransportBehaviour pipe = FluidPropagator.getPipe(world, currentPos);
iterator.remove(); if (pipe == null)
FluidStack copy = drained.copy();
copy.setAmount(drained.getAmount() + transfer.getAmount());
transfer = copy;
continue; 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; continue;
} }
} FluidTransportBehaviour pipeBehaviour = FluidPropagator.getPipe(world, connectedPos);
if (pipeBehaviour == null)
List<FluidNetworkEndpoint> availableOutputs = new ArrayList<>(outputs); continue;
while (!availableOutputs.isEmpty() && transfer.getAmount() > 0) { if (pipeBehaviour instanceof PumpFluidTransferBehaviour)
int dividedTransfer = transfer.getAmount() / availableOutputs.size(); continue;
int remainder = transfer.getAmount() % availableOutputs.size(); if (visited.contains(connectedPos))
continue;
for (Iterator<FluidNetworkEndpoint> iterator = availableOutputs.iterator(); iterator.hasNext();) { if (distance + 1 >= maxDistance) {
FluidNetworkEndpoint ne = iterator.next(); pipeGraph.computeIfAbsent(currentPos, $ -> Pair.of(distance, new IdentityHashMap<>()))
int toTransfer = dividedTransfer; .getSecond()
if (remainder > 0) { .put(face, pull);
toTransfer++; targets.add(blockFace);
remainder--;
}
if (transfer.isEmpty())
break;
IFluidHandler handler = ne.provideHandler()
.orElse(null);
if (handler == null) {
iterator.remove();
continue; continue;
} }
FluidStack divided = transfer.copy(); pipeGraph.computeIfAbsent(currentPos, $ -> Pair.of(distance, new IdentityHashMap<>()))
divided.setAmount(toTransfer); .getSecond()
int fill = handler.fill(divided, action); .put(face, pull);
transfer.setAmount(transfer.getAmount() - fill); pipeGraph.computeIfAbsent(connectedPos, $ -> Pair.of(distance + 1, new IdentityHashMap<>()))
if (fill < toTransfer) .getSecond()
iterator.remove(); .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(); protected boolean searchForEndpointRecursively(Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph,
transfer = FluidStack.EMPTY; 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() { if (map.get(nextFacing) != pull)
float rotationSpeed = Math.abs(getSpeed()); continue;
int flowSpeed = (int) (rotationSpeed / 2f); if (!searchForEndpointRecursively(pipeGraph, targets, validFaces,
if (rotationSpeed != 0 && flowSpeed == 0) new BlockFace(currentPos.offset(nextFacing), nextFacing.getOpposite()), pull))
flowSpeed = 1; continue;
return flowSpeed;
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 @Override
public void write(CompoundNBT compound, boolean clientPacket) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Reversed", reversed); compound.putBoolean("Reversed", reversed);
serializeOpenEnds(compound);
super.write(compound, clientPacket); super.write(compound, clientPacket);
} }
@Override @Override
protected void read(CompoundNBT compound, boolean clientPacket) { protected void read(CompoundNBT compound, boolean clientPacket) {
reversed = compound.getBoolean("Reversed"); reversed = compound.getBoolean("Reversed");
deserializeOpenEnds(compound);
super.read(compound, clientPacket); super.read(compound, clientPacket);
} }
@ -274,11 +304,10 @@ public class PumpTileEntity extends KineticTileEntity {
if (!isSideAccessible(side)) if (!isSideAccessible(side))
return; return;
updatePipeNetwork(isFront(side)); updatePipeNetwork(isFront(side));
getBehaviour(FluidTransportBehaviour.TYPE).wipePressure();
} }
protected boolean isFront(Direction side) { protected boolean isFront(Direction side) {
if (networks == null)
return false;
BlockState blockState = getBlockState(); BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof PumpBlock)) if (!(blockState.getBlock() instanceof PumpBlock))
return false; return false;
@ -296,13 +325,8 @@ public class PumpTileEntity extends KineticTileEntity {
} }
protected void updatePipeNetwork(boolean front) { protected void updatePipeNetwork(boolean front) {
if (networks != null) sidesToUpdate.get(front)
networks.get(front)
.clearFlows(world, true);
networksToUpdate.get(front)
.setTrue(); .setTrue();
if (getSpeed() == 0 || (isPullingOnSide(front)) && networks != null)
setProvidedFluid(FluidStack.EMPTY);
} }
public boolean isSideAccessible(Direction side) { public boolean isSideAccessible(Direction side) {
@ -317,113 +341,32 @@ public class PumpTileEntity extends KineticTileEntity {
return front == reversed; return front == reversed;
} }
public void spawnParticles() { class PumpFluidTransferBehaviour extends FluidTransportBehaviour {
DistExecutor.runWhenOn(Dist.CLIENT, () -> this::spawnParticlesInner);
}
@OnlyIn(Dist.CLIENT) public PumpFluidTransferBehaviour(SmartTileEntity te) {
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) {
super(te); super(te);
} }
@Override @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); return isSideAccessible(direction);
} }
@Override @Override
public AttachmentTypes getAttachment(ILightReader world, BlockPos pos, BlockState state, Direction direction) { public AttachmentTypes getRenderedRimAttachment(ILightReader world, BlockPos pos, BlockState state,
AttachmentTypes attachment = super.getAttachment(world, pos, state, direction); Direction direction) {
AttachmentTypes attachment = super.getRenderedRimAttachment(world, pos, state, direction);
if (attachment == AttachmentTypes.RIM) if (attachment == AttachmentTypes.RIM)
return AttachmentTypes.NONE; return AttachmentTypes.NONE;
return attachment; return attachment;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,16 @@
package com.simibubi.create.content.contraptions.fluids.pipes; package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.Random;
import com.simibubi.create.AllShapes; import com.simibubi.create.AllShapes;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.base.DirectionalAxisKineticBlock; import com.simibubi.create.content.contraptions.base.DirectionalAxisKineticBlock;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.network.DebugPacketSender;
import net.minecraft.state.BooleanProperty; import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer.Builder; import net.minecraft.state.StateContainer.Builder;
import net.minecraft.tileentity.TileEntity; 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.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader; 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 { public class FluidValveBlock extends DirectionalAxisKineticBlock implements IAxisPipe {
@ -75,4 +82,49 @@ public class FluidValveBlock extends DirectionalAxisKineticBlock implements IAxi
return getPipeAxis(state); 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 java.util.List;
import com.simibubi.create.content.contraptions.base.KineticTileEntity; 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.StraightPipeFluidTransportBehaviour;
import com.simibubi.create.content.contraptions.fluids.pipes.StraightPipeTileEntity.StraightPipeAttachmentBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity; import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.LerpedFloat; import com.simibubi.create.foundation.utility.LerpedFloat;
@ -24,7 +23,8 @@ public class FluidValveTileEntity extends KineticTileEntity {
public FluidValveTileEntity(TileEntityType<?> tileEntityTypeIn) { public FluidValveTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn); super(tileEntityTypeIn);
pointer = LerpedFloat.linear() pointer = LerpedFloat.linear()
.startWithValue(0).chase(0, 0, Chaser.LINEAR); .startWithValue(0)
.chase(0, 0, Chaser.LINEAR);
} }
@Override @Override
@ -77,24 +77,23 @@ public class FluidValveTileEntity extends KineticTileEntity {
@Override @Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) { public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviours.add(new ValvePipeBehaviour(this)); behaviours.add(new ValvePipeBehaviour(this));
behaviours.add(new StraightPipeAttachmentBehaviour(this));
} }
class ValvePipeBehaviour extends FluidPipeBehaviour { class ValvePipeBehaviour extends StraightPipeFluidTransportBehaviour {
public ValvePipeBehaviour(SmartTileEntity te) { public ValvePipeBehaviour(SmartTileEntity te) {
super(te); super(te);
} }
@Override @Override
public boolean isConnectedTo(BlockState state, Direction direction) { public boolean canHaveFlowToward(BlockState state, Direction direction) {
return FluidValveBlock.getPipeAxis(state) == direction.getAxis(); return FluidValveBlock.getPipeAxis(state) == direction.getAxis();
} }
@Override @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)) if (state.has(FluidValveBlock.ENABLED) && state.get(FluidValveBlock.ENABLED))
return super.canTransferToward(fluid, state, direction, inbound); return super.canPullFluidFrom(fluid, state, direction);
return false; return false;
} }

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
package com.simibubi.create.content.contraptions.fluids.pipes; package com.simibubi.create.content.contraptions.fluids.pipes;
import com.mojang.blaze3d.matrix.MatrixStack; 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.fluid.FluidRenderer;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.renderer.SafeTileEntityRenderer; import com.simibubi.create.foundation.tileEntity.renderer.SafeTileEntityRenderer;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat; 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.IRenderTypeBuffer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
@ -23,32 +23,40 @@ public class TransparentStraightPipeRenderer extends SafeTileEntityRenderer<Stra
@Override @Override
protected void renderSafe(StraightPipeTileEntity te, float partialTicks, MatrixStack ms, IRenderTypeBuffer buffer, protected void renderSafe(StraightPipeTileEntity te, float partialTicks, MatrixStack ms, IRenderTypeBuffer buffer,
int light, int overlay) { int light, int overlay) {
FluidPipeBehaviour pipe = te.getBehaviour(FluidPipeBehaviour.TYPE); FluidTransportBehaviour pipe = te.getBehaviour(FluidTransportBehaviour.TYPE);
if (pipe == null) if (pipe == null)
return; return;
FluidStack fluidStack = pipe.getFluid();
if (fluidStack.isEmpty())
return;
for (Direction side : Iterate.directions) { for (Direction side : Iterate.directions) {
if (!pipe.isConnectedTo(te.getBlockState(), side))
Flow flow = pipe.getFlow(side);
if (flow == null)
continue; continue;
Pair<Boolean, LerpedFloat> strogestFlow = pipe.getStrogestFlow(side); FluidStack fluidStack = flow.fluid;
if (strogestFlow == null) if (fluidStack.isEmpty())
continue; continue;
LerpedFloat second = strogestFlow.getSecond(); LerpedFloat progress = flow.progress;
if (second == null) if (progress == null)
continue; continue;
float value = second.getValue(partialTicks); float value = progress.getValue(partialTicks);
Boolean inbound = strogestFlow.getFirst(); boolean inbound = flow.inbound;
if (value == 1 && !inbound) { if (value == 1) {
FluidPipeBehaviour adjacent = TileEntityBehaviour.get(te.getWorld(), te.getPos() if (inbound) {
.offset(side), FluidPipeBehaviour.TYPE); Flow opposite = pipe.getFlow(side.getOpposite());
if (opposite == null)
if (adjacent != null && adjacent.getFluid()
.isEmpty())
value -= 1e-6f; 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); 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; package com.simibubi.create.content.contraptions.relays.elementary;
import java.util.Optional; 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.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType; import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil; import net.minecraft.nbt.NBTUtil;
import net.minecraft.world.World;
public class BracketedTileEntityBehaviour extends TileEntityBehaviour { public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
@ -19,8 +23,15 @@ public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
private Optional<BlockState> bracket; private Optional<BlockState> bracket;
private boolean reRender; private boolean reRender;
private Predicate<BlockState> pred;
public BracketedTileEntityBehaviour(SmartTileEntity te) { public BracketedTileEntityBehaviour(SmartTileEntity te) {
this(te, Predicates.alwaysTrue());
}
public BracketedTileEntityBehaviour(SmartTileEntity te, Predicate<BlockState> pred) {
super(te); super(te);
this.pred = pred;
bracket = Optional.empty(); bracket = Optional.empty();
} }
@ -36,11 +47,18 @@ public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
} }
public void removeBracket() { public void removeBracket() {
World world = getWorld();
if (!world.isRemote)
world.playEvent(2001, getPos(), Block.getStateId(getBracket()));
this.bracket = Optional.empty(); this.bracket = Optional.empty();
reRender = true; reRender = true;
tileEntity.notifyUpdate(); tileEntity.notifyUpdate();
} }
public boolean isBacketPresent() {
return getBracket() != Blocks.AIR.getDefaultState();
}
public BlockState getBracket() { public BlockState getBracket() {
return bracket.orElse(Blocks.AIR.getDefaultState()); return bracket.orElse(Blocks.AIR.getDefaultState());
} }
@ -66,10 +84,7 @@ public class BracketedTileEntityBehaviour extends TileEntityBehaviour {
} }
public boolean canHaveBracket() { public boolean canHaveBracket() {
BlockState blockState = tileEntity.getBlockState(); return pred.test(tileEntity.getBlockState());
if (blockState.getBlock() instanceof AbstractShaftBlock)
return true;
return false;
} }
} }

View File

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

View File

@ -51,6 +51,14 @@ public class FluidHelper {
return blockState != null && blockState != Blocks.AIR.getDefaultState(); 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) { public static Fluid convertToFlowing(Fluid fluid) {
if (fluid == Fluids.WATER) if (fluid == Fluids.WATER)
return Fluids.FLOWING_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) { 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()); IVertexBuilder builder = buffer.getBuffer(RenderTypes.getOutlineSolid());
Vec3d diff = end.subtract(start); Vec3d diff = end.subtract(start);
@ -58,7 +62,6 @@ public abstract class Outline {
diff = diff.scale(-1); diff = diff.scale(-1);
} }
float lineWidth = params.getLineWidth();
Vec3d extension = diff.normalize() Vec3d extension = diff.normalize()
.scale(lineWidth / 2); .scale(lineWidth / 2);
Vec3d plane = VecHelper.axisAlingedPlaneOf(diff); Vec3d plane = VecHelper.axisAlingedPlaneOf(diff);