aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower/net/minecraft/world/level/material/FlowingFluid.java.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-vineflower/net/minecraft/world/level/material/FlowingFluid.java.patch')
-rw-r--r--patch-remap/mache-vineflower/net/minecraft/world/level/material/FlowingFluid.java.patch718
1 files changed, 718 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower/net/minecraft/world/level/material/FlowingFluid.java.patch b/patch-remap/mache-vineflower/net/minecraft/world/level/material/FlowingFluid.java.patch
new file mode 100644
index 0000000000..74b87828a0
--- /dev/null
+++ b/patch-remap/mache-vineflower/net/minecraft/world/level/material/FlowingFluid.java.patch
@@ -0,0 +1,718 @@
+--- a/net/minecraft/world/level/material/FlowingFluid.java
++++ b/net/minecraft/world/level/material/FlowingFluid.java
+@@ -7,11 +7,13 @@
+ import it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap;
+ import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
+ import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
++import java.util.Iterator;
+ import java.util.Map;
+ import java.util.Map.Entry;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.tags.BlockTags;
++import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.level.BlockGetter;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.LevelAccessor;
+@@ -21,7 +23,7 @@
+ 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.IBlockData;
+ 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;
+@@ -29,73 +31,94 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.block.BlockFace;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.block.BlockFromToEvent;
++import org.bukkit.event.block.FluidLevelChangeEvent;
++// CraftBukkit end
+
+ 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<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> {
+- Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> map = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(200) {
+- @Override
+- protected void rehash(int newSize) {
+- }
++ Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2bytelinkedopenhashmap = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(200) {
++ protected void rehash(int i) {}
+ };
+- map.defaultReturnValue((byte)127);
+- return map;
++
++ object2bytelinkedopenhashmap.defaultReturnValue((byte) 127);
++ return object2bytelinkedopenhashmap;
+ });
+ private final Map<FluidState, VoxelShape> shapes = Maps.newIdentityHashMap();
+
++ public FlowingFluid() {}
++
+ @Override
+ protected void createFluidStateDefinition(StateDefinition.Builder<Fluid, FluidState> builder) {
+- builder.add(FALLING);
++ builder.add(FlowingFluid.FALLING);
+ }
+
+ @Override
+ public Vec3 getFlow(BlockGetter blockReader, BlockPos pos, FluidState fluidState) {
+- double d = 0.0;
+- double d1 = 0.0;
+- BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
++ double d0 = 0.0D;
++ double d1 = 0.0D;
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
++ Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+- for (Direction direction : Direction.Plane.HORIZONTAL) {
+- mutableBlockPos.setWithOffset(pos, direction);
+- FluidState fluidState1 = blockReader.getFluidState(mutableBlockPos);
+- if (this.affectsFlow(fluidState1)) {
+- float ownHeight = fluidState1.getOwnHeight();
+- float f = 0.0F;
+- if (ownHeight == 0.0F) {
+- if (!blockReader.getBlockState(mutableBlockPos).blocksMotion()) {
+- BlockPos blockPos = mutableBlockPos.below();
+- FluidState fluidState2 = blockReader.getFluidState(blockPos);
+- if (this.affectsFlow(fluidState2)) {
+- ownHeight = fluidState2.getOwnHeight();
+- if (ownHeight > 0.0F) {
+- f = fluidState.getOwnHeight() - (ownHeight - 0.8888889F);
++ while (iterator.hasNext()) {
++ Direction enumdirection = (Direction) iterator.next();
++
++ blockposition_mutableblockposition.setWithOffset(pos, enumdirection);
++ FluidState fluid1 = blockReader.getFluidState(blockposition_mutableblockposition);
++
++ if (this.affectsFlow(fluid1)) {
++ float f = fluid1.getOwnHeight();
++ float f1 = 0.0F;
++
++ if (f == 0.0F) {
++ if (!blockReader.getBlockState(blockposition_mutableblockposition).blocksMotion()) {
++ BlockPos blockposition1 = blockposition_mutableblockposition.below();
++ FluidState fluid2 = blockReader.getFluidState(blockposition1);
++
++ if (this.affectsFlow(fluid2)) {
++ f = fluid2.getOwnHeight();
++ if (f > 0.0F) {
++ f1 = fluidState.getOwnHeight() - (f - 0.8888889F);
+ }
+ }
+ }
+- } else if (ownHeight > 0.0F) {
+- f = fluidState.getOwnHeight() - ownHeight;
++ } else if (f > 0.0F) {
++ f1 = fluidState.getOwnHeight() - f;
+ }
+
+- if (f != 0.0F) {
+- d += (double)((float)direction.getStepX() * f);
+- d1 += (double)((float)direction.getStepZ() * f);
++ if (f1 != 0.0F) {
++ d0 += (double) ((float) enumdirection.getStepX() * f1);
++ d1 += (double) ((float) enumdirection.getStepZ() * f1);
+ }
+ }
+ }
+
+- Vec3 vec3 = new Vec3(d, 0.0, d1);
+- if (fluidState.getValue(FALLING)) {
+- for (Direction direction1 : Direction.Plane.HORIZONTAL) {
+- mutableBlockPos.setWithOffset(pos, direction1);
+- if (this.isSolidFace(blockReader, mutableBlockPos, direction1) || this.isSolidFace(blockReader, mutableBlockPos.above(), direction1)) {
+- vec3 = vec3.normalize().add(0.0, -6.0, 0.0);
++ Vec3 vec3d = new Vec3(d0, 0.0D, d1);
++
++ if ((Boolean) fluidState.getValue(FlowingFluid.FALLING)) {
++ Iterator iterator1 = Direction.Plane.HORIZONTAL.iterator();
++
++ while (iterator1.hasNext()) {
++ Direction enumdirection1 = (Direction) iterator1.next();
++
++ blockposition_mutableblockposition.setWithOffset(pos, enumdirection1);
++ if (this.isSolidFace(blockReader, blockposition_mutableblockposition, enumdirection1) || this.isSolidFace(blockReader, blockposition_mutableblockposition.above(), enumdirection1)) {
++ vec3d = vec3d.normalize().add(0.0D, -6.0D, 0.0D);
+ break;
+ }
+ }
+ }
+
+- return vec3.normalize();
++ return vec3d.normalize();
+ }
+
+ private boolean affectsFlow(FluidState state) {
+@@ -103,116 +126,149 @@
+ }
+
+ protected boolean isSolidFace(BlockGetter level, BlockPos neighborPos, Direction side) {
+- BlockState blockState = level.getBlockState(neighborPos);
+- FluidState fluidState = level.getFluidState(neighborPos);
+- return !fluidState.getType().isSame(this)
+- && (side == Direction.UP || !(blockState.getBlock() instanceof IceBlock) && blockState.isFaceSturdy(level, neighborPos, side));
++ IBlockData iblockdata = level.getBlockState(neighborPos);
++ FluidState fluid = level.getFluidState(neighborPos);
++
++ return fluid.getType().isSame(this) ? false : (side == Direction.UP ? true : (iblockdata.getBlock() instanceof IceBlock ? false : iblockdata.isFaceSturdy(level, neighborPos, side)));
+ }
+
+ protected void spread(Level level, BlockPos pos, FluidState state) {
+ if (!state.isEmpty()) {
+- BlockState blockState = level.getBlockState(pos);
+- BlockPos blockPos = pos.below();
+- BlockState blockState1 = level.getBlockState(blockPos);
+- FluidState newLiquid = this.getNewLiquid(level, blockPos, blockState1);
+- if (this.canSpreadTo(level, pos, blockState, Direction.DOWN, blockPos, blockState1, level.getFluidState(blockPos), newLiquid.getType())) {
+- this.spreadTo(level, blockPos, blockState1, Direction.DOWN, newLiquid);
++ IBlockData iblockdata = level.getBlockState(pos);
++ BlockPos blockposition1 = pos.below();
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++ FluidState fluid1 = this.getNewLiquid(level, blockposition1, iblockdata1);
++
++ if (this.canSpreadTo(level, pos, iblockdata, Direction.DOWN, blockposition1, iblockdata1, level.getFluidState(blockposition1), fluid1.getType())) {
++ // CraftBukkit start
++ org.bukkit.block.Block source = CraftBlock.at(level, pos);
++ BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ this.spreadTo(level, blockposition1, iblockdata1, Direction.DOWN, fluid1);
+ if (this.sourceNeighborCount(level, pos) >= 3) {
+- this.spreadToSides(level, pos, state, blockState);
++ this.spreadToSides(level, pos, state, iblockdata);
+ }
+- } else if (state.isSource() || !this.isWaterHole(level, newLiquid.getType(), pos, blockState, blockPos, blockState1)) {
+- this.spreadToSides(level, pos, state, blockState);
++ } else if (state.isSource() || !this.isWaterHole(level, fluid1.getType(), pos, iblockdata, blockposition1, iblockdata1)) {
++ this.spreadToSides(level, pos, state, iblockdata);
+ }
++
+ }
+ }
+
+- private void spreadToSides(Level level, BlockPos pos, FluidState fluidState, BlockState blockState) {
++ private void spreadToSides(Level level, BlockPos pos, FluidState fluidState, IBlockData blockState) {
+ int i = fluidState.getAmount() - this.getDropOff(level);
+- if (fluidState.getValue(FALLING)) {
++
++ if ((Boolean) fluidState.getValue(FlowingFluid.FALLING)) {
+ i = 7;
+ }
+
+ if (i > 0) {
+- Map<Direction, FluidState> spread = this.getSpread(level, pos, blockState);
++ Map<Direction, FluidState> map = this.getSpread(level, pos, blockState);
++ Iterator iterator = map.entrySet().iterator();
+
+- for (Entry<Direction, FluidState> entry : spread.entrySet()) {
+- Direction direction = entry.getKey();
+- FluidState fluidState1 = entry.getValue();
+- BlockPos blockPos = pos.relative(direction);
+- BlockState blockState1 = level.getBlockState(blockPos);
+- if (this.canSpreadTo(level, pos, blockState, direction, blockPos, blockState1, level.getFluidState(blockPos), fluidState1.getType())) {
+- this.spreadTo(level, blockPos, blockState1, direction, fluidState1);
++ while (iterator.hasNext()) {
++ Entry<Direction, FluidState> entry = (Entry) iterator.next();
++ Direction enumdirection = (Direction) entry.getKey();
++ FluidState fluid1 = (FluidState) entry.getValue();
++ BlockPos blockposition1 = pos.relative(enumdirection);
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++
++ if (this.canSpreadTo(level, pos, blockState, enumdirection, blockposition1, iblockdata1, level.getFluidState(blockposition1), fluid1.getType())) {
++ // CraftBukkit start
++ org.bukkit.block.Block source = CraftBlock.at(level, pos);
++ BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection));
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
++ this.spreadTo(level, blockposition1, iblockdata1, enumdirection, fluid1);
+ }
+ }
++
+ }
+ }
+
+- protected FluidState getNewLiquid(Level level, BlockPos pos, BlockState blockState) {
++ protected FluidState getNewLiquid(Level level, BlockPos pos, IBlockData blockState) {
+ int i = 0;
+- int i1 = 0;
++ int j = 0;
++ Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+- for (Direction direction : Direction.Plane.HORIZONTAL) {
+- BlockPos blockPos = pos.relative(direction);
+- BlockState blockState1 = level.getBlockState(blockPos);
+- FluidState fluidState = blockState1.getFluidState();
+- if (fluidState.getType().isSame(this) && this.canPassThroughWall(direction, level, pos, blockState, blockPos, blockState1)) {
+- if (fluidState.isSource()) {
+- i1++;
++ while (iterator.hasNext()) {
++ Direction enumdirection = (Direction) iterator.next();
++ BlockPos blockposition1 = pos.relative(enumdirection);
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++ FluidState fluid = iblockdata1.getFluidState();
++
++ if (fluid.getType().isSame(this) && this.canPassThroughWall(enumdirection, level, pos, blockState, blockposition1, iblockdata1)) {
++ if (fluid.isSource()) {
++ ++j;
+ }
+
+- i = Math.max(i, fluidState.getAmount());
++ i = Math.max(i, fluid.getAmount());
+ }
+ }
+
+- if (this.canConvertToSource(level) && i1 >= 2) {
+- BlockState blockState2 = level.getBlockState(pos.below());
+- FluidState fluidState1 = blockState2.getFluidState();
+- if (blockState2.isSolid() || this.isSourceBlockOfThisType(fluidState1)) {
++ if (this.canConvertToSource(level) && j >= 2) {
++ IBlockData iblockdata2 = level.getBlockState(pos.below());
++ FluidState fluid1 = iblockdata2.getFluidState();
++
++ if (iblockdata2.isSolid() || this.isSourceBlockOfThisType(fluid1)) {
+ return this.getSource(false);
+ }
+ }
+
+- BlockPos blockPos1 = pos.above();
+- BlockState blockState3 = level.getBlockState(blockPos1);
+- FluidState fluidState2 = blockState3.getFluidState();
+- if (!fluidState2.isEmpty()
+- && fluidState2.getType().isSame(this)
+- && this.canPassThroughWall(Direction.UP, level, pos, blockState, blockPos1, blockState3)) {
++ BlockPos blockposition2 = pos.above();
++ IBlockData iblockdata3 = level.getBlockState(blockposition2);
++ FluidState fluid2 = iblockdata3.getFluidState();
++
++ if (!fluid2.isEmpty() && fluid2.getType().isSame(this) && this.canPassThroughWall(Direction.UP, level, pos, blockState, blockposition2, iblockdata3)) {
+ return this.getFlowing(8, true);
+ } else {
+- int i2 = i - this.getDropOff(level);
+- return i2 <= 0 ? Fluids.EMPTY.defaultFluidState() : this.getFlowing(i2, false);
++ int k = i - this.getDropOff(level);
++
++ return k <= 0 ? Fluids.EMPTY.defaultFluidState() : this.getFlowing(k, false);
+ }
+ }
+
+- private boolean canPassThroughWall(Direction direction, BlockGetter level, BlockPos pos, BlockState state, BlockPos spreadPos, BlockState spreadState) {
+- Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> map;
++ private boolean canPassThroughWall(Direction direction, BlockGetter level, BlockPos pos, IBlockData state, BlockPos spreadPos, IBlockData spreadState) {
++ Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap;
++
+ if (!state.getBlock().hasDynamicShape() && !spreadState.getBlock().hasDynamicShape()) {
+- map = OCCLUSION_CACHE.get();
++ object2bytelinkedopenhashmap = (Object2ByteLinkedOpenHashMap) FlowingFluid.OCCLUSION_CACHE.get();
+ } else {
+- map = null;
++ object2bytelinkedopenhashmap = null;
+ }
+
+- Block.BlockStatePairKey blockStatePairKey;
+- if (map != null) {
+- blockStatePairKey = new Block.BlockStatePairKey(state, spreadState, direction);
+- byte andMoveToFirst = map.getAndMoveToFirst(blockStatePairKey);
+- if (andMoveToFirst != 127) {
+- return andMoveToFirst != 0;
++ Block.BlockStatePairKey block_a;
++
++ if (object2bytelinkedopenhashmap != null) {
++ block_a = new Block.BlockStatePairKey(state, spreadState, direction);
++ byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(block_a);
++
++ if (b0 != 127) {
++ return b0 != 0;
+ }
+ } else {
+- blockStatePairKey = null;
++ block_a = null;
+ }
+
+- VoxelShape collisionShape = state.getCollisionShape(level, pos);
+- VoxelShape collisionShape1 = spreadState.getCollisionShape(level, spreadPos);
+- boolean flag = !Shapes.mergedFaceOccludes(collisionShape, collisionShape1, direction);
+- if (map != null) {
+- if (map.size() == 200) {
+- map.removeLastByte();
++ VoxelShape voxelshape = state.getCollisionShape(level, pos);
++ VoxelShape voxelshape1 = spreadState.getCollisionShape(level, spreadPos);
++ boolean flag = !Shapes.mergedFaceOccludes(voxelshape, voxelshape1, direction);
++
++ if (object2bytelinkedopenhashmap != null) {
++ if (object2bytelinkedopenhashmap.size() == 200) {
++ object2bytelinkedopenhashmap.removeLastByte();
+ }
+
+- map.putAndMoveToFirst(blockStatePairKey, (byte)(flag ? 1 : 0));
++ object2bytelinkedopenhashmap.putAndMoveToFirst(block_a, (byte) (flag ? 1 : 0));
+ }
+
+ return flag;
+@@ -221,20 +277,20 @@
+ public abstract Fluid getFlowing();
+
+ public FluidState getFlowing(int level, boolean falling) {
+- return this.getFlowing().defaultFluidState().setValue(LEVEL, Integer.valueOf(level)).setValue(FALLING, Boolean.valueOf(falling));
++ return (FluidState) ((FluidState) this.getFlowing().defaultFluidState().setValue(FlowingFluid.LEVEL, level)).setValue(FlowingFluid.FALLING, falling);
+ }
+
+ public abstract Fluid getSource();
+
+ public FluidState getSource(boolean falling) {
+- return this.getSource().defaultFluidState().setValue(FALLING, Boolean.valueOf(falling));
++ return (FluidState) this.getSource().defaultFluidState().setValue(FlowingFluid.FALLING, falling);
+ }
+
+ protected abstract boolean canConvertToSource(Level level);
+
+- protected void spreadTo(LevelAccessor level, BlockPos pos, BlockState blockState, Direction direction, FluidState fluidState) {
++ protected void spreadTo(LevelAccessor level, BlockPos pos, IBlockData blockState, Direction direction, FluidState fluidState) {
+ if (blockState.getBlock() instanceof LiquidBlockContainer) {
+- ((LiquidBlockContainer)blockState.getBlock()).placeLiquid(level, pos, blockState, fluidState);
++ ((LiquidBlockContainer) blockState.getBlock()).placeLiquid(level, pos, blockState, fluidState);
+ } else {
+ if (!blockState.isAir()) {
+ this.beforeDestroyingBlock(level, pos, blockState);
+@@ -242,74 +298,68 @@
+
+ level.setBlock(pos, fluidState.createLegacyBlock(), 3);
+ }
++
+ }
+
+- protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state);
++ protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, IBlockData state);
+
+ private static short getCacheKey(BlockPos sourcePos, BlockPos spreadPos) {
+ int i = spreadPos.getX() - sourcePos.getX();
+- int i1 = spreadPos.getZ() - sourcePos.getZ();
+- return (short)((i + 128 & 0xFF) << 8 | i1 + 128 & 0xFF);
++ int j = spreadPos.getZ() - sourcePos.getZ();
++
++ return (short) ((i + 128 & 255) << 8 | j + 128 & 255);
+ }
+
+- protected int getSlopeDistance(
+- LevelReader level,
+- BlockPos spreadPos,
+- int distance,
+- Direction direction,
+- BlockState currentSpreadState,
+- BlockPos sourcePos,
+- Short2ObjectMap<Pair<BlockState, FluidState>> stateCache,
+- Short2BooleanMap waterHoleCache
+- ) {
+- int i = 1000;
++ protected int getSlopeDistance(LevelReader level, BlockPos spreadPos, int distance, Direction direction, IBlockData currentSpreadState, BlockPos sourcePos, Short2ObjectMap<Pair<IBlockData, FluidState>> stateCache, Short2BooleanMap waterHoleCache) {
++ int j = 1000;
++ Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+- for (Direction direction1 : Direction.Plane.HORIZONTAL) {
+- if (direction1 != direction) {
+- BlockPos blockPos = spreadPos.relative(direction1);
+- short cacheKey = getCacheKey(sourcePos, blockPos);
+- Pair<BlockState, FluidState> pair = stateCache.computeIfAbsent(cacheKey, key -> {
+- BlockState blockState1 = level.getBlockState(blockPos);
+- return Pair.of(blockState1, blockState1.getFluidState());
++ while (iterator.hasNext()) {
++ Direction enumdirection1 = (Direction) iterator.next();
++
++ if (enumdirection1 != direction) {
++ BlockPos blockposition2 = spreadPos.relative(enumdirection1);
++ short short0 = getCacheKey(sourcePos, blockposition2);
++ Pair<IBlockData, FluidState> pair = (Pair) stateCache.computeIfAbsent(short0, (short1) -> {
++ IBlockData iblockdata1 = level.getBlockState(blockposition2);
++
++ return Pair.of(iblockdata1, iblockdata1.getFluidState());
+ });
+- BlockState blockState = pair.getFirst();
+- FluidState fluidState = pair.getSecond();
+- if (this.canPassThrough(level, this.getFlowing(), spreadPos, currentSpreadState, direction1, blockPos, blockState, fluidState)) {
+- boolean flag = waterHoleCache.computeIfAbsent(cacheKey, key -> {
+- BlockPos blockPos1 = blockPos.below();
+- BlockState blockState1 = level.getBlockState(blockPos1);
+- return this.isWaterHole(level, this.getFlowing(), blockPos, blockState, blockPos1, blockState1);
++ IBlockData iblockdata1 = (IBlockData) pair.getFirst();
++ FluidState fluid = (FluidState) pair.getSecond();
++
++ if (this.canPassThrough(level, this.getFlowing(), spreadPos, currentSpreadState, enumdirection1, blockposition2, iblockdata1, fluid)) {
++ boolean flag = waterHoleCache.computeIfAbsent(short0, (short1) -> {
++ BlockPos blockposition3 = blockposition2.below();
++ IBlockData iblockdata2 = level.getBlockState(blockposition3);
++
++ return this.isWaterHole(level, this.getFlowing(), blockposition2, iblockdata1, blockposition3, iblockdata2);
+ });
++
+ if (flag) {
+ return distance;
+ }
+
+ if (distance < this.getSlopeFindDistance(level)) {
+- int slopeDistance = this.getSlopeDistance(
+- level, blockPos, distance + 1, direction1.getOpposite(), blockState, sourcePos, stateCache, waterHoleCache
+- );
+- if (slopeDistance < i) {
+- i = slopeDistance;
++ int k = this.getSlopeDistance(level, blockposition2, distance + 1, enumdirection1.getOpposite(), iblockdata1, sourcePos, stateCache, waterHoleCache);
++
++ if (k < j) {
++ j = k;
+ }
+ }
+ }
+ }
+ }
+
+- return i;
++ return j;
+ }
+
+- private boolean isWaterHole(BlockGetter level, Fluid fluid, BlockPos pos, BlockState state, BlockPos spreadPos, BlockState spreadState) {
+- return this.canPassThroughWall(Direction.DOWN, level, pos, state, spreadPos, spreadState)
+- && (spreadState.getFluidState().getType().isSame(this) || this.canHoldFluid(level, spreadPos, spreadState, fluid));
++ private boolean isWaterHole(BlockGetter level, Fluid fluid, BlockPos pos, IBlockData state, BlockPos spreadPos, IBlockData spreadState) {
++ return !this.canPassThroughWall(Direction.DOWN, level, pos, state, spreadPos, spreadState) ? false : (spreadState.getFluidState().getType().isSame(this) ? true : this.canHoldFluid(level, spreadPos, spreadState, fluid));
+ }
+
+- private boolean canPassThrough(
+- BlockGetter level, Fluid fluid, BlockPos pos, BlockState state, Direction direction, BlockPos spreadPos, BlockState spreadState, FluidState fluidState
+- ) {
+- return !this.isSourceBlockOfThisType(fluidState)
+- && this.canPassThroughWall(direction, level, pos, state, spreadPos, spreadState)
+- && this.canHoldFluid(level, spreadPos, spreadState, fluid);
++ private boolean canPassThrough(BlockGetter level, Fluid fluid, BlockPos pos, IBlockData state, Direction direction, BlockPos spreadPos, IBlockData spreadState, FluidState fluidState) {
++ return !this.isSourceBlockOfThisType(fluidState) && this.canPassThroughWall(direction, level, pos, state, spreadPos, spreadState) && this.canHoldFluid(level, spreadPos, spreadState, fluid);
+ }
+
+ private boolean isSourceBlockOfThisType(FluidState state) {
+@@ -320,54 +370,63 @@
+
+ private int sourceNeighborCount(LevelReader level, BlockPos pos) {
+ int i = 0;
++ Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+- for (Direction direction : Direction.Plane.HORIZONTAL) {
+- BlockPos blockPos = pos.relative(direction);
+- FluidState fluidState = level.getFluidState(blockPos);
+- if (this.isSourceBlockOfThisType(fluidState)) {
+- i++;
++ while (iterator.hasNext()) {
++ Direction enumdirection = (Direction) iterator.next();
++ BlockPos blockposition1 = pos.relative(enumdirection);
++ FluidState fluid = level.getFluidState(blockposition1);
++
++ if (this.isSourceBlockOfThisType(fluid)) {
++ ++i;
+ }
+ }
+
+ return i;
+ }
+
+- protected Map<Direction, FluidState> getSpread(Level level, BlockPos pos, BlockState state) {
++ protected Map<Direction, FluidState> getSpread(Level level, BlockPos pos, IBlockData state) {
+ int i = 1000;
+ Map<Direction, FluidState> map = Maps.newEnumMap(Direction.class);
+- Short2ObjectMap<Pair<BlockState, FluidState>> map1 = new Short2ObjectOpenHashMap<>();
+- Short2BooleanMap map2 = new Short2BooleanOpenHashMap();
++ Short2ObjectMap<Pair<IBlockData, FluidState>> short2objectmap = new Short2ObjectOpenHashMap();
++ Short2BooleanOpenHashMap short2booleanopenhashmap = new Short2BooleanOpenHashMap();
++ Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+- for (Direction direction : Direction.Plane.HORIZONTAL) {
+- BlockPos blockPos = pos.relative(direction);
+- short cacheKey = getCacheKey(pos, blockPos);
+- Pair<BlockState, FluidState> pair = map1.computeIfAbsent(cacheKey, key -> {
+- BlockState blockState1 = level.getBlockState(blockPos);
+- return Pair.of(blockState1, blockState1.getFluidState());
++ while (iterator.hasNext()) {
++ Direction enumdirection = (Direction) iterator.next();
++ BlockPos blockposition1 = pos.relative(enumdirection);
++ short short0 = getCacheKey(pos, blockposition1);
++ Pair<IBlockData, FluidState> pair = (Pair) short2objectmap.computeIfAbsent(short0, (short1) -> {
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++
++ return Pair.of(iblockdata1, iblockdata1.getFluidState());
+ });
+- BlockState blockState = pair.getFirst();
+- FluidState fluidState = pair.getSecond();
+- FluidState newLiquid = this.getNewLiquid(level, blockPos, blockState);
+- if (this.canPassThrough(level, newLiquid.getType(), pos, state, direction, blockPos, blockState, fluidState)) {
+- BlockPos blockPos1 = blockPos.below();
+- boolean flag = map2.computeIfAbsent(cacheKey, key -> {
+- BlockState blockState1 = level.getBlockState(blockPos1);
+- return this.isWaterHole(level, this.getFlowing(), blockPos, blockState, blockPos1, blockState1);
++ IBlockData iblockdata1 = (IBlockData) pair.getFirst();
++ FluidState fluid = (FluidState) pair.getSecond();
++ FluidState fluid1 = this.getNewLiquid(level, blockposition1, iblockdata1);
++
++ if (this.canPassThrough(level, fluid1.getType(), pos, state, enumdirection, blockposition1, iblockdata1, fluid)) {
++ BlockPos blockposition2 = blockposition1.below();
++ boolean flag = short2booleanopenhashmap.computeIfAbsent(short0, (short1) -> {
++ IBlockData iblockdata2 = level.getBlockState(blockposition2);
++
++ return this.isWaterHole(level, this.getFlowing(), blockposition1, iblockdata1, blockposition2, iblockdata2);
+ });
+- int i1;
++ int j;
++
+ if (flag) {
+- i1 = 0;
++ j = 0;
+ } else {
+- i1 = this.getSlopeDistance(level, blockPos, 1, direction.getOpposite(), blockState, pos, map1, map2);
++ j = this.getSlopeDistance(level, blockposition1, 1, enumdirection.getOpposite(), iblockdata1, pos, short2objectmap, short2booleanopenhashmap);
+ }
+
+- if (i1 < i) {
++ if (j < i) {
+ map.clear();
+ }
+
+- if (i1 <= i) {
+- map.put(direction, newLiquid);
+- i = i1;
++ if (j <= i) {
++ map.put(enumdirection, fluid1);
++ i = j;
+ }
+ }
+ }
+@@ -375,35 +434,20 @@
+ return map;
+ }
+
+- private boolean canHoldFluid(BlockGetter level, BlockPos pos, BlockState state, Fluid fluid) {
++ private boolean canHoldFluid(BlockGetter level, BlockPos pos, IBlockData state, Fluid fluid) {
+ Block block = state.getBlock();
+- return block instanceof LiquidBlockContainer liquidBlockContainer
+- ? liquidBlockContainer.canPlaceLiquid(null, level, pos, state, fluid)
+- : !(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)
+- && !state.blocksMotion();
++
++ if (block instanceof LiquidBlockContainer) {
++ LiquidBlockContainer ifluidcontainer = (LiquidBlockContainer) block;
++
++ return ifluidcontainer.canPlaceLiquid((Player) null, level, pos, state, fluid);
++ } else {
++ 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) ? !state.blocksMotion() : false) : false;
++ }
+ }
+
+- protected boolean canSpreadTo(
+- BlockGetter level,
+- BlockPos fromPos,
+- BlockState fromBlockState,
+- Direction direction,
+- BlockPos toPos,
+- BlockState toBlockState,
+- FluidState toFluidState,
+- Fluid fluid
+- ) {
+- return toFluidState.canBeReplacedWith(level, toPos, fluid, direction)
+- && this.canPassThroughWall(direction, level, fromPos, fromBlockState, toPos, toBlockState)
+- && this.canHoldFluid(level, toPos, toBlockState, fluid);
++ protected boolean canSpreadTo(BlockGetter level, BlockPos fromPos, IBlockData fromBlockState, Direction direction, BlockPos toPos, IBlockData toBlockState, FluidState toFluidState, Fluid fluid) {
++ return toFluidState.canBeReplacedWith(level, toPos, fluid, direction) && this.canPassThroughWall(direction, level, fromPos, fromBlockState, toPos, toBlockState) && this.canHoldFluid(level, toPos, toBlockState, fluid);
+ }
+
+ protected abstract int getDropOff(LevelReader level);
+@@ -415,17 +459,30 @@
+ @Override
+ public void tick(Level level, BlockPos pos, FluidState state) {
+ if (!state.isSource()) {
+- FluidState newLiquid = this.getNewLiquid(level, pos, level.getBlockState(pos));
+- int spreadDelay = this.getSpreadDelay(level, pos, state, newLiquid);
+- if (newLiquid.isEmpty()) {
+- state = newLiquid;
+- level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
+- } else if (!newLiquid.equals(state)) {
+- state = newLiquid;
+- BlockState blockState = newLiquid.createLegacyBlock();
+- level.setBlock(pos, blockState, 2);
+- level.scheduleTick(pos, newLiquid.getType(), spreadDelay);
+- level.updateNeighborsAt(pos, blockState.getBlock());
++ FluidState fluid1 = this.getNewLiquid(level, pos, level.getBlockState(pos));
++ int i = this.getSpreadDelay(level, pos, state, fluid1);
++
++ if (fluid1.isEmpty()) {
++ state = fluid1;
++ // CraftBukkit start
++ FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(level, pos, Blocks.AIR.defaultBlockState());
++ if (event.isCancelled()) {
++ return;
++ }
++ level.setBlock(pos, ((CraftBlockData) event.getNewData()).getState(), 3);
++ // CraftBukkit end
++ } else if (!fluid1.equals(state)) {
++ state = fluid1;
++ IBlockData iblockdata = fluid1.createLegacyBlock();
++ // CraftBukkit start
++ FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(level, pos, iblockdata);
++ if (event.isCancelled()) {
++ return;
++ }
++ level.setBlock(pos, ((CraftBlockData) event.getNewData()).getState(), 2);
++ // CraftBukkit end
++ level.scheduleTick(pos, fluid1.getType(), i);
++ level.updateNeighborsAt(pos, iblockdata.getBlock());
+ }
+ }
+
+@@ -433,7 +490,7 @@
+ }
+
+ protected static int getLegacyLevel(FluidState state) {
+- return state.isSource() ? 0 : 8 - Math.min(state.getAmount(), 8) + (state.getValue(FALLING) ? 8 : 0);
++ return state.isSource() ? 0 : 8 - Math.min(state.getAmount(), 8) + ((Boolean) state.getValue(FlowingFluid.FALLING) ? 8 : 0);
+ }
+
+ private static boolean hasSameAbove(FluidState fluidState, BlockGetter level, BlockPos pos) {
+@@ -447,7 +504,7 @@
+
+ @Override
+ public float getOwnHeight(FluidState state) {
+- return (float)state.getAmount() / 9.0F;
++ return (float) state.getAmount() / 9.0F;
+ }
+
+ @Override
+@@ -455,8 +512,8 @@
+
+ @Override
+ public VoxelShape getShape(FluidState state, BlockGetter level, BlockPos pos) {
+- return state.getAmount() == 9 && hasSameAbove(state, level, pos)
+- ? Shapes.block()
+- : this.shapes.computeIfAbsent(state, stateKey -> Shapes.box(0.0, 0.0, 0.0, 1.0, (double)stateKey.getHeight(level, pos), 1.0));
++ return state.getAmount() == 9 && hasSameAbove(state, level, pos) ? Shapes.block() : (VoxelShape) this.shapes.computeIfAbsent(state, (fluid1) -> {
++ return Shapes.box(0.0D, 0.0D, 0.0D, 1.0D, (double) fluid1.getHeight(level, pos), 1.0D);
++ });
+ }
+ }