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;
82 protected ?array $collisionBoxes =
null;
84 private int $requiredBlockItemStateDataBits;
85 private int $requiredBlockOnlyStateDataBits;
87 private Block $defaultState;
89 private int $stateIdXorMask;
100 private static function computeStateIdXorMask(
int $typeId) :
int{
102 $typeId << self::INTERNAL_STATE_DATA_BITS |
110 $this->idInfo = $idInfo;
111 $this->fallbackName = $name;
112 $this->typeInfo = $typeInfo;
113 $this->position =
new Position(0, 0, 0,
null);
117 $this->requiredBlockItemStateDataBits = $calculator->getBitsUsed();
121 $this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
123 $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
126 $defaultState = clone $this;
127 $this->defaultState = $defaultState;
128 $defaultState->defaultState = $defaultState;
131 public function __clone(){
132 $this->position = clone $this->position;
140 return $this->idInfo;
147 return $this->fallbackName;
160 return $this->idInfo->getBlockTypeId();
180 public function getStateId() : int{
181 return $this->encodeFullState() ^ $this->stateIdXorMask;
188 return $this->getTypeId() === $other->getTypeId();
197 return $this->getStateId() === $other->getStateId();
204 return $this->typeInfo->getTypeTags();
216 return $this->typeInfo->hasTypeTag($tag);
226 $normalized = clone $this->defaultState;
227 $normalized->decodeBlockItemState($this->encodeBlockItemState());
231 private function decodeBlockItemState(
int $data) : void{
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");
241 private function decodeBlockOnlyState(
int $data) : void{
242 $reader = new RuntimeDataReader($this->requiredBlockOnlyStateDataBits, $data);
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");
251 private function encodeBlockItemState() : int{
252 $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
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");
260 return $writer->getValue();
263 private function encodeBlockOnlyState() : int{
264 $writer = new RuntimeDataWriter($this->requiredBlockOnlyStateDataBits);
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");
272 return $writer->getValue();
275 private function encodeFullState() : int{
276 $blockItemBits = $this->requiredBlockItemStateDataBits;
277 $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
279 if($blockOnlyBits === 0 && $blockItemBits === 0){
284 if($blockItemBits > 0){
285 $result |= $this->encodeBlockItemState();
287 if($blockOnlyBits > 0){
288 $result |= $this->encodeBlockOnlyState() << $blockItemBits;
325 public function generateStatePermutations() : \
Generator{
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");
332 for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
333 $withType = clone $this;
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)");
340 }
catch(InvalidSerializedRuntimeDataException){
344 for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
345 $withState = clone $withType;
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)");
352 }
catch(InvalidSerializedRuntimeDataException){
382 $world = $this->position->getWorld();
383 $chunk = $world->getOrLoadChunkAtPosition($this->position);
385 throw new AssumptionFailedError(
"World::setBlock() should have loaded the chunk before calling this method");
387 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
389 $tileType = $this->idInfo->getTileClass();
390 $oldTile = $world->getTile($this->position);
391 if($oldTile !==
null){
392 if($tileType === null || !($oldTile instanceof $tileType)){
396 $oldTile->clearSpawnCompoundCache();
399 if($oldTile ===
null && $tileType !==
null){
404 $tile =
new $tileType($world, $this->position->asVector3());
405 $world->addTile($tile);
428 return $blockReplace->canBeReplaced();
446 $tx->addBlock($blockReplace->position, $this);
462 return $this->typeInfo->getBreakInfo();
475 return $this->typeInfo->getEnchantmentTags();
484 $world = $this->position->getWorld();
485 if(($t = $world->getTile($this->position)) !==
null){
486 $t->onBlockDestroyed();
488 $world->setBlock($this->position, VanillaBlocks::AIR());
502 public function ticksRandomly() : bool{
517 public function onScheduledUpdate() : void{
527 public function onInteract(
Item $item, int $face,
Vector3 $clickVector, ?
Player $player = null, array &$returnedItems = []) : bool{
566 return $this->isTransparent() ? 0 : 15;
577 return $this->getLightFilter() > 0;
614 final public function getPosition() :
Position{
615 return $this->position;
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;
632 if($this->getBreakInfo()->isToolCompatible($item)){
633 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
634 return $this->getSilkTouchDrops($item);
637 return $this->getDropsForCompatibleTool($item);
640 return $this->getDropsForIncompatibleTool($item);
649 return [$this->asItem()];
667 return [$this->asItem()];
674 if($item->hasEnchantment(
VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
678 return $this->getXpDropAmount();
700 $item = $this->asItem();
702 $tile = $this->position->getWorld()->getTile($this->position);
703 if($tile instanceof
Tile){
704 $nbt = $tile->getCleanedNBT();
706 $item->setCustomBlockData($nbt);
707 $item->setLore([
"+(DATA)"]);
728 public function isFireProofAsItem() : bool{
758 return $this->getFlammability() > 0;
773 public function getSide(int $side, int $step = 1){
774 $position = $this->position;
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)
784 throw new \LogicException(
"Block does not have a valid world");
794 $world = $this->position->getWorld();
795 foreach(Facing::HORIZONTAL as $facing){
796 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
798 yield $world->getBlockAt(
799 $this->position->x + $dx,
800 $this->position->y + $dy,
801 $this->position->z + $dz
813 $world = $this->position->getWorld();
814 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
816 yield $world->getBlockAt(
817 $this->position->x + $dx,
818 $this->position->y + $dy,
819 $this->position->z + $dz
838 return "Block[" . $this->getName() .
"] (" . $this->getTypeId() .
":" . $this->encodeFullState() .
")";
845 foreach($this->getCollisionBoxes() as $bb2){
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);
925 return $this->collisionBoxes;
949 return SupportType::FULL;
952 protected function getAdjacentSupportType(
int $facing) : SupportType{
953 return $this->getSide($facing)->getSupportType(
Facing::opposite($facing));
956 public function isFullCube() : bool{
957 $bb = $this->getCollisionBoxes();
959 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
967 $bbs = $this->getCollisionBoxes();
968 if(count($bbs) === 0){
975 $currentDistance = PHP_INT_MAX;
977 foreach($bbs as $bb){
978 $nextHit = $bb->calculateIntercept($pos1, $pos2);
979 if($nextHit ===
null){
983 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
984 if($nextDistance < $currentDistance){
985 $currentHit = $nextHit;
986 $currentDistance = $nextDistance;
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player=null)