41 public const MAX_DECAY = 7;
43 public int $adjacentSources = 0;
45 protected ?
Vector3 $flowVector =
null;
47 protected bool $falling =
false;
48 protected int $decay = 0;
49 protected bool $still =
false;
52 $w->boundedIntAuto(0, self::MAX_DECAY, $this->decay);
53 $w->bool($this->falling);
54 $w->bool($this->still);
57 public function isFalling() : bool{ return $this->falling; }
61 $this->falling = $falling;
65 public function getDecay() : int{ return $this->decay; }
69 if($decay < 0 || $decay > self::MAX_DECAY){
70 throw new \InvalidArgumentException(
"Decay must be in range 0 ... " . self::MAX_DECAY);
72 $this->decay = $decay;
100 return SupportType::NONE;
107 public function getStillForm() :
Block{
113 public function getFlowingForm() : Block{
119 abstract public function getBucketFillSound() : Sound;
121 abstract public function getBucketEmptySound() : Sound;
123 public function isSource() : bool{
124 return !$this->falling && $this->decay === 0;
131 return (($this->falling ? 0 : $this->decay) + 1) / 9;
134 public function isStill() : bool{
141 public function setStill(
bool $still =
true) : self{
142 $this->still = $still;
146 protected function getEffectiveFlowDecay(
Block $block) : int{
147 if(!($block instanceof
Liquid) || !$block->hasSameTypeId($this)){
151 return $block->falling ? 0 : $block->decay;
155 parent::readStateFromWorld();
156 $this->flowVector =
null;
161 public function getFlowVector() :
Vector3{
162 if($this->flowVector !== null){
163 return $this->flowVector;
168 $x = $this->position->getFloorX();
169 $y = $this->position->getFloorY();
170 $z = $this->position->getFloorZ();
172 $decay = $this->getEffectiveFlowDecay($this);
174 $world = $this->position->getWorld();
176 foreach(Facing::HORIZONTAL as $j){
177 [$dx, $dy, $dz] = Facing::OFFSET[$j];
183 $sideBlock = $world->getBlockAt($sideX, $sideY, $sideZ);
184 $blockDecay = $this->getEffectiveFlowDecay($sideBlock);
187 if(!$sideBlock->canBeFlowedInto()){
191 $blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($sideX, $sideY - 1, $sideZ));
193 if($blockDecay >= 0){
194 $realDecay = $blockDecay - ($decay - 8);
195 $vX += $dx * $realDecay;
196 $vY += $dy * $realDecay;
197 $vZ += $dz * $realDecay;
202 $realDecay = $blockDecay - $decay;
203 $vX += $dx * $realDecay;
204 $vY += $dy * $realDecay;
205 $vZ += $dz * $realDecay;
209 $vector =
new Vector3($vX, $vY, $vZ);
212 foreach(Facing::HORIZONTAL as $facing){
213 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
215 !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy, $z + $dz)) ||
216 !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy + 1, $z + $dz))
218 $vector = $vector->normalize()->add(0, -6, 0);
224 return $this->flowVector = $vector->normalize();
228 if($entity->canBeMovedByCurrents()){
229 return $this->getFlowVector();
234 abstract public function tickRate() : int;
252 if(!$this->checkForHarden()){
253 $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->tickRate());
258 $multiplier = $this->getFlowDecayPerBlock();
260 $world = $this->position->getWorld();
262 $x = $this->position->getFloorX();
263 $y = $this->position->getFloorY();
264 $z = $this->position->getFloorZ();
266 if(!$this->isSource()){
267 $smallestFlowDecay = -100;
268 $this->adjacentSources = 0;
269 $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z - 1), $smallestFlowDecay);
270 $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z + 1), $smallestFlowDecay);
271 $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x - 1, $y, $z), $smallestFlowDecay);
272 $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x + 1, $y, $z), $smallestFlowDecay);
274 $newDecay = $smallestFlowDecay + $multiplier;
277 if($newDecay > self::MAX_DECAY || $smallestFlowDecay < 0){
281 if($this->getEffectiveFlowDecay($world->getBlockAt($x, $y + 1, $z)) >= 0){
285 $minAdjacentSources = $this->getMinAdjacentSourcesToFormSource();
286 if($minAdjacentSources !==
null && $this->adjacentSources >= $minAdjacentSources){
287 $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
288 if($bottomBlock->isSolid() || ($bottomBlock instanceof
Liquid && $bottomBlock->
hasSameTypeId($this) && $bottomBlock->isSource())){
294 if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
295 if(!$falling && $newDecay < 0){
296 $world->setBlockAt($x, $y, $z, VanillaBlocks::AIR());
300 $this->falling = $falling;
301 $this->decay = $falling ? 0 : $newDecay;
302 $world->setBlockAt($x, $y, $z, $this);
306 $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
308 $this->flowIntoBlock($bottomBlock, 0,
true);
310 if($this->isSource() || !$bottomBlock->canBeFlowedInto()){
314 $adjacentDecay = $this->decay + $multiplier;
317 if($adjacentDecay <= self::MAX_DECAY){
318 $calculator =
new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), $this->canFlowInto(...));
319 foreach($calculator->getOptimalFlowDirections($x, $y, $z) as $facing){
320 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
321 $this->flowIntoBlock($world->getBlockAt($x + $dx, $y + $dy, $z + $dz), $adjacentDecay,
false);
326 $this->checkForHarden();
329 protected function flowIntoBlock(Block $block,
int $newFlowDecay,
bool $falling) : void{
330 if($this->canFlowInto($block) && !($block instanceof Liquid)){
332 $new->falling = $falling;
333 $new->decay = $falling ? 0 : $newFlowDecay;
335 $ev =
new BlockSpreadEvent($block, $this, $new);
337 if(!$ev->isCancelled()){
338 $world = $this->position->getWorld();
339 if($block->getTypeId() !== BlockTypeIds::AIR){
340 $world->useBreakOn($block->position);
343 $world->setBlock($block->position, $ev->getNewState());
349 private function getSmallestFlowDecay(Block $block,
int $decay) : int{
350 if(!($block instanceof Liquid) || !$block->hasSameTypeId($this)){
354 $blockDecay = $block->decay;
356 if($block->isSource()){
357 ++$this->adjacentSources;
358 }elseif($block->falling){
362 return ($decay >= 0 && $blockDecay >= $decay) ? $decay : $blockDecay;
365 protected function checkForHarden() : bool{
369 protected function liquidCollide(Block $cause, Block $result) : bool{
370 if(BlockEventHelper::form($this, $result, $cause)){
371 $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5),
new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
376 protected function canFlowInto(Block $block) : bool{
378 $this->position->getWorld()->isInWorld($block->position->x, $block->position->y, $block->position->z) &&
379 $block->canBeFlowedInto() &&
380 !($block instanceof Liquid && $block->isSource());