320 lines
9.7 KiB
Java
320 lines
9.7 KiB
Java
package com.simibubi.create.content.fluids.transfer;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
import com.simibubi.create.foundation.advancement.AllAdvancements;
|
|
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
|
import com.simibubi.create.foundation.blockEntity.behaviour.BehaviourType;
|
|
import com.simibubi.create.foundation.fluid.FluidHelper;
|
|
import com.simibubi.create.foundation.utility.BBHelper;
|
|
import com.simibubi.create.foundation.utility.Iterate;
|
|
import com.simibubi.create.infrastructure.config.AllConfigs;
|
|
|
|
import it.unimi.dsi.fastutil.PriorityQueue;
|
|
import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.world.level.BlockGetter;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.DoorBlock;
|
|
import net.minecraft.world.level.block.LiquidBlock;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
|
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
|
import net.minecraft.world.level.material.Fluid;
|
|
import net.minecraft.world.level.material.FluidState;
|
|
import net.minecraft.world.level.material.Fluids;
|
|
import net.minecraft.world.level.material.Material;
|
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
|
import net.minecraft.world.ticks.LevelTickAccess;
|
|
import net.minecraft.world.ticks.LevelTicks;
|
|
|
|
public class FluidFillingBehaviour extends FluidManipulationBehaviour {
|
|
|
|
public static final BehaviourType<FluidFillingBehaviour> TYPE = new BehaviourType<>();
|
|
|
|
PriorityQueue<BlockPosEntry> queue;
|
|
|
|
List<BlockPosEntry> infinityCheckFrontier;
|
|
Set<BlockPos> infinityCheckVisited;
|
|
|
|
public FluidFillingBehaviour(SmartBlockEntity be) {
|
|
super(be);
|
|
queue = new ObjectHeapPriorityQueue<>((p, p2) -> -comparePositions(p, p2));
|
|
revalidateIn = 1;
|
|
infinityCheckFrontier = new ArrayList<>();
|
|
infinityCheckVisited = new HashSet<>();
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
if (!infinityCheckFrontier.isEmpty() && rootPos != null) {
|
|
Fluid fluid = getWorld().getFluidState(rootPos)
|
|
.getType();
|
|
if (fluid != Fluids.EMPTY)
|
|
continueValidation(fluid);
|
|
}
|
|
if (revalidateIn > 0)
|
|
revalidateIn--;
|
|
}
|
|
|
|
protected void continueValidation(Fluid fluid) {
|
|
try {
|
|
search(fluid, infinityCheckFrontier, infinityCheckVisited,
|
|
(p, d) -> infinityCheckFrontier.add(new BlockPosEntry(p, d)), true);
|
|
} catch (ChunkNotLoadedException e) {
|
|
infinityCheckFrontier.clear();
|
|
infinityCheckVisited.clear();
|
|
setLongValidationTimer();
|
|
return;
|
|
}
|
|
|
|
int maxBlocks = maxBlocks();
|
|
|
|
if (infinityCheckVisited.size() > maxBlocks && maxBlocks != -1 && !fillInfinite()) {
|
|
if (!infinite) {
|
|
reset();
|
|
infinite = true;
|
|
blockEntity.sendData();
|
|
}
|
|
infinityCheckFrontier.clear();
|
|
setLongValidationTimer();
|
|
return;
|
|
}
|
|
|
|
if (!infinityCheckFrontier.isEmpty())
|
|
return;
|
|
if (infinite) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
infinityCheckVisited.clear();
|
|
}
|
|
|
|
public boolean tryDeposit(Fluid fluid, BlockPos root, boolean simulate) {
|
|
if (!Objects.equals(root, rootPos)) {
|
|
reset();
|
|
rootPos = root;
|
|
queue.enqueue(new BlockPosEntry(root, 0));
|
|
affectedArea = BoundingBox.fromCorners(rootPos, rootPos);
|
|
return false;
|
|
}
|
|
|
|
if (counterpartActed) {
|
|
counterpartActed = false;
|
|
softReset(root);
|
|
return false;
|
|
}
|
|
|
|
if (affectedArea == null)
|
|
affectedArea = BoundingBox.fromCorners(root, root);
|
|
|
|
if (revalidateIn == 0) {
|
|
visited.clear();
|
|
infinityCheckFrontier.clear();
|
|
infinityCheckVisited.clear();
|
|
infinityCheckFrontier.add(new BlockPosEntry(root, 0));
|
|
setValidationTimer();
|
|
softReset(root);
|
|
}
|
|
|
|
Level world = getWorld();
|
|
int maxRange = maxRange();
|
|
int maxRangeSq = maxRange * maxRange;
|
|
int maxBlocks = maxBlocks();
|
|
boolean evaporate = world.dimensionType()
|
|
.ultraWarm() && FluidHelper.isTag(fluid, FluidTags.WATER);
|
|
boolean canPlaceSources = AllConfigs.server().fluids.fluidFillPlaceFluidSourceBlocks.get();
|
|
|
|
if ((!fillInfinite() && infinite) || evaporate || !canPlaceSources) {
|
|
FluidState fluidState = world.getFluidState(rootPos);
|
|
boolean equivalentTo = fluidState.getType()
|
|
.isSame(fluid);
|
|
if (!equivalentTo && !evaporate && canPlaceSources)
|
|
return false;
|
|
if (simulate)
|
|
return true;
|
|
playEffect(world, root, fluid, false);
|
|
if (evaporate) {
|
|
int i = root.getX();
|
|
int j = root.getY();
|
|
int k = root.getZ();
|
|
world.playSound(null, i, j, k, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F,
|
|
2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F);
|
|
} else if (!canPlaceSources)
|
|
blockEntity.award(AllAdvancements.HOSE_PULLEY);
|
|
return true;
|
|
}
|
|
|
|
boolean success = false;
|
|
for (int i = 0; !success && !queue.isEmpty() && i < searchedPerTick; i++) {
|
|
BlockPosEntry entry = queue.first();
|
|
BlockPos currentPos = entry.pos();
|
|
|
|
if (visited.contains(currentPos)) {
|
|
queue.dequeue();
|
|
continue;
|
|
}
|
|
|
|
if (!simulate)
|
|
visited.add(currentPos);
|
|
|
|
if (visited.size() >= maxBlocks && maxBlocks != -1) {
|
|
infinite = true;
|
|
if (!fillInfinite()) {
|
|
visited.clear();
|
|
queue.clear();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SpaceType spaceType = getAtPos(world, currentPos, fluid);
|
|
if (spaceType == SpaceType.BLOCKING)
|
|
continue;
|
|
if (spaceType == SpaceType.FILLABLE) {
|
|
success = true;
|
|
if (!simulate) {
|
|
playEffect(world, currentPos, fluid, false);
|
|
|
|
BlockState blockState = world.getBlockState(currentPos);
|
|
if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && fluid.isSame(Fluids.WATER)) {
|
|
if (!blockEntity.isVirtual())
|
|
world.setBlock(currentPos,
|
|
updatePostWaterlogging(blockState.setValue(BlockStateProperties.WATERLOGGED, true)),
|
|
2 | 16);
|
|
} else {
|
|
replaceBlock(world, currentPos, blockState);
|
|
if (!blockEntity.isVirtual())
|
|
world.setBlock(currentPos, FluidHelper.convertToStill(fluid)
|
|
.defaultFluidState()
|
|
.createLegacyBlock(), 2 | 16);
|
|
}
|
|
|
|
LevelTickAccess<Fluid> pendingFluidTicks = world.getFluidTicks();
|
|
if (pendingFluidTicks instanceof LevelTicks) {
|
|
LevelTicks<Fluid> serverTickList = (LevelTicks<Fluid>) pendingFluidTicks;
|
|
serverTickList.clearArea(new BoundingBox(currentPos));
|
|
}
|
|
|
|
affectedArea = BBHelper.encapsulate(affectedArea, currentPos);
|
|
}
|
|
}
|
|
|
|
if (simulate && success)
|
|
return true;
|
|
|
|
visited.add(currentPos);
|
|
queue.dequeue();
|
|
|
|
for (Direction side : Iterate.directions) {
|
|
if (side == Direction.UP)
|
|
continue;
|
|
|
|
BlockPos offsetPos = currentPos.relative(side);
|
|
if (visited.contains(offsetPos))
|
|
continue;
|
|
if (offsetPos.distSqr(rootPos) > maxRangeSq)
|
|
continue;
|
|
|
|
SpaceType nextSpaceType = getAtPos(world, offsetPos, fluid);
|
|
if (nextSpaceType != SpaceType.BLOCKING)
|
|
queue.enqueue(new BlockPosEntry(offsetPos, entry.distance() + 1));
|
|
}
|
|
}
|
|
|
|
if (!simulate && success)
|
|
blockEntity.award(AllAdvancements.HOSE_PULLEY);
|
|
return success;
|
|
}
|
|
|
|
protected void softReset(BlockPos root) {
|
|
visited.clear();
|
|
queue.clear();
|
|
queue.enqueue(new BlockPosEntry(root, 0));
|
|
infinite = false;
|
|
setValidationTimer();
|
|
blockEntity.sendData();
|
|
}
|
|
|
|
enum SpaceType {
|
|
FILLABLE, FILLED, BLOCKING
|
|
}
|
|
|
|
protected SpaceType getAtPos(Level world, BlockPos pos, Fluid toFill) {
|
|
BlockState blockState = world.getBlockState(pos);
|
|
FluidState fluidState = blockState.getFluidState();
|
|
|
|
if (blockState.hasProperty(BlockStateProperties.WATERLOGGED))
|
|
return toFill.isSame(Fluids.WATER)
|
|
? blockState.getValue(BlockStateProperties.WATERLOGGED) ? SpaceType.FILLED : SpaceType.FILLABLE
|
|
: SpaceType.BLOCKING;
|
|
|
|
if (blockState.getBlock() instanceof LiquidBlock)
|
|
return blockState.getValue(LiquidBlock.LEVEL) == 0
|
|
? toFill.isSame(fluidState.getType()) ? SpaceType.FILLED : SpaceType.BLOCKING
|
|
: SpaceType.FILLABLE;
|
|
|
|
if (fluidState.getType() != Fluids.EMPTY
|
|
&& blockState.getCollisionShape(getWorld(), pos, CollisionContext.empty())
|
|
.isEmpty())
|
|
return toFill.isSame(fluidState.getType()) ? SpaceType.FILLED : SpaceType.BLOCKING;
|
|
|
|
return canBeReplacedByFluid(world, pos, blockState) ? SpaceType.FILLABLE : SpaceType.BLOCKING;
|
|
}
|
|
|
|
protected void replaceBlock(Level world, BlockPos pos, BlockState state) {
|
|
BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
|
|
Block.dropResources(state, world, pos, blockEntity);
|
|
}
|
|
|
|
// From FlowingFluidBlock#isBlocked
|
|
protected boolean canBeReplacedByFluid(BlockGetter world, BlockPos pos, BlockState state) {
|
|
Block block = state.getBlock();
|
|
if (!(block instanceof DoorBlock) && !state.is(BlockTags.SIGNS) && block != Blocks.LADDER
|
|
&& block != Blocks.SUGAR_CANE && block != Blocks.BUBBLE_COLUMN) {
|
|
Material material = state.getMaterial();
|
|
if (material != Material.PORTAL && material != Material.STRUCTURAL_AIR && material != Material.WATER_PLANT
|
|
&& material != Material.REPLACEABLE_WATER_PLANT) {
|
|
return !material.blocksMotion();
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected BlockState updatePostWaterlogging(BlockState state) {
|
|
if (state.hasProperty(BlockStateProperties.LIT))
|
|
state = state.setValue(BlockStateProperties.LIT, false);
|
|
return state;
|
|
}
|
|
|
|
@Override
|
|
public void reset() {
|
|
super.reset();
|
|
queue.clear();
|
|
infinityCheckFrontier.clear();
|
|
infinityCheckVisited.clear();
|
|
}
|
|
|
|
@Override
|
|
public BehaviourType<?> getType() {
|
|
return TYPE;
|
|
}
|
|
|
|
}
|