PocketMine-MP 5.15.1 git-be6754494fdbbb9dd57c058ba0e33a4a78c4581f
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
366 public function readStateFromWorld() : Block{
367 return $this;
368 }
369
376 public function writeStateToWorld() : void{
377 $world = $this->position->getWorld();
378 $chunk = $world->getOrLoadChunkAtPosition($this->position);
379 if($chunk === null){
380 throw new AssumptionFailedError("World::setBlock() should have loaded the chunk before calling this method");
381 }
382 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
383
384 $tileType = $this->idInfo->getTileClass();
385 $oldTile = $world->getTile($this->position);
386 if($oldTile !== null){
387 if($tileType === null || !($oldTile instanceof $tileType)){
388 $oldTile->close();
389 $oldTile = null;
390 }elseif($oldTile instanceof Spawnable){
391 $oldTile->clearSpawnCompoundCache(); //destroy old network cache
392 }
393 }
394 if($oldTile === null && $tileType !== null){
399 $tile = new $tileType($world, $this->position->asVector3());
400 $world->addTile($tile);
401 }
402 }
403
407 public function canBePlaced() : bool{
408 return true;
409 }
410
414 public function canBeReplaced() : bool{
415 return false;
416 }
417
422 public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
423 return $blockReplace->canBeReplaced();
424 }
425
440 public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
441 $tx->addBlock($blockReplace->position, $this);
442 return true;
443 }
444
449 public function onPostPlace() : void{
450
451 }
452
456 public function getBreakInfo() : BlockBreakInfo{
457 return $this->typeInfo->getBreakInfo();
458 }
459
469 public function getEnchantmentTags() : array{
470 return $this->typeInfo->getEnchantmentTags();
471 }
472
478 public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{
479 $world = $this->position->getWorld();
480 if(($t = $world->getTile($this->position)) !== null){
481 $t->onBlockDestroyed();
482 }
483 $world->setBlock($this->position, VanillaBlocks::AIR());
484 return true;
485 }
486
490 public function onNearbyBlockChange() : void{
491
492 }
493
497 public function ticksRandomly() : bool{
498 return false;
499 }
500
505 public function onRandomTick() : void{
506
507 }
508
512 public function onScheduledUpdate() : void{
513
514 }
515
522 public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
523 return false;
524 }
525
531 public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
532 return false;
533 }
534
541 public function getFrictionFactor() : float{
542 return 0.6;
543 }
544
550 public function getLightLevel() : int{
551 return 0;
552 }
553
560 public function getLightFilter() : int{
561 return $this->isTransparent() ? 0 : 15;
562 }
563
571 public function blocksDirectSkyLight() : bool{
572 return $this->getLightFilter() > 0;
573 }
574
578 public function isTransparent() : bool{
579 return false;
580 }
581
591 public function isSolid() : bool{
592 return true;
593 }
594
598 public function canBeFlowedInto() : bool{
599 return false;
600 }
601
605 public function canClimb() : bool{
606 return false;
607 }
608
609 final public function getPosition() : Position{
610 return $this->position;
611 }
612
616 final public function position(World $world, int $x, int $y, int $z) : void{
617 $this->position = new Position($x, $y, $z, $world);
618 $this->collisionBoxes = null;
619 }
620
626 public function getDrops(Item $item) : array{
627 if($this->getBreakInfo()->isToolCompatible($item)){
628 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
629 return $this->getSilkTouchDrops($item);
630 }
631
632 return $this->getDropsForCompatibleTool($item);
633 }
634
635 return $this->getDropsForIncompatibleTool($item);
636 }
637
643 public function getDropsForCompatibleTool(Item $item) : array{
644 return [$this->asItem()];
645 }
646
652 public function getDropsForIncompatibleTool(Item $item) : array{
653 return [];
654 }
655
661 public function getSilkTouchDrops(Item $item) : array{
662 return [$this->asItem()];
663 }
664
668 public function getXpDropForTool(Item $item) : int{
669 if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
670 return 0;
671 }
672
673 return $this->getXpDropAmount();
674 }
675
679 protected function getXpDropAmount() : int{
680 return 0;
681 }
682
686 public function isAffectedBySilkTouch() : bool{
687 return false;
688 }
689
694 public function getPickedItem(bool $addUserData = false) : Item{
695 $item = $this->asItem();
696 if($addUserData){
697 $tile = $this->position->getWorld()->getTile($this->position);
698 if($tile instanceof Tile){
699 $nbt = $tile->getCleanedNBT();
700 if($nbt instanceof CompoundTag){
701 $item->setCustomBlockData($nbt);
702 $item->setLore(["+(DATA)"]);
703 }
704 }
705 }
706 return $item;
707 }
708
712 public function getFuelTime() : int{
713 return 0;
714 }
715
719 public function getMaxStackSize() : int{
720 return 64;
721 }
722
723 public function isFireProofAsItem() : bool{
724 return false;
725 }
726
731 public function getFlameEncouragement() : int{
732 return 0;
733 }
734
738 public function getFlammability() : int{
739 return 0;
740 }
741
745 public function burnsForever() : bool{
746 return false;
747 }
748
752 public function isFlammable() : bool{
753 return $this->getFlammability() > 0;
754 }
755
759 public function onIncinerate() : void{
760
761 }
762
768 public function getSide(int $side, int $step = 1){
769 $position = $this->position;
770 if($position->isValid()){
771 [$dx, $dy, $dz] = Facing::OFFSET[$side] ?? [0, 0, 0];
772 return $position->getWorld()->getBlockAt(
773 $position->x + ($dx * $step),
774 $position->y + ($dy * $step),
775 $position->z + ($dz * $step)
776 );
777 }
778
779 throw new \LogicException("Block does not have a valid world");
780 }
781
788 public function getHorizontalSides() : \Generator{
789 $world = $this->position->getWorld();
790 foreach(Facing::HORIZONTAL as $facing){
791 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
792 //TODO: yield Facing as the key?
793 yield $world->getBlockAt(
794 $this->position->x + $dx,
795 $this->position->y + $dy,
796 $this->position->z + $dz
797 );
798 }
799 }
800
807 public function getAllSides() : \Generator{
808 $world = $this->position->getWorld();
809 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
810 //TODO: yield Facing as the key?
811 yield $world->getBlockAt(
812 $this->position->x + $dx,
813 $this->position->y + $dy,
814 $this->position->z + $dz
815 );
816 }
817 }
818
825 public function getAffectedBlocks() : array{
826 return [$this];
827 }
828
832 public function __toString(){
833 return "Block[" . $this->getName() . "] (" . $this->getTypeId() . ":" . $this->encodeFullState() . ")";
834 }
835
839 public function collidesWithBB(AxisAlignedBB $bb) : bool{
840 foreach($this->getCollisionBoxes() as $bb2){
841 if($bb->intersectsWith($bb2)){
842 return true;
843 }
844 }
845
846 return false;
847 }
848
854 public function hasEntityCollision() : bool{
855 return false;
856 }
857
867 public function onEntityInside(Entity $entity) : bool{
868 return true;
869 }
870
880 public function addVelocityToEntity(Entity $entity) : ?Vector3{
881 return null;
882 }
883
888 public function onEntityLand(Entity $entity) : ?float{
889 return null;
890 }
891
895 public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
896 //NOOP
897 }
898
909 final public function getCollisionBoxes() : array{
910 if($this->collisionBoxes === null){
911 $this->collisionBoxes = $this->recalculateCollisionBoxes();
912 $extraOffset = $this->getModelPositionOffset();
913 $offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
914 foreach($this->collisionBoxes as $bb){
915 $bb->offset($offset->x, $offset->y, $offset->z);
916 }
917 }
918
919 return $this->collisionBoxes;
920 }
921
926 public function getModelPositionOffset() : ?Vector3{
927 return null;
928 }
929
933 protected function recalculateCollisionBoxes() : array{
934 return [AxisAlignedBB::one()];
935 }
936
941 public function getSupportType(int $facing) : SupportType{
942 return SupportType::FULL;
943 }
944
945 protected function getAdjacentSupportType(int $facing) : SupportType{
946 return $this->getSide($facing)->getSupportType(Facing::opposite($facing));
947 }
948
949 public function isFullCube() : bool{
950 $bb = $this->getCollisionBoxes();
951
952 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
953 }
954
959 public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
960 $bbs = $this->getCollisionBoxes();
961 if(count($bbs) === 0){
962 return null;
963 }
964
966 $currentHit = null;
968 $currentDistance = PHP_INT_MAX;
969
970 foreach($bbs as $bb){
971 $nextHit = $bb->calculateIntercept($pos1, $pos2);
972 if($nextHit === null){
973 continue;
974 }
975
976 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
977 if($nextDistance < $currentDistance){
978 $currentHit = $nextHit;
979 $currentDistance = $nextDistance;
980 }
981 }
982
983 return $currentHit;
984 }
985}
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
Definition: Block.php:895
isSameState(Block $other)
Definition: Block.php:193
getSupportType(int $facing)
Definition: Block.php:941
__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:867
getSilkTouchDrops(Item $item)
Definition: Block.php:661
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player=null)
Definition: Block.php:440
onEntityLand(Entity $entity)
Definition: Block.php:888
getDropsForCompatibleTool(Item $item)
Definition: Block.php:643
onAttack(Item $item, int $face, ?Player $player=null)
Definition: Block.php:531
getXpDropForTool(Item $item)
Definition: Block.php:668
collidesWithBB(AxisAlignedBB $bb)
Definition: Block.php:839
getPickedItem(bool $addUserData=false)
Definition: Block.php:694
getDrops(Item $item)
Definition: Block.php:626
canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock)
Definition: Block.php:422
describeBlockItemState(RuntimeDataDescriber $w)
Definition: Block.php:299
onBreak(Item $item, ?Player $player=null, array &$returnedItems=[])
Definition: Block.php:478
hasSameTypeId(Block $other)
Definition: Block.php:184
addVelocityToEntity(Entity $entity)
Definition: Block.php:880
calculateIntercept(Vector3 $pos1, Vector3 $pos2)
Definition: Block.php:959
getDropsForIncompatibleTool(Item $item)
Definition: Block.php:652
setCustomBlockData(CompoundTag $compound)
Definition: Item.php:136
setLore(array $lines)
Definition: Item.php:183
intersectsWith(AxisAlignedBB $bb, float $epsilon=0.00001)
static readLong(string $str)
Definition: Binary.php:356
static writeLLong(int $value)
Definition: Binary.php:379