PocketMine-MP 5.15.1 git-be6754494fdbbb9dd57c058ba0e33a4a78c4581f
Liquid.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\block;
25
28use pocketmine\block\utils\SupportType;
38use function lcg_value;
39
40abstract class Liquid extends Transparent{
41 public const MAX_DECAY = 7;
42
43 public int $adjacentSources = 0;
44
45 protected ?Vector3 $flowVector = null;
46
47 protected bool $falling = false;
48 protected int $decay = 0; //PC "level" property
49 protected bool $still = false;
50
51 protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
52 $w->boundedIntAuto(0, self::MAX_DECAY, $this->decay);
53 $w->bool($this->falling);
54 $w->bool($this->still);
55 }
56
57 public function isFalling() : bool{ return $this->falling; }
58
60 public function setFalling(bool $falling) : self{
61 $this->falling = $falling;
62 return $this;
63 }
64
65 public function getDecay() : int{ return $this->decay; }
66
68 public function setDecay(int $decay) : self{
69 if($decay < 0 || $decay > self::MAX_DECAY){
70 throw new \InvalidArgumentException("Decay must be in range 0 ... " . self::MAX_DECAY);
71 }
72 $this->decay = $decay;
73 return $this;
74 }
75
76 public function hasEntityCollision() : bool{
77 return true;
78 }
79
80 public function canBeReplaced() : bool{
81 return true;
82 }
83
84 public function canBeFlowedInto() : bool{
85 return true;
86 }
87
88 public function isSolid() : bool{
89 return false;
90 }
91
95 protected function recalculateCollisionBoxes() : array{
96 return [];
97 }
98
99 public function getSupportType(int $facing) : SupportType{
100 return SupportType::NONE;
101 }
102
103 public function getDropsForCompatibleTool(Item $item) : array{
104 return [];
105 }
106
107 public function getStillForm() : Block{
108 $b = clone $this;
109 $b->still = true;
110 return $b;
111 }
112
113 public function getFlowingForm() : Block{
114 $b = clone $this;
115 $b->still = false;
116 return $b;
117 }
118
119 abstract public function getBucketFillSound() : Sound;
120
121 abstract public function getBucketEmptySound() : Sound;
122
123 public function isSource() : bool{
124 return !$this->falling && $this->decay === 0;
125 }
126
130 public function getFluidHeightPercent(){
131 return (($this->falling ? 0 : $this->decay) + 1) / 9;
132 }
133
134 public function isStill() : bool{
135 return $this->still;
136 }
137
141 public function setStill(bool $still = true) : self{
142 $this->still = $still;
143 return $this;
144 }
145
146 protected function getEffectiveFlowDecay(Block $block) : int{
147 if(!($block instanceof Liquid) || !$block->hasSameTypeId($this)){
148 return -1;
149 }
150
151 return $block->falling ? 0 : $block->decay;
152 }
153
154 public function readStateFromWorld() : Block{
155 parent::readStateFromWorld();
156 $this->flowVector = null;
157
158 return $this;
159 }
160
161 public function getFlowVector() : Vector3{
162 if($this->flowVector !== null){
163 return $this->flowVector;
164 }
165
166 $vX = $vY = $vZ = 0;
167
168 $x = $this->position->getFloorX();
169 $y = $this->position->getFloorY();
170 $z = $this->position->getFloorZ();
171
172 $decay = $this->getEffectiveFlowDecay($this);
173
174 $world = $this->position->getWorld();
175
176 foreach(Facing::HORIZONTAL as $j){
177 [$dx, $dy, $dz] = Facing::OFFSET[$j];
178
179 $sideX = $x + $dx;
180 $sideY = $y + $dy;
181 $sideZ = $z + $dz;
182
183 $sideBlock = $world->getBlockAt($sideX, $sideY, $sideZ);
184 $blockDecay = $this->getEffectiveFlowDecay($sideBlock);
185
186 if($blockDecay < 0){
187 if(!$sideBlock->canBeFlowedInto()){
188 continue;
189 }
190
191 $blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($sideX, $sideY - 1, $sideZ));
192
193 if($blockDecay >= 0){
194 $realDecay = $blockDecay - ($decay - 8);
195 $vX += $dx * $realDecay;
196 $vY += $dy * $realDecay;
197 $vZ += $dz * $realDecay;
198 }
199
200 continue;
201 }else{
202 $realDecay = $blockDecay - $decay;
203 $vX += $dx * $realDecay;
204 $vY += $dy * $realDecay;
205 $vZ += $dz * $realDecay;
206 }
207 }
208
209 $vector = new Vector3($vX, $vY, $vZ);
210
211 if($this->falling){
212 foreach(Facing::HORIZONTAL as $facing){
213 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
214 if(
215 !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy, $z + $dz)) ||
216 !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy + 1, $z + $dz))
217 ){
218 $vector = $vector->normalize()->add(0, -6, 0);
219 break;
220 }
221 }
222 }
223
224 return $this->flowVector = $vector->normalize();
225 }
226
227 public function addVelocityToEntity(Entity $entity) : ?Vector3{
228 if($entity->canBeMovedByCurrents()){
229 return $this->getFlowVector();
230 }
231 return null;
232 }
233
234 abstract public function tickRate() : int;
235
239 public function getFlowDecayPerBlock() : int{
240 return 1;
241 }
242
247 public function getMinAdjacentSourcesToFormSource() : ?int{
248 return null;
249 }
250
251 public function onNearbyBlockChange() : void{
252 if(!$this->checkForHarden()){
253 $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->tickRate());
254 }
255 }
256
257 public function onScheduledUpdate() : void{
258 $multiplier = $this->getFlowDecayPerBlock();
259
260 $world = $this->position->getWorld();
261
262 $x = $this->position->getFloorX();
263 $y = $this->position->getFloorY();
264 $z = $this->position->getFloorZ();
265
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);
273
274 $newDecay = $smallestFlowDecay + $multiplier;
275 $falling = false;
276
277 if($newDecay > self::MAX_DECAY || $smallestFlowDecay < 0){
278 $newDecay = -1;
279 }
280
281 if($this->getEffectiveFlowDecay($world->getBlockAt($x, $y + 1, $z)) >= 0){
282 $falling = true;
283 }
284
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())){
289 $newDecay = 0;
290 $falling = false;
291 }
292 }
293
294 if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
295 if(!$falling && $newDecay < 0){
296 $world->setBlockAt($x, $y, $z, VanillaBlocks::AIR());
297 return;
298 }
299
300 $this->falling = $falling;
301 $this->decay = $falling ? 0 : $newDecay;
302 $world->setBlockAt($x, $y, $z, $this); //local block update will cause an update to be scheduled
303 }
304 }
305
306 $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
307
308 $this->flowIntoBlock($bottomBlock, 0, true);
309
310 if($this->isSource() || !$bottomBlock->canBeFlowedInto()){
311 if($this->falling){
312 $adjacentDecay = 1; //falling liquid behaves like source block
313 }else{
314 $adjacentDecay = $this->decay + $multiplier;
315 }
316
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);
322 }
323 }
324 }
325
326 $this->checkForHarden();
327 }
328
329 protected function flowIntoBlock(Block $block, int $newFlowDecay, bool $falling) : void{
330 if($this->canFlowInto($block) && !($block instanceof Liquid)){
331 $new = clone $this;
332 $new->falling = $falling;
333 $new->decay = $falling ? 0 : $newFlowDecay;
334
335 $ev = new BlockSpreadEvent($block, $this, $new);
336 $ev->call();
337 if(!$ev->isCancelled()){
338 $world = $this->position->getWorld();
339 if($block->getTypeId() !== BlockTypeIds::AIR){
340 $world->useBreakOn($block->position);
341 }
342
343 $world->setBlock($block->position, $ev->getNewState());
344 }
345 }
346 }
347
349 private function getSmallestFlowDecay(Block $block, int $decay) : int{
350 if(!($block instanceof Liquid) || !$block->hasSameTypeId($this)){
351 return $decay;
352 }
353
354 $blockDecay = $block->decay;
355
356 if($block->isSource()){
357 ++$this->adjacentSources;
358 }elseif($block->falling){
359 $blockDecay = 0;
360 }
361
362 return ($decay >= 0 && $blockDecay >= $decay) ? $decay : $blockDecay;
363 }
364
365 protected function checkForHarden() : bool{
366 return false;
367 }
368
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));
372 }
373 return true;
374 }
375
376 protected function canFlowInto(Block $block) : bool{
377 return
378 $this->position->getWorld()->isInWorld($block->position->x, $block->position->y, $block->position->z) &&
379 $block->canBeFlowedInto() &&
380 !($block instanceof Liquid && $block->isSource()); //TODO: I think this should only be liquids of the same type
381 }
382}
hasSameTypeId(Block $other)
Definition: Block.php:184
addVelocityToEntity(Entity $entity)
Definition: Liquid.php:227
setDecay(int $decay)
Definition: Liquid.php:68
getSupportType(int $facing)
Definition: Liquid.php:99
setFalling(bool $falling)
Definition: Liquid.php:60
getDropsForCompatibleTool(Item $item)
Definition: Liquid.php:103
describeBlockOnlyState(RuntimeDataDescriber $w)
Definition: Liquid.php:51
setStill(bool $still=true)
Definition: Liquid.php:141