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
}
calculateLiquidPaths calculates paths in the world that the liquid passed can flow in to reach lower
grounds, starting at the position passed.
If none of these paths can be found, the returned slice has a length of 0.
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