PocketMine-MP 5.23.3 git-976fc63567edab7a6fb6aeae739f43cf9fe57de4
Loading...
Searching...
No Matches
Block.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
27namespace pocketmine\block;
28
31use pocketmine\block\utils\SupportType;
57use function count;
58use function get_class;
59use function hash;
60use const PHP_INT_MAX;
61
62class Block{
63 public const INTERNAL_STATE_DATA_BITS = 11;
64 public const INTERNAL_STATE_DATA_MASK = ~(~0 << self::INTERNAL_STATE_DATA_BITS);
65
71 public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (-7482769108513497636 & self::INTERNAL_STATE_DATA_MASK);
72
73 protected BlockIdentifier $idInfo;
74 protected string $fallbackName;
75 protected BlockTypeInfo $typeInfo;
76 protected Position $position;
77
82 protected ?array $collisionBoxes = null;
83
84 private int $requiredBlockItemStateDataBits;
85 private int $requiredBlockOnlyStateDataBits;
86
87 private Block $defaultState;
88
89 private int $stateIdXorMask;
90
100 private static function computeStateIdXorMask(int $typeId) : int{
101 return
102 $typeId << self::INTERNAL_STATE_DATA_BITS |
103 (Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
104 }
105
109 public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
110 $this->idInfo = $idInfo;
111 $this->fallbackName = $name;
112 $this->typeInfo = $typeInfo;
113 $this->position = new Position(0, 0, 0, null);
114
115 $calculator = new RuntimeDataSizeCalculator();
116 $this->describeBlockItemState($calculator);
117 $this->requiredBlockItemStateDataBits = $calculator->getBitsUsed();
118
119 $calculator = new RuntimeDataSizeCalculator();
120 $this->describeBlockOnlyState($calculator);
121 $this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
122
123 $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
124
125 //this must be done last, otherwise the defaultState could have uninitialized fields
126 $defaultState = clone $this;
127 $this->defaultState = $defaultState;
128 $defaultState->defaultState = $defaultState;
129 }
130
131 public function __clone(){
132 $this->position = clone $this->position;
133 }
134
139 public function getIdInfo() : BlockIdentifier{
140 return $this->idInfo;
141 }
142
146 public function getName() : string{
147 return $this->fallbackName;
148 }
149
159 public function getTypeId() : int{
160 return $this->idInfo->getBlockTypeId();
161 }
162
180 public function getStateId() : int{
181 return $this->encodeFullState() ^ $this->stateIdXorMask;
182 }
183
187 public function hasSameTypeId(Block $other) : bool{
188 return $this->getTypeId() === $other->getTypeId();
189 }
190
196 public function isSameState(Block $other) : bool{
197 return $this->getStateId() === $other->getStateId();
198 }
199
203 public function getTypeTags() : array{
204 return $this->typeInfo->getTypeTags();
205 }
206
215 public function hasTypeTag(string $tag) : bool{
216 return $this->typeInfo->hasTypeTag($tag);
217 }
218
225 public function asItem() : Item{
226 $normalized = clone $this->defaultState;
227 $normalized->decodeBlockItemState($this->encodeBlockItemState());
228 return new ItemBlock($normalized);
229 }
230
231 private function decodeBlockItemState(int $data) : void{
232 $reader = new RuntimeDataReader($this->requiredBlockItemStateDataBits, $data);
233
234 $this->describeBlockItemState($reader);
235 $readBits = $reader->getOffset();
236 if($this->requiredBlockItemStateDataBits !== $readBits){
237 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockItemStateDataBits bits of block-item state data were provided, but $readBits were read");
238 }
239 }
240
241 private function decodeBlockOnlyState(int $data) : void{
242 $reader = new RuntimeDataReader($this->requiredBlockOnlyStateDataBits, $data);
243
244 $this->describeBlockOnlyState($reader);
245 $readBits = $reader->getOffset();
246 if($this->requiredBlockOnlyStateDataBits !== $readBits){
247 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockOnlyStateDataBits bits of block-only state data were provided, but $readBits were read");
248 }
249 }
250
251 private function encodeBlockItemState() : int{
252 $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
253
254 $this->describeBlockItemState($writer);
255 $writtenBits = $writer->getOffset();
256 if($this->requiredBlockItemStateDataBits !== $writtenBits){
257 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockItemStateDataBits bits of block-item state data were expected, but $writtenBits were written");
258 }
259
260 return $writer->getValue();
261 }
262
263 private function encodeBlockOnlyState() : int{
264 $writer = new RuntimeDataWriter($this->requiredBlockOnlyStateDataBits);
265
266 $this->describeBlockOnlyState($writer);
267 $writtenBits = $writer->getOffset();
268 if($this->requiredBlockOnlyStateDataBits !== $writtenBits){
269 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockOnlyStateDataBits bits of block-only state data were expected, but $writtenBits were written");
270 }
271
272 return $writer->getValue();
273 }
274
275 private function encodeFullState() : int{
276 $blockItemBits = $this->requiredBlockItemStateDataBits;
277 $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
278
279 if($blockOnlyBits === 0 && $blockItemBits === 0){
280 return 0;
281 }
282
283 $result = 0;
284 if($blockItemBits > 0){
285 $result |= $this->encodeBlockItemState();
286 }
287 if($blockOnlyBits > 0){
288 $result |= $this->encodeBlockOnlyState() << $blockItemBits;
289 }
290
291 return $result;
292 }
293
303 //NOOP
304 }
305
314 protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
315 //NOOP
316 }
317
325 public function generateStatePermutations() : \Generator{
326 //TODO: this bruteforce approach to discovering all valid states is very inefficient for larger state data sizes
327 //at some point we'll need to find a better way to do this
328 $bits = $this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits;
329 if($bits > Block::INTERNAL_STATE_DATA_BITS){
330 throw new \LogicException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
331 }
332 for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
333 $withType = clone $this;
334 try{
335 $withType->decodeBlockItemState($blockItemStateData);
336 $encoded = $withType->encodeBlockItemState();
337 if($encoded !== $blockItemStateData){
338 throw new \LogicException(static::class . "::decodeBlockItemState() accepts invalid inputs (returned $encoded for input $blockItemStateData)");
339 }
340 }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
341 continue;
342 }
343
344 for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
345 $withState = clone $withType;
346 try{
347 $withState->decodeBlockOnlyState($blockOnlyStateData);
348 $encoded = $withState->encodeBlockOnlyState();
349 if($encoded !== $blockOnlyStateData){
350 throw new \LogicException(static::class . "::decodeBlockOnlyState() accepts invalid inputs (returned $encoded for input $blockOnlyStateData)");
351 }
352 }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
353 continue;
354 }
355
356 yield $withState;
357 }
358 }
359 }
360
371 public function readStateFromWorld() : Block{
372 return $this;
373 }
374
381 public function writeStateToWorld() : void{
382 $world = $this->position->getWorld();
383 $chunk = $world->getOrLoadChunkAtPosition($this->position);
384 if($chunk === null){
385 throw new AssumptionFailedError("World::setBlock() should have loaded the chunk before calling this method");
386 }
387 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
388
389 $tileType = $this->idInfo->getTileClass();
390 $oldTile = $world->getTile($this->position);
391 if($oldTile !== null){
392 if($tileType === null || !($oldTile instanceof $tileType)){
393 $oldTile->close();
394 $oldTile = null;
395 }elseif($oldTile instanceof Spawnable){
396 $oldTile->clearSpawnCompoundCache(); //destroy old network cache
397 }
398 }
399 if($oldTile === null && $tileType !== null){
404 $tile = new $tileType($world, $this->position->asVector3());
405 $world->addTile($tile);
406 }
407 }
408
412 public function canBePlaced() : bool{
413 return true;
414 }
415
419 public function canBeReplaced() : bool{
420 return false;
421 }
422
427 public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
428 return $blockReplace->canBeReplaced();
429 }
430
445 public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
446 $tx->addBlock($blockReplace->position, $this);
447 return true;
448 }
449
454 public function onPostPlace() : void{
455
456 }
457
461 public function getBreakInfo() : BlockBreakInfo{
462 return $this->typeInfo->getBreakInfo();
463 }
464
474 public function getEnchantmentTags() : array{
475 return $this->typeInfo->getEnchantmentTags();
476 }
477
483 public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{
484 $world = $this->position->getWorld();
485 if(($t = $world->getTile($this->position)) !== null){
486 $t->onBlockDestroyed();
487 }
488 $world->setBlock($this->position, VanillaBlocks::AIR());
489 return true;
490 }
491
495 public function onNearbyBlockChange() : void{
496
497 }
498
502 public function ticksRandomly() : bool{
503 return false;
504 }
505
510 public function onRandomTick() : void{
511
512 }
513
517 public function onScheduledUpdate() : void{
518
519 }
520
527 public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
528 return false;
529 }
530
536 public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
537 return false;
538 }
539
546 public function getFrictionFactor() : float{
547 return 0.6;
548 }
549
555 public function getLightLevel() : int{
556 return 0;
557 }
558
565 public function getLightFilter() : int{
566 return $this->isTransparent() ? 0 : 15;
567 }
568
576 public function blocksDirectSkyLight() : bool{
577 return $this->getLightFilter() > 0;
578 }
579
583 public function isTransparent() : bool{
584 return false;
585 }
586
596 public function isSolid() : bool{
597 return true;
598 }
599
603 public function canBeFlowedInto() : bool{
604 return false;
605 }
606
610 public function canClimb() : bool{
611 return false;
612 }
613
614 final public function getPosition() : Position{
615 return $this->position;
616 }
617
621 final public function position(World $world, int $x, int $y, int $z) : void{
622 $this->position = new Position($x, $y, $z, $world);
623 $this->collisionBoxes = null;
624 }
625
631 public function getDrops(Item $item) : array{
632 if($this->getBreakInfo()->isToolCompatible($item)){
633 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
634 return $this->getSilkTouchDrops($item);
635 }
636
637 return $this->getDropsForCompatibleTool($item);
638 }
639
640 return $this->getDropsForIncompatibleTool($item);
641 }
642
648 public function getDropsForCompatibleTool(Item $item) : array{
649 return [$this->asItem()];
650 }
651
657 public function getDropsForIncompatibleTool(Item $item) : array{
658 return [];
659 }
660
666 public function getSilkTouchDrops(Item $item) : array{
667 return [$this->asItem()];
668 }
669
673 public function getXpDropForTool(Item $item) : int{
674 if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
675 return 0;
676 }
677
678 return $this->getXpDropAmount();
679 }
680
684 protected function getXpDropAmount() : int{
685 return 0;
686 }
687
691 public function isAffectedBySilkTouch() : bool{
692 return false;
693 }
694
699 public function getPickedItem(bool $addUserData = false) : Item{
700 $item = $this->asItem();
701 if($addUserData){
702 $tile = $this->position->getWorld()->getTile($this->position);
703 if($tile instanceof Tile){
704 $nbt = $tile->getCleanedNBT();
705 if($nbt instanceof CompoundTag){
706 $item->setCustomBlockData($nbt);
707 $item->setLore(["+(DATA)"]);
708 }
709 }
710 }
711 return $item;
712 }
713
717 public function getFuelTime() : int{
718 return 0;
719 }
720
724 public function getMaxStackSize() : int{
725 return 64;
726 }
727
728 public function isFireProofAsItem() : bool{
729 return false;
730 }
731
736 public function getFlameEncouragement() : int{
737 return 0;
738 }
739
743 public function getFlammability() : int{
744 return 0;
745 }
746
750 public function burnsForever() : bool{
751 return false;
752 }
753
757 public function isFlammable() : bool{
758 return $this->getFlammability() > 0;
759 }
760
764 public function onIncinerate() : void{
765
766 }
767
773 public function getSide(int $side, int $step = 1){
774 $position = $this->position;
775 if($position->isValid()){
776 [$dx, $dy, $dz] = Facing::OFFSET[$side] ?? [0, 0, 0];
777 return $position->getWorld()->getBlockAt(
778 $position->x + ($dx * $step),
779 $position->y + ($dy * $step),
780 $position->z + ($dz * $step)
781 );
782 }
783
784 throw new \LogicException("Block does not have a valid world");
785 }
786
793 public function getHorizontalSides() : \Generator{
794 $world = $this->position->getWorld();
795 foreach(Facing::HORIZONTAL as $facing){
796 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
797 //TODO: yield Facing as the key?
798 yield $world->getBlockAt(
799 $this->position->x + $dx,
800 $this->position->y + $dy,
801 $this->position->z + $dz
802 );
803 }
804 }
805
812 public function getAllSides() : \Generator{
813 $world = $this->position->getWorld();
814 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
815 //TODO: yield Facing as the key?
816 yield $world->getBlockAt(
817 $this->position->x + $dx,
818 $this->position->y + $dy,
819 $this->position->z + $dz
820 );
821 }
822 }
823
830 public function getAffectedBlocks() : array{
831 return [$this];
832 }
833
837 public function __toString(){
838 return "Block[" . $this->getName() . "] (" . $this->getTypeId() . ":" . $this->encodeFullState() . ")";
839 }
840
844 public function collidesWithBB(AxisAlignedBB $bb) : bool{
845 foreach($this->getCollisionBoxes() as $bb2){
846 if($bb->intersectsWith($bb2)){
847 return true;
848 }
849 }
850
851 return false;
852 }
853
859 public function hasEntityCollision() : bool{
860 return false;
861 }
862
872 public function onEntityInside(Entity $entity) : bool{
873 return true;
874 }
875
885 public function addVelocityToEntity(Entity $entity) : ?Vector3{
886 return null;
887 }
888
893 public function onEntityLand(Entity $entity) : ?float{
894 return null;
895 }
896
900 public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
901 //NOOP
902 }
903
915 final public function getCollisionBoxes() : array{
916 if($this->collisionBoxes === null){
917 $this->collisionBoxes = $this->recalculateCollisionBoxes();
918 $extraOffset = $this->getModelPositionOffset();
919 $offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
920 foreach($this->collisionBoxes as $bb){
921 $bb->offset($offset->x, $offset->y, $offset->z);
922 }
923 }
924
925 return $this->collisionBoxes;
926 }
927
932 public function getModelPositionOffset() : ?Vector3{
933 return null;
934 }
935
940 protected function recalculateCollisionBoxes() : array{
941 return [AxisAlignedBB::one()];
942 }
943
948 public function getSupportType(int $facing) : SupportType{
949 return SupportType::FULL;
950 }
951
952 protected function getAdjacentSupportType(int $facing) : SupportType{
953 return $this->getSide($facing)->getSupportType(Facing::opposite($facing));
954 }
955
956 public function isFullCube() : bool{
957 $bb = $this->getCollisionBoxes();
958
959 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
960 }
961
966 public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
967 $bbs = $this->getCollisionBoxes();
968 if(count($bbs) === 0){
969 return null;
970 }
971
973 $currentHit = null;
975 $currentDistance = PHP_INT_MAX;
976
977 foreach($bbs as $bb){
978 $nextHit = $bb->calculateIntercept($pos1, $pos2);
979 if($nextHit === null){
980 continue;
981 }
982
983 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
984 if($nextDistance < $currentDistance){
985 $currentHit = $nextHit;
986 $currentDistance = $nextDistance;
987 }
988 }
989
990 return $currentHit;
991 }
992}
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
Definition Block.php:900
isSameState(Block $other)
Definition Block.php:196
getSupportType(int $facing)
Definition Block.php:948
__construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo)
Definition Block.php:109
hasTypeTag(string $tag)
Definition Block.php:215
describeBlockOnlyState(RuntimeDataDescriber $w)
Definition Block.php:314
onEntityInside(Entity $entity)
Definition Block.php:872
getSilkTouchDrops(Item $item)
Definition Block.php:666
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player=null)
Definition Block.php:445
onEntityLand(Entity $entity)
Definition Block.php:893
getDropsForCompatibleTool(Item $item)
Definition Block.php:648
onAttack(Item $item, int $face, ?Player $player=null)
Definition Block.php:536
getXpDropForTool(Item $item)
Definition Block.php:673
collidesWithBB(AxisAlignedBB $bb)
Definition Block.php:844
getPickedItem(bool $addUserData=false)
Definition Block.php:699
getDrops(Item $item)
Definition Block.php:631
canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock)
Definition Block.php:427
describeBlockItemState(RuntimeDataDescriber $w)
Definition Block.php:302
onBreak(Item $item, ?Player $player=null, array &$returnedItems=[])
Definition Block.php:483
hasSameTypeId(Block $other)
Definition Block.php:187
addVelocityToEntity(Entity $entity)
Definition Block.php:885
calculateIntercept(Vector3 $pos1, Vector3 $pos2)
Definition Block.php:966
getDropsForIncompatibleTool(Item $item)
Definition Block.php:657
intersectsWith(AxisAlignedBB $bb, float $epsilon=0.00001)
static readLong(string $str)
Definition Binary.php:356
static writeLLong(int $value)
Definition Binary.php:379