thick = ($stateMeta & BlockLegacyMetadata::BAMBOO_FLAG_THICK) !== 0; $this->leafSize = BlockDataSerializer::readBoundedInt("leafSize", ($stateMeta >> BlockLegacyMetadata::BAMBOO_LEAF_SIZE_SHIFT) & BlockLegacyMetadata::BAMBOO_LEAF_SIZE_MASK, self::NO_LEAVES, self::LARGE_LEAVES); $this->ready = ($stateMeta & BlockLegacyMetadata::BAMBOO_FLAG_READY) !== 0; } public function writeStateToMeta() : int{ return ($this->thick ? BlockLegacyMetadata::BAMBOO_FLAG_THICK : 0) | ($this->leafSize << BlockLegacyMetadata::BAMBOO_LEAF_SIZE_SHIFT) | ($this->ready ? BlockLegacyMetadata::BAMBOO_FLAG_READY : 0); } public function getStateBitmask() : int{ return 0b1111; } public function isThick() : bool{ return $this->thick; } /** @return $this */ public function setThick(bool $thick) : self{ $this->thick = $thick; return $this; } public function isReady() : bool{ return $this->ready; } /** @return $this */ public function setReady(bool $ready) : self{ $this->ready = $ready; return $this; } public function getLeafSize() : int{ return $this->leafSize; } /** @return $this */ public function setLeafSize(int $leafSize) : self{ $this->leafSize = $leafSize; return $this; } /** * @return AxisAlignedBB[] */ protected function recalculateCollisionBoxes() : array{ //this places the BB at the northwest corner, not the center $inset = 1 - (($this->thick ? 3 : 2) / 16); return [AxisAlignedBB::one()->trim(Facing::SOUTH, $inset)->trim(Facing::EAST, $inset)]; } private static function getOffsetSeed(int $x, int $y, int $z) : int{ $p1 = gmp_mul($z, 0x6ebfff5); $p2 = gmp_mul($x, 0x2fc20f); $p3 = $y; $xord = gmp_xor(gmp_xor($p1, $p2), $p3); $fullResult = gmp_mul(gmp_add(gmp_mul($xord, 0x285b825), 0xb), $xord); return gmp_intval(gmp_and($fullResult, 0xffffffff)); } private static function getMaxHeight(int $x, int $z) : int{ return 12 + (self::getOffsetSeed($x, 0, $z) % 5); } public function getModelPositionOffset() : ?Vector3{ $seed = self::getOffsetSeed($this->position->getFloorX(), 0, $this->position->getFloorZ()); $retX = (($seed % 12) + 1) / 16; $retZ = ((($seed >> 8) % 12) + 1) / 16; return new Vector3($retX, 0, $retZ); } private function canBeSupportedBy(Block $block) : bool{ //TODO: tags would be better for this return $block instanceof Dirt || $block instanceof Grass || $block instanceof Gravel || $block instanceof Sand || $block instanceof Mycelium || $block instanceof Podzol; } private function seekToTop() : Bamboo{ $world = $this->position->getWorld(); $top = $this; while(($next = $world->getBlock($top->position->up())) instanceof Bamboo && $next->isSameType($this)){ $top = $next; } return $top; } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ if($item instanceof Fertilizer){ $top = $this->seekToTop(); if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2), $player)){ $item->pop(); return true; } }elseif($item instanceof ItemBamboo){ if($this->seekToTop()->grow(PHP_INT_MAX, 1, $player)){ $item->pop(); return true; } } return false; } public function onNearbyBlockChange() : void{ $below = $this->position->getWorld()->getBlock($this->position->down()); if(!$this->canBeSupportedBy($below) and !$below->isSameType($this)){ $this->position->getWorld()->useBreakOn($this->position); } } private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{ $world = $this->position->getWorld(); if(!$world->getBlock($this->position->up())->canBeReplaced()){ return false; } $height = 1; while($world->getBlock($this->position->subtract(0, $height, 0))->isSameType($this)){ if(++$height >= $maxHeight){ return false; } } $newHeight = $height + $growAmount; $stemBlock = (clone $this)->setReady(false)->setLeafSize(self::NO_LEAVES); if($newHeight >= 4 && !$stemBlock->isThick()){ //don't change it to false if height is less, because it might have been chopped $stemBlock = $stemBlock->setThick(true); } $smallLeavesBlock = (clone $stemBlock)->setLeafSize(self::SMALL_LEAVES); $bigLeavesBlock = (clone $stemBlock)->setLeafSize(self::LARGE_LEAVES); $newBlocks = []; if($newHeight === 2){ $newBlocks[] = $smallLeavesBlock; }elseif($newHeight === 3){ $newBlocks[] = $smallLeavesBlock; $newBlocks[] = $smallLeavesBlock; }elseif($newHeight === 4){ $newBlocks[] = $bigLeavesBlock; $newBlocks[] = $smallLeavesBlock; $newBlocks[] = $stemBlock; $newBlocks[] = $stemBlock; }elseif($newHeight > 4){ $newBlocks[] = $bigLeavesBlock; $newBlocks[] = $bigLeavesBlock; $newBlocks[] = $smallLeavesBlock; for($i = 0, $max = min($growAmount, $newHeight - count($newBlocks)); $i < $max; ++$i){ $newBlocks[] = $stemBlock; //to replace the bottom blocks that currently have leaves } } $tx = new BlockTransaction($this->position->getWorld()); foreach($newBlocks as $idx => $newBlock){ $tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock); } $ev = new StructureGrowEvent($this, $tx, $player); $ev->call(); if($ev->isCancelled()){ return false; } return $tx->apply(); } public function ticksRandomly() : bool{ return true; } public function onRandomTick() : void{ $world = $this->position->getWorld(); if($this->ready){ $this->ready = false; if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1, null)){ $world->setBlock($this->position, $this); } }elseif($world->getBlock($this->position->up())->canBeReplaced()){ $this->ready = true; $world->setBlock($this->position, $this); } } }