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());