/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Maps * it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap * it.unimi.dsi.fastutil.shorts.Short2BooleanMap * it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap * it.unimi.dsi.fastutil.shorts.Short2ObjectMap * it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap */ package net.minecraft.world.level.material; import com.google.common.collect.Maps; import it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2BooleanMap; import it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import java.util.EnumMap; import java.util.Map; import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BlockTags; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; 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.IceBlock; import net.minecraft.world.level.block.LiquidBlockContainer; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.IntegerProperty; 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.phys.Vec3; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; public abstract class FlowingFluid extends Fluid { public static final BooleanProperty FALLING = BlockStateProperties.FALLING; public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL_FLOWING; private static final int CACHE_SIZE = 200; private static final ThreadLocal> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> { Object2ByteLinkedOpenHashMap map = new Object2ByteLinkedOpenHashMap(200){ protected void rehash(int newN) { } }; map.defaultReturnValue((byte)127); return map; }); private final Map shapes = Maps.newIdentityHashMap(); @Override protected void createFluidStateDefinition(StateDefinition.Builder builder) { builder.add(FALLING); } @Override public Vec3 getFlow(BlockGetter level, BlockPos pos, FluidState fluidState) { double flowX = 0.0; double flowZ = 0.0; BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); for (Direction direction : Direction.Plane.HORIZONTAL) { blockPos.setWithOffset((Vec3i)pos, direction); FluidState neighbourFluid = level.getFluidState(blockPos); if (!this.affectsFlow(neighbourFluid)) continue; float neighborHeight = neighbourFluid.getOwnHeight(); float distance = 0.0f; if (neighborHeight == 0.0f) { Vec3i neighborPos; FluidState belowNeighborState; if (!level.getBlockState(blockPos).blocksMotion() && this.affectsFlow(belowNeighborState = level.getFluidState((BlockPos)(neighborPos = blockPos.below()))) && (neighborHeight = belowNeighborState.getOwnHeight()) > 0.0f) { distance = fluidState.getOwnHeight() - (neighborHeight - 0.8888889f); } } else if (neighborHeight > 0.0f) { distance = fluidState.getOwnHeight() - neighborHeight; } if (distance == 0.0f) continue; flowX += (double)((float)direction.getStepX() * distance); flowZ += (double)((float)direction.getStepZ() * distance); } Vec3 flow = new Vec3(flowX, 0.0, flowZ); if (fluidState.getValue(FALLING).booleanValue()) { for (Direction direction : Direction.Plane.HORIZONTAL) { blockPos.setWithOffset((Vec3i)pos, direction); if (!this.isSolidFace(level, blockPos, direction) && !this.isSolidFace(level, (BlockPos)blockPos.above(), direction)) continue; flow = flow.normalize().add(0.0, -6.0, 0.0); break; } } return flow.normalize(); } private boolean affectsFlow(FluidState neighbourFluid) { return neighbourFluid.isEmpty() || neighbourFluid.getType().isSame(this); } protected boolean isSolidFace(BlockGetter level, BlockPos pos, Direction direction) { BlockState state = level.getBlockState(pos); FluidState fluidState = level.getFluidState(pos); if (fluidState.getType().isSame(this)) { return false; } if (direction == Direction.UP) { return true; } if (state.getBlock() instanceof IceBlock) { return false; } return state.isFaceSturdy(level, pos, direction); } protected void spread(ServerLevel level, BlockPos pos, BlockState state, FluidState fluidState) { FluidState newBelowFluid; Fluid newBelowFluidType; FluidState belowFluid; BlockState belowState; if (fluidState.isEmpty()) { return; } BlockPos belowPos = pos.below(); if (this.canMaybePassThrough(level, pos, state, Direction.DOWN, belowPos, belowState = level.getBlockState(belowPos), belowFluid = belowState.getFluidState()) && belowFluid.canBeReplacedWith(level, belowPos, newBelowFluidType = (newBelowFluid = this.getNewLiquid(level, belowPos, belowState)).getType(), Direction.DOWN) && FlowingFluid.canHoldSpecificFluid(level, belowPos, belowState, newBelowFluidType)) { this.spreadTo(level, belowPos, belowState, Direction.DOWN, newBelowFluid); if (this.sourceNeighborCount(level, pos) >= 3) { this.spreadToSides(level, pos, fluidState, state); } return; } if (fluidState.isSource() || !this.isWaterHole(level, pos, state, belowPos, belowState)) { this.spreadToSides(level, pos, fluidState, state); } } private void spreadToSides(ServerLevel level, BlockPos pos, FluidState fluidState, BlockState state) { int neighbor = fluidState.getAmount() - this.getDropOff(level); if (fluidState.getValue(FALLING).booleanValue()) { neighbor = 7; } if (neighbor <= 0) { return; } Map spreads = this.getSpread(level, pos, state); for (Map.Entry entry : spreads.entrySet()) { Direction spread = entry.getKey(); FluidState newNeighborFluid = entry.getValue(); BlockPos neighborPos = pos.relative(spread); this.spreadTo(level, neighborPos, level.getBlockState(neighborPos), spread, newNeighborFluid); } } protected FluidState getNewLiquid(ServerLevel level, BlockPos pos, BlockState state) { BlockPos.MutableBlockPos abovePos; BlockState aboveState; FluidState aboveFluid; int highestNeighbor = 0; int neighbourSources = 0; BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); for (Direction direction : Direction.Plane.HORIZONTAL) { BlockPos.MutableBlockPos relativePos = mutablePos.setWithOffset((Vec3i)pos, direction); BlockState blockState = level.getBlockState(relativePos); FluidState fluidState = blockState.getFluidState(); if (!fluidState.getType().isSame(this) || !FlowingFluid.canPassThroughWall(direction, level, pos, state, relativePos, blockState)) continue; if (fluidState.isSource()) { ++neighbourSources; } highestNeighbor = Math.max(highestNeighbor, fluidState.getAmount()); } if (neighbourSources >= 2 && this.canConvertToSource(level)) { BlockState belowState = level.getBlockState(mutablePos.setWithOffset((Vec3i)pos, Direction.DOWN)); FluidState belowFluid = belowState.getFluidState(); if (belowState.isSolid() || this.isSourceBlockOfThisType(belowFluid)) { return this.getSource(false); } } if (!(aboveFluid = (aboveState = level.getBlockState(abovePos = mutablePos.setWithOffset((Vec3i)pos, Direction.UP))).getFluidState()).isEmpty() && aboveFluid.getType().isSame(this) && FlowingFluid.canPassThroughWall(Direction.UP, level, pos, state, abovePos, aboveState)) { return this.getFlowing(8, true); } int amount = highestNeighbor - this.getDropOff(level); if (amount <= 0) { return Fluids.EMPTY.defaultFluidState(); } return this.getFlowing(amount, false); } private static boolean canPassThroughWall(Direction direction, BlockGetter level, BlockPos sourcePos, BlockState sourceState, BlockPos targetPos, BlockState targetState) { boolean result; BlockStatePairKey key; if (SharedConstants.DEBUG_DISABLE_LIQUID_SPREADING || SharedConstants.DEBUG_ONLY_GENERATE_HALF_THE_WORLD && targetPos.getZ() < 0) { return false; } VoxelShape targetShape = targetState.getCollisionShape(level, targetPos); if (targetShape == Shapes.block()) { return false; } VoxelShape sourceShape = sourceState.getCollisionShape(level, sourcePos); if (sourceShape == Shapes.block()) { return false; } if (sourceShape == Shapes.empty() && targetShape == Shapes.empty()) { return true; } Object2ByteLinkedOpenHashMap cache = sourceState.getBlock().hasDynamicShape() || targetState.getBlock().hasDynamicShape() ? null : OCCLUSION_CACHE.get(); if (cache != null) { key = new BlockStatePairKey(sourceState, targetState, direction); byte cached = cache.getAndMoveToFirst((Object)key); if (cached != 127) { return cached != 0; } } else { key = null; } boolean bl = result = !Shapes.mergedFaceOccludes(sourceShape, targetShape, direction); if (cache != null) { if (cache.size() == 200) { cache.removeLastByte(); } cache.putAndMoveToFirst((Object)key, (byte)(result ? 1 : 0)); } return result; } public abstract Fluid getFlowing(); public FluidState getFlowing(int amount, boolean falling) { return (FluidState)((FluidState)this.getFlowing().defaultFluidState().setValue(LEVEL, amount)).setValue(FALLING, falling); } public abstract Fluid getSource(); public FluidState getSource(boolean falling) { return (FluidState)this.getSource().defaultFluidState().setValue(FALLING, falling); } protected abstract boolean canConvertToSource(ServerLevel var1); protected void spreadTo(LevelAccessor level, BlockPos pos, BlockState state, Direction direction, FluidState target) { Block block = state.getBlock(); if (block instanceof LiquidBlockContainer) { LiquidBlockContainer container = (LiquidBlockContainer)((Object)block); container.placeLiquid(level, pos, state, target); } else { if (!state.isAir()) { this.beforeDestroyingBlock(level, pos, state); } level.setBlock(pos, target.createLegacyBlock(), 3); } } protected abstract void beforeDestroyingBlock(LevelAccessor var1, BlockPos var2, BlockState var3); protected int getSlopeDistance(LevelReader level, BlockPos pos, int pass, Direction from, BlockState state, SpreadContext context) { int lowest = 1000; for (Direction direction : Direction.Plane.HORIZONTAL) { int v; if (direction == from) continue; BlockPos testPos = pos.relative(direction); BlockState testState = context.getBlockState(testPos); FluidState testFluidState = testState.getFluidState(); if (!this.canPassThrough(level, this.getFlowing(), pos, state, direction, testPos, testState, testFluidState)) continue; if (context.isHole(testPos)) { return pass; } if (pass >= this.getSlopeFindDistance(level) || (v = this.getSlopeDistance(level, testPos, pass + 1, direction.getOpposite(), testState, context)) >= lowest) continue; lowest = v; } return lowest; } private boolean isWaterHole(BlockGetter level, BlockPos topPos, BlockState topState, BlockPos bottomPos, BlockState bottomState) { if (!FlowingFluid.canPassThroughWall(Direction.DOWN, level, topPos, topState, bottomPos, bottomState)) { return false; } if (bottomState.getFluidState().getType().isSame(this)) { return true; } return FlowingFluid.canHoldFluid(level, bottomPos, bottomState, this.getFlowing()); } private boolean canPassThrough(BlockGetter level, Fluid fluid, BlockPos sourcePos, BlockState sourceState, Direction direction, BlockPos testPos, BlockState testState, FluidState testFluidState) { return this.canMaybePassThrough(level, sourcePos, sourceState, direction, testPos, testState, testFluidState) && FlowingFluid.canHoldSpecificFluid(level, testPos, testState, fluid); } private boolean canMaybePassThrough(BlockGetter level, BlockPos sourcePos, BlockState sourceState, Direction direction, BlockPos testPos, BlockState testState, FluidState testFluidState) { return !this.isSourceBlockOfThisType(testFluidState) && FlowingFluid.canHoldAnyFluid(testState) && FlowingFluid.canPassThroughWall(direction, level, sourcePos, sourceState, testPos, testState); } private boolean isSourceBlockOfThisType(FluidState state) { return state.getType().isSame(this) && state.isSource(); } protected abstract int getSlopeFindDistance(LevelReader var1); private int sourceNeighborCount(LevelReader level, BlockPos pos) { int count = 0; for (Direction direction : Direction.Plane.HORIZONTAL) { BlockPos testPos = pos.relative(direction); FluidState testFluidState = level.getFluidState(testPos); if (!this.isSourceBlockOfThisType(testFluidState)) continue; ++count; } return count; } protected Map getSpread(ServerLevel level, BlockPos pos, BlockState state) { int lowest = 1000; EnumMap result = Maps.newEnumMap(Direction.class); SpreadContext context = null; for (Direction direction : Direction.Plane.HORIZONTAL) { int distance; FluidState newFluid; FluidState testFluidState; BlockState testState; BlockPos testPos; if (!this.canMaybePassThrough(level, pos, state, direction, testPos = pos.relative(direction), testState = level.getBlockState(testPos), testFluidState = testState.getFluidState()) || !FlowingFluid.canHoldSpecificFluid(level, testPos, testState, (newFluid = this.getNewLiquid(level, testPos, testState)).getType())) continue; if (context == null) { context = new SpreadContext(level, pos); } if ((distance = context.isHole(testPos) ? 0 : this.getSlopeDistance(level, testPos, 1, direction.getOpposite(), testState, context)) < lowest) { result.clear(); } if (distance > lowest) continue; if (testFluidState.canBeReplacedWith(level, testPos, newFluid.getType(), direction)) { result.put(direction, newFluid); } lowest = distance; } return result; } private static boolean canHoldAnyFluid(BlockState state) { Block block = state.getBlock(); if (block instanceof LiquidBlockContainer) { return true; } if (state.blocksMotion()) { return false; } return !(block instanceof DoorBlock) && !state.is(BlockTags.SIGNS) && !state.is(Blocks.LADDER) && !state.is(Blocks.SUGAR_CANE) && !state.is(Blocks.BUBBLE_COLUMN) && !state.is(Blocks.NETHER_PORTAL) && !state.is(Blocks.END_PORTAL) && !state.is(Blocks.END_GATEWAY) && !state.is(Blocks.STRUCTURE_VOID); } private static boolean canHoldFluid(BlockGetter level, BlockPos pos, BlockState state, Fluid newFluid) { return FlowingFluid.canHoldAnyFluid(state) && FlowingFluid.canHoldSpecificFluid(level, pos, state, newFluid); } private static boolean canHoldSpecificFluid(BlockGetter level, BlockPos pos, BlockState state, Fluid newFluid) { Block block = state.getBlock(); if (block instanceof LiquidBlockContainer) { LiquidBlockContainer container = (LiquidBlockContainer)((Object)block); return container.canPlaceLiquid(null, level, pos, state, newFluid); } return true; } protected abstract int getDropOff(LevelReader var1); protected int getSpreadDelay(Level level, BlockPos pos, FluidState oldFluidState, FluidState newFluidState) { return this.getTickDelay(level); } @Override public void tick(ServerLevel level, BlockPos pos, BlockState blockState, FluidState fluidState) { if (!fluidState.isSource()) { FluidState newFluidState = this.getNewLiquid(level, pos, level.getBlockState(pos)); int tickDelay = this.getSpreadDelay(level, pos, fluidState, newFluidState); if (newFluidState.isEmpty()) { fluidState = newFluidState; blockState = Blocks.AIR.defaultBlockState(); level.setBlock(pos, blockState, 3); } else if (newFluidState != fluidState) { fluidState = newFluidState; blockState = fluidState.createLegacyBlock(); level.setBlock(pos, blockState, 3); level.scheduleTick(pos, fluidState.getType(), tickDelay); } } this.spread(level, pos, blockState, fluidState); } protected static int getLegacyLevel(FluidState fluidState) { if (fluidState.isSource()) { return 0; } return 8 - Math.min(fluidState.getAmount(), 8) + (fluidState.getValue(FALLING) != false ? 8 : 0); } private static boolean hasSameAbove(FluidState fluidState, BlockGetter level, BlockPos pos) { return fluidState.getType().isSame(level.getFluidState(pos.above()).getType()); } @Override public float getHeight(FluidState fluidState, BlockGetter level, BlockPos pos) { if (FlowingFluid.hasSameAbove(fluidState, level, pos)) { return 1.0f; } return fluidState.getOwnHeight(); } @Override public float getOwnHeight(FluidState fluidState) { return (float)fluidState.getAmount() / 9.0f; } @Override public abstract int getAmount(FluidState var1); @Override public VoxelShape getShape(FluidState state, BlockGetter level, BlockPos pos) { if (state.getAmount() == 9 && FlowingFluid.hasSameAbove(state, level, pos)) { return Shapes.block(); } return this.shapes.computeIfAbsent(state, fluidState -> Shapes.box(0.0, 0.0, 0.0, 1.0, fluidState.getHeight(level, pos), 1.0)); } private record BlockStatePairKey(BlockState first, BlockState second, Direction direction) { /* * Enabled force condition propagation * Lifted jumps to return sites */ @Override public boolean equals(Object o) { if (!(o instanceof BlockStatePairKey)) return false; BlockStatePairKey that = (BlockStatePairKey)o; if (this.first != that.first) return false; if (this.second != that.second) return false; if (this.direction != that.direction) return false; return true; } @Override public int hashCode() { int result = System.identityHashCode(this.first); result = 31 * result + System.identityHashCode(this.second); result = 31 * result + this.direction.hashCode(); return result; } } protected class SpreadContext { private final BlockGetter level; private final BlockPos origin; private final Short2ObjectMap stateCache = new Short2ObjectOpenHashMap(); private final Short2BooleanMap holeCache = new Short2BooleanOpenHashMap(); private SpreadContext(BlockGetter level, BlockPos origin) { this.level = level; this.origin = origin; } public BlockState getBlockState(BlockPos pos) { return this.getBlockState(pos, this.getCacheKey(pos)); } private BlockState getBlockState(BlockPos pos, short key) { return (BlockState)this.stateCache.computeIfAbsent(key, k -> this.level.getBlockState(pos)); } public boolean isHole(BlockPos pos) { return this.holeCache.computeIfAbsent(this.getCacheKey(pos), key -> { BlockState state = this.getBlockState(pos, key); BlockPos below = pos.below(); BlockState belowState = this.level.getBlockState(below); return FlowingFluid.this.isWaterHole(this.level, pos, state, below, belowState); }); } private short getCacheKey(BlockPos pos) { int relativeX = pos.getX() - this.origin.getX(); int relativeZ = pos.getZ() - this.origin.getZ(); return (short)((relativeX + 128 & 0xFF) << 8 | relativeZ + 128 & 0xFF); } } }