64 public const INTERNAL_STATE_DATA_BITS = 11;
65 public const INTERNAL_STATE_DATA_MASK = ~(~0 << self::INTERNAL_STATE_DATA_BITS);
72 public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (-7482769108513497636 & self::INTERNAL_STATE_DATA_MASK);
75 protected string $fallbackName;
83 protected ?array $collisionBoxes =
null;
85 private int $requiredBlockItemStateDataBits;
86 private int $requiredBlockOnlyStateDataBits;
88 private Block $defaultState;
90 private int $stateIdXorMask;
101 private static function computeStateIdXorMask(
int $typeId) :
int{
104 $typeId << self::INTERNAL_STATE_DATA_BITS |
105 (BE::unpackSignedLong(hash(
'xxh3', LE::packSignedLong($typeId), binary:
true)) & self::INTERNAL_STATE_DATA_MASK);
112 $this->idInfo = $idInfo;
113 $this->fallbackName = $name;
114 $this->typeInfo = $typeInfo;
115 $this->position =
new Position(0, 0, 0,
null);
119 $this->requiredBlockItemStateDataBits = $calculator->getBitsUsed();
123 $this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
125 $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
128 $defaultState = clone $this;
129 $this->defaultState = $defaultState;
130 $defaultState->defaultState = $defaultState;
133 public function __clone(){
134 $this->position = clone $this->position;
142 return $this->idInfo;
149 return $this->fallbackName;
162 return $this->idInfo->getBlockTypeId();
182 public function getStateId() : int{
183 return $this->encodeFullState() ^ $this->stateIdXorMask;
190 return $this->getTypeId() === $other->getTypeId();
199 return $this->getStateId() === $other->getStateId();
206 return $this->typeInfo->getTypeTags();
218 return $this->typeInfo->hasTypeTag($tag);
228 $normalized = clone $this->defaultState;
229 $normalized->decodeBlockItemState($this->encodeBlockItemState());
233 private function decodeBlockItemState(
int $data) : void{
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");
243 private function decodeBlockOnlyState(
int $data) : void{
244 $reader = new RuntimeDataReader($this->requiredBlockOnlyStateDataBits, $data);
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");
253 private function encodeBlockItemState() : int{
254 $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
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");
262 return $writer->getValue();
265 private function encodeBlockOnlyState() : int{
266 $writer = new RuntimeDataWriter($this->requiredBlockOnlyStateDataBits);
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");
274 return $writer->getValue();
277 private function encodeFullState() : int{
278 $blockItemBits = $this->requiredBlockItemStateDataBits;
279 $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
281 if($blockOnlyBits === 0 && $blockItemBits === 0){
286 if($blockItemBits > 0){
287 $result |= $this->encodeBlockItemState();
289 if($blockOnlyBits > 0){
290 $result |= $this->encodeBlockOnlyState() << $blockItemBits;
327 public function generateStatePermutations() : \
Generator{
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");
334 for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
335 $withType = clone $this;
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)");
342 }
catch(InvalidSerializedRuntimeDataException){
346 for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
347 $withState = clone $withType;
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)");
354 }
catch(InvalidSerializedRuntimeDataException){
384 $world = $this->position->getWorld();
385 $chunk = $world->getOrLoadChunkAtPosition($this->position);
387 throw new AssumptionFailedError(
"World::setBlock() should have loaded the chunk before calling this method");
389 $chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
391 $tileType = $this->idInfo->getTileClass();
392 $oldTile = $world->getTile($this->position);
393 if($oldTile !==
null){
394 if($tileType === null || !($oldTile instanceof $tileType)){
398 $oldTile->clearSpawnCompoundCache();
401 if($oldTile ===
null && $tileType !==
null){
406 $tile =
new $tileType($world, $this->position->asVector3());
407 $world->addTile($tile);
430 return $blockReplace->canBeReplaced();
448 $tx->addBlock($blockReplace->position, $this);
464 return $this->typeInfo->getBreakInfo();
477 return $this->typeInfo->getEnchantmentTags();
486 $world = $this->position->getWorld();
487 if(($t = $world->getTile($this->position)) !==
null){
488 $t->onBlockDestroyed();
490 $world->setBlock($this->position, VanillaBlocks::AIR());
504 public function ticksRandomly() : bool{
519 public function onScheduledUpdate() : void{
529 public function onInteract(
Item $item,
Facing $face,
Vector3 $clickVector, ?
Player $player = null, array &$returnedItems = []) : bool{
568 return $this->isTransparent() ? 0 : 15;
579 return $this->getLightFilter() > 0;
616 final public function getPosition() :
Position{
617 return $this->position;
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;
634 if($this->getBreakInfo()->isToolCompatible($item)){
635 if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
636 return $this->getSilkTouchDrops($item);
639 return $this->getDropsForCompatibleTool($item);
642 return $this->getDropsForIncompatibleTool($item);
651 return [$this->asItem()];
669 return [$this->asItem()];
676 if($item->hasEnchantment(
VanillaEnchantments::SILK_TOUCH()) || !$this->getBreakInfo()->isToolCompatible($item)){
680 return $this->getXpDropAmount();
702 $item = $this->asItem();
704 $tile = $this->position->getWorld()->getTile($this->position);
705 if($tile instanceof
Tile){
706 $nbt = $tile->getCleanedNBT();
708 $item->setCustomBlockData($nbt);
709 $item->setLore([
"+(DATA)"]);
730 public function isFireProofAsItem() : bool{
760 return $this->getFlammability() > 0;
775 public function getSide(
Facing $side, int $step = 1){
776 $position = $this->position;
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)
786 throw new \LogicException(
"Block does not have a valid world");
796 $world = $this->position->getWorld();
797 foreach(Facing::HORIZONTAL as $facing){
798 [$dx, $dy, $dz] = Facing::OFFSET[$facing->value];
800 yield $world->getBlockAt(
801 $this->position->x + $dx,
802 $this->position->y + $dy,
803 $this->position->z + $dz
815 $world = $this->position->getWorld();
816 foreach(Facing::OFFSET as [$dx, $dy, $dz]){
818 yield $world->getBlockAt(
819 $this->position->x + $dx,
820 $this->position->y + $dy,
821 $this->position->z + $dz
840 return "Block[" . $this->getName() .
"] (" . $this->getTypeId() .
":" . $this->encodeFullState() .
")";
847 foreach($this->getCollisionBoxes() as $bb2){
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);
928 return $this->collisionBoxes;
952 return SupportType::FULL;
955 protected function getAdjacentSupportType(
Facing $facing) : SupportType{
956 return $this->getSide($facing)->getSupportType(
Facing::opposite($facing));
959 public function isFullCube() : bool{
960 $bb = $this->getCollisionBoxes();
962 return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
970 $bbs = $this->getCollisionBoxes();
971 if(count($bbs) === 0){
978 $currentDistance = PHP_INT_MAX;
980 foreach($bbs as $bb){
981 $nextHit = $bb->calculateIntercept($pos1, $pos2);
982 if($nextHit ===
null){
986 $nextDistance = $nextHit->hitVector->distanceSquared($pos1);
987 if($nextDistance < $currentDistance){
988 $currentHit = $nextHit;
989 $currentDistance = $nextDistance;
place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player=null)