func canFlowInto(b world.Liquid, w *world.World, pos cube.Pos, sideways bool) bool {
	bl := w.Block(pos)
	if _, air := bl.(Air); air {
		return true
	}
	if _, ok := bl.(LiquidRemovable); ok {
		if liq, ok := bl.(world.Liquid); ok && sideways {
			if (liq.LiquidDepth() == 8 && !liq.LiquidFalling()) || liq.LiquidType() != b.LiquidType() {
				return false
			}
		}
		return true
	}
	return false
}
canFlowInto checks if a liquid can flow into the block present in the world at a specific block position.
package block
import (
	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/df-mc/dragonfly/server/entity"
	"github.com/df-mc/dragonfly/server/event"
	"github.com/df-mc/dragonfly/server/item/tool"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/go-gl/mathgl/mgl64"
	"math"
	"math/rand"
	"sync"
)
type LiquidRemovable interface {
	HasLiquidDrops() bool
}
func tickLiquid(b world.Liquid, pos cube.Pos, w *world.World) {
	if !source(b) && !sourceAround(b, pos, w) {
		if b.LiquidDepth()-4 <= 0 {
			w.SetLiquid(pos, nil)
			return
		}
		w.SetLiquid(pos, b.WithDepth(b.LiquidDepth()-2*b.SpreadDecay(), false))
		return
	}
	displacer, _ := w.Block(pos).(world.LiquidDisplacer)
	canFlowBelow := canFlowInto(b, w, pos.Add(cube.Pos{0, -1}), false)
	if b.LiquidFalling() && !canFlowBelow {
		b = b.WithDepth(8, true)
	} else if canFlowBelow {
		below := pos.Add(cube.Pos{0, -1})
		if displacer == nil || !displacer.SideClosed(pos, below, w) {
			flowInto(b.WithDepth(8, true), pos, below, w, true)
		}
	}
	depth, decay := b.LiquidDepth(), b.SpreadDecay()
	if depth <= decay {
		return
	}
	if source(b) || !canFlowBelow {
		paths := calculateLiquidPaths(b, pos, w, displacer)
		if len(paths) == 0 {
			spreadOutwards(b, pos, w, displacer)
			return
		}
		smallestLen := len(paths[0])
		for _, path := range paths {
			if len(path) <= smallestLen {
				flowInto(b, pos, path[0], w, false)
			}
		}
	}
}
func source(b world.Liquid) bool {
	return b.LiquidDepth() == 8 && !b.LiquidFalling()
}
func spreadOutwards(b world.Liquid, pos cube.Pos, w *world.World, displacer world.LiquidDisplacer) {
	pos.Neighbours(func(neighbour cube.Pos) {
		if neighbour[1] == pos[1] {
			if displacer == nil || !displacer.SideClosed(pos, neighbour, w) {
				flowInto(b, pos, neighbour, w, false)
			}
		}
	})
}
func sourceAround(b world.Liquid, pos cube.Pos, w *world.World) (sourcePresent bool) {
	pos.Neighbours(func(neighbour cube.Pos) {
		if neighbour[1] == pos[1]-1 {
			return
		}
		side, ok := w.Liquid(neighbour)
		if !ok || side.LiquidType() != b.LiquidType() {
			return
		}
		if displacer, ok := w.Block(neighbour).(world.LiquidDisplacer); ok && displacer.SideClosed(neighbour, pos, w) {
			return
		}
		if neighbour[1] == pos[1]+1 || source(side) || side.LiquidDepth() > b.LiquidDepth() {
			sourcePresent = true
		}
	})
	return
}
func flowInto(b world.Liquid, src, pos cube.Pos, w *world.World, falling bool) bool {
	newDepth := b.LiquidDepth() - b.SpreadDecay()
	if falling {
		newDepth = b.LiquidDepth()
	}
	if newDepth <= 0 && !falling {
		return false
	}
	existing := w.Block(pos)
	if existingLiquid, alsoLiquid := existing.(world.Liquid); alsoLiquid && existingLiquid.LiquidType() == b.LiquidType() {
		if existingLiquid.LiquidDepth() >= newDepth || existingLiquid.LiquidFalling() {
			return true
		}
		ctx := event.C()
		w.Handler().HandleLiquidFlow(ctx, src, pos, b.WithDepth(newDepth, falling), existing)
		ctx.Continue(func() {
			w.SetLiquid(pos, b.WithDepth(newDepth, falling))
		})
		return true
	} else if alsoLiquid {
		existingLiquid.Harden(pos, w, &src)
		return false
	}
	if _, ok := existing.(world.LiquidDisplacer); ok {
		if _, ok := w.Liquid(pos); ok {
			return false
		}
	}
	removable, ok := existing.(LiquidRemovable)
	if !ok {
		return false
	}
	if _, air := existing.(Air); !air {
		w.BreakBlockWithoutParticles(pos)
	}
	if removable.HasLiquidDrops() {
		if b, ok := existing.(Breakable); ok {
			for _, d := range b.BreakInfo().Drops(tool.None{}, nil) {
				itemEntity := entity.NewItem(d, pos.Vec3Centre())
				itemEntity.SetVelocity(mgl64.Vec3{rand.Float64()*0.2 - 0.1, 0.2, rand.Float64()*0.2 - 0.1})
				w.AddEntity(itemEntity)
			}
		} else {
			panic("liquid drops should always implement breakable")
		}
	}
	ctx := event.C()
	w.Handler().HandleLiquidFlow(ctx, src, pos, b.WithDepth(newDepth, falling), existing)
	ctx.Continue(func() {
		w.SetLiquid(pos, b.WithDepth(newDepth, falling))
	})
	return true
}
type liquidPath []cube.Pos
func calculateLiquidPaths(b world.Liquid, pos cube.Pos, w *world.World, displacer world.LiquidDisplacer) []liquidPath {
	queue := liquidQueuePool.Get().(*liquidQueue)
	defer func() {
		queue.Reset()
		liquidQueuePool.Put(queue)
	}()
	queue.PushBack(liquidNode{x: pos[0], z: pos[2], depth: int8(b.LiquidDepth())})
	decay := int8(b.SpreadDecay())
	paths := make([]liquidPath, 0, 3)
	first := true
	for {
		if queue.Len() == 0 {
			break
		}
		node := queue.Front()
		neighA, neighB, neighC, neighD := node.neighbours(decay * 2)
		if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighA.x, pos[1], neighA.z}, w)) {
			if spreadNeighbour(b, pos, w, neighA, queue) {
				queue.shortestPath = neighA.Len()
				paths = append(paths, neighA.Path(pos))
			}
		}
		if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighB.x, pos[1], neighB.z}, w)) {
			if spreadNeighbour(b, pos, w, neighB, queue) {
				queue.shortestPath = neighB.Len()
				paths = append(paths, neighB.Path(pos))
			}
		}
		if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighC.x, pos[1], neighC.z}, w)) {
			if spreadNeighbour(b, pos, w, neighC, queue) {
				queue.shortestPath = neighC.Len()
				paths = append(paths, neighC.Path(pos))
			}
		}
		if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighD.x, pos[1], neighD.z}, w)) {
			if spreadNeighbour(b, pos, w, neighD, queue) {
				queue.shortestPath = neighD.Len()
				paths = append(paths, neighD.Path(pos))
			}
		}
		first = false
	}
	return paths
}
func spreadNeighbour(b world.Liquid, src cube.Pos, w *world.World, node liquidNode, queue *liquidQueue) bool {
	if node.depth+3 <= 0 {
		return false
	}
	if node.Len() > queue.shortestPath {
		return false
	}
	pos := cube.Pos{node.x, src[1], node.z}
	if !canFlowInto(b, w, pos, true) {
		return false
	}
	pos[1]--
	if canFlowInto(b, w, pos, false) {
		return true
	}
	queue.PushBack(node)
	return false
}