40    public const MAX_DECAY = 7;
 
   42    public int $adjacentSources = 0;
 
   44    protected ?
Vector3 $flowVector = 
null;
 
   46    protected bool $falling = 
false;
 
   47    protected int $decay = 0; 
 
   48    protected bool $still = 
false;
 
   51        $w->boundedIntAuto(0, self::MAX_DECAY, $this->decay);
 
   52        $w->bool($this->falling);
 
   53        $w->bool($this->still);
 
 
   56    public function isFalling() : bool{ return $this->falling; }
 
   60        $this->falling = $falling;
 
 
   64    public function getDecay() : int{ return $this->decay; }
 
   68        if($decay < 0 || $decay > self::MAX_DECAY){
 
   69            throw new \InvalidArgumentException(
"Decay must be in range 0 ... " . self::MAX_DECAY);
 
   71        $this->decay = $decay;
 
 
   96        return SupportType::NONE;
 
 
  103    public function getStillForm() : 
Block{
 
  109    public function getFlowingForm() : Block{
 
  115    abstract public function getBucketFillSound() : Sound;
 
  117    abstract public function getBucketEmptySound() : Sound;
 
  119    public function isSource() : bool{
 
  120        return !$this->falling && $this->decay === 0;
 
  127        return (($this->falling ? 0 : $this->decay) + 1) / 9;
 
 
  130    public function isStill() : bool{
 
  137    public function setStill(
bool $still = 
true) : self{
 
  138        $this->still = $still;
 
 
  142    protected function getEffectiveFlowDecay(
Block $block) : int{
 
  143        if(!($block instanceof 
Liquid) || !$block->hasSameTypeId($this)){
 
  147        return $block->falling ? 0 : $block->decay;
 
  151        parent::readStateFromWorld();
 
  152        $this->flowVector = 
null;
 
 
  157    public function getFlowVector() : 
Vector3{
 
  158        if($this->flowVector !== null){
 
  159            return $this->flowVector;
 
  164        $x = $this->position->getFloorX();
 
  165        $y = $this->position->getFloorY();
 
  166        $z = $this->position->getFloorZ();
 
  168        $decay = $this->getEffectiveFlowDecay($this);
 
  170        $world = $this->position->getWorld();
 
  172        foreach(Facing::HORIZONTAL as $j){
 
  173            [$dx, $dy, $dz] = Facing::OFFSET[$j];
 
  179            $sideBlock = $world->getBlockAt($sideX, $sideY, $sideZ);
 
  180            $blockDecay = $this->getEffectiveFlowDecay($sideBlock);
 
  183                if(!$sideBlock->canBeFlowedInto()){
 
  187                $blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($sideX, $sideY - 1, $sideZ));
 
  189                if($blockDecay >= 0){
 
  190                    $realDecay = $blockDecay - ($decay - 8);
 
  191                    $vX += $dx * $realDecay;
 
  192                    $vY += $dy * $realDecay;
 
  193                    $vZ += $dz * $realDecay;
 
  198                $realDecay = $blockDecay - $decay;
 
  199                $vX += $dx * $realDecay;
 
  200                $vY += $dy * $realDecay;
 
  201                $vZ += $dz * $realDecay;
 
  205        $vector = 
new Vector3($vX, $vY, $vZ);
 
  208            foreach(Facing::HORIZONTAL as $facing){
 
  209                [$dx, $dy, $dz] = Facing::OFFSET[$facing];
 
  211                    !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy, $z + $dz)) ||
 
  212                    !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy + 1, $z + $dz))
 
  214                    $vector = $vector->normalize()->add(0, -6, 0);
 
  220        return $this->flowVector = $vector->normalize();
 
  224        if($entity->canBeMovedByCurrents()){
 
  225            return $this->getFlowVector();
 
 
  230    abstract public function tickRate() : int;
 
  248        if(!$this->checkForHarden()){
 
  249            $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->tickRate());
 
 
  254        $multiplier = $this->getFlowDecayPerBlock();
 
  256        $world = $this->position->getWorld();
 
  258        $x = $this->position->getFloorX();
 
  259        $y = $this->position->getFloorY();
 
  260        $z = $this->position->getFloorZ();
 
  262        if(!$this->isSource()){
 
  263            $smallestFlowDecay = -100;
 
  264            $this->adjacentSources = 0;
 
  265            $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z - 1), $smallestFlowDecay);
 
  266            $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z + 1), $smallestFlowDecay);
 
  267            $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x - 1, $y, $z), $smallestFlowDecay);
 
  268            $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x + 1, $y, $z), $smallestFlowDecay);
 
  270            $newDecay = $smallestFlowDecay + $multiplier;
 
  273            if($newDecay > self::MAX_DECAY || $smallestFlowDecay < 0){
 
  277            if($this->getEffectiveFlowDecay($world->getBlockAt($x, $y + 1, $z)) >= 0){
 
  281            $minAdjacentSources = $this->getMinAdjacentSourcesToFormSource();
 
  282            if($minAdjacentSources !== 
null && $this->adjacentSources >= $minAdjacentSources){
 
  283                $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
 
  284                if($bottomBlock->isSolid() || ($bottomBlock instanceof 
Liquid && $bottomBlock->
hasSameTypeId($this) && $bottomBlock->isSource())){
 
  290            if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
 
  291                if(!$falling && $newDecay < 0){
 
  292                    $world->setBlockAt($x, $y, $z, VanillaBlocks::AIR());
 
  296                $this->falling = $falling;
 
  297                $this->decay = $falling ? 0 : $newDecay;
 
  298                $world->setBlockAt($x, $y, $z, $this); 
 
  302        $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
 
  304        $this->flowIntoBlock($bottomBlock, 0, 
true);
 
  306        if($this->isSource() || !$bottomBlock->canBeFlowedInto()){
 
  310                $adjacentDecay = $this->decay + $multiplier;
 
  313            if($adjacentDecay <= self::MAX_DECAY){
 
  314                $calculator = 
new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), $this->canFlowInto(...));
 
  315                foreach($calculator->getOptimalFlowDirections($x, $y, $z) as $facing){
 
  316                    [$dx, $dy, $dz] = Facing::OFFSET[$facing];
 
  317                    $this->flowIntoBlock($world->getBlockAt($x + $dx, $y + $dy, $z + $dz), $adjacentDecay, 
false);
 
  322        $this->checkForHarden();
 
 
  325    protected function flowIntoBlock(Block $block, 
int $newFlowDecay, 
bool $falling) : void{
 
  326        if($this->canFlowInto($block) && !($block instanceof Liquid)){
 
  328            $new->falling = $falling;
 
  329            $new->decay = $falling ? 0 : $newFlowDecay;
 
  331            $ev = 
new BlockSpreadEvent($block, $this, $new);
 
  333            if(!$ev->isCancelled()){
 
  334                $world = $this->position->getWorld();
 
  335                if($block->getTypeId() !== BlockTypeIds::AIR){
 
  336                    $world->useBreakOn($block->position);
 
  339                $world->setBlock($block->position, $ev->getNewState());
 
  345    private function getSmallestFlowDecay(Block $block, 
int $decay) : int{
 
  346        if(!($block instanceof Liquid) || !$block->hasSameTypeId($this)){
 
  350        $blockDecay = $block->decay;
 
  352        if($block->isSource()){
 
  353            ++$this->adjacentSources;
 
  354        }elseif($block->falling){
 
  358        return ($decay >= 0 && $blockDecay >= $decay) ? $decay : $blockDecay;
 
  361    protected function checkForHarden() : bool{
 
  365    protected function liquidCollide(Block $cause, Block $result) : bool{
 
  366        if(BlockEventHelper::form($this, $result, $cause)){
 
  367            $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), 
new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
 
  372    protected function canFlowInto(Block $block) : bool{
 
  374            $this->position->getWorld()->isInWorld($block->position->x, $block->position->y, $block->position->z) &&
 
  375            $block->canBeFlowedInto() &&
 
  376            !($block instanceof Liquid && $block->isSource());