PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
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
29use pmmp\encoding\BE;
30use pmmp\encoding\LE;
33use pocketmine\block\utils\SupportType;
58use function count;
59use function get_class;
60use function hash;
61use const PHP_INT_MAX;
62
63class Block{
64 public const INTERNAL_STATE_DATA_BITS = 11;
65 public const INTERNAL_STATE_DATA_MASK = ~(~0 << self::INTERNAL_STATE_DATA_BITS);
66
72 public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (-7482769108513497636 & self::INTERNAL_STATE_DATA_MASK);
73
74 protected BlockIdentifier $idInfo;
75 protected string $fallbackName;
76 protected BlockTypeInfo $typeInfo;
77 protected Position $position;
78
83 protected ?array $collisionBoxes = null;
84
85 private int $requiredBlockItemStateDataBits;
86 private int $requiredBlockOnlyStateDataBits;
87
88 private Block $defaultState;
89
90 private int $stateIdXorMask;
91
101 private static function computeStateIdXorMask(int $typeId) : int{
102 //TODO: the mixed byte order here is probably a mistake, but it doesn't break anything for now
103 return
104 $typeId << self::INTERNAL_STATE_DATA_BITS |
105 (BE::unpackSignedLong(hash('xxh3', LE::packSignedLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
106 }
107
111 public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
112 $this->idInfo = $idInfo;
113 $this->fallbackName = $name;
114 $this->typeInfo = $typeInfo;
115 $this->position = new Position(0, 0, 0, null);
116
117 $calculator = new RuntimeDataSizeCalculator();
118 $this->describeBlockItemState($calculator);
119 $this->requiredBlockItemStateDataBits = $calculator->getBitsUsed();
120
121 $calculator = new RuntimeDataSizeCalculator();
122 $this->describeBlockOnlyState($calculator);
123 $this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
124
125 $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
126
127 //this must be done last, otherwise the defaultState could have uninitialized fields
128 $defaultState = clone $this;
129 $this->defaultState = $defaultState;
130 $defaultState->defaultState = $defaultState;
131 }
132
133 public function __clone(){
134 $this->position = clone $this->position;
135 }
136
141 public function getIdInfo() : BlockIdentifier{
142 return $this->idInfo;
143 }
144
148 public function getName() : string{
149 return $this->fallbackName;
150 }
151
161 public function getTypeId() : int{
162 return $this->idInfo->getBlockTypeId();
163 }
164
182 public function getStateId() : int{
183 return $this->encodeFullState() ^ $this->stateIdXorMask;
184 }
185
189 public function hasSameTypeId(Block $other) : bool{
190 return $this->getTypeId() === $other->getTypeId();
191 }
192
198 public function isSameState(Block $other) : bool{
199 return $this->getStateId() === $other->getStateId();
200 }
201
205 public function getTypeTags() : array{
206 return $this->typeInfo->getTypeTags();
207 }
208
217 public function hasTypeTag(string $tag) : bool{
218 return $this->typeInfo->hasTypeTag($tag);
219 }
220
227 public function asItem() : Item{
228 $normalized = clone $this->defaultState;
229 $normalized->decodeBlockItemState($this->encodeBlockItemState());
230 return new ItemBlock($normalized);
231 }
232
233 private function decodeBlockItemState(int $data) : void{
234 $reader = new RuntimeDataReader($this->requiredBlockItemStateDataBits, $data);
235
236 $this->describeBlockItemState($reader);
237 $readBits = $reader->getOffset();
238 if($this->requiredBlockItemStateDataBits !== $readBits){
239 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockItemStateDataBits bits of block-item state data were provided, but $readBits were read");
240 }
241 }
242
243 private function decodeBlockOnlyState(int $data) : void{
244 $reader = new RuntimeDataReader($this->requiredBlockOnlyStateDataBits, $data);
245
246 $this->describeBlockOnlyState($reader);
247 $readBits = $reader->getOffset();
248 if($this->requiredBlockOnlyStateDataBits !== $readBits){
249 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockOnlyStateDataBits bits of block-only state data were provided, but $readBits were read");
250 }
251 }
252
253 private function encodeBlockItemState() : int{
254 $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
255
256 $this->describeBlockItemState($writer);
257 $writtenBits = $writer->getOffset();
258 if($this->requiredBlockItemStateDataBits !== $writtenBits){
259 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockItemStateDataBits bits of block-item state data were expected, but $writtenBits were written");
260 }
261
262 return $writer->getValue();
263 }
264
265 private function encodeBlockOnlyState() : int{
266 $writer = new RuntimeDataWriter($this->requiredBlockOnlyStateDataBits);
267
268 $this->describeBlockOnlyState($writer);
269 $writtenBits = $writer->getOffset();
270 if($this->requiredBlockOnlyStateDataBits !== $writtenBits){
271 throw new \LogicException(get_class($this) . ": Exactly $this->requiredBlockOnlyStateDataBits bits of block-only state data were expected, but $writtenBits were written");
272 }
273
274 return $writer->getValue();
275 }
276
277 private function encodeFullState() : int{
278 $blockItemBits = $this->requiredBlockItemStateDataBits;
279 $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
280
281 if($blockOnlyBits === 0 && $blockItemBits === 0){
282 return 0;
283 }
284
285 $result = 0;
286 if($blockItemBits > 0){
287 $result |= $this->encodeBlockItemState();
288 }
289 if($blockOnlyBits > 0){
290 $result |= $this->encodeBlockOnlyState() << $blockItemBits;
291 }
292
293 return $result;
294 }
295
305 //NOOP
306 }
307
316 protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
317 //NOOP
318 }
319
327 public function generateStatePermutations() : \Generator{
328 //TODO: this bruteforce approach to discovering all valid states is very inefficient for larger state data sizes
329 //at some point we'll need to find a better way to do this
330 $bits = $this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits;
331 if($bits > Block::INTERNAL_STATE_DATA_BITS){
332 throw new \LogicException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
333 }
334 for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
335 $withType = clone $this;
336 try{
337 $withType->decodeBlockItemState($blockItemStateData);
338 $encoded = $withType->encodeBlockItemState();
339 if($encoded !== $blockItemStateData){
340 throw new \LogicException(static::class . "::decodeBlockItemState() accepts invalid inputs (returned $encoded for input $blockItemStateData)");
341 }
342 }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
343 continue;
344 }
345
346 for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
347 $withState = clone $withType;
348 try{
349 $withState->decodeBlockOnlyState($blockOnlyStateData);
350 $encoded = $withState->encodeBlockOnlyState();
351 if($encoded !== $blockOnlyStateData){
352 throw new \LogicException(static::class . "::decodeBlockOnlyState() accepts invalid inputs (returned $encoded for input $blockOnlyStateData)");
353 }
354 }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
355 continue;
356 }
357
358 yield $withState;
359 }
360 }
361 }
362
373 public function readStateFromWorld() : Block{
374 return $this;
375 }
376
383 public function writeStateToWorld() : void{
384 $world = $this->position->getWorld();
385 $chunk = $world->getOrLoadChunkAtPosition($this->position);
386 if($chunk === null){
387 throw new AssumptionFailedError("World::setBlock() should have loaded the chunk before calling this method");
388 }
389 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
390
391 $tileType = $this->idInfo->getTileClass();
392 $oldTile = $world->getTile($this->position);
393 if($oldTile !== null){
394 if($tileType === null || !($oldTile instanceof $tileType)){
395 $oldTile->close();
396 $oldTile = null;
397 }elseif($oldTile instanceof Spawnable){
398 $oldTile->clearSpawnCompoundCache(); //destroy old network cache
399 }
400 }
401 if($oldTile === null && $tileType !== null){
406 $tile = new $tileType($world, $this->position->asVector3());
407 $world->addTile($tile);
408 }
409 }
410
414 public function canBePlaced() : bool{
415 return true;
416 }
417
421 public function canBeReplaced() : bool{
422 return false;
423 }
424
429 public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, Facing $face, bool $isClickedBlock) : bool{
430 return $blockReplace->canBeReplaced();
431 }
432
447 public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
448 $tx->addBlock($blockReplace->position, $this);
449 return true;
450 }
451
456 public function onPostPlace() : void{
457
458 }
459
463 public function getBreakInfo() : BlockBreakInfo{
464 return $this->typeInfo->getBreakInfo();
465 }
466
476 public function getEnchantmentTags() : array{
477 return $this->typeInfo->getEnchantmentTags();
478 }
479
485 public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{
486 $world = $this->position->getWorld();
487 if(($t = $world->getTile($this->position)) !== null){
488 $t->onBlockDestroyed();
489 }
490 $world->setBlock($this->position, VanillaBlocks::AIR());
491 return true;
492 }
493
497 public function onNearbyBlockChange() : void{
498
499 }
500
504 public function ticksRandomly() : bool{
505 return false;
506 }
507
512 public function onRandomTick() : void{
513
514 }
515
519 public function onScheduledUpdate() : void{
520
521 }
522
529 public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
530 return false;
531 }
532
538 public function onAttack(Item $item, Facing $face, ?Player $player = null) : bool{
539 return false;
540 }
541
548 public function getFrictionFactor() : float{
549 return 0.6;
550 }
551
557 public function getLightLevel() : int{
558 return 0;
559 }
560
567 public function getLightFilter() : int{
568 return $this->isTransparent() ? 0 : 15;
569 }
570
578 public function blocksDirectSkyLight() : bool{
579 return $this->getLightFilter() > 0;
580 }
581
585 public function isTransparent() : bool{
586 return false;
587 }
588
598 public function isSolid() : bool{
599 return true;
600 }
601
605 public function canBeFlowedInto() : bool{
606 return false;
607 }
608
612 public function canClimb() : bool{
613 return false;
614 }
615
616 final public function getPosition() : Position{
617 return $this->position;
618 }
619
623 final public function position(World $world, int $x, int $y, int $z) : void{
624 $this->position = new Position($x, $y, $z, $world);
625 $this->collisionBoxes = null;
626 }
627
633 public function getDrops(Item $item) : array{
634 if($this->getBreakInfo()->isToolCompatible($item)){
635 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
636 return $this->getSilkTouchDrops($item);
637 }
638
639 return $this->getDropsForCompatibleTool($item);
640 }
641
642 return $this->getDropsForIncompatibleTool($item);
643 }
644
650 public function getDropsForCompatibleTool(Item $item) : array{
651 return [$this->asItem()];
652 }
653
659 public function getDropsForIncompatibleTool(Item $item) : array{
660 return [];
661 }
662
668 public function getSilkTouchDrops(Item $item) : array{
669 return [$this->asItem()];
670 }
671
675 public function getXpDropForTool(Item $item) : int{
676 if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
677 return 0;
678 }
679
680 return $this->getXpDropAmount();
681 }
682
686 protected function getXpDropAmount() : int{
687 return 0;
688 }
689
693 public function isAffectedBySilkTouch() : bool{
694 return false;
695 }
696
701 public function getPickedItem(bool $addUserData = false) : Item{
702 $item = $this->asItem();
703 if($addUserData){
704 $tile = $this->position->getWorld()->getTile($this->position);
705 if($tile instanceof Tile){
706 $nbt = $tile->getCleanedNBT();
707 if($nbt instanceof CompoundTag){
708 $item->setCustomBlockData($nbt);
709 $item->setLore(["+(DATA)"]);
710 }
711 }
712 }
713 return $item;
714 }
715
719 public function getFuelTime() : int{
720 return 0;
721 }
722
726 public function getMaxStackSize() : int{
727 return 64;
728 }
729
730 public function isFireProofAsItem() : bool{
731 return false;
732 }
733
738 public function getFlameEncouragement() : int{
739 return 0;
740 }
741
745 public function getFlammability() : int{
746 return 0;
747 }
748
752 public function burnsForever() : bool{
753 return false;
754 }
755
759 public function isFlammable() : bool{
760 return $this->getFlammability() > 0;
761 }
762
766 public function onIncinerate() : void{
767
768 }
769
775 public function getSide(Facing $side, int $step = 1){
776 $position = $this->position;
777 if($position->isValid()){
778 [$dx, $dy, $dz] = Facing::OFFSET[$side->value] ?? [0, 0, 0];
779 return $position->getWorld()->getBlockAt(
780 $position->x + ($dx * $step),
781 $position->y + ($dy * $step),
782 $position->z + ($dz * $step)
783 );
784 }
785
786 throw new \LogicException("Block does not have a valid world");
787 }
788
795 public function getHorizontalSides() : \Generator{
796 $world = $this->position->getWorld();
797 foreach(Facing::HORIZONTAL as $facing){
798 [$dx, $dy, $dz] = Facing::OFFSET[$facing->value];
799 //TODO: yield Facing as the key?
800 yield $world->getBlockAt(
801 $this->position->x + $dx,
802 $this->position->y + $dy,
803 $this->position->z + $dz
804 );
805 }
806 }
807
814 public function getAllSides() : \Generator{
815 $world = $this->position->getWorld();
816 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
817 //TODO: yield Facing as the key?
818 yield $world->getBlockAt(
819 $this->position->x + $dx,
820 $this->position->y + $dy,
821 $this->position->z + $dz
822 );
823 }
824 }
825
832 public function getAffectedBlocks() : array{
833 return [$this];
834 }
835
839 public function __toString(){
840 return "Block[" . $this->getName() . "] (" . $this->getTypeId() . ":" . $this->encodeFullState() . ")";
841 }
842
846 public function collidesWithBB(AxisAlignedBB $bb) : bool{
847 foreach($this->getCollisionBoxes() as $bb2){
848 if($bb->intersectsWith($bb2)){
849 return true;
850 }
851 }
852
853 return false;
854 }
855
861 public function hasEntityCollision() : bool{
862 return false;
863 }
864
874 public function onEntityInside(Entity $entity) : bool{
875 return true;
876 }
877
887 public function addVelocityToEntity(Entity $entity) : ?Vector3{
888 return null;
889 }
890
895 public function onEntityLand(Entity $entity) : ?float{
896 return null;
897 }
898
902 public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
903 //NOOP
904 }
905
917 final public function getCollisionBoxes() : array{
918 if($this->collisionBoxes === null){
919 $collisionBoxes = $this->recalculateCollisionBoxes();
920 $extraOffset = $this->getModelPositionOffset();
921 $offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
922 $this->collisionBoxes = [];
923 foreach($collisionBoxes as $bb){
924 $this->collisionBoxes[] = $bb->offsetCopy($offset->x, $offset->y, $offset->z);
925 }
926 }
927
928 return $this->collisionBoxes;
929 }
930
935 public function getModelPositionOffset() : ?Vector3{
936 return null;
937 }
938
943 protected function recalculateCollisionBoxes() : array{
944 return [AxisAlignedBB::one()];
945 }
946
951 public function getSupportType(Facing $facing) : SupportType{
952 return SupportType::FULL;
953 }
954
955 protected function getAdjacentSupportType(Facing $facing) : SupportType{
956 return $this->getSide($facing)->getSupportType(Facing::opposite($facing));
957 }
958
959 public function isFullCube() : bool{
960 $bb = $this->getCollisionBoxes();
961
962 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
963 }
964
969 public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
970 $bbs = $this->getCollisionBoxes();
971 if(count($bbs) === 0){
972 return null;
973 }
974
976 $currentHit = null;
978 $currentDistance = PHP_INT_MAX;
979
980 foreach($bbs as $bb){
981 $nextHit = $bb->calculateIntercept($pos1, $pos2);
982 if($nextHit === null){
983 continue;
984 }
985
986 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
987 if($nextDistance < $currentDistance){
988 $currentHit = $nextHit;
989 $currentDistance = $nextDistance;
990 }
991 }
992
993 return $currentHit;
994 }
995}
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
Definition Block.php:902
isSameState(Block $other)
Definition Block.php:198
canBePlacedAt(Block $blockReplace, Vector3 $clickVector, Facing $face, bool $isClickedBlock)
Definition Block.php:429
__construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo)
Definition Block.php:111
hasTypeTag(string $tag)
Definition Block.php:217
describeBlockOnlyState(RuntimeDataDescriber $w)
Definition Block.php:316
onEntityInside(Entity $entity)
Definition Block.php:874
getSilkTouchDrops(Item $item)
Definition Block.php:668
onEntityLand(Entity $entity)
Definition Block.php:895
getDropsForCompatibleTool(Item $item)
Definition Block.php:650
getXpDropForTool(Item $item)
Definition Block.php:675
onAttack(Item $item, Facing $face, ?Player $player=null)
Definition Block.php:538
collidesWithBB(AxisAlignedBB $bb)
Definition Block.php:846
getPickedItem(bool $addUserData=false)
Definition Block.php:701
getDrops(Item $item)
Definition Block.php:633
describeBlockItemState(RuntimeDataDescriber $w)
Definition Block.php:304
onBreak(Item $item, ?Player $player=null, array &$returnedItems=[])
Definition Block.php:485
hasSameTypeId(Block $other)
Definition Block.php:189
addVelocityToEntity(Entity $entity)
Definition Block.php:887
calculateIntercept(Vector3 $pos1, Vector3 $pos2)
Definition Block.php:969
getSupportType(Facing $facing)
Definition Block.php:951
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player=null)
Definition Block.php:447
getDropsForIncompatibleTool(Item $item)
Definition Block.php:659
intersectsWith(AxisAlignedBB $bb, float $epsilon=0.00001)