diff --git a/src/pocketmine/block/Door.php b/src/pocketmine/block/Door.php index f927860b1..d2602c952 100644 --- a/src/pocketmine/block/Door.php +++ b/src/pocketmine/block/Door.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\BlockDataValidator; use pocketmine\item\Item; +use pocketmine\level\BlockWriteBatch; use pocketmine\level\sound\DoorSound; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Bearing; @@ -124,9 +125,10 @@ abstract class Door extends Transparent{ $topHalf = clone $this; $topHalf->top = true; - parent::place($item, $blockReplace, $blockClicked, $face, $clickVector, $player); - $this->level->setBlock($blockUp, $topHalf); //Top - return true; + $write = new BlockWriteBatch(); + $write->addBlock($blockReplace, $this)->addBlock($blockUp, $topHalf); + + return $write->apply($this->level); } return false; diff --git a/src/pocketmine/block/DoublePlant.php b/src/pocketmine/block/DoublePlant.php index 594e901ea..c730bf696 100644 --- a/src/pocketmine/block/DoublePlant.php +++ b/src/pocketmine/block/DoublePlant.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\item\Item; +use pocketmine\level\BlockWriteBatch; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\Player; @@ -49,12 +50,12 @@ class DoublePlant extends Flowable{ public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{ $id = $blockReplace->getSide(Facing::DOWN)->getId(); if(($id === Block::GRASS or $id === Block::DIRT) and $blockReplace->getSide(Facing::UP)->canBeReplaced()){ - $this->getLevel()->setBlock($blockReplace, $this, false); $top = clone $this; $top->top = true; - $this->getLevel()->setBlock($blockReplace->getSide(Facing::UP), $top, false); - return true; + $write = new BlockWriteBatch(); + $write->addBlock($blockReplace, $this)->addBlock($blockReplace->getSide(Facing::UP), $top); + return $write->apply($this->level); } return false; diff --git a/src/pocketmine/level/BlockWriteBatch.php b/src/pocketmine/level/BlockWriteBatch.php new file mode 100644 index 000000000..8638930fd --- /dev/null +++ b/src/pocketmine/level/BlockWriteBatch.php @@ -0,0 +1,133 @@ +addValidator(function(ChunkManager $world, int $x, int $y, int $z) : bool{ + return $world->isInWorld($x, $y, $z); + }); + } + + /** + * Adds a block to the batch at the given position. + * + * @param Vector3 $pos + * @param Block $state + * + * @return $this + */ + public function addBlock(Vector3 $pos, Block $state) : self{ + return $this->addBlockAt($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ(), $state); + } + + /** + * Adds a block to the batch at the given coordinates. + * + * @param int $x + * @param int $y + * @param int $z + * @param Block $state + * + * @return $this + */ + public function addBlockAt(int $x, int $y, int $z, Block $state) : self{ + $this->blocks[$x][$y][$z] = $state; + return $this; + } + + /** + * Validates and attempts to apply the batch to the given world. If any part of the batch fails to validate, no + * changes will be made to the world. + * + * @param ChunkManager $world + * + * @return bool if the application was successful + */ + public function apply(ChunkManager $world) : bool{ + foreach($this->getBlocks() as [$x, $y, $z, $_]){ + foreach($this->validators as $validator){ + if(!$validator($world, $x, $y, $z)){ + return false; + } + } + } + foreach($this->getBlocks() as [$x, $y, $z, $block]){ + $world->setBlockAt($x, $y, $z, $block); + } + return true; + } + + /** + * @return \Generator|mixed[] [int $x, int $y, int $z, Block $block] + */ + public function getBlocks() : \Generator{ + foreach($this->blocks as $x => $yLine){ + foreach($yLine as $y => $zLine){ + foreach($zLine as $z => $block){ + yield [$x, $y, $z, $block]; + } + } + } + } + + /** + * Add a validation predicate which will be used to validate every block. + * The callable signature should be the same as the below dummy function. + * @see BlockWriteBatch::dummyValidator() + * + * @param callable $validator + */ + public function addValidator(callable $validator) : void{ + Utils::validateCallableSignature([$this, 'dummyValidator'], $validator); + $this->validators[] = $validator; + } + + /** + * Dummy function demonstrating the required closure signature for validators. + * @see BlockWriteBatch::addValidator() + * + * @dummy + * + * @param ChunkManager $world + * @param int $x + * @param int $y + * @param int $z + * + * @return bool + */ + public function dummyValidator(ChunkManager $world, int $x, int $y, int $z) : bool{ + return true; + } +} diff --git a/src/pocketmine/level/generator/object/SpruceTree.php b/src/pocketmine/level/generator/object/SpruceTree.php index 7333a5923..e75778488 100644 --- a/src/pocketmine/level/generator/object/SpruceTree.php +++ b/src/pocketmine/level/generator/object/SpruceTree.php @@ -26,6 +26,7 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Block; use pocketmine\block\BlockFactory; use pocketmine\block\Wood; +use pocketmine\level\BlockWriteBatch; use pocketmine\level\ChunkManager; use pocketmine\utils\Random; @@ -35,14 +36,18 @@ class SpruceTree extends Tree{ parent::__construct(BlockFactory::get(Block::LOG, Wood::SPRUCE), BlockFactory::get(Block::LEAVES, Wood::SPRUCE), 10); } + protected function generateChunkHeight(Random $random) : int{ + return $this->treeHeight - $random->nextBoundedInt(3); + } + public function placeObject(ChunkManager $level, int $x, int $y, int $z, Random $random) : void{ $this->treeHeight = $random->nextBoundedInt(4) + 6; + parent::placeObject($level, $x, $y, $z, $random); + } + protected function placeCanopy(ChunkManager $level, int $x, int $y, int $z, Random $random, BlockWriteBatch $write) : void{ $topSize = $this->treeHeight - (1 + $random->nextBoundedInt(2)); $lRadius = 2 + $random->nextBoundedInt(2); - - $this->placeTrunk($level, $x, $y, $z, $random, $this->treeHeight - $random->nextBoundedInt(3)); - $radius = $random->nextBoundedInt(2); $maxR = 1; $minR = 0; @@ -59,7 +64,7 @@ class SpruceTree extends Tree{ } if(!$level->getBlockAt($xx, $yyy, $zz)->isSolid()){ - $level->setBlockAt($xx, $yyy, $zz, $this->leafBlock); + $write->addBlockAt($xx, $yyy, $zz, $this->leafBlock); } } } diff --git a/src/pocketmine/level/generator/object/Tree.php b/src/pocketmine/level/generator/object/Tree.php index f3b3f5492..0287f672a 100644 --- a/src/pocketmine/level/generator/object/Tree.php +++ b/src/pocketmine/level/generator/object/Tree.php @@ -29,6 +29,7 @@ use pocketmine\block\Leaves; use pocketmine\block\Sapling; use pocketmine\block\utils\WoodType; use pocketmine\block\Wood; +use pocketmine\level\BlockWriteBatch; use pocketmine\level\ChunkManager; use pocketmine\utils\Random; @@ -100,8 +101,29 @@ abstract class Tree{ } public function placeObject(ChunkManager $level, int $x, int $y, int $z, Random $random) : void{ - $this->placeTrunk($level, $x, $y, $z, $random, $this->treeHeight - 1); + $write = new BlockWriteBatch(); + $this->placeTrunk($level, $x, $y, $z, $random, $this->generateChunkHeight($random), $write); + $this->placeCanopy($level, $x, $y, $z, $random, $write); + $write->apply($level); //TODO: handle return value on failure + } + + protected function generateChunkHeight(Random $random) : int{ + return $this->treeHeight - 1; + } + + protected function placeTrunk(ChunkManager $level, int $x, int $y, int $z, Random $random, int $trunkHeight, BlockWriteBatch $write) : void{ + // The base dirt block + $write->addBlockAt($x, $y - 1, $z, BlockFactory::get(Block::DIRT)); + + for($yy = 0; $yy < $trunkHeight; ++$yy){ + if($this->canOverride($level->getBlockAt($x, $y + $yy, $z))){ + $write->addBlockAt($x, $y + $yy, $z, $this->trunkBlock); + } + } + } + + protected function placeCanopy(ChunkManager $level, int $x, int $y, int $z, Random $random, BlockWriteBatch $write) : void{ for($yy = $y - 3 + $this->treeHeight; $yy <= $y + $this->treeHeight; ++$yy){ $yOff = $yy - ($y + $this->treeHeight); $mid = (int) (1 - $yOff / 2); @@ -113,24 +135,13 @@ abstract class Tree{ continue; } if(!$level->getBlockAt($xx, $yy, $zz)->isSolid()){ - $level->setBlockAt($xx, $yy, $zz, $this->leafBlock); + $write->addBlockAt($xx, $yy, $zz, $this->leafBlock); } } } } } - protected function placeTrunk(ChunkManager $level, int $x, int $y, int $z, Random $random, int $trunkHeight) : void{ - // The base dirt block - $level->setBlockAt($x, $y - 1, $z, BlockFactory::get(Block::DIRT)); - - for($yy = 0; $yy < $trunkHeight; ++$yy){ - if($this->canOverride($level->getBlockAt($x, $y + $yy, $z))){ - $level->setBlockAt($x, $y + $yy, $z, $this->trunkBlock); - } - } - } - protected function canOverride(Block $block) : bool{ return $block->canBeReplaced() or $block instanceof Sapling or $block instanceof Leaves; }