22declare(strict_types=1);
31use pocketmine\block\utils\SupportType;
58use
function get_class;
63 public const INTERNAL_STATE_DATA_BITS = 11;
64 public const INTERNAL_STATE_DATA_MASK = ~(~0 << self::INTERNAL_STATE_DATA_BITS);
71 public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (-7482769108513497636 & self::INTERNAL_STATE_DATA_MASK);
74 protected string $fallbackName;
79 protected ?array $collisionBoxes =
null;
81 private int $requiredBlockItemStateDataBits;
82 private int $requiredBlockOnlyStateDataBits;
84 private Block $defaultState;
86 private int $stateIdXorMask;
97 private static function computeStateIdXorMask(
int $typeId) :
int{
99 $typeId << self::INTERNAL_STATE_DATA_BITS |
107 $this->idInfo = $idInfo;
108 $this->fallbackName = $name;
109 $this->typeInfo = $typeInfo;
110 $this->position =
new Position(0, 0, 0,
null);
114 $this->requiredBlockItemStateDataBits = $calculator->getBitsUsed();
118 $this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
120 $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
123 $defaultState = clone $this;
124 $this->defaultState = $defaultState;
125 $defaultState->defaultState = $defaultState;
128 public function __clone(){
129 $this->position = clone $this->position;
137 return $this->idInfo;
144 return $this->fallbackName;
157 return $this->idInfo->getBlockTypeId();
177 public function getStateId() : int{
178 return $this->encodeFullState() ^ $this->stateIdXorMask;
185 return $this->getTypeId() === $other->getTypeId();
194 return $this->getStateId() === $other->getStateId();
201 return $this->typeInfo->getTypeTags();
213 return $this->typeInfo->hasTypeTag($tag);
223 $normalized = clone $this->defaultState;
224 $normalized->decodeBlockItemState($this->encodeBlockItemState());
228 private function decodeBlockItemState(
int $data) : void{
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");
238 private function decodeBlockOnlyState(
int $data) : void{
239 $reader = new RuntimeDataReader($this->requiredBlockOnlyStateDataBits, $data);
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");
248 private function encodeBlockItemState() : int{
249 $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
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");
257 return $writer->getValue();
260 private function encodeBlockOnlyState() : int{
261 $writer = new RuntimeDataWriter($this->requiredBlockOnlyStateDataBits);
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");
269 return $writer->getValue();
272 private function encodeFullState() : int{
273 $blockItemBits = $this->requiredBlockItemStateDataBits;
274 $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
276 if($blockOnlyBits === 0 && $blockItemBits === 0){
281 if($blockItemBits > 0){
282 $result |= $this->encodeBlockItemState();
284 if($blockOnlyBits > 0){
285 $result |= $this->encodeBlockOnlyState() << $blockItemBits;
322 public function generateStatePermutations() : \
Generator{
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");
329 for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
330 $withType = clone $this;
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)");
337 }
catch(InvalidSerializedRuntimeDataException){
341 for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
342 $withState = clone $withType;
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)");
349 }
catch(InvalidSerializedRuntimeDataException){
377 $world = $this->position->getWorld();
378 $chunk = $world->getOrLoadChunkAtPosition($this->position);
380 throw new AssumptionFailedError(
"World::setBlock() should have loaded the chunk before calling this method");
382 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
384 $tileType = $this->idInfo->getTileClass();
385 $oldTile = $world->getTile($this->position);
386 if($oldTile !==
null){
387 if($tileType ===
null || !($oldTile instanceof $tileType)){
390 }elseif($oldTile instanceof Spawnable){
391 $oldTile->clearSpawnCompoundCache();
394 if($oldTile ===
null && $tileType !==
null){
399 $tile =
new $tileType($world, $this->position->asVector3());
400 $world->addTile($tile);
423 return $blockReplace->canBeReplaced();
441 $tx->addBlock($blockReplace->position, $this);
457 return $this->typeInfo->getBreakInfo();
470 return $this->typeInfo->getEnchantmentTags();
479 $world = $this->position->getWorld();
480 if(($t = $world->getTile($this->position)) !==
null){
481 $t->onBlockDestroyed();
483 $world->setBlock($this->position, VanillaBlocks::AIR());
497 public function ticksRandomly() : bool{
512 public function onScheduledUpdate() : void{
522 public function onInteract(
Item $item, int $face,
Vector3 $clickVector, ?
Player $player = null, array &$returnedItems = []) : bool{
561 return $this->isTransparent() ? 0 : 15;
572 return $this->getLightFilter() > 0;
609 final public function getPosition() :
Position{
610 return $this->position;
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;
627 if($this->getBreakInfo()->isToolCompatible($item)){
628 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
629 return $this->getSilkTouchDrops($item);
632 return $this->getDropsForCompatibleTool($item);
635 return $this->getDropsForIncompatibleTool($item);
644 return [$this->asItem()];
662 return [$this->asItem()];
669 if($item->hasEnchantment(
VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
673 return $this->getXpDropAmount();
695 $item = $this->asItem();
697 $tile = $this->position->getWorld()->getTile($this->position);
698 if($tile instanceof
Tile){
699 $nbt = $tile->getCleanedNBT();
723 public function isFireProofAsItem() : bool{
753 return $this->getFlammability() > 0;
768 public function getSide(int $side, int $step = 1){
769 $position = $this->position;
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)
779 throw new \LogicException(
"Block does not have a valid world");
789 $world = $this->position->getWorld();
790 foreach(Facing::HORIZONTAL as $facing){
791 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
793 yield $world->getBlockAt(
794 $this->position->x + $dx,
795 $this->position->y + $dy,
796 $this->position->z + $dz
808 $world = $this->position->getWorld();
809 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
811 yield $world->getBlockAt(
812 $this->position->x + $dx,
813 $this->position->y + $dy,
814 $this->position->z + $dz
833 return "Block[" . $this->getName() .
"] (" . $this->getTypeId() .
":" . $this->encodeFullState() .
")";
840 foreach($this->getCollisionBoxes() as $bb2){
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);
919 return $this->collisionBoxes;
942 return SupportType::FULL;
945 protected function getAdjacentSupportType(
int $facing) : SupportType{
946 return $this->getSide($facing)->getSupportType(
Facing::opposite($facing));
949 public function isFullCube() : bool{
950 $bb = $this->getCollisionBoxes();
952 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
960 $bbs = $this->getCollisionBoxes();
961 if(count($bbs) === 0){
968 $currentDistance = PHP_INT_MAX;
970 foreach($bbs as $bb){
971 $nextHit = $bb->calculateIntercept($pos1, $pos2);
972 if($nextHit ===
null){
976 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
977 if($nextDistance < $currentDistance){
978 $currentHit = $nextHit;
979 $currentDistance = $nextDistance;
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
isSameState(Block $other)
getSupportType(int $facing)
__construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo)
describeBlockOnlyState(RuntimeDataDescriber $w)
onEntityInside(Entity $entity)
getSilkTouchDrops(Item $item)
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player=null)
onEntityLand(Entity $entity)
getDropsForCompatibleTool(Item $item)
onAttack(Item $item, int $face, ?Player $player=null)
getXpDropForTool(Item $item)
recalculateCollisionBoxes()
collidesWithBB(AxisAlignedBB $bb)
getPickedItem(bool $addUserData=false)
canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock)
describeBlockItemState(RuntimeDataDescriber $w)
onBreak(Item $item, ?Player $player=null, array &$returnedItems=[])
hasSameTypeId(Block $other)
addVelocityToEntity(Entity $entity)
calculateIntercept(Vector3 $pos1, Vector3 $pos2)
getDropsForIncompatibleTool(Item $item)
setCustomBlockData(CompoundTag $compound)
intersectsWith(AxisAlignedBB $bb, float $epsilon=0.00001)
static readLong(string $str)
static writeLLong(int $value)