PocketMine-MP 5.23.3 git-976fc63567edab7a6fb6aeae739f43cf9fe57de4
Loading...
Searching...
No Matches
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;
38
39abstract class Liquid extends Transparent{
40 public const MAX_DECAY = 7;
41
42 public int $adjacentSources = 0;
43
44 protected ?Vector3 $flowVector = null;
45
46 protected bool $falling = false;
47 protected int $decay = 0; //PC "level" property
48 protected bool $still = false;
49
50 protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
51 $w->boundedIntAuto(0, self::MAX_DECAY, $this->decay);
52 $w->bool($this->falling);
53 $w->bool($this->still);
54 }
55
56 public function isFalling() : bool{ return $this->falling; }
57
59 public function setFalling(bool $falling) : self{
60 $this->falling = $falling;
61 return $this;
62 }
63
64 public function getDecay() : int{ return $this->decay; }
65
67 public function setDecay(int $decay) : self{
68 if($decay < 0 || $decay > self::MAX_DECAY){
69 throw new \InvalidArgumentException("Decay must be in range 0 ... " . self::MAX_DECAY);
70 }
71 $this->decay = $decay;
72 return $this;
73 }
74
75 public function hasEntityCollision() : bool{
76 return true;
77 }
78
79 public function canBeReplaced() : bool{
80 return true;
81 }
82
83 public function canBeFlowedInto() : bool{
84 return true;
85 }
86
87 public function isSolid() : bool{
88 return false;
89 }
90
91 protected function recalculateCollisionBoxes() : array{
92 return [];
93 }
94
95 public function getSupportType(int $facing) : SupportType{
96 return SupportType::NONE;
97 }
98
99 public function getDropsForCompatibleTool(Item $item) : array{
100 return [];
101 }
102
103 public function getStillForm() : Block{
104 $b = clone $this;
105 $b->still = true;
106 return $b;
107 }
108
109 public function getFlowingForm() : Block{
110 $b = clone $this;
111 $b->still = false;
112 return $b;
113 }
114
115 abstract public function getBucketFillSound() : Sound;
116
117 abstract public function getBucketEmptySound() : Sound;
118
119 public function isSource() : bool{
120 return !$this->falling && $this->decay === 0;
121 }
122
126 public function getFluidHeightPercent(){
127 return (($this->falling ? 0 : $this->decay) + 1) / 9;
128 }
129
130 public function isStill() : bool{
131 return $this->still;
132 }
133
137 public function setStill(bool $still = true) : self{
138 $this->still = $still;
139 return $this;
140 }
141
142 protected function getEffectiveFlowDecay(Block $block) : int{
143 if(!($block instanceof Liquid) || !$block->hasSameTypeId($this)){
144 return -1;
145 }
146
147 return $block->falling ? 0 : $block->decay;
148 }
149
150 public function readStateFromWorld() : Block{
151 parent::readStateFromWorld();
152 $this->flowVector = null;
153
154 return $this;
155 }
156
157 public function getFlowVector() : Vector3{
158 if($this->flowVector !== null){
159 return $this->flowVector;
160 }
161
162 $vX = $vY = $vZ = 0;
163
164 $x = $this->position->getFloorX();
165 $y = $this->position->getFloorY();
166 $z = $this->position->getFloorZ();
167
168 $decay = $this->getEffectiveFlowDecay($this);
169
170 $world = $this->position->getWorld();
171
172 foreach(Facing::HORIZONTAL as $j){
173 [$dx, $dy, $dz] = Facing::OFFSET[$j];
174
175 $sideX = $x + $dx;
176 $sideY = $y + $dy;
177 $sideZ = $z + $dz;
178
179 $sideBlock = $world->getBlockAt($sideX, $sideY, $sideZ);
180 $blockDecay = $this->getEffectiveFlowDecay($sideBlock);
181
182 if($blockDecay < 0){
183 if(!$sideBlock->canBeFlowedInto()){
184 continue;
185 }
186
187 $blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($sideX, $sideY - 1, $sideZ));
188
189 if($blockDecay >= 0){
190 $realDecay = $blockDecay - ($decay - 8);
191 $vX += $dx * $realDecay;
192 $vY += $dy * $realDecay;
193 $vZ += $dz * $realDecay;
194 }
195
196 continue;
197 }else{
198 $realDecay = $blockDecay - $decay;
199 $vX += $dx * $realDecay;
200 $vY += $dy * $realDecay;
201 $vZ += $dz * $realDecay;
202 }
203 }
204
205 $vector = new Vector3($vX, $vY, $vZ);
206
207 if($this->falling){
208 foreach(Facing::HORIZONTAL as $facing){
209 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
210 if(
211 !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy, $z + $dz)) ||
212 !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy + 1, $z + $dz))
213 ){
214 $vector = $vector->normalize()->add(0, -6, 0);
215 break;
216 }
217 }
218 }
219
220 return $this->flowVector = $vector->normalize();
221 }
222
223 public function addVelocityToEntity(Entity $entity) : ?Vector3{
224 if($entity->canBeMovedByCurrents()){
225 return $this->getFlowVector();
226 }
227 return null;
228 }
229
230 abstract public function tickRate() : int;
231
235 public function getFlowDecayPerBlock() : int{
236 return 1;
237 }
238
243 public function getMinAdjacentSourcesToFormSource() : ?int{
244 return null;
245 }
246
247 public function onNearbyBlockChange() : void{
248 if(!$this->checkForHarden()){
249 $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->tickRate());
250 }
251 }
252
253 public function onScheduledUpdate() : void{
254 $multiplier = $this->getFlowDecayPerBlock();
255
256 $world = $this->position->getWorld();
257
258 $x = $this->position->getFloorX();
259 $y = $this->position->getFloorY();
260 $z = $this->position->getFloorZ();
261
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);
269
270 $newDecay = $smallestFlowDecay + $multiplier;
271 $falling = false;
272
273 if($newDecay > self::MAX_DECAY || $smallestFlowDecay < 0){
274 $newDecay = -1;
275 }
276
277 if($this->getEffectiveFlowDecay($world->getBlockAt($x, $y + 1, $z)) >= 0){
278 $falling = true;
279 }
280
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())){
285 $newDecay = 0;
286 $falling = false;
287 }
288 }
289
290 if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
291 if(!$falling && $newDecay < 0){
292 $world->setBlockAt($x, $y, $z, VanillaBlocks::AIR());
293 return;
294 }
295
296 $this->falling = $falling;
297 $this->decay = $falling ? 0 : $newDecay;
298 $world->setBlockAt($x, $y, $z, $this); //local block update will cause an update to be scheduled
299 }
300 }
301
302 $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
303
304 $this->flowIntoBlock($bottomBlock, 0, true);
305
306 if($this->isSource() || !$bottomBlock->canBeFlowedInto()){
307 if($this->falling){
308 $adjacentDecay = 1; //falling liquid behaves like source block
309 }else{
310 $adjacentDecay = $this->decay + $multiplier;
311 }
312
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);
318 }
319 }
320 }
321
322 $this->checkForHarden();
323 }
324
325 protected function flowIntoBlock(Block $block, int $newFlowDecay, bool $falling) : void{
326 if($this->canFlowInto($block) && !($block instanceof Liquid)){
327 $new = clone $this;
328 $new->falling = $falling;
329 $new->decay = $falling ? 0 : $newFlowDecay;
330
331 $ev = new BlockSpreadEvent($block, $this, $new);
332 $ev->call();
333 if(!$ev->isCancelled()){
334 $world = $this->position->getWorld();
335 if($block->getTypeId() !== BlockTypeIds::AIR){
336 $world->useBreakOn($block->position);
337 }
338
339 $world->setBlock($block->position, $ev->getNewState());
340 }
341 }
342 }
343
345 private function getSmallestFlowDecay(Block $block, int $decay) : int{
346 if(!($block instanceof Liquid) || !$block->hasSameTypeId($this)){
347 return $decay;
348 }
349
350 $blockDecay = $block->decay;
351
352 if($block->isSource()){
353 ++$this->adjacentSources;
354 }elseif($block->falling){
355 $blockDecay = 0;
356 }
357
358 return ($decay >= 0 && $blockDecay >= $decay) ? $decay : $blockDecay;
359 }
360
361 protected function checkForHarden() : bool{
362 return false;
363 }
364
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));
368 }
369 return true;
370 }
371
372 protected function canFlowInto(Block $block) : bool{
373 return
374 $this->position->getWorld()->isInWorld($block->position->x, $block->position->y, $block->position->z) &&
375 $block->canBeFlowedInto() &&
376 !($block instanceof Liquid && $block->isSource()); //TODO: I think this should only be liquids of the same type
377 }
378}
hasSameTypeId(Block $other)
Definition Block.php:187
addVelocityToEntity(Entity $entity)
Definition Liquid.php:223
setDecay(int $decay)
Definition Liquid.php:67
getSupportType(int $facing)
Definition Liquid.php:95
setFalling(bool $falling)
Definition Liquid.php:59
getDropsForCompatibleTool(Item $item)
Definition Liquid.php:99
describeBlockOnlyState(RuntimeDataDescriber $w)
Definition Liquid.php:50
setStill(bool $still=true)
Definition Liquid.php:137