diff --git a/build/generate-runtime-enum-serializers.php b/build/generate-runtime-enum-serializers.php index 1273e26f8..805ed18e1 100644 --- a/build/generate-runtime-enum-serializers.php +++ b/build/generate-runtime-enum-serializers.php @@ -27,6 +27,7 @@ use pocketmine\block\utils\BellAttachmentType; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\DirtType; +use pocketmine\block\utils\DripleafState; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FroglightType; use pocketmine\block\utils\LeverFacing; @@ -145,6 +146,7 @@ $enumsUsed = [ CopperOxidation::getAll(), CoralType::getAll(), DirtType::getAll(), + DripleafState::getAll(), DyeColor::getAll(), FroglightType::getAll(), LeverFacing::getAll(), diff --git a/changelogs/4.23.md b/changelogs/4.23.md index c87364d67..d51413e82 100644 --- a/changelogs/4.23.md +++ b/changelogs/4.23.md @@ -24,3 +24,9 @@ Released 14th July 2023. ## Fixes - Hardened validation of JWT signing keys in `LoginPacket`. - Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper). + +# 4.23.2 +Released 18th July 2023. + +## Fixes +- Fixed login errors due to a new `sandboxId` field appearing in the Xbox Live authentication data in `LoginPacket`. All clients, regardless of version, are affected by this change. diff --git a/changelogs/5.3.md b/changelogs/5.3.md index 0a776a11e..3dfd035de 100644 --- a/changelogs/5.3.md +++ b/changelogs/5.3.md @@ -31,3 +31,19 @@ Released 14th July 2023. ## General - Updated `build/php` submodule to pmmp/PHP-Binaries@e0c918d1379465964acefd562d9e48f87cfc2c9e. + +# 5.3.2 +Released 18th July 2023. + +## Included releases +**This release includes changes from the following releases:** +- [4.23.2](https://github.com/pmmp/PocketMine-MP/blob/4.23.2/changelogs/4.23.md#4232) - Fix for `sandboxId`-related login errors + +## Documentation +- Fixed documentation error in `StringToTParser`. + +## Fixes +- Fixed turtle helmet not being able to be unequipped. + +## Internals +- Armor pieces are no longer set back into the armor inventory if no change was made. This reduces the number of slot updates sent to clients, as well as avoiding unnecessary updates for armor pieces which have Unbreaking enchantments. diff --git a/composer.lock b/composer.lock index e005f6ca7..d9043b2c2 100644 --- a/composer.lock +++ b/composer.lock @@ -276,16 +276,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "23.0.0+bedrock-1.20.10", + "version": "23.0.1+bedrock-1.20.10", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "0cfaafdc02cca882a50773d6c02ebfeb622614e2" + "reference": "db48400736799cc3833a2644a02e308992a98fa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/0cfaafdc02cca882a50773d6c02ebfeb622614e2", - "reference": "0cfaafdc02cca882a50773d6c02ebfeb622614e2", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/db48400736799cc3833a2644a02e308992a98fa8", + "reference": "db48400736799cc3833a2644a02e308992a98fa8", "shasum": "" }, "require": { @@ -317,9 +317,9 @@ "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "support": { "issues": "https://github.com/pmmp/BedrockProtocol/issues", - "source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.0+bedrock-1.20.10" + "source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.1+bedrock-1.20.10" }, - "time": "2023-07-12T12:19:40+00:00" + "time": "2023-07-18T21:07:24+00:00" }, { "name": "pocketmine/binaryutils", @@ -1937,16 +1937,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.2.5", + "version": "10.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd" + "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd", - "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c17815c129f133f3019cc18e8d0c8622e6d9bcd", + "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd", "shasum": "" }, "require": { @@ -2018,7 +2018,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.6" }, "funding": [ { @@ -2034,7 +2034,7 @@ "type": "tidelift" } ], - "time": "2023-07-14T04:18:47+00:00" + "time": "2023-07-17T12:08:28+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 4c6553e27..7cbd05735 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.3.2"; + public const BASE_VERSION = "5.3.3"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/block/BaseBigDripleaf.php b/src/block/BaseBigDripleaf.php new file mode 100644 index 000000000..dcd81af0a --- /dev/null +++ b/src/block/BaseBigDripleaf.php @@ -0,0 +1,136 @@ +isHead() === $head) || + $block->getTypeId() === BlockTypeIds::CLAY || + $block->hasTypeTag(BlockTypeTags::DIRT) || + $block->hasTypeTag(BlockTypeTags::MUD); + } + + public function onNearbyBlockChange() : void{ + if( + (!$this->isHead() && !$this->getSide(Facing::UP) instanceof BaseBigDripleaf) || + !$this->canBeSupportedBy($this->getSide(Facing::DOWN), false) + ){ + $this->position->getWorld()->useBreakOn($this->position); + } + } + + public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + $block = $blockReplace->getSide(Facing::DOWN); + if(!$this->canBeSupportedBy($block, true)){ + return false; + } + if($player !== null){ + $this->facing = Facing::opposite($player->getHorizontalFacing()); + } + if($block instanceof BaseBigDripleaf){ + $this->facing = $block->getFacing(); + $tx->addBlock($block->getPosition(), VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($this->facing)); + } + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } + + public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + if($item instanceof Fertilizer && $this->grow($player)){ + $item->pop(); + return true; + } + return false; + } + + private function seekToHead() : ?BaseBigDripleaf{ + if($this->isHead()){ + return $this; + } + $step = 1; + while(($next = $this->getSide(Facing::UP, $step)) instanceof BaseBigDripleaf){ + if($next->isHead()){ + return $next; + } + $step++; + } + return null; + } + + private function grow(?Player $player) : bool{ + $head = $this->seekToHead(); + if($head === null){ + return false; + } + $pos = $head->getPosition(); + $up = $pos->up(); + $world = $pos->getWorld(); + if( + !$world->isInWorld($up->getFloorX(), $up->getFloorY(), $up->getFloorZ()) || + $world->getBlock($up)->getTypeId() !== BlockTypeIds::AIR + ){ + return false; + } + + $tx = new BlockTransaction($world); + + $tx->addBlock($pos, VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($head->getFacing())); + $tx->addBlock($up, VanillaBlocks::BIG_DRIPLEAF_HEAD()->setFacing($head->getFacing())); + + $ev = new StructureGrowEvent($head, $tx, $player); + $ev->call(); + + if(!$ev->isCancelled()){ + return $tx->apply(); + } + return false; + } + + public function getFlameEncouragement() : int{ + return 15; + } + + public function getFlammability() : int{ + return 100; + } + + public function getSupportType(int $facing) : SupportType{ + return SupportType::NONE(); + } +} diff --git a/src/block/BigDripleafHead.php b/src/block/BigDripleafHead.php new file mode 100644 index 000000000..d5bd226ca --- /dev/null +++ b/src/block/BigDripleafHead.php @@ -0,0 +1,132 @@ +leafState = DripleafState::STABLE(); + parent::__construct($idInfo, $name, $typeInfo); + } + + protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ + parent::describeBlockOnlyState($w); + $w->dripleafState($this->leafState); + } + + protected function isHead() : bool{ + return true; + } + + public function getLeafState() : DripleafState{ + return $this->leafState; + } + + /** @return $this */ + public function setLeafState(DripleafState $leafState) : self{ + $this->leafState = $leafState; + return $this; + } + + public function hasEntityCollision() : bool{ + return true; + } + + private function setTiltAndScheduleTick(DripleafState $tilt) : void{ + $this->position->getWorld()->setBlock($this->position, $this->setLeafState($tilt)); + $delay = $tilt->getScheduledUpdateDelayTicks(); + if($delay !== null){ + $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $delay); + } + } + + private function getLeafTopOffset() : float{ + return match($this->leafState){ + DripleafState::STABLE(), DripleafState::UNSTABLE() => 1 / 16, + DripleafState::PARTIAL_TILT() => 3 / 16, + default => 0 + }; + } + + public function onEntityInside(Entity $entity) : bool{ + if(!$entity instanceof Projectile && $this->leafState->equals(DripleafState::STABLE())){ + //the entity must be standing on top of the leaf - do not collapse if the entity is standing underneath + $intersection = AxisAlignedBB::one() + ->offset($this->position->x, $this->position->y, $this->position->z) + ->trim(Facing::DOWN, 1 - $this->getLeafTopOffset()); + if($entity->getBoundingBox()->intersectsWith($intersection)){ + $this->setTiltAndScheduleTick(DripleafState::UNSTABLE()); + return false; + } + } + return true; + } + + public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{ + if(!$this->leafState->equals(DripleafState::FULL_TILT())){ + $this->setTiltAndScheduleTick(DripleafState::FULL_TILT()); + $this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound()); + } + } + + public function onScheduledUpdate() : void{ + if(!$this->leafState->equals(DripleafState::STABLE())){ + if($this->leafState->equals(DripleafState::FULL_TILT())){ + $this->position->getWorld()->setBlock($this->position, $this->setLeafState(DripleafState::STABLE())); + $this->position->getWorld()->addSound($this->position, new DripleafTiltUpSound()); + }else{ + $this->setTiltAndScheduleTick(match($this->leafState->id()){ + DripleafState::UNSTABLE()->id() => DripleafState::PARTIAL_TILT(), + DripleafState::PARTIAL_TILT()->id() => DripleafState::FULL_TILT(), + default => throw new AssumptionFailedError("All types should be covered") + }); + $this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound()); + } + } + } + + protected function recalculateCollisionBoxes() : array{ + if(!$this->leafState->equals(DripleafState::FULL_TILT())){ + return [ + AxisAlignedBB::one() + ->trim(Facing::DOWN, 11 / 16) + ->trim(Facing::UP, $this->getLeafTopOffset()) + ]; + } + return []; + } +} diff --git a/src/block/BigDripleafStem.php b/src/block/BigDripleafStem.php new file mode 100644 index 000000000..d44c47ccb --- /dev/null +++ b/src/block/BigDripleafStem.php @@ -0,0 +1,41 @@ +asItem(); + } +} diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index b48a1ed03..fe2101e1f 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -733,8 +733,11 @@ final class BlockTypeIds{ public const CHERRY_TRAPDOOR = 10703; public const CHERRY_WALL_SIGN = 10704; public const CHERRY_WOOD = 10705; + public const SMALL_DRIPLEAF = 10706; + public const BIG_DRIPLEAF_HEAD = 10707; + public const BIG_DRIPLEAF_STEM = 10708; - public const FIRST_UNUSED_BLOCK_ID = 10706; + public const FIRST_UNUSED_BLOCK_ID = 10709; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/SmallDripleaf.php b/src/block/SmallDripleaf.php new file mode 100644 index 000000000..e08e6f6e9 --- /dev/null +++ b/src/block/SmallDripleaf.php @@ -0,0 +1,170 @@ +horizontalFacing($this->facing); + $w->bool($this->top); + } + + public function isTop() : bool{ + return $this->top; + } + + /** @return $this */ + public function setTop(bool $top) : self{ + $this->top = $top; + return $this; + } + + private function canBeSupportedBy(Block $block) : bool{ + //TODO: Moss + //TODO: Small Dripleaf also can be placed on dirt, coarse dirt, farmland, grass blocks, + // podzol, rooted dirt, mycelium, and mud if these blocks are underwater (needs waterlogging) + return $block->getTypeId() === BlockTypeIds::CLAY; + } + + public function onNearbyBlockChange() : void{ + if(!$this->top && !$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + $this->position->getWorld()->useBreakOn($this->position); + return; + } + $face = $this->top ? Facing::DOWN : Facing::UP; + if(!$this->getSide($face)->hasSameTypeId($this)){ + $this->position->getWorld()->useBreakOn($this->position); + } + } + + public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + $block = $blockReplace->getSide(Facing::UP); + if($block->getTypeId() !== BlockTypeIds::AIR || !$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){ + return false; + } + if($player !== null){ + $this->facing = Facing::opposite($player->getHorizontalFacing()); + } + + $tx->addBlock($block->getPosition(), VanillaBlocks::SMALL_DRIPLEAF() + ->setFacing($this->facing) + ->setTop(true) + ); + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } + + public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + if($item instanceof Fertilizer && $this->grow($player)){ + $item->pop(); + return true; + } + return false; + } + + private function canGrowTo(Position $pos) : bool{ + $world = $pos->getWorld(); + if(!$world->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ())){ + return false; + } + $block = $world->getBlock($pos); + return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::AIR; + } + + private function grow(?Player $player) : bool{ + $bottomBlock = $this->top ? $this->getSide(Facing::DOWN) : $this; + if(!$this->hasSameTypeId($bottomBlock)){ + return false; + } + $world = $this->position->getWorld(); + $tx = new BlockTransaction($world); + $height = mt_rand(2, 5); + $grown = 0; + for($i = 0; $i < $height; $i++){ + $pos = $bottomBlock->getSide(Facing::UP, $i)->getPosition(); + if(!$this->canGrowTo($pos)){ + break; + } + $block = ++$grown < $height && $this->canGrowTo($pos->getSide(Facing::UP)) ? + VanillaBlocks::BIG_DRIPLEAF_STEM() : + VanillaBlocks::BIG_DRIPLEAF_HEAD(); + $tx->addBlock($pos, $block->setFacing($this->facing)); + } + if($grown > 1){ + $ev = new StructureGrowEvent($bottomBlock, $tx, $player); + $ev->call(); + if(!$ev->isCancelled()){ + return $tx->apply(); + } + } + + return false; + } + + public function getAffectedBlocks() : array{ + $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); + if($other->hasSameTypeId($this)){ + return [$this, $other]; + } + return parent::getAffectedBlocks(); + } + + public function getDropsForCompatibleTool(Item $item) : array{ + if(!$this->top){ + return [$this->asItem()]; + } + return []; + } + + public function getFlameEncouragement() : int{ + return 15; + } + + public function getFlammability() : int{ + return 100; + } + + public function getSupportType(int $facing) : SupportType{ + return SupportType::NONE(); + } + + protected function recalculateCollisionBoxes() : array{ + return []; + } +} diff --git a/src/block/Stem.php b/src/block/Stem.php index b0d49b28e..252b28aeb 100644 --- a/src/block/Stem.php +++ b/src/block/Stem.php @@ -55,6 +55,7 @@ abstract class Stem extends Crops{ if($this->facing !== Facing::UP && !$this->getSide($this->facing)->hasSameTypeId($this->getPlant())){ $this->position->getWorld()->setBlock($this->position, $this->setFacing(Facing::UP)); } + parent::onNearbyBlockChange(); } public function onRandomTick() : void{ diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index a45e18a1e..9f2996abd 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -113,6 +113,8 @@ use function mb_strtolower; * @method static Bedrock BEDROCK() * @method static Beetroot BEETROOTS() * @method static Bell BELL() + * @method static BigDripleafHead BIG_DRIPLEAF_HEAD() + * @method static BigDripleafStem BIG_DRIPLEAF_STEM() * @method static WoodenButton BIRCH_BUTTON() * @method static WoodenDoor BIRCH_DOOR() * @method static WoodenFence BIRCH_FENCE() @@ -658,6 +660,7 @@ use function mb_strtolower; * @method static Opaque SHROOMLIGHT() * @method static ShulkerBox SHULKER_BOX() * @method static Slime SLIME() + * @method static SmallDripleaf SMALL_DRIPLEAF() * @method static SmithingTable SMITHING_TABLE() * @method static Furnace SMOKER() * @method static Opaque SMOOTH_BASALT() @@ -1598,6 +1601,10 @@ final class VanillaBlocks{ self::register("hanging_roots", new HangingRoots(new BID(Ids::HANGING_ROOTS), "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1)))); self::register("cave_vines", new CaveVines(new BID(Ids::CAVE_VINES), "Cave Vines", new Info(BreakInfo::instant()))); + + self::register("small_dripleaf", new SmallDripleaf(new BID(Ids::SMALL_DRIPLEAF), "Small Dripleaf", new Info(BreakInfo::instant(BlockToolType::SHEARS, toolHarvestLevel: 1)))); + self::register("big_dripleaf_head", new BigDripleafHead(new BID(Ids::BIG_DRIPLEAF_HEAD), "Big Dripleaf", new Info(BreakInfo::instant()))); + self::register("big_dripleaf_stem", new BigDripleafStem(new BID(Ids::BIG_DRIPLEAF_STEM), "Big Dripleaf Stem", new Info(BreakInfo::instant()))); } private static function registerBlocksR18() : void{ diff --git a/src/block/utils/DripleafState.php b/src/block/utils/DripleafState.php new file mode 100644 index 000000000..3c2e20a13 --- /dev/null +++ b/src/block/utils/DripleafState.php @@ -0,0 +1,65 @@ +Enum___construct($enumName); + } + + public function getScheduledUpdateDelayTicks() : ?int{ + return $this->scheduledUpdateDelayTicks; + } + +} diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 0ba6496dd..8ec9a1ddc 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -31,6 +31,8 @@ use pocketmine\block\Barrel; use pocketmine\block\Bed; use pocketmine\block\Beetroot; use pocketmine\block\Bell; +use pocketmine\block\BigDripleafHead; +use pocketmine\block\BigDripleafStem; use pocketmine\block\Block; use pocketmine\block\BoneBlock; use pocketmine\block\BrewingStand; @@ -115,6 +117,7 @@ use pocketmine\block\SeaPickle; use pocketmine\block\SimplePillar; use pocketmine\block\SimplePressurePlate; use pocketmine\block\Slab; +use pocketmine\block\SmallDripleaf; use pocketmine\block\SnowLayer; use pocketmine\block\Sponge; use pocketmine\block\StainedGlass; @@ -138,6 +141,7 @@ use pocketmine\block\UnderwaterTorch; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\DirtType; +use pocketmine\block\utils\DripleafState; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FroglightType; use pocketmine\block\utils\LeverFacing; @@ -958,6 +962,24 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeLegacyHorizontalFacing($block->getFacing()); }); + $this->map(Blocks::BIG_DRIPLEAF_HEAD(), function(BigDripleafHead $block) : Writer{ + return Writer::create(Ids::BIG_DRIPLEAF) + ->writeLegacyHorizontalFacing($block->getFacing()) + ->writeString(StateNames::BIG_DRIPLEAF_TILT, match($block->getLeafState()->id()){ + DripleafState::STABLE()->id() => StringValues::BIG_DRIPLEAF_TILT_NONE, + DripleafState::UNSTABLE()->id() => StringValues::BIG_DRIPLEAF_TILT_UNSTABLE, + DripleafState::PARTIAL_TILT()->id() => StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT, + DripleafState::FULL_TILT()->id() => StringValues::BIG_DRIPLEAF_TILT_FULL_TILT, + default => throw new BlockStateSerializeException("Invalid Dripleaf tilt type " . $block->getLeafState()->name()) + }) + ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, true); + }); + $this->map(Blocks::BIG_DRIPLEAF_STEM(), function(BigDripleafStem $block) : Writer{ + return Writer::create(Ids::BIG_DRIPLEAF) + ->writeLegacyHorizontalFacing($block->getFacing()) + ->writeString(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE) + ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, false); + }); $this->map(Blocks::BIRCH_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_BIRCH)); $this->mapSlab(Blocks::BLACKSTONE_SLAB(), Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB); $this->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS); @@ -1454,6 +1476,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeBool(StateNames::DEAD_BIT, !$block->isUnderwater()) ->writeInt(StateNames::CLUSTER_COUNT, $block->getCount() - 1); }); + $this->map(Blocks::SMALL_DRIPLEAF(), function(SmallDripleaf $block) : Writer{ + return Writer::create(Ids::SMALL_DRIPLEAF_BLOCK) + ->writeLegacyHorizontalFacing($block->getFacing()) + ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); + }); $this->map(Blocks::SMOKER(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::SMOKER, Ids::LIT_SMOKER)); $this->map(Blocks::SMOOTH_QUARTZ(), fn() => Helper::encodeQuartz(StringValues::CHISEL_TYPE_SMOOTH, Axis::Y)); $this->map(Blocks::SMOOTH_QUARTZ_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_SMOOTH_QUARTZ)); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 65b896523..08c9b5914 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -35,6 +35,7 @@ use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\DirtType; +use pocketmine\block\utils\DripleafState; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FroglightType; use pocketmine\block\utils\LeverFacing; @@ -826,6 +827,22 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readLegacyHorizontalFacing()) ->setAttachmentType($in->readBellAttachmentType()); }); + $this->map(Ids::BIG_DRIPLEAF, function(Reader $in) : Block{ + if($in->readBool(StateNames::BIG_DRIPLEAF_HEAD)){ + return Blocks::BIG_DRIPLEAF_HEAD() + ->setFacing($in->readLegacyHorizontalFacing()) + ->setLeafState(match($type = $in->readString(StateNames::BIG_DRIPLEAF_TILT)){ + StringValues::BIG_DRIPLEAF_TILT_NONE => DripleafState::STABLE(), + StringValues::BIG_DRIPLEAF_TILT_UNSTABLE => DripleafState::UNSTABLE(), + StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT => DripleafState::PARTIAL_TILT(), + StringValues::BIG_DRIPLEAF_TILT_FULL_TILT => DripleafState::FULL_TILT(), + default => throw $in->badValueException(StateNames::BIG_DRIPLEAF_TILT, $type), + }); + }else{ + $in->ignored(StateNames::BIG_DRIPLEAF_TILT); + return Blocks::BIG_DRIPLEAF_STEM()->setFacing($in->readLegacyHorizontalFacing()); + } + }); $this->mapSlab(Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB, fn() => Blocks::BLACKSTONE_SLAB()); $this->mapStairs(Ids::BLACKSTONE_STAIRS, fn() => Blocks::BLACKSTONE_STAIRS()); $this->map(Ids::BLACKSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::BLACKSTONE_WALL(), $in)); @@ -1332,6 +1349,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readHorizontalFacing()) ->setLit(false); }); + $this->map(Ids::SMALL_DRIPLEAF_BLOCK, function(Reader $in) : Block{ + return Blocks::SMALL_DRIPLEAF() + ->setFacing($in->readLegacyHorizontalFacing()) + ->setTop($in->readBool(StateNames::UPPER_BLOCK_BIT)); + }); $this->mapStairs(Ids::SMOOTH_QUARTZ_STAIRS, fn() => Blocks::SMOOTH_QUARTZ_STAIRS()); $this->mapStairs(Ids::SMOOTH_RED_SANDSTONE_STAIRS, fn() => Blocks::SMOOTH_RED_SANDSTONE_STAIRS()); $this->mapStairs(Ids::SMOOTH_SANDSTONE_STAIRS, fn() => Blocks::SMOOTH_SANDSTONE_STAIRS()); diff --git a/src/data/runtime/RuntimeEnumDescriber.php b/src/data/runtime/RuntimeEnumDescriber.php index 643ecd301..7103017f7 100644 --- a/src/data/runtime/RuntimeEnumDescriber.php +++ b/src/data/runtime/RuntimeEnumDescriber.php @@ -37,6 +37,8 @@ interface RuntimeEnumDescriber{ public function dirtType(\pocketmine\block\utils\DirtType &$value) : void; + public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void; + public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void; public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void; diff --git a/src/data/runtime/RuntimeEnumDeserializerTrait.php b/src/data/runtime/RuntimeEnumDeserializerTrait.php index 4774304a9..7d80a6f54 100644 --- a/src/data/runtime/RuntimeEnumDeserializerTrait.php +++ b/src/data/runtime/RuntimeEnumDeserializerTrait.php @@ -71,6 +71,16 @@ trait RuntimeEnumDeserializerTrait{ }; } + public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{ + $value = match($this->readInt(2)){ + 0 => \pocketmine\block\utils\DripleafState::FULL_TILT(), + 1 => \pocketmine\block\utils\DripleafState::PARTIAL_TILT(), + 2 => \pocketmine\block\utils\DripleafState::STABLE(), + 3 => \pocketmine\block\utils\DripleafState::UNSTABLE(), + default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for DripleafState") + }; + } + public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{ $value = match($this->readInt(4)){ 0 => \pocketmine\block\utils\DyeColor::BLACK(), diff --git a/src/data/runtime/RuntimeEnumSerializerTrait.php b/src/data/runtime/RuntimeEnumSerializerTrait.php index c570046c6..8c3d67118 100644 --- a/src/data/runtime/RuntimeEnumSerializerTrait.php +++ b/src/data/runtime/RuntimeEnumSerializerTrait.php @@ -71,6 +71,16 @@ trait RuntimeEnumSerializerTrait{ }); } + public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{ + $this->writeInt(2, match($value){ + \pocketmine\block\utils\DripleafState::FULL_TILT() => 0, + \pocketmine\block\utils\DripleafState::PARTIAL_TILT() => 1, + \pocketmine\block\utils\DripleafState::STABLE() => 2, + \pocketmine\block\utils\DripleafState::UNSTABLE() => 3, + default => throw new \pocketmine\utils\AssumptionFailedError("All DripleafState cases should be covered") + }); + } + public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{ $this->writeInt(4, match($value){ \pocketmine\block\utils\DyeColor::BLACK() => 0, diff --git a/src/data/runtime/RuntimeEnumSizeCalculatorTrait.php b/src/data/runtime/RuntimeEnumSizeCalculatorTrait.php index 3c8d189e1..2ab62f03e 100644 --- a/src/data/runtime/RuntimeEnumSizeCalculatorTrait.php +++ b/src/data/runtime/RuntimeEnumSizeCalculatorTrait.php @@ -47,6 +47,10 @@ trait RuntimeEnumSizeCalculatorTrait{ $this->addBits(2); } + public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{ + $this->addBits(2); + } + public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{ $this->addBits(4); } diff --git a/src/entity/Living.php b/src/entity/Living.php index 2cf5d4053..016146b3a 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -487,14 +487,16 @@ abstract class Living extends Entity{ public function damageArmor(float $damage) : void{ $durabilityRemoved = (int) max(floor($damage / 4), 1); - $armor = $this->armorInventory->getContents(true); - foreach($armor as $item){ + $armor = $this->armorInventory->getContents(); + foreach($armor as $slotId => $item){ if($item instanceof Armor){ + $oldItem = clone $item; $this->damageItem($item, $durabilityRemoved); + if(!$item->equalsExact($oldItem)){ + $this->armorInventory->setItem($slotId, $item); + } } } - - $this->armorInventory->setContents($armor); } private function damageItem(Durable $item, int $durabilityRemoved) : void{ @@ -640,9 +642,12 @@ abstract class Living extends Entity{ } foreach($this->armorInventory->getContents() as $index => $item){ + $oldItem = clone $item; if($item->onTickWorn($this)){ $hasUpdate = true; - $this->armorInventory->setItem($index, $item); + if(!$item->equalsExact($oldItem)){ + $this->armorInventory->setItem($index, $item); + } } } } diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index e5e5252e1..ea9923c21 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -155,6 +155,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("beetroot_block", fn() => Blocks::BEETROOTS()); $result->registerBlock("beetroots", fn() => Blocks::BEETROOTS()); $result->registerBlock("bell", fn() => Blocks::BELL()); + $result->registerBlock("big_dripleaf", fn() => Blocks::BIG_DRIPLEAF_HEAD()); $result->registerBlock("birch_button", fn() => Blocks::BIRCH_BUTTON()); $result->registerBlock("birch_door", fn() => Blocks::BIRCH_DOOR()); $result->registerBlock("birch_door_block", fn() => Blocks::BIRCH_DOOR()); @@ -972,6 +973,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("slabs", fn() => Blocks::SMOOTH_STONE_SLAB()); $result->registerBlock("slime", fn() => Blocks::SLIME()); $result->registerBlock("slime_block", fn() => Blocks::SLIME()); + $result->registerBlock("small_dripleaf", fn() => Blocks::SMALL_DRIPLEAF()); $result->registerBlock("smoker", fn() => Blocks::SMOKER()); $result->registerBlock("smooth_basalt", fn() => Blocks::SMOOTH_BASALT()); $result->registerBlock("smooth_quartz", fn() => Blocks::SMOOTH_QUARTZ()); diff --git a/src/scheduler/AsyncPool.php b/src/scheduler/AsyncPool.php index a326bc87f..7540294ef 100644 --- a/src/scheduler/AsyncPool.php +++ b/src/scheduler/AsyncPool.php @@ -259,7 +259,7 @@ class AsyncPool{ if($task->isTerminated()){ $this->checkCrashedWorker($worker, $task); throw new AssumptionFailedError("checkCrashedWorker() should have thrown an exception, making this unreachable"); - }elseif(!$task->hasCancelledRun()){ + }else{ /* * It's possible for a task to submit a progress update and then finish before the progress * update is detected by the parent thread, so here we consume any missed updates. diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index 9d69bda5b..ba5cc424c 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -72,17 +72,14 @@ abstract class AsyncTask extends Runnable{ private ?ThreadSafeArray $progressUpdates = null; private ThreadSafe|string|int|bool|null|float $result = null; - private bool $cancelRun = false; - private bool $submitted = false; + private bool $submitted = false; private bool $finished = false; public function run() : void{ $this->result = null; - if(!$this->cancelRun){ - $this->onRun(); - } + $this->onRun(); $this->finished = true; $worker = NativeThread::getCurrentThread(); @@ -123,12 +120,18 @@ abstract class AsyncTask extends Runnable{ $this->result = is_scalar($result) || is_null($result) || $result instanceof ThreadSafe ? $result : new NonThreadSafeValue($result); } + /** + * @deprecated + */ public function cancelRun() : void{ - $this->cancelRun = true; + //NOOP } + /** + * @deprecated + */ public function hasCancelledRun() : bool{ - return $this->cancelRun; + return false; } public function setSubmitted() : void{ diff --git a/src/world/generator/object/AcaciaTree.php b/src/world/generator/object/AcaciaTree.php new file mode 100644 index 000000000..75e58a9b3 --- /dev/null +++ b/src/world/generator/object/AcaciaTree.php @@ -0,0 +1,132 @@ +nextRange(0, 2) + $random->nextRange(0, 2); + } + + protected function placeTrunk(int $x, int $y, int $z, Random $random, int $trunkHeight, BlockTransaction $transaction) : void{ + // The base dirt block + $transaction->addBlockAt($x, $y - 1, $z, VanillaBlocks::DIRT()); + + $firstBranchHeight = $trunkHeight - 1 - $random->nextRange(0, 3); + + for($yy = 0; $yy <= $firstBranchHeight; ++$yy){ + $transaction->addBlockAt($x, $y + $yy, $z, $this->trunkBlock); + } + + $mainBranchFacing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]; + + //this branch may grow a second trunk if the diagonal length is less than the max length + $this->mainBranchTip = $this->placeBranch( + $transaction, + new Vector3($x, $y + $firstBranchHeight, $z), + $mainBranchFacing, + $random->nextRange(1, 3), + $trunkHeight - $firstBranchHeight + ); + + $secondBranchFacing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]; + if($secondBranchFacing !== $mainBranchFacing){ + $secondBranchLength = $random->nextRange(1, 3); + $this->secondBranchTip = $this->placeBranch( + $transaction, + new Vector3($x, $y + ($firstBranchHeight - $random->nextRange(0, 1)), $z), + $secondBranchFacing, + $secondBranchLength, + $secondBranchLength //the secondary branch may not form a second trunk + ); + } + } + + protected function placeBranch(BlockTransaction $transaction, Vector3 $start, int $branchFacing, int $maxDiagonal, int $length) : Vector3{ + $diagonalPlaced = 0; + + $nextBlockPos = $start; + for($yy = 0; $yy < $length; $yy++){ + $nextBlockPos = $nextBlockPos->up(); + if($diagonalPlaced < $maxDiagonal){ + $nextBlockPos = $nextBlockPos->getSide($branchFacing); + $diagonalPlaced++; + } + $transaction->addBlock($nextBlockPos, $this->trunkBlock); + } + + return $nextBlockPos; + } + + protected function placeCanopyLayer(BlockTransaction $transaction, Vector3 $center, int $radius, int $maxTaxicabDistance) : void{ + $centerX = $center->getFloorX(); + $centerY = $center->getFloorY(); + $centerZ = $center->getFloorZ(); + + for($x = $centerX - $radius; $x <= $centerX + $radius; ++$x){ + for($z = $centerZ - $radius; $z <= $centerZ + $radius; ++$z){ + if( + abs($x - $centerX) + abs($z - $centerZ) <= $maxTaxicabDistance && + $transaction->fetchBlockAt($x, $centerY, $z)->canBeReplaced() + ){ + $transaction->addBlockAt($x, $centerY, $z, $this->leafBlock); + } + } + } + } + + protected function placeCanopy(int $x, int $y, int $z, Random $random, BlockTransaction $transaction) : void{ + $mainBranchTip = $this->mainBranchTip; + if($mainBranchTip !== null){ + $this->placeCanopyLayer($transaction, $mainBranchTip, radius: 3, maxTaxicabDistance: 5); + $this->placeCanopyLayer($transaction, $mainBranchTip->up(), radius: 2, maxTaxicabDistance: 2); + } + $secondBranchTip = $this->secondBranchTip; + if($secondBranchTip !== null){ + $this->placeCanopyLayer($transaction, $secondBranchTip, radius: 2, maxTaxicabDistance: 3); + $this->placeCanopyLayer($transaction, $secondBranchTip->up(), radius: 1, maxTaxicabDistance: 2); + } + } +} diff --git a/src/world/generator/object/TreeFactory.php b/src/world/generator/object/TreeFactory.php index ecab73c79..1d95a77b1 100644 --- a/src/world/generator/object/TreeFactory.php +++ b/src/world/generator/object/TreeFactory.php @@ -49,6 +49,8 @@ final class TreeFactory{ }else{*/ //} + }elseif($type->equals(TreeType::ACACIA())){ + return new AcaciaTree(); } return null; } diff --git a/src/world/sound/DripleafTiltDownSound.php b/src/world/sound/DripleafTiltDownSound.php new file mode 100644 index 000000000..1366f1b71 --- /dev/null +++ b/src/world/sound/DripleafTiltDownSound.php @@ -0,0 +1,35 @@ +