PocketMine-MP 5.21.2 git-a6534ecbbbcf369264567d27e5ed70f7f5be9816
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
79 protected ?array $collisionBoxes = null;
80
81 private int $requiredBlockItemStateDataBits;
82 private int $requiredBlockOnlyStateDataBits;
83
84 private Block $defaultState;
85
86 private int $stateIdXorMask;
87
97 private static function computeStateIdXorMask(int $typeId) : int{
98 return
99 $typeId << self::INTERNAL_STATE_DATA_BITS |
100 (Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
101 }
102
106 public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
107 $this->idInfo = $idInfo;
108 $this->fallbackName = $name;
109 $this->typeInfo = $typeInfo;
110 $this->position = new Position(0, 0, 0, null);
111
112 $calculator = new RuntimeDataSizeCalculator();
113 $this->describeBlockItemState($calculator);
114 $this->requiredBlockItemStateDataBits = $calculator->getBitsUsed();
115
116 $calculator = new RuntimeDataSizeCalculator();
117 $this->describeBlockOnlyState($calculator);
118 $this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
119
120 $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
121
122 //this must be done last, otherwise the defaultState could have uninitialized fields
123 $defaultState = clone $this;
124 $this->defaultState = $defaultState;
125 $defaultState->defaultState = $defaultState;
126 }
127
128 public function __clone(){
129 $this->position = clone $this->position;
130 }
131
136 public function getIdInfo() : BlockIdentifier{
137 return $this->idInfo;
138 }
139
143 public function getName() : string{
144 return $this->fallbackName;
145 }
146
156 public function getTypeId() : int{
157 return $this->idInfo->getBlockTypeId();
158 }
159
177 public function getStateId() : int{
178 return $this->encodeFullState() ^ $this->stateIdXorMask;
179 }
180
184 public function hasSameTypeId(Block $other) : bool{
185 return $this->getTypeId() === $other->getTypeId();
186 }
187
193 public function isSameState(Block $other) : bool{
194 return $this->getStateId() === $other->getStateId();
195 }
196
200 public function getTypeTags() : array{
201 return $this->typeInfo->getTypeTags();
202 }
203
212 public function hasTypeTag(string $tag) : bool{
213 return $this->typeInfo->hasTypeTag($tag);
214 }
215
222 public function asItem() : Item{
223 $normalized = clone $this->defaultState;
224 $normalized->decodeBlockItemState($this->encodeBlockItemState());
225 return new ItemBlock($normalized);
226 }
227
228 private function decodeBlockItemState(int $data) : void{
229 $reader = new RuntimeDataReader($this->requiredBlockItemStateDataBits, $data);
230
231 $this->describeBlockItemState($reader);
232 $readBits = $reader->getOffset();
233 if($this->requiredBlockItemStateDataBits !== $readBits){
234 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockItemStateDataBits bits of block-item state data were provided, but $readBits were read");
235 }
236 }
237
238 private function decodeBlockOnlyState(int $data) : void{
239 $reader = new RuntimeDataReader($this->requiredBlockOnlyStateDataBits, $data);
240
241 $this->describeBlockOnlyState($reader);
242 $readBits = $reader->getOffset();
243 if($this->requiredBlockOnlyStateDataBits !== $readBits){
244 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockOnlyStateDataBits bits of block-only state data were provided, but $readBits were read");
245 }
246 }
247
248 private function encodeBlockItemState() : int{
249 $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
250
251 $this->describeBlockItemState($writer);
252 $writtenBits = $writer->getOffset();
253 if($this->requiredBlockItemStateDataBits !== $writtenBits){
254 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockItemStateDataBits bits of block-item state data were expected, but $writtenBits were written");
255 }
256
257 return $writer->getValue();
258 }
259
260 private function encodeBlockOnlyState() : int{
261 $writer = new RuntimeDataWriter($this->requiredBlockOnlyStateDataBits);
262
263 $this->describeBlockOnlyState($writer);
264 $writtenBits = $writer->getOffset();
265 if($this->requiredBlockOnlyStateDataBits !== $writtenBits){
266 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockOnlyStateDataBits bits of block-only state data were expected, but $writtenBits were written");
267 }
268
269 return $writer->getValue();
270 }
271
272 private function encodeFullState() : int{
273 $blockItemBits = $this->requiredBlockItemStateDataBits;
274 $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
275
276 if($blockOnlyBits === 0 && $blockItemBits === 0){
277 return 0;
278 }
279
280 $result = 0;
281 if($blockItemBits > 0){
282 $result |= $this->encodeBlockItemState();
283 }
284 if($blockOnlyBits > 0){
285 $result |= $this->encodeBlockOnlyState() << $blockItemBits;
286 }
287
288 return $result;
289 }
290
300 //NOOP
301 }
302
311 protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
312 //NOOP
313 }
314
322 public function generateStatePermutations() : \Generator{
323 //TODO: this bruteforce approach to discovering all valid states is very inefficient for larger state data sizes
324 //at some point we'll need to find a better way to do this
325 $bits = $this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits;
326 if($bits > Block::INTERNAL_STATE_DATA_BITS){
327 throw new \LogicException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
328 }
329 for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
330 $withType = clone $this;
331 try{
332 $withType->decodeBlockItemState($blockItemStateData);
333 $encoded = $withType->encodeBlockItemState();
334 if($encoded !== $blockItemStateData){
335 throw new \LogicException(static::class . "::decodeBlockItemState() accepts invalid inputs (returned $encoded for input $blockItemStateData)");
336 }
337 }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
338 continue;
339 }
340
341 for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
342 $withState = clone $withType;
343 try{
344 $withState->decodeBlockOnlyState($blockOnlyStateData);
345 $encoded = $withState->encodeBlockOnlyState();
346 if($encoded !== $blockOnlyStateData){
347 throw new \LogicException(static::class . "::decodeBlockOnlyState() accepts invalid inputs (returned $encoded for input $blockOnlyStateData)");
348 }
349 }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
350 continue;
351 }
352
353 yield $withState;
354 }
355 }
356 }
357
368 public function readStateFromWorld() : Block{
369 return $this;
370 }
371
378 public function writeStateToWorld() : void{
379 $world = $this->position->getWorld();
380 $chunk = $world->getOrLoadChunkAtPosition($this->position);
381 if($chunk === null){
382 throw new AssumptionFailedError("World::setBlock() should have loaded the chunk before calling this method");
383 }
384 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
385
386 $tileType = $this->idInfo->getTileClass();
387 $oldTile = $world->getTile($this->position);
388 if($oldTile !== null){
389 if($tileType === null || !($oldTile instanceof $tileType)){
390 $oldTile->close();
391 $oldTile = null;
392 }elseif($oldTile instanceof Spawnable){
393 $oldTile->clearSpawnCompoundCache(); //destroy old network cache
394 }
395 }
396 if($oldTile === null && $tileType !== null){
401 $tile = new $tileType($world, $this->position->asVector3());
402 $world->addTile($tile);
403 }
404 }
405
409 public function canBePlaced() : bool{
410 return true;
411 }
412
416 public function canBeReplaced() : bool{
417 return false;
418 }
419
424 public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
425 return $blockReplace->canBeReplaced();
426 }
427
442 public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
443 $tx->addBlock($blockReplace->position, $this);
444 return true;
445 }
446
451 public function onPostPlace() : void{
452
453 }
454
458 public function getBreakInfo() : BlockBreakInfo{
459 return $this->typeInfo->getBreakInfo();
460 }
461
471 public function getEnchantmentTags() : array{
472 return $this->typeInfo->getEnchantmentTags();
473 }
474
480 public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{
481 $world = $this->position->getWorld();
482 if(($t = $world->getTile($this->position)) !== null){
483 $t->onBlockDestroyed();
484 }
485 $world->setBlock($this->position, VanillaBlocks::AIR());
486 return true;
487 }
488
492 public function onNearbyBlockChange() : void{
493
494 }
495
499 public function ticksRandomly() : bool{
500 return false;
501 }
502
507 public function onRandomTick() : void{
508
509 }
510
514 public function onScheduledUpdate() : void{
515
516 }
517
524 public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
525 return false;
526 }
527
533 public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
534 return false;
535 }
536
543 public function getFrictionFactor() : float{
544 return 0.6;
545 }
546
552 public function getLightLevel() : int{
553 return 0;
554 }
555
562 public function getLightFilter() : int{
563 return $this->isTransparent() ? 0 : 15;
564 }
565
573 public function blocksDirectSkyLight() : bool{
574 return $this->getLightFilter() > 0;
575 }
576
580 public function isTransparent() : bool{
581 return false;
582 }
583
593 public function isSolid() : bool{
594 return true;
595 }
596
600 public function canBeFlowedInto() : bool{
601 return false;
602 }
603
607 public function canClimb() : bool{
608 return false;
609 }
610
611 final public function getPosition() : Position{
612 return $this->position;
613 }
614
618 final public function position(World $world, int $x, int $y, int $z) : void{
619 $this->position = new Position($x, $y, $z, $world);
620 $this->collisionBoxes = null;
621 }
622
628 public function getDrops(Item $item) : array{
629 if($this->getBreakInfo()->isToolCompatible($item)){
630 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
631 return $this->getSilkTouchDrops($item);
632 }
633
634 return $this->getDropsForCompatibleTool($item);
635 }
636
637 return $this->getDropsForIncompatibleTool($item);
638 }
639
645 public function getDropsForCompatibleTool(Item $item) : array{
646 return [$this->asItem()];
647 }
648
654 public function getDropsForIncompatibleTool(Item $item) : array{
655 return [];
656 }
657
663 public function getSilkTouchDrops(Item $item) : array{
664 return [$this->asItem()];
665 }
666
670 public function getXpDropForTool(Item $item) : int{
671 if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
672 return 0;
673 }
674
675 return $this->getXpDropAmount();
676 }
677
681 protected function getXpDropAmount() : int{
682 return 0;
683 }
684
688 public function isAffectedBySilkTouch() : bool{
689 return false;
690 }
691
696 public function getPickedItem(bool $addUserData = false) : Item{
697 $item = $this->asItem();
698 if($addUserData){
699 $tile = $this->position->getWorld()->getTile($this->position);
700 if($tile instanceof Tile){
701 $nbt = $tile->getCleanedNBT();
702 if($nbt instanceof CompoundTag){
703 $item->setCustomBlockData($nbt);
704 $item->setLore(["+(DATA)"]);
705 }
706 }
707 }
708 return $item;
709 }
710
714 public function getFuelTime() : int{
715 return 0;
716 }
717
721 public function getMaxStackSize() : int{
722 return 64;
723 }
724
725 public function isFireProofAsItem() : bool{
726 return false;
727 }
728
733 public function getFlameEncouragement() : int{
734 return 0;
735 }
736
740 public function getFlammability() : int{
741 return 0;
742 }
743
747 public function burnsForever() : bool{
748 return false;
749 }
750
754 public function isFlammable() : bool{
755 return $this->getFlammability() > 0;
756 }
757
761 public function onIncinerate() : void{
762
763 }
764
770 public function getSide(int $side, int $step = 1){
771 $position = $this->position;
772 if($position->isValid()){
773 [$dx, $dy, $dz] = Facing::OFFSET[$side] ?? [0, 0, 0];
774 return $position->getWorld()->getBlockAt(
775 $position->x + ($dx * $step),
776 $position->y + ($dy * $step),
777 $position->z + ($dz * $step)
778 );
779 }
780
781 throw new \LogicException("Block does not have a valid world");
782 }
783
790 public function getHorizontalSides() : \Generator{
791 $world = $this->position->getWorld();
792 foreach(Facing::HORIZONTAL as $facing){
793 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
794 //TODO: yield Facing as the key?
795 yield $world->getBlockAt(
796 $this->position->x + $dx,
797 $this->position->y + $dy,
798 $this->position->z + $dz
799 );
800 }
801 }
802
809 public function getAllSides() : \Generator{
810 $world = $this->position->getWorld();
811 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
812 //TODO: yield Facing as the key?
813 yield $world->getBlockAt(
814 $this->position->x + $dx,
815 $this->position->y + $dy,
816 $this->position->z + $dz
817 );
818 }
819 }
820
827 public function getAffectedBlocks() : array{
828 return [$this];
829 }
830
834 public function __toString(){
835 return "Block[" . $this->getName() . "] (" . $this->getTypeId() . ":" . $this->encodeFullState() . ")";
836 }
837
841 public function collidesWithBB(AxisAlignedBB $bb) : bool{
842 foreach($this->getCollisionBoxes() as $bb2){
843 if($bb->intersectsWith($bb2)){
844 return true;
845 }
846 }
847
848 return false;
849 }
850
856 public function hasEntityCollision() : bool{
857 return false;
858 }
859
869 public function onEntityInside(Entity $entity) : bool{
870 return true;
871 }
872
882 public function addVelocityToEntity(Entity $entity) : ?Vector3{
883 return null;
884 }
885
890 public function onEntityLand(Entity $entity) : ?float{
891 return null;
892 }
893
897 public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
898 //NOOP
899 }
900
911 final public function getCollisionBoxes() : array{
912 if($this->collisionBoxes === null){
913 $this->collisionBoxes = $this->recalculateCollisionBoxes();
914 $extraOffset = $this->getModelPositionOffset();
915 $offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
916 foreach($this->collisionBoxes as $bb){
917 $bb->offset($offset->x, $offset->y, $offset->z);
918 }
919 }
920
921 return $this->collisionBoxes;
922 }
923
928 public function getModelPositionOffset() : ?Vector3{
929 return null;
930 }
931
935 protected function recalculateCollisionBoxes() : array{
936 return [AxisAlignedBB::one()];
937 }
938
943 public function getSupportType(int $facing) : SupportType{
944 return SupportType::FULL;
945 }
946
947 protected function getAdjacentSupportType(int $facing) : SupportType{
948 return $this->getSide($facing)->getSupportType(Facing::opposite($facing));
949 }
950
951 public function isFullCube() : bool{
952 $bb = $this->getCollisionBoxes();
953
954 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
955 }
956
961 public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
962 $bbs = $this->getCollisionBoxes();
963 if(count($bbs) === 0){
964 return null;
965 }
966
968 $currentHit = null;
970 $currentDistance = PHP_INT_MAX;
971
972 foreach($bbs as $bb){
973 $nextHit = $bb->calculateIntercept($pos1, $pos2);
974 if($nextHit === null){
975 continue;
976 }
977
978 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
979 if($nextDistance < $currentDistance){
980 $currentHit = $nextHit;
981 $currentDistance = $nextDistance;
982 }
983 }
984
985 return $currentHit;
986 }
987}
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
Definition Block.php:897
isSameState(Block $other)
Definition Block.php:193
getSupportType(int $facing)
Definition Block.php:943
__construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo)
Definition Block.php:106
hasTypeTag(string $tag)
Definition Block.php:212
describeBlockOnlyState(RuntimeDataDescriber $w)
Definition Block.php:311
onEntityInside(Entity $entity)
Definition Block.php:869
getSilkTouchDrops(Item $item)
Definition Block.php:663
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player=null)
Definition Block.php:442
onEntityLand(Entity $entity)
Definition Block.php:890
getDropsForCompatibleTool(Item $item)
Definition Block.php:645
onAttack(Item $item, int $face, ?Player $player=null)
Definition Block.php:533
getXpDropForTool(Item $item)
Definition Block.php:670
collidesWithBB(AxisAlignedBB $bb)
Definition Block.php:841
getPickedItem(bool $addUserData=false)
Definition Block.php:696
getDrops(Item $item)
Definition Block.php:628
canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock)
Definition Block.php:424
describeBlockItemState(RuntimeDataDescriber $w)
Definition Block.php:299
onBreak(Item $item, ?Player $player=null, array &$returnedItems=[])
Definition Block.php:480
hasSameTypeId(Block $other)
Definition Block.php:184
addVelocityToEntity(Entity $entity)
Definition Block.php:882
calculateIntercept(Vector3 $pos1, Vector3 $pos2)
Definition Block.php:961
getDropsForIncompatibleTool(Item $item)
Definition Block.php:654
intersectsWith(AxisAlignedBB $bb, float $epsilon=0.00001)
static readLong(string $str)
Definition Binary.php:356
static writeLLong(int $value)
Definition Binary.php:379