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){
379 $world = $this->position->getWorld();
380 $chunk = $world->getOrLoadChunkAtPosition($this->position);
382 throw new AssumptionFailedError(
"World::setBlock() should have loaded the chunk before calling this method");
384 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
386 $tileType = $this->idInfo->getTileClass();
387 $oldTile = $world->getTile($this->position);
388 if($oldTile !==
null){
389 if($tileType === null || !($oldTile instanceof $tileType)){
393 $oldTile->clearSpawnCompoundCache();
396 if($oldTile ===
null && $tileType !==
null){
401 $tile =
new $tileType($world, $this->position->asVector3());
402 $world->addTile($tile);
425 return $blockReplace->canBeReplaced();
443 $tx->addBlock($blockReplace->position, $this);
459 return $this->typeInfo->getBreakInfo();
472 return $this->typeInfo->getEnchantmentTags();
481 $world = $this->position->getWorld();
482 if(($t = $world->getTile($this->position)) !==
null){
483 $t->onBlockDestroyed();
485 $world->setBlock($this->position, VanillaBlocks::AIR());
499 public function ticksRandomly() : bool{
514 public function onScheduledUpdate() : void{
524 public function onInteract(
Item $item, int $face,
Vector3 $clickVector, ?
Player $player = null, array &$returnedItems = []) : bool{
563 return $this->isTransparent() ? 0 : 15;
574 return $this->getLightFilter() > 0;
611 final public function getPosition() :
Position{
612 return $this->position;
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;
629 if($this->getBreakInfo()->isToolCompatible($item)){
630 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
631 return $this->getSilkTouchDrops($item);
634 return $this->getDropsForCompatibleTool($item);
637 return $this->getDropsForIncompatibleTool($item);
646 return [$this->asItem()];
664 return [$this->asItem()];
671 if($item->hasEnchantment(
VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
675 return $this->getXpDropAmount();
697 $item = $this->asItem();
699 $tile = $this->position->getWorld()->getTile($this->position);
700 if($tile instanceof
Tile){
701 $nbt = $tile->getCleanedNBT();
703 $item->setCustomBlockData($nbt);
704 $item->setLore([
"+(DATA)"]);
725 public function isFireProofAsItem() : bool{
755 return $this->getFlammability() > 0;
770 public function getSide(int $side, int $step = 1){
771 $position = $this->position;
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)
781 throw new \LogicException(
"Block does not have a valid world");
791 $world = $this->position->getWorld();
792 foreach(Facing::HORIZONTAL as $facing){
793 [$dx, $dy, $dz] = Facing::OFFSET[$facing];
795 yield $world->getBlockAt(
796 $this->position->x + $dx,
797 $this->position->y + $dy,
798 $this->position->z + $dz
810 $world = $this->position->getWorld();
811 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
813 yield $world->getBlockAt(
814 $this->position->x + $dx,
815 $this->position->y + $dy,
816 $this->position->z + $dz
835 return "Block[" . $this->getName() .
"] (" . $this->getTypeId() .
":" . $this->encodeFullState() .
")";
842 foreach($this->getCollisionBoxes() as $bb2){
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);
921 return $this->collisionBoxes;
944 return SupportType::FULL;
947 protected function getAdjacentSupportType(
int $facing) : SupportType{
948 return $this->getSide($facing)->getSupportType(
Facing::opposite($facing));
951 public function isFullCube() : bool{
952 $bb = $this->getCollisionBoxes();
954 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
962 $bbs = $this->getCollisionBoxes();
963 if(count($bbs) === 0){
970 $currentDistance = PHP_INT_MAX;
972 foreach($bbs as $bb){
973 $nextHit = $bb->calculateIntercept($pos1, $pos2);
974 if($nextHit ===
null){
978 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
979 if($nextDistance < $currentDistance){
980 $currentHit = $nextHit;
981 $currentDistance = $nextDistance;
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player=null)