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/composer.json b/composer.json index e6a57a4d9..5cb32d2ac 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,6 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", - "fgrosse/phpasn1": "~2.5.0", "pocketmine/netresearch-jsonmapper": "~v4.2.1000", "pocketmine/bedrock-block-upgrade-schema": "~3.1.0+bedrock-1.20.10", "pocketmine/bedrock-data": "~2.4.0+bedrock-1.20.10", @@ -50,7 +49,7 @@ "pocketmine/raklib-ipc": "^0.2.0", "pocketmine/snooze": "^0.5.0", "ramsey/uuid": "~4.7.0", - "symfony/filesystem": "~6.2.0" + "symfony/filesystem": "~6.3.0" }, "require-dev": { "phpstan/phpstan": "1.10.16", diff --git a/composer.lock b/composer.lock index 1756fb57a..a97b576ee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e14717125dd180235fb888662a48846d", + "content-hash": "ccd20e7656bc05ec2acd8e28aad9fcf2", "packages": [ { "name": "adhocore/json-comment", @@ -120,82 +120,6 @@ ], "time": "2023-01-15T23:15:59+00:00" }, - { - "name": "fgrosse/phpasn1", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b", - "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "~2.0", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" - }, - "suggest": { - "ext-bcmath": "BCmath is the fallback extension for big integer calculations", - "ext-curl": "For loading OID information from the web if they have not bee defined statically", - "ext-gmp": "GMP is the preferred extension for big integer calculations", - "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "FG\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Friedrich Große", - "email": "friedrich.grosse@gmail.com", - "homepage": "https://github.com/FGrosse", - "role": "Author" - }, - { - "name": "All contributors", - "homepage": "https://github.com/FGrosse/PHPASN1/contributors" - } - ], - "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", - "homepage": "https://github.com/FGrosse/PHPASN1", - "keywords": [ - "DER", - "asn.1", - "asn1", - "ber", - "binary", - "decoding", - "encoding", - "x.509", - "x.690", - "x509", - "x690" - ], - "support": { - "issues": "https://github.com/fgrosse/PHPASN1/issues", - "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0" - }, - "abandoned": true, - "time": "2022-12-19T11:08:26+00:00" - }, { "name": "pocketmine/bedrock-block-upgrade-schema", "version": "3.1.0", @@ -998,16 +922,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.2.12", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b0818e7203e53540f2a5c9a5017d97897df1e9bb" + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b0818e7203e53540f2a5c9a5017d97897df1e9bb", - "reference": "b0818e7203e53540f2a5c9a5017d97897df1e9bb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", "shasum": "" }, "require": { @@ -1041,7 +965,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.2.12" + "source": "https://github.com/symfony/filesystem/tree/v6.3.1" }, "funding": [ { @@ -1057,7 +981,7 @@ "type": "tidelift" } ], - "time": "2023-06-01T08:29:37+00:00" + "time": "2023-06-01T08:30:39+00:00" }, { "name": "symfony/polyfill-ctype", diff --git a/src/Server.php b/src/Server.php index 3e8efb7ad..148c93b8e 100644 --- a/src/Server.php +++ b/src/Server.php @@ -93,6 +93,7 @@ use pocketmine\scheduler\AsyncPool; use pocketmine\snooze\SleeperHandler; use pocketmine\stats\SendUsageTask; use pocketmine\thread\log\AttachableThreadSafeLogger; +use pocketmine\thread\ThreadCrashException; use pocketmine\thread\ThreadSafeClassLoader; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; @@ -1009,7 +1010,7 @@ class Server{ $this->playerDataProvider = new DatFilePlayerDataProvider(Path::join($this->dataPath, "players")); - register_shutdown_function([$this, "crashDump"]); + register_shutdown_function($this->crashDump(...)); $loadErrorCount = 0; $this->pluginManager->loadPlugins($this->pluginPath, $loadErrorCount); @@ -1516,23 +1517,38 @@ class Server{ $trace = $e->getTrace(); } - $errstr = $e->getMessage(); - $errfile = $e->getFile(); - $errline = $e->getLine(); + //If this is a thread crash, this logs where the exception came from on the main thread, as opposed to the + //crashed thread. This is intentional, and might be useful for debugging + //Assume that the thread already logged the original exception with the correct stack trace + $this->logger->logException($e, $trace); + + if($e instanceof ThreadCrashException){ + $info = $e->getCrashInfo(); + $type = $info->getType(); + $errstr = $info->getMessage(); + $errfile = $info->getFile(); + $errline = $info->getLine(); + $printableTrace = $info->getTrace(); + $thread = $info->getThreadName(); + }else{ + $type = get_class($e); + $errstr = $e->getMessage(); + $errfile = $e->getFile(); + $errline = $e->getLine(); + $printableTrace = Utils::printableTraceWithMetadata($trace); + $thread = "Main"; + } $errstr = preg_replace('/\s+/', ' ', trim($errstr)); - $errfile = Filesystem::cleanPath($errfile); - - $this->logger->logException($e, $trace); - $lastError = [ - "type" => get_class($e), + "type" => $type, "message" => $errstr, - "fullFile" => $e->getFile(), - "file" => $errfile, + "fullFile" => $errfile, + "file" => Filesystem::cleanPath($errfile), "line" => $errline, - "trace" => $trace + "trace" => $printableTrace, + "thread" => $thread ]; global $lastExceptionError, $lastError; 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/BaseRail.php b/src/block/BaseRail.php index 971beffac..0bcb2f340 100644 --- a/src/block/BaseRail.php +++ b/src/block/BaseRail.php @@ -38,7 +38,7 @@ use function in_array; abstract class BaseRail extends Flowable{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($blockReplace->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){ + if($blockReplace->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } @@ -222,7 +222,7 @@ abstract class BaseRail extends Flowable{ public function onNearbyBlockChange() : void{ $world = $this->position->getWorld(); - if(!$this->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){ + if(!$this->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){ $world->useBreakOn($this->position); }else{ foreach($this->getCurrentShapeConnections() as $connection){ diff --git a/src/block/Bed.php b/src/block/Bed.php index 13b466026..312b21cd1 100644 --- a/src/block/Bed.php +++ b/src/block/Bed.php @@ -177,11 +177,11 @@ class Bed extends Transparent{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if($this->canBeSupportedAt($blockReplace)){ $this->facing = $player !== null ? $player->getHorizontalFacing() : Facing::NORTH; $next = $this->getSide($this->getOtherHalfSide()); - if($next->canBeReplaced() && $this->canBeSupportedBy($next->getSide(Facing::DOWN))){ + if($next->canBeReplaced() && $this->canBeSupportedAt($next)){ $nextState = clone $this; $nextState->head = true; $tx->addBlock($blockReplace->position, $this)->addBlock($next->position, $nextState); @@ -208,8 +208,8 @@ class Bed extends Transparent{ return parent::getAffectedBlocks(); } - private function canBeSupportedBy(Block $block) : bool{ - return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE()); + private function canBeSupportedAt(Block $block) : bool{ + return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE()); } public function getMaxStackSize() : int{ return 1; } diff --git a/src/block/Beetroot.php b/src/block/Beetroot.php index b87a841ea..cf92fdda2 100644 --- a/src/block/Beetroot.php +++ b/src/block/Beetroot.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; class Beetroot extends Crops{ @@ -33,7 +33,7 @@ class Beetroot extends Crops{ if($this->age >= self::MAX_AGE){ return [ VanillaItems::BEETROOT(), - VanillaItems::BEETROOT_SEEDS()->setCount(mt_rand(0, 3)) + VanillaItems::BEETROOT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0)) ]; } diff --git a/src/block/Bell.php b/src/block/Bell.php index 753d6453d..3f15d6909 100644 --- a/src/block/Bell.php +++ b/src/block/Bell.php @@ -35,6 +35,7 @@ use pocketmine\math\Facing; use pocketmine\math\RayTraceResult; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\BellRingSound; @@ -87,46 +88,44 @@ final class Bell extends Transparent{ return $this; } - private function canBeSupportedBy(Block $block, int $face) : bool{ - return !$block->getSupportType($face)->equals(SupportType::NONE()); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return !$block->getAdjacentSupportType($face)->equals(SupportType::NONE()); } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){ + return false; + } if($face === Facing::UP){ - if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()), Facing::UP)){ - return false; - } if($player !== null){ $this->setFacing(Facing::opposite($player->getHorizontalFacing())); } $this->setAttachmentType(BellAttachmentType::FLOOR()); }elseif($face === Facing::DOWN){ - if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()), Facing::DOWN)){ - return false; - } $this->setAttachmentType(BellAttachmentType::CEILING()); }else{ $this->setFacing($face); - if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))), $face)){ - $this->setAttachmentType(BellAttachmentType::ONE_WALL()); - }else{ - return false; - } - if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)), Facing::opposite($face))){ - $this->setAttachmentType(BellAttachmentType::TWO_WALLS()); - } + $this->setAttachmentType( + $this->canBeSupportedAt($blockReplace, $face) ? + BellAttachmentType::TWO_WALLS() : + BellAttachmentType::ONE_WALL() + ); } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } public function onNearbyBlockChange() : void{ - if( - ($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP), Facing::DOWN)) || - ($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN), Facing::UP)) || - ($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)) || - ($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing), Facing::opposite($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing))) - ){ - $this->position->getWorld()->useBreakOn($this->position); + foreach(match($this->attachmentType){ + BellAttachmentType::CEILING() => [Facing::UP], + BellAttachmentType::FLOOR() => [Facing::DOWN], + BellAttachmentType::ONE_WALL() => [Facing::opposite($this->facing)], + BellAttachmentType::TWO_WALLS() => [$this->facing, Facing::opposite($this->facing)], + default => throw new AssumptionFailedError("All cases of BellAttachmentType must be handled") + } as $supportBlockDirection){ + if(!$this->canBeSupportedAt($this, $supportBlockDirection)){ + $this->position->getWorld()->useBreakOn($this->position); + break; + } } } @@ -159,13 +158,11 @@ final class Bell extends Transparent{ } private function isValidFaceToRing(int $faceHit) : bool{ - return ( - $this->attachmentType->equals(BellAttachmentType::CEILING()) || - ($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)) || - ( - ($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) && - ($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true)) - ) - ); + return match($this->attachmentType){ + BellAttachmentType::CEILING() => true, + BellAttachmentType::FLOOR() => Facing::axis($faceHit) === Facing::axis($this->facing), + BellAttachmentType::ONE_WALL(), BellAttachmentType::TWO_WALLS() => $faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true), + default => throw new AssumptionFailedError("All cases of BellAttachmentType must be handled") + }; } } 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/Block.php b/src/block/Block.php index b4203e6b6..0e045792f 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -41,6 +41,7 @@ use pocketmine\item\Item; use pocketmine\item\ItemBlock; use pocketmine\math\Axis; use pocketmine\math\AxisAlignedBB; +use pocketmine\math\Facing; use pocketmine\math\RayTraceResult; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -863,6 +864,10 @@ class Block{ return SupportType::FULL(); } + protected function getAdjacentSupportType(int $facing) : SupportType{ + return $this->getSide($facing)->getSupportType(Facing::opposite($facing)); + } + public function isFullCube() : bool{ $bb = $this->getCollisionBoxes(); 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/Button.php b/src/block/Button.php index 85d1d3e09..73bd1d556 100644 --- a/src/block/Button.php +++ b/src/block/Button.php @@ -52,7 +52,7 @@ abstract class Button extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ + if($this->canBeSupportedAt($blockReplace, $face)){ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } @@ -83,12 +83,12 @@ abstract class Button extends Flowable{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)){ + if(!$this->canBeSupportedAt($this, $this->facing)){ $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $support, int $face) : bool{ - return $support->getSupportType($face)->hasCenterSupport(); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return $block->getAdjacentSupportType(Facing::opposite($face))->hasCenterSupport(); } } diff --git a/src/block/Candle.php b/src/block/Candle.php index 5936a0812..7009acef6 100644 --- a/src/block/Candle.php +++ b/src/block/Candle.php @@ -104,8 +104,7 @@ class Candle extends Transparent{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - $down = $blockReplace->getSide(Facing::DOWN); - if(!$down->getSupportType(Facing::UP)->hasCenterSupport()){ + if(!$blockReplace->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport()){ return false; } $existing = $this->getCandleIfCompatibleType($blockReplace); diff --git a/src/block/Carrot.php b/src/block/Carrot.php index 895b0a37d..7d8947afa 100644 --- a/src/block/Carrot.php +++ b/src/block/Carrot.php @@ -23,15 +23,15 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; class Carrot extends Crops{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 4) : 1) + VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1) ]; } diff --git a/src/block/CarvedPumpkin.php b/src/block/CarvedPumpkin.php index 5fc73d088..98f3c2307 100644 --- a/src/block/CarvedPumpkin.php +++ b/src/block/CarvedPumpkin.php @@ -24,9 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; class CarvedPumpkin extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; } diff --git a/src/block/CaveVines.php b/src/block/CaveVines.php index 55f73fb65..e56b8b720 100644 --- a/src/block/CaveVines.php +++ b/src/block/CaveVines.php @@ -87,18 +87,18 @@ class CaveVines extends Flowable{ return $this->berries ? 14 : 0; } - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::DOWN)->equals(SupportType::FULL()) || $block->hasSameTypeId($this); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::UP)->equals(SupportType::FULL()) || $block->hasSameTypeId($this); } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::UP))){ + if(!$this->canBeSupportedAt($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{ - if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP))){ + if(!$this->canBeSupportedAt($blockReplace)){ return false; } $this->age = mt_rand(0, self::MAX_AGE); diff --git a/src/block/ChemistryTable.php b/src/block/ChemistryTable.php index 27fb63674..058e40288 100644 --- a/src/block/ChemistryTable.php +++ b/src/block/ChemistryTable.php @@ -24,14 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; final class ChemistryTable extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ //TODO diff --git a/src/block/Chest.php b/src/block/Chest.php index 45c190505..270c696c3 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\event\block\ChestPairEvent; use pocketmine\item\Item; @@ -36,7 +35,6 @@ use pocketmine\player\Player; class Chest extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; /** * @return AxisAlignedBB[] diff --git a/src/block/CoalOre.php b/src/block/CoalOre.php index 47a0e255d..19032b011 100644 --- a/src/block/CoalOre.php +++ b/src/block/CoalOre.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -31,7 +32,7 @@ class CoalOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::COAL() + VanillaItems::COAL()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1)) ]; } diff --git a/src/block/CopperOre.php b/src/block/CopperOre.php index ad02cc50f..3b8ef12a0 100644 --- a/src/block/CopperOre.php +++ b/src/block/CopperOre.php @@ -23,13 +23,16 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; final class CopperOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ - return [VanillaItems::RAW_COPPER()]; + return [ + VanillaItems::RAW_COPPER()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 5)), + ]; } public function isAffectedBySilkTouch() : bool{ return true; } diff --git a/src/block/Coral.php b/src/block/Coral.php index b621a3ab0..837a81857 100644 --- a/src/block/Coral.php +++ b/src/block/Coral.php @@ -32,7 +32,7 @@ use pocketmine\world\BlockTransaction; final class Coral extends BaseCoral{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($tx->fetchBlock($blockReplace->getPosition()->down()))){ + if(!$this->canBeSupportedAt($blockReplace)){ return false; } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); @@ -40,14 +40,14 @@ final class Coral extends BaseCoral{ public function onNearbyBlockChange() : void{ $world = $this->position->getWorld(); - if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){ + if(!$this->canBeSupportedAt($this)){ $world->useBreakOn($this->position); }else{ parent::onNearbyBlockChange(); } } - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::UP)->hasCenterSupport(); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport(); } } diff --git a/src/block/DiamondOre.php b/src/block/DiamondOre.php index 8407bdf15..b0486dcfb 100644 --- a/src/block/DiamondOre.php +++ b/src/block/DiamondOre.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -31,7 +32,7 @@ class DiamondOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::DIAMOND() + VanillaItems::DIAMOND()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1)) ]; } diff --git a/src/block/Door.php b/src/block/Door.php index 06da8e68b..a03427d5a 100644 --- a/src/block/Door.php +++ b/src/block/Door.php @@ -106,7 +106,7 @@ class Door extends Transparent{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN)) && !$this->getSide(Facing::DOWN) instanceof Door){ //Replace with common break method + if(!$this->canBeSupportedAt($this) && !$this->getSide(Facing::DOWN) instanceof Door){ //Replace with common break method $this->position->getWorld()->useBreakOn($this->position); //this will delete both halves if they exist } } @@ -114,8 +114,7 @@ class Door extends Transparent{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ if($face === Facing::UP){ $blockUp = $this->getSide(Facing::UP); - $blockDown = $this->getSide(Facing::DOWN); - if(!$blockUp->canBeReplaced() || !$this->canBeSupportedBy($blockDown)){ + if(!$blockUp->canBeReplaced() || !$this->canBeSupportedAt($blockReplace)){ return false; } @@ -172,7 +171,7 @@ class Door extends Transparent{ return parent::getAffectedBlocks(); } - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::UP)->hasEdgeSupport(); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport(); } } diff --git a/src/block/DoubleTallGrass.php b/src/block/DoubleTallGrass.php index fc37442f5..e90f2ec61 100644 --- a/src/block/DoubleTallGrass.php +++ b/src/block/DoubleTallGrass.php @@ -23,9 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; -use pocketmine\item\VanillaItems; -use function mt_rand; class DoubleTallGrass extends DoublePlant{ @@ -34,8 +33,8 @@ class DoubleTallGrass extends DoublePlant{ } public function getDropsForIncompatibleTool(Item $item) : array{ - if($this->top && mt_rand(0, 7) === 0){ - return [VanillaItems::WHEAT_SEEDS()]; + if($this->top){ + return FortuneDropHelper::grass($item); } return []; } diff --git a/src/block/EmeraldOre.php b/src/block/EmeraldOre.php index 6e997223d..8e5d96191 100644 --- a/src/block/EmeraldOre.php +++ b/src/block/EmeraldOre.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -31,7 +32,7 @@ class EmeraldOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::EMERALD() + VanillaItems::EMERALD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1)) ]; } diff --git a/src/block/EndPortalFrame.php b/src/block/EndPortalFrame.php index 08c903f11..612cf3723 100644 --- a/src/block/EndPortalFrame.php +++ b/src/block/EndPortalFrame.php @@ -24,14 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; class EndPortalFrame extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; protected bool $eye = false; diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 68c2805f9..26596eac9 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -26,7 +26,6 @@ namespace pocketmine\block; use pocketmine\block\inventory\EnderChestInventory; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -36,7 +35,6 @@ use pocketmine\player\Player; class EnderChest extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function getLightLevel() : int{ return 7; diff --git a/src/block/FloorCoralFan.php b/src/block/FloorCoralFan.php index efa560467..a267a0385 100644 --- a/src/block/FloorCoralFan.php +++ b/src/block/FloorCoralFan.php @@ -53,7 +53,7 @@ final class FloorCoralFan extends BaseCoral{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($tx->fetchBlock($blockReplace->getPosition()->down()))){ + if(!$this->canBeSupportedAt($blockReplace)){ return false; } if($player !== null){ @@ -75,15 +75,15 @@ final class FloorCoralFan extends BaseCoral{ public function onNearbyBlockChange() : void{ $world = $this->position->getWorld(); - if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){ + if(!$this->canBeSupportedAt($this)){ $world->useBreakOn($this->position); }else{ parent::onNearbyBlockChange(); } } - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::UP)->hasCenterSupport(); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport(); } public function asItem() : Item{ diff --git a/src/block/FlowerPot.php b/src/block/FlowerPot.php index 1c85ea0d8..4e4dbfa6e 100644 --- a/src/block/FlowerPot.php +++ b/src/block/FlowerPot.php @@ -90,7 +90,7 @@ class FlowerPot extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if(!$this->canBeSupportedAt($blockReplace)){ return false; } @@ -98,13 +98,13 @@ class FlowerPot extends Flowable{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::UP)->hasCenterSupport(); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport(); } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ diff --git a/src/block/Furnace.php b/src/block/Furnace.php index d943f8cc6..fbff73c93 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\crafting\FurnaceType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -35,7 +34,6 @@ use function mt_rand; class Furnace extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; protected FurnaceType $furnaceType; diff --git a/src/block/GildedBlackstone.php b/src/block/GildedBlackstone.php index e01d6bfdc..75c822f8e 100644 --- a/src/block/GildedBlackstone.php +++ b/src/block/GildedBlackstone.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -30,7 +31,7 @@ use function mt_rand; final class GildedBlackstone extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ - if(mt_rand(1, 10) === 1){ + if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){ return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 5))]; } diff --git a/src/block/GlazedTerracotta.php b/src/block/GlazedTerracotta.php index 5568703c2..b49347aef 100644 --- a/src/block/GlazedTerracotta.php +++ b/src/block/GlazedTerracotta.php @@ -26,12 +26,10 @@ namespace pocketmine\block; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; class GlazedTerracotta extends Opaque{ use ColoredTrait; use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ $this->color = DyeColor::BLACK(); diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index d1baaa7d2..39ce512a6 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -121,7 +121,7 @@ class GlowLichen extends Transparent{ $changed = false; foreach($this->faces as $face){ - if(!$this->getSide($face)->getSupportType(Facing::opposite($face))->equals(SupportType::FULL())){ + if(!$this->getAdjacentSupportType($face)->equals(SupportType::FULL())){ unset($this->faces[$face]); $changed = true; } @@ -275,7 +275,7 @@ class GlowLichen extends Transparent{ private function getAvailableFaces() : array{ $faces = []; foreach(Facing::ALL as $face){ - if(!$this->hasFace($face) && $this->getSide($face)->getSupportType(Facing::opposite($face))->equals(SupportType::FULL())){ + if(!$this->hasFace($face) && $this->getAdjacentSupportType($face)->equals(SupportType::FULL())){ $faces[$face] = $face; } } diff --git a/src/block/Glowstone.php b/src/block/Glowstone.php index 3b567aa5b..1fead9abe 100644 --- a/src/block/Glowstone.php +++ b/src/block/Glowstone.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; +use function min; class Glowstone extends Transparent{ @@ -35,7 +36,7 @@ class Glowstone extends Transparent{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::GLOWSTONE_DUST()->setCount(mt_rand(2, 4)) + VanillaItems::GLOWSTONE_DUST()->setCount(min(4, FortuneDropHelper::discrete($item, 2, 4))) ]; } diff --git a/src/block/Gravel.php b/src/block/Gravel.php index 4bff2208f..6445ce30e 100644 --- a/src/block/Gravel.php +++ b/src/block/Gravel.php @@ -25,15 +25,15 @@ namespace pocketmine\block; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; class Gravel extends Opaque implements Fallable{ use FallableTrait; public function getDropsForCompatibleTool(Item $item) : array{ - if(mt_rand(1, 10) === 1){ + if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){ return [ VanillaItems::FLINT() ]; diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php index f14720fe4..21bc5c20a 100644 --- a/src/block/ItemFrame.php +++ b/src/block/ItemFrame.php @@ -163,18 +163,18 @@ class ItemFrame extends Flowable{ return true; } - private function canBeSupportedBy(Block $block, int $face) : bool{ - return !$block->getSupportType($face)->equals(SupportType::NONE()); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return !$block->getAdjacentSupportType($face)->equals(SupportType::NONE()); } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)){ + if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){ $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{ - if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ + if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){ return false; } diff --git a/src/block/Ladder.php b/src/block/Ladder.php index 66e5f28cd..83adada82 100644 --- a/src/block/Ladder.php +++ b/src/block/Ladder.php @@ -70,7 +70,7 @@ class Ladder extends Transparent{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face) && Facing::axis($face) !== Axis::Y){ + if($this->canBeSupportedAt($blockReplace, Facing::opposite($face)) && Facing::axis($face) !== Axis::Y){ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } @@ -79,12 +79,12 @@ class Ladder extends Transparent{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)){ //Replace with common break method + if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){ //Replace with common break method $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $block, int $face) : bool{ - return $block->getSupportType($face)->equals(SupportType::FULL()); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return $block->getAdjacentSupportType($face)->equals(SupportType::FULL()); } } diff --git a/src/block/Lantern.php b/src/block/Lantern.php index bc50c3cb6..8ebc8ba2c 100644 --- a/src/block/Lantern.php +++ b/src/block/Lantern.php @@ -77,22 +77,23 @@ class Lantern extends Transparent{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP), Facing::DOWN) && !$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN), Facing::UP)){ + $downSupport = $this->canBeSupportedAt($blockReplace, Facing::DOWN); + if(!$downSupport && !$this->canBeSupportedAt($blockReplace, Facing::UP)){ return false; } - $this->hanging = ($face === Facing::DOWN || !$this->canBeSupportedBy($this->position->getWorld()->getBlock($blockReplace->getPosition()->down()), Facing::UP)); + $this->hanging = $face === Facing::DOWN || !$downSupport; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } public function onNearbyBlockChange() : void{ $face = $this->hanging ? Facing::UP : Facing::DOWN; - if(!$this->canBeSupportedBy($this->getSide($face), Facing::opposite($face))){ + if(!$this->canBeSupportedAt($this, $face)){ $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $block, int $face) : bool{ - return $block->getSupportType($face)->hasCenterSupport(); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return $block->getAdjacentSupportType($face)->hasCenterSupport(); } } diff --git a/src/block/LapisOre.php b/src/block/LapisOre.php index 656b0a895..ce54321a4 100644 --- a/src/block/LapisOre.php +++ b/src/block/LapisOre.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -31,7 +32,7 @@ class LapisOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::LAPIS_LAZULI()->setCount(mt_rand(4, 8)) + VanillaItems::LAPIS_LAZULI()->setCount(FortuneDropHelper::weighted($item, min: 4, maxBase: 9)) ]; } diff --git a/src/block/Leaves.php b/src/block/Leaves.php index dc5e9ce80..235190454 100644 --- a/src/block/Leaves.php +++ b/src/block/Leaves.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\block\utils\LeavesType; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -138,7 +139,8 @@ class Leaves extends Transparent{ } $drops = []; - if(mt_rand(1, 20) === 1){ //Saplings + if(FortuneDropHelper::bonusChanceDivisor($item, 20, 4)){ //Saplings + // TODO: according to the wiki, the jungle saplings have a different drop rate $sapling = (match($this->leavesType){ LeavesType::ACACIA() => VanillaBlocks::ACACIA_SAPLING(), LeavesType::BIRCH() => VanillaBlocks::BIRCH_SAPLING(), @@ -155,10 +157,13 @@ class Leaves extends Transparent{ $drops[] = $sapling; } } - if(($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) && mt_rand(1, 200) === 1){ //Apples + if( + ($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) && + FortuneDropHelper::bonusChanceDivisor($item, 200, 20) + ){ //Apples $drops[] = VanillaItems::APPLE(); } - if(mt_rand(1, 50) === 1){ + if(FortuneDropHelper::bonusChanceDivisor($item, 50, 5)){ $drops[] = VanillaItems::STICK()->setCount(mt_rand(1, 2)); } diff --git a/src/block/Lectern.php b/src/block/Lectern.php index a80426acf..d9f07d22b 100644 --- a/src/block/Lectern.php +++ b/src/block/Lectern.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\tile\Lectern as TileLectern; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -39,7 +38,6 @@ use function count; class Lectern extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; protected int $viewedPage = 0; protected ?WritableBookBase $book = null; diff --git a/src/block/Lever.php b/src/block/Lever.php index 5d86ac7d2..e4b8c0811 100644 --- a/src/block/Lever.php +++ b/src/block/Lever.php @@ -66,7 +66,7 @@ class Lever extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ + if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){ return false; } @@ -90,8 +90,7 @@ class Lever extends Flowable{ } public function onNearbyBlockChange() : void{ - $facing = $this->facing->getFacing(); - if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($facing)), $facing)){ + if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing->getFacing()))){ $this->position->getWorld()->useBreakOn($this->position); } } @@ -107,8 +106,8 @@ class Lever extends Flowable{ return true; } - private function canBeSupportedBy(Block $block, int $face) : bool{ - return $block->getSupportType($face)->hasCenterSupport(); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return $block->getAdjacentSupportType($face)->hasCenterSupport(); } //TODO diff --git a/src/block/Liquid.php b/src/block/Liquid.php index 98f1d5627..a56f666a2 100644 --- a/src/block/Liquid.php +++ b/src/block/Liquid.php @@ -312,7 +312,7 @@ abstract class Liquid extends Transparent{ } if($adjacentDecay <= self::MAX_DECAY){ - $calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto'])); + $calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), $this->canFlowInto(...)); foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){ $this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false); } diff --git a/src/block/Loom.php b/src/block/Loom.php index a10b57723..d3dd4f3c7 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -25,14 +25,12 @@ namespace pocketmine\block; use pocketmine\block\inventory\LoomInventory; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; final class Loom extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ diff --git a/src/block/Melon.php b/src/block/Melon.php index 42d54c0ab..4a32332e1 100644 --- a/src/block/Melon.php +++ b/src/block/Melon.php @@ -23,15 +23,16 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; +use function min; class Melon extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::MELON()->setCount(mt_rand(3, 7)) + VanillaItems::MELON()->setCount(min(9, FortuneDropHelper::discrete($item, 3, 7))) ]; } diff --git a/src/block/NetherGoldOre.php b/src/block/NetherGoldOre.php index 7782f4fc4..fd6745df4 100644 --- a/src/block/NetherGoldOre.php +++ b/src/block/NetherGoldOre.php @@ -23,14 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; final class NetherGoldOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ - return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 6))]; + return [VanillaItems::GOLD_NUGGET()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 6))]; } public function isAffectedBySilkTouch() : bool{ return true; } diff --git a/src/block/NetherQuartzOre.php b/src/block/NetherQuartzOre.php index c2ab20491..0981974ee 100644 --- a/src/block/NetherQuartzOre.php +++ b/src/block/NetherQuartzOre.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -31,7 +32,7 @@ class NetherQuartzOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::NETHER_QUARTZ() + VanillaItems::NETHER_QUARTZ()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1)) ]; } diff --git a/src/block/NetherVines.php b/src/block/NetherVines.php index adf611785..c78000fa1 100644 --- a/src/block/NetherVines.php +++ b/src/block/NetherVines.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; @@ -82,16 +83,13 @@ class NetherVines extends Flowable{ return true; } - private function getSupportFace() : int{ - return Facing::opposite($this->growthFace); - } - - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType($this->getSupportFace())->hasCenterSupport() || $block->hasSameTypeId($this); + private function canBeSupportedAt(Block $block) : bool{ + $supportBlock = $block->getSide(Facing::opposite($this->growthFace)); + return $supportBlock->getSupportType($this->growthFace)->hasCenterSupport() || $supportBlock->hasSameTypeId($this); } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide($this->getSupportFace()))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } @@ -108,7 +106,7 @@ class NetherVines extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($blockReplace->getSide($this->getSupportFace()))){ + if(!$this->canBeSupportedAt($blockReplace)){ return false; } $this->age = mt_rand(0, self::MAX_AGE - 1); @@ -179,7 +177,7 @@ class NetherVines extends Flowable{ } public function getDropsForCompatibleTool(Item $item) : array{ - if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || mt_rand(1, 3) === 1){ + if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || FortuneDropHelper::bonusChanceFixed($item, 1 / 3, 2 / 9)){ return [$this->asItem()]; } return []; diff --git a/src/block/NetherWartPlant.php b/src/block/NetherWartPlant.php index 76de2a470..6a8fe1f7a 100644 --- a/src/block/NetherWartPlant.php +++ b/src/block/NetherWartPlant.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Item; @@ -85,7 +86,7 @@ class NetherWartPlant extends Flowable{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - $this->asItem()->setCount($this->age === self::MAX_AGE ? mt_rand(2, 4) : 1) + $this->asItem()->setCount($this->age === self::MAX_AGE ? FortuneDropHelper::discrete($item, 2, 4) : 1) ]; } } diff --git a/src/block/Potato.php b/src/block/Potato.php index 47d39f612..4d1bbf979 100644 --- a/src/block/Potato.php +++ b/src/block/Potato.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; @@ -31,7 +32,8 @@ class Potato extends Crops{ public function getDropsForCompatibleTool(Item $item) : array{ $result = [ - VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 5) : 1) + //min/max would be 2-5 in Java + VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1) ]; if($this->age >= self::MAX_AGE && mt_rand(0, 49) === 0){ $result[] = VanillaItems::POISONOUS_POTATO(); diff --git a/src/block/PressurePlate.php b/src/block/PressurePlate.php index 7f9403b74..4df0bf927 100644 --- a/src/block/PressurePlate.php +++ b/src/block/PressurePlate.php @@ -45,18 +45,18 @@ abstract class PressurePlate extends Transparent{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){ + if($this->canBeSupportedAt($blockReplace)){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } return false; } - private function canBeSupportedBy(Block $block) : bool{ - return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE()); + private function canBeSupportedAt(Block $block) : bool{ + return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE()); } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } diff --git a/src/block/RedstoneComparator.php b/src/block/RedstoneComparator.php index 2158f1a84..8b436020b 100644 --- a/src/block/RedstoneComparator.php +++ b/src/block/RedstoneComparator.php @@ -85,7 +85,7 @@ class RedstoneComparator extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){ + if($this->canBeSupportedAt($blockReplace)){ if($player !== null){ $this->facing = Facing::opposite($player->getHorizontalFacing()); } @@ -102,13 +102,13 @@ class RedstoneComparator extends Flowable{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $block) : bool{ - return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE()); + private function canBeSupportedAt(Block $block) : bool{ + return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE()); } //TODO: redstone functionality diff --git a/src/block/RedstoneOre.php b/src/block/RedstoneOre.php index 74708c2bd..75f5063ee 100644 --- a/src/block/RedstoneOre.php +++ b/src/block/RedstoneOre.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; use pocketmine\item\VanillaItems; @@ -81,7 +82,7 @@ class RedstoneOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::REDSTONE_DUST()->setCount(mt_rand(4, 5)) + VanillaItems::REDSTONE_DUST()->setCount(FortuneDropHelper::discrete($item, 4, 5)) ]; } diff --git a/src/block/RedstoneRepeater.php b/src/block/RedstoneRepeater.php index d4f145238..518eeb9e5 100644 --- a/src/block/RedstoneRepeater.php +++ b/src/block/RedstoneRepeater.php @@ -68,7 +68,7 @@ class RedstoneRepeater extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){ + if($this->canBeSupportedAt($blockReplace)){ if($player !== null){ $this->facing = Facing::opposite($player->getHorizontalFacing()); } @@ -88,13 +88,13 @@ class RedstoneRepeater extends Flowable{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $block) : bool{ - return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE()); + private function canBeSupportedAt(Block $block) : bool{ + return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE()); } //TODO: redstone functionality diff --git a/src/block/RedstoneWire.php b/src/block/RedstoneWire.php index 022672b5d..167365f56 100644 --- a/src/block/RedstoneWire.php +++ b/src/block/RedstoneWire.php @@ -35,7 +35,7 @@ class RedstoneWire extends Flowable{ use AnalogRedstoneSignalEmitterTrait; public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if($this->canBeSupportedAt($blockReplace)){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } return false; @@ -49,13 +49,13 @@ class RedstoneWire extends Flowable{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::UP)->hasCenterSupport(); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport(); } public function asItem() : Item{ diff --git a/src/block/SeaLantern.php b/src/block/SeaLantern.php index 27ed73f0d..ff0c97d73 100644 --- a/src/block/SeaLantern.php +++ b/src/block/SeaLantern.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use function min; class SeaLantern extends Transparent{ @@ -34,7 +36,7 @@ class SeaLantern extends Transparent{ public function getDropsForCompatibleTool(Item $item) : array{ return [ - VanillaItems::PRISMARINE_CRYSTALS()->setCount(3) + VanillaItems::PRISMARINE_CRYSTALS()->setCount(min(5, FortuneDropHelper::discrete($item, 2, 3))) ]; } 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/SnowLayer.php b/src/block/SnowLayer.php index f2425455c..f561c8ff5 100644 --- a/src/block/SnowLayer.php +++ b/src/block/SnowLayer.php @@ -80,8 +80,8 @@ class SnowLayer extends Flowable implements Fallable{ return SupportType::NONE(); } - private function canBeSupportedBy(Block $b) : bool{ - return $b->getSupportType(Facing::UP)->equals(SupportType::FULL()); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::FULL()); } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ @@ -91,7 +91,7 @@ class SnowLayer extends Flowable implements Fallable{ } $this->layers = $blockReplace->layers + 1; } - if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){ + if($this->canBeSupportedAt($blockReplace)){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } diff --git a/src/block/SporeBlossom.php b/src/block/SporeBlossom.php index 73e31edf2..909932178 100644 --- a/src/block/SporeBlossom.php +++ b/src/block/SporeBlossom.php @@ -32,12 +32,12 @@ use pocketmine\world\BlockTransaction; final class SporeBlossom extends Flowable{ - private function canBeSupportedBy(Block $block) : bool{ - return $block->getSupportType(Facing::DOWN)->equals(SupportType::FULL()); + private function canBeSupportedAt(Block $block) : bool{ + return $block->getAdjacentSupportType(Facing::UP)->equals(SupportType::FULL()); } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP))){ + if(!$this->canBeSupportedAt($blockReplace)){ return false; } @@ -45,7 +45,7 @@ final class SporeBlossom extends Flowable{ } public function onNearbyBlockChange() : void{ - if(!$this->canBeSupportedBy($this->getSide(Facing::UP))){ + if(!$this->canBeSupportedAt($this)){ $this->position->getWorld()->useBreakOn($this->position); } } diff --git a/src/block/Stem.php b/src/block/Stem.php index 5f06a46cb..252b28aeb 100644 --- a/src/block/Stem.php +++ b/src/block/Stem.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -30,11 +31,35 @@ use function array_rand; use function mt_rand; abstract class Stem extends Crops{ + protected int $facing = Facing::UP; + + protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ + parent::describeBlockOnlyState($w); + $w->facingExcept($this->facing, Facing::DOWN); + } + + public function getFacing() : int{ return $this->facing; } + + /** @return $this */ + public function setFacing(int $facing) : self{ + if($facing === Facing::DOWN){ + throw new \InvalidArgumentException("DOWN is not a valid facing for this block"); + } + $this->facing = $facing; + return $this; + } abstract protected function getPlant() : Block; + public function onNearbyBlockChange() : void{ + 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{ - if(mt_rand(0, 2) === 1){ + if($this->facing === Facing::UP && mt_rand(0, 2) === 1){ $world = $this->position->getWorld(); if($this->age < self::MAX_AGE){ $block = clone $this; @@ -52,11 +77,13 @@ abstract class Stem extends Crops{ } } - $side = $this->getSide(Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]); + $facing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]; + $side = $this->getSide($facing); if($side->getTypeId() === BlockTypeIds::AIR && $side->getSide(Facing::DOWN)->hasTypeTag(BlockTypeTags::DIRT)){ $ev = new BlockGrowEvent($side, $grow); $ev->call(); if(!$ev->isCancelled()){ + $world->setBlock($this->position, $this->setFacing($facing)); $world->setBlock($side->position, $ev->getNewState()); } } diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 7736381e4..eb7dc68c2 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\inventory\StonecutterInventory; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -35,7 +34,6 @@ use pocketmine\player\Player; class Stonecutter extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ diff --git a/src/block/SweetBerryBush.php b/src/block/SweetBerryBush.php index b75a343ec..5ead39390 100644 --- a/src/block/SweetBerryBush.php +++ b/src/block/SweetBerryBush.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; use pocketmine\entity\Living; @@ -108,13 +109,14 @@ class SweetBerryBush extends Flowable{ } public function getDropsForCompatibleTool(Item $item) : array{ - if(($dropAmount = $this->getBerryDropAmount()) > 0){ - return [ - $this->asItem()->setCount($dropAmount) - ]; - } - - return []; + $count = match($this->age){ + self::STAGE_MATURE => FortuneDropHelper::discrete($item, 2, 3), + self::STAGE_BUSH_SOME_BERRIES => FortuneDropHelper::discrete($item, 1, 2), + default => 0 + }; + return [ + $this->asItem()->setCount($count) + ]; } public function onNearbyBlockChange() : void{ diff --git a/src/block/TallGrass.php b/src/block/TallGrass.php index e8f533325..019b911bc 100644 --- a/src/block/TallGrass.php +++ b/src/block/TallGrass.php @@ -23,13 +23,12 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; -use pocketmine\item\VanillaItems; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -use function mt_rand; class TallGrass extends Flowable{ @@ -56,13 +55,7 @@ class TallGrass extends Flowable{ } public function getDropsForIncompatibleTool(Item $item) : array{ - if(mt_rand(0, 15) === 0){ - return [ - VanillaItems::WHEAT_SEEDS() - ]; - } - - return []; + return FortuneDropHelper::grass($item); } public function getFlameEncouragement() : int{ diff --git a/src/block/Torch.php b/src/block/Torch.php index 163c0d115..66b62bc19 100644 --- a/src/block/Torch.php +++ b/src/block/Torch.php @@ -26,7 +26,6 @@ namespace pocketmine\block; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; -use pocketmine\math\Axis; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -56,15 +55,13 @@ class Torch extends Flowable{ } public function onNearbyBlockChange() : void{ - $face = Facing::opposite($this->facing); - - if(!$this->canBeSupportedBy($this->getSide($face), $this->facing)){ + if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){ $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{ - if($face !== Facing::DOWN && $this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ + if($face !== Facing::DOWN && $this->canBeSupportedAt($blockReplace, Facing::opposite($face))){ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); }else{ @@ -75,8 +72,7 @@ class Torch extends Flowable{ Facing::EAST, Facing::DOWN ] as $side){ - $block = $this->getSide($side); - if($this->canBeSupportedBy($block, Facing::opposite($side))){ + if($this->canBeSupportedAt($blockReplace, $side)){ $this->facing = Facing::opposite($side); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } @@ -85,8 +81,9 @@ class Torch extends Flowable{ return false; } - private function canBeSupportedBy(Block $support, int $face) : bool{ - return ($face === Facing::UP && $support->getSupportType($face)->hasCenterSupport()) || - (Facing::axis($face) !== Axis::Y && $support->getSupportType($face)->equals(SupportType::FULL())); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return $face === Facing::DOWN ? + $block->getAdjacentSupportType($face)->hasCenterSupport() : + $block->getAdjacentSupportType($face)->equals(SupportType::FULL()); } } 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/WallCoralFan.php b/src/block/WallCoralFan.php index 432dd5ddb..f9dece1cd 100644 --- a/src/block/WallCoralFan.php +++ b/src/block/WallCoralFan.php @@ -42,7 +42,7 @@ final class WallCoralFan extends BaseCoral{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ $axis = Facing::axis($face); - if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ + if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){ return false; } $this->facing = $face; @@ -54,15 +54,15 @@ final class WallCoralFan extends BaseCoral{ public function onNearbyBlockChange() : void{ $world = $this->position->getWorld(); - if(!$this->canBeSupportedBy($world->getBlock($this->position->getSide(Facing::opposite($this->facing))), $this->facing)){ + if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){ $world->useBreakOn($this->position); }else{ parent::onNearbyBlockChange(); } } - private function canBeSupportedBy(Block $block, int $face) : bool{ - return $block->getSupportType($face)->hasCenterSupport(); + private function canBeSupportedAt(Block $block, int $face) : bool{ + return $block->getAdjacentSupportType($face)->hasCenterSupport(); } public function asItem() : Item{ diff --git a/src/block/Wheat.php b/src/block/Wheat.php index 15701c976..92f155f76 100644 --- a/src/block/Wheat.php +++ b/src/block/Wheat.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use function mt_rand; class Wheat extends Crops{ @@ -33,7 +33,7 @@ class Wheat extends Crops{ if($this->age >= self::MAX_AGE){ return [ VanillaItems::WHEAT(), - VanillaItems::WHEAT_SEEDS()->setCount(mt_rand(0, 3)) + VanillaItems::WHEAT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0)) ]; }else{ return [ 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/block/utils/FortuneDropHelper.php b/src/block/utils/FortuneDropHelper.php new file mode 100644 index 000000000..4bce36138 --- /dev/null +++ b/src/block/utils/FortuneDropHelper.php @@ -0,0 +1,150 @@ +getEnchantmentLevel(VanillaEnchantments::FORTUNE()); + + return mt_rand($min, + $fortuneLevel > 0 && mt_rand() / mt_getrandmax() > 2 / ($fortuneLevel + 2) ? + $maxBase * ($fortuneLevel + 1) : + $maxBase + ); + } + + /** + * Increases the drop amount according to a binomial distribution. The function will roll maxBase+level times, and add 1 + * if a random number between 0-1 is less than the given probability. Each level of fortune adds one extra roll. + * + * As many as maxBase+level items can be dropped. This applies even if the fortune level is 0. + * + * @param float $chance The chance of adding 1 to the amount for each roll, must be in the range 0-1 + * @param int $min Minimum amount + * @param int $minRolls Number of rolls if fortune level is 0, added to fortune level to calculate total rolls + * + * @return int the number of items to drop + */ + public static function binomial(Item $usedItem, int $min, int $minRolls = 3, float $chance = 4 / 7) : int{ + $fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE()); + + $count = $min; + $rolls = $minRolls + $fortuneLevel; + for($i = 0; $i < $rolls; ++$i){ + if(mt_rand() / mt_getrandmax() < $chance){ + ++$count; + } + } + + return $count; + } + + /** + * Grass have a fixed chance to drop wheat seed. + * Fortune level increases the maximum number of seeds that can be dropped. + * A discrete uniform distribution is used to determine the number of seeds dropped. + * + * TODO: I'm not sure this really belongs here, but it's preferable not to duplicate this code between grass and + * tall grass. + * + * @return Item[] + */ + public static function grass(Item $usedItem) : array{ + if(FortuneDropHelper::bonusChanceDivisor($usedItem, 8, 2)){ + return [ + VanillaItems::WHEAT_SEEDS() + ]; + } + + return []; + } + + /** + * Adds the fortune level to the base max and picks a random number between the minimim and adjusted maximum. + * Each amount in the range has an equal chance of being picked. + * + * @param int $maxBase Maximum base amount, as if the fortune level was 0 + * + * @return int the number of items to drop + */ + public static function discrete(Item $usedItem, int $min, int $maxBase) : int{ + if($maxBase < $min){ + throw new \InvalidArgumentException("Minimum base drop amount must be less than or equal to maximum base drop amount"); + } + + $max = $maxBase + $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE()); + return mt_rand($min, $max); + } + + /** + * Calculates a chance of getting an extra bonus drop by reducing the chance divisor by a given amount per fortune + * level. + * + * @param int $divisorBase The number to divide 1 by to get the chance, as if the fortune level was 0 + * @param int $divisorSubtractPerLevel The amount to subtract from the divisor for each level of fortune + * + * @return bool whether the bonus drop should be added + */ + public static function bonusChanceDivisor(Item $usedItem, int $divisorBase, int $divisorSubtractPerLevel) : bool{ + $fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE()); + return mt_rand(1, max(1, $divisorBase - ($fortuneLevel * $divisorSubtractPerLevel))) === 1; + } + + /** + * Calculates a chance of getting an extra bonus drop by increasing the chance by a fixed amount per fortune level. + * + * @param float $chanceBase The base chance of getting a bonus drop, as if the fortune level was 0 + * @param float $addedChancePerLevel The amount to add to the chance for each level of fortune + */ + public static function bonusChanceFixed(Item $usedItem, float $chanceBase, float $addedChancePerLevel) : bool{ + $fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE()); + $chance = min(1, $chanceBase + ($fortuneLevel * $addedChancePerLevel)); + return mt_rand() / mt_getrandmax() < $chance; + } +} diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 0a28ca328..812ff83e0 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -209,9 +209,6 @@ final class CraftingManagerFromDataHelper{ public static function make(string $directoryPath) : CraftingManager{ $result = new CraftingManager(); - $ingredientDeserializerFunc = \Closure::fromCallable([self::class, "deserializeIngredient"]); - $itemDeserializerFunc = \Closure::fromCallable([self::class, 'deserializeItemStack']); - foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'shapeless_crafting.json'), ShapelessRecipeData::class) as $recipe){ $recipeType = match($recipe->block){ "crafting_table" => ShapelessRecipeType::CRAFTING(), @@ -225,7 +222,7 @@ final class CraftingManagerFromDataHelper{ } $inputs = []; foreach($recipe->input as $inputData){ - $input = $ingredientDeserializerFunc($inputData); + $input = self::deserializeIngredient($inputData); if($input === null){ //unknown input item continue 2; } @@ -233,7 +230,7 @@ final class CraftingManagerFromDataHelper{ } $outputs = []; foreach($recipe->output as $outputData){ - $output = $itemDeserializerFunc($outputData); + $output = self::deserializeItemStack($outputData); if($output === null){ //unknown output item continue 2; } @@ -251,7 +248,7 @@ final class CraftingManagerFromDataHelper{ } $inputs = []; foreach(Utils::stringifyKeys($recipe->input) as $symbol => $inputData){ - $input = $ingredientDeserializerFunc($inputData); + $input = self::deserializeIngredient($inputData); if($input === null){ //unknown input item continue 2; } @@ -259,7 +256,7 @@ final class CraftingManagerFromDataHelper{ } $outputs = []; foreach($recipe->output as $outputData){ - $output = $itemDeserializerFunc($outputData); + $output = self::deserializeItemStack($outputData); if($output === null){ //unknown output item continue 2; } diff --git a/src/crash/CrashDump.php b/src/crash/CrashDump.php index d7223eb2f..40af53fd0 100644 --- a/src/crash/CrashDump.php +++ b/src/crash/CrashDump.php @@ -29,11 +29,13 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\plugin\PluginBase; use pocketmine\plugin\PluginManager; use pocketmine\Server; +use pocketmine\thread\ThreadCrashInfoFrame; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; use pocketmine\VersionInfo; use Symfony\Component\Filesystem\Path; +use function array_map; use function base64_encode; use function error_get_last; use function file; @@ -186,7 +188,7 @@ class CrashDump{ if($error === null){ throw new \RuntimeException("Crash error information missing - did something use exit()?"); } - $error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump + $error["trace"] = Utils::printableTrace(Utils::currentTrace(3)); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump $error["fullFile"] = $error["file"]; $error["file"] = Filesystem::cleanPath($error["file"]); try{ @@ -201,9 +203,6 @@ class CrashDump{ $error["message"] = mb_scrub($error["message"], 'UTF-8'); if(isset($lastError)){ - if(isset($lastError["trace"])){ - $lastError["trace"] = Utils::printableTrace($lastError["trace"]); - } $this->data->lastError = $lastError; $this->data->lastError["message"] = mb_scrub($this->data->lastError["message"], 'UTF-8'); } @@ -215,10 +214,11 @@ class CrashDump{ $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE; if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace foreach($error["trace"] as $frame){ - if(!isset($frame["file"])){ + $frameFile = $frame->getFile(); + if($frameFile === null){ continue; //PHP core } - if($this->determinePluginFromFile($frame["file"], false)){ + if($this->determinePluginFromFile($frameFile, false)){ break; } } @@ -233,7 +233,8 @@ class CrashDump{ } } - $this->data->trace = Utils::printableTrace($error["trace"]); + $this->data->trace = array_map(array: $error["trace"], callback: fn(ThreadCrashInfoFrame $frame) => $frame->getPrintableFrame()); + $this->data->thread = $error["thread"]; } private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{ diff --git a/src/crash/CrashDumpData.php b/src/crash/CrashDumpData.php index 0f5358be5..b71e6f405 100644 --- a/src/crash/CrashDumpData.php +++ b/src/crash/CrashDumpData.php @@ -37,6 +37,8 @@ final class CrashDumpData implements \JsonSerializable{ /** @var mixed[] */ public array $error; + public string $thread; + public string $plugin_involvement; public string $plugin = ""; diff --git a/src/crash/CrashDumpRenderer.php b/src/crash/CrashDumpRenderer.php index 2858f43ec..617dcb7ab 100644 --- a/src/crash/CrashDumpRenderer.php +++ b/src/crash/CrashDumpRenderer.php @@ -64,6 +64,7 @@ final class CrashDumpRenderer{ $this->addLine(); + $this->addLine("Thread: " . $this->data->thread); $this->addLine("Error: " . $this->data->error["message"]); $this->addLine("File: " . $this->data->error["file"]); $this->addLine("Line: " . $this->data->error["line"]); diff --git a/src/data/bedrock/EnchantmentIdMap.php b/src/data/bedrock/EnchantmentIdMap.php index d7f436fa3..1d974ed6e 100644 --- a/src/data/bedrock/EnchantmentIdMap.php +++ b/src/data/bedrock/EnchantmentIdMap.php @@ -62,6 +62,7 @@ final class EnchantmentIdMap{ $this->register(EnchantmentIds::FIRE_ASPECT, VanillaEnchantments::FIRE_ASPECT()); $this->register(EnchantmentIds::EFFICIENCY, VanillaEnchantments::EFFICIENCY()); + $this->register(EnchantmentIds::FORTUNE, VanillaEnchantments::FORTUNE()); $this->register(EnchantmentIds::SILK_TOUCH, VanillaEnchantments::SILK_TOUCH()); $this->register(EnchantmentIds::UNBREAKING, VanillaEnchantments::UNBREAKING()); diff --git a/src/data/bedrock/PotionTypeIdMap.php b/src/data/bedrock/PotionTypeIdMap.php index 2e6fef3cc..8194f24a2 100644 --- a/src/data/bedrock/PotionTypeIdMap.php +++ b/src/data/bedrock/PotionTypeIdMap.php @@ -84,6 +84,7 @@ final class PotionTypeIdMap{ $this->register(PotionTypeIds::STRONG_TURTLE_MASTER, PotionType::STRONG_TURTLE_MASTER()); $this->register(PotionTypeIds::SLOW_FALLING, PotionType::SLOW_FALLING()); $this->register(PotionTypeIds::LONG_SLOW_FALLING, PotionType::LONG_SLOW_FALLING()); + $this->register(PotionTypeIds::STRONG_SLOWNESS, PotionType::STRONG_SLOWNESS()); } private function register(int $id, PotionType $type) : void{ diff --git a/src/data/bedrock/PotionTypeIds.php b/src/data/bedrock/PotionTypeIds.php index aa69461ce..f2d82f39f 100644 --- a/src/data/bedrock/PotionTypeIds.php +++ b/src/data/bedrock/PotionTypeIds.php @@ -66,4 +66,5 @@ final class PotionTypeIds{ public const STRONG_TURTLE_MASTER = 39; public const SLOW_FALLING = 40; public const LONG_SLOW_FALLING = 41; + public const STRONG_SLOWNESS = 42; } 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/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index fab50717c..8018d71b8 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -236,9 +236,12 @@ final class BlockStateDeserializerHelper{ /** @throws BlockStateDeserializeException */ public static function decodeStem(Stem $block, BlockStateReader $in) : Stem{ - //TODO: our stems don't support facings yet (facing_direction) - $in->todo(BlockStateNames::FACING_DIRECTION); - return self::decodeCrops($block, $in); + //In PM, we use Facing::UP to indicate that the stem is not attached to a pumpkin/melon, since this makes the + //most intuitive sense (the stem is pointing at the sky). However, Bedrock uses the DOWN state for this, which + //is absurd, and I refuse to make our API similarly absurd. + $facing = $in->readFacingWithoutUp(); + return self::decodeCrops($block, $in) + ->setFacing($facing === Facing::DOWN ? Facing::UP : $facing); } /** @throws BlockStateDeserializeException */ diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php index 7fc15fce6..97fa5532f 100644 --- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php @@ -223,8 +223,12 @@ final class BlockStateSerializerHelper{ } public static function encodeStem(Stem $block, BlockStateWriter $out) : BlockStateWriter{ + //In PM, we use Facing::UP to indicate that the stem is not attached to a pumpkin/melon, since this makes the + //most intuitive sense (the stem is pointing at the sky). However, Bedrock uses the DOWN state for this, which + //is absurd, and I refuse to make our API similarly absurd. + $facing = $block->getFacing(); return self::encodeCrops($block, $out) - ->writeHorizontalFacing(Facing::NORTH); //TODO: PM impl doesn't support this yet + ->writeFacingWithoutUp($facing === Facing::UP ? Facing::DOWN : $facing); } public static function encodeStone(string $type) : BlockStateWriter{ 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 c4864a7e7..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(), @@ -190,16 +200,17 @@ trait RuntimeEnumDeserializerTrait{ 29 => \pocketmine\item\PotionType::STRONG_LEAPING(), 30 => \pocketmine\item\PotionType::STRONG_POISON(), 31 => \pocketmine\item\PotionType::STRONG_REGENERATION(), - 32 => \pocketmine\item\PotionType::STRONG_STRENGTH(), - 33 => \pocketmine\item\PotionType::STRONG_SWIFTNESS(), - 34 => \pocketmine\item\PotionType::STRONG_TURTLE_MASTER(), - 35 => \pocketmine\item\PotionType::SWIFTNESS(), - 36 => \pocketmine\item\PotionType::THICK(), - 37 => \pocketmine\item\PotionType::TURTLE_MASTER(), - 38 => \pocketmine\item\PotionType::WATER(), - 39 => \pocketmine\item\PotionType::WATER_BREATHING(), - 40 => \pocketmine\item\PotionType::WEAKNESS(), - 41 => \pocketmine\item\PotionType::WITHER(), + 32 => \pocketmine\item\PotionType::STRONG_SLOWNESS(), + 33 => \pocketmine\item\PotionType::STRONG_STRENGTH(), + 34 => \pocketmine\item\PotionType::STRONG_SWIFTNESS(), + 35 => \pocketmine\item\PotionType::STRONG_TURTLE_MASTER(), + 36 => \pocketmine\item\PotionType::SWIFTNESS(), + 37 => \pocketmine\item\PotionType::THICK(), + 38 => \pocketmine\item\PotionType::TURTLE_MASTER(), + 39 => \pocketmine\item\PotionType::WATER(), + 40 => \pocketmine\item\PotionType::WATER_BREATHING(), + 41 => \pocketmine\item\PotionType::WEAKNESS(), + 42 => \pocketmine\item\PotionType::WITHER(), default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for PotionType") }; } diff --git a/src/data/runtime/RuntimeEnumSerializerTrait.php b/src/data/runtime/RuntimeEnumSerializerTrait.php index fd65ffb42..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, @@ -190,16 +200,17 @@ trait RuntimeEnumSerializerTrait{ \pocketmine\item\PotionType::STRONG_LEAPING() => 29, \pocketmine\item\PotionType::STRONG_POISON() => 30, \pocketmine\item\PotionType::STRONG_REGENERATION() => 31, - \pocketmine\item\PotionType::STRONG_STRENGTH() => 32, - \pocketmine\item\PotionType::STRONG_SWIFTNESS() => 33, - \pocketmine\item\PotionType::STRONG_TURTLE_MASTER() => 34, - \pocketmine\item\PotionType::SWIFTNESS() => 35, - \pocketmine\item\PotionType::THICK() => 36, - \pocketmine\item\PotionType::TURTLE_MASTER() => 37, - \pocketmine\item\PotionType::WATER() => 38, - \pocketmine\item\PotionType::WATER_BREATHING() => 39, - \pocketmine\item\PotionType::WEAKNESS() => 40, - \pocketmine\item\PotionType::WITHER() => 41, + \pocketmine\item\PotionType::STRONG_SLOWNESS() => 32, + \pocketmine\item\PotionType::STRONG_STRENGTH() => 33, + \pocketmine\item\PotionType::STRONG_SWIFTNESS() => 34, + \pocketmine\item\PotionType::STRONG_TURTLE_MASTER() => 35, + \pocketmine\item\PotionType::SWIFTNESS() => 36, + \pocketmine\item\PotionType::THICK() => 37, + \pocketmine\item\PotionType::TURTLE_MASTER() => 38, + \pocketmine\item\PotionType::WATER() => 39, + \pocketmine\item\PotionType::WATER_BREATHING() => 40, + \pocketmine\item\PotionType::WEAKNESS() => 41, + \pocketmine\item\PotionType::WITHER() => 42, default => throw new \pocketmine\utils\AssumptionFailedError("All PotionType cases should be covered") }); } 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 c6fdf34a7..4d5e10cb3 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -80,6 +80,17 @@ use const M_PI; abstract class Living extends Entity{ protected const DEFAULT_BREATH_TICKS = 300; + /** + * The default knockback multiplier when an entity is hit by another entity. + * Larger values knock the entity back with increased velocity. + */ + public const DEFAULT_KNOCKBACK_FORCE = 0.4; + /** + * Limit of an entity's vertical knockback velocity when hit by another entity. Without this limit, the entity + * may be knocked far up into the air with large knockback forces. + */ + public const DEFAULT_KNOCKBACK_VERTICAL_LIMIT = 0.4; + private const TAG_LEGACY_HEALTH = "HealF"; //TAG_Float private const TAG_HEALTH = "Health"; //TAG_Float private const TAG_BREATH_TICKS = "Air"; //TAG_Short @@ -217,6 +228,7 @@ abstract class Living extends Entity{ public function setSneaking(bool $value = true) : void{ $this->sneaking = $value; $this->networkPropertiesDirty = true; + $this->recalculateSize(); } public function isSprinting() : bool{ @@ -258,6 +270,8 @@ abstract class Living extends Entity{ if($this->isSwimming() || $this->isGliding()){ $width = $size->getWidth(); $this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale())); + }elseif($this->isSneaking()){ + $this->setSize((new EntitySizeInfo(3 / 4 * $size->getHeight(), $size->getWidth(), 3 / 4 * $size->getEyeHeight()))->scale($this->getScale())); }else{ $this->setSize($size->scale($this->getScale())); } @@ -543,14 +557,14 @@ abstract class Living extends Entity{ $e = $source->getChild(); if($e !== null){ $motion = $e->getMotion(); - $this->knockBack($motion->x, $motion->z, $source->getKnockBack()); + $this->knockBack($motion->x, $motion->z, $source->getKnockBack(), $source->getVerticalKnockBackLimit()); } }elseif($source instanceof EntityDamageByEntityEvent){ $e = $source->getDamager(); if($e !== null){ $deltaX = $this->location->x - $e->location->x; $deltaZ = $this->location->z - $e->location->z; - $this->knockBack($deltaX, $deltaZ, $source->getKnockBack()); + $this->knockBack($deltaX, $deltaZ, $source->getKnockBack(), $source->getVerticalKnockBackLimit()); } } @@ -564,7 +578,7 @@ abstract class Living extends Entity{ $this->broadcastAnimation(new HurtAnimation($this)); } - public function knockBack(float $x, float $z, float $force = 0.4, ?float $verticalLimit = 0.4) : void{ + public function knockBack(float $x, float $z, float $force = self::DEFAULT_KNOCKBACK_FORCE, ?float $verticalLimit = self::DEFAULT_KNOCKBACK_VERTICAL_LIMIT) : void{ $f = sqrt($x * $x + $z * $z); if($f <= 0){ return; diff --git a/src/entity/animation/ConsumingItemAnimation.php b/src/entity/animation/ConsumingItemAnimation.php index aa6152a57..49c8a0a51 100644 --- a/src/entity/animation/ConsumingItemAnimation.php +++ b/src/entity/animation/ConsumingItemAnimation.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\entity\animation; -use pocketmine\entity\Human; +use pocketmine\entity\Living; use pocketmine\item\Item; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\ActorEventPacket; @@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\types\ActorEvent; final class ConsumingItemAnimation implements Animation{ public function __construct( - private Human $human, //TODO: maybe this can be expanded to more than just player entities? + private Living $entity, private Item $item ){} @@ -40,7 +40,7 @@ final class ConsumingItemAnimation implements Animation{ [$netId, $netData] = TypeConverter::getInstance()->getItemTranslator()->toNetworkId($this->item); return [ //TODO: need to check the data values - ActorEventPacket::create($this->human->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData) + ActorEventPacket::create($this->entity->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData) ]; } } diff --git a/src/event/entity/EntityDamageByEntityEvent.php b/src/event/entity/EntityDamageByEntityEvent.php index 6264375bb..5ef6c4b8e 100644 --- a/src/event/entity/EntityDamageByEntityEvent.php +++ b/src/event/entity/EntityDamageByEntityEvent.php @@ -36,7 +36,15 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ /** * @param float[] $modifiers */ - public function __construct(Entity $damager, Entity $entity, int $cause, float $damage, array $modifiers = [], private float $knockBack = 0.4){ + public function __construct( + Entity $damager, + Entity $entity, + int $cause, + float $damage, + array $modifiers = [], + private float $knockBack = Living::DEFAULT_KNOCKBACK_FORCE, + private float $verticalKnockBackLimit = Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT + ){ $this->damagerEntityId = $damager->getId(); parent::__construct($entity, $cause, $damage, $modifiers); $this->addAttackerModifiers($damager); @@ -62,11 +70,39 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ return $this->getEntity()->getWorld()->getServer()->getWorldManager()->findEntity($this->damagerEntityId); } + /** + * Returns the force with which the victim will be knocked back from the attacking entity. + * + * @see Living::DEFAULT_KNOCKBACK_FORCE + */ public function getKnockBack() : float{ return $this->knockBack; } + /** + * Sets the force with which the victim will be knocked back from the attacking entity. + * Larger values will knock the victim back further. + * Negative values will pull the victim towards the attacker. + */ public function setKnockBack(float $knockBack) : void{ $this->knockBack = $knockBack; } + + /** + * Returns the maximum upwards velocity the victim may have after being knocked back. + * This ensures that the victim doesn't fly up into the sky when high levels of knockback are applied. + * + * @see Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT + */ + public function getVerticalKnockBackLimit() : float{ + return $this->verticalKnockBackLimit; + } + + /** + * Sets the maximum upwards velocity the victim may have after being knocked back. + * Larger values will allow the victim to fly higher if the knockback force is also large. + */ + public function setVerticalKnockBackLimit(float $verticalKnockBackLimit) : void{ + $this->verticalKnockBackLimit = $verticalKnockBackLimit; + } } diff --git a/src/event/player/PlayerMissedSwingEvent.php b/src/event/player/PlayerMissedSwingEvent.php new file mode 100644 index 000000000..3157f62d1 --- /dev/null +++ b/src/event/player/PlayerMissedSwingEvent.php @@ -0,0 +1,39 @@ +player = $player; + } +} diff --git a/src/inventory/transaction/action/CreateItemAction.php b/src/inventory/transaction/action/CreateItemAction.php index 99605bf96..3ebda6900 100644 --- a/src/inventory/transaction/action/CreateItemAction.php +++ b/src/inventory/transaction/action/CreateItemAction.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\inventory\transaction\action; -use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; use pocketmine\item\VanillaItems; @@ -43,7 +42,7 @@ class CreateItemAction extends InventoryAction{ if($source->hasFiniteResources()){ throw new TransactionValidationException("Player has finite resources, cannot create items"); } - if(!CreativeInventory::getInstance()->contains($this->sourceItem)){ + if(!$source->getCreativeInventory()->contains($this->sourceItem)){ throw new TransactionValidationException("Creative inventory does not contain requested item"); } } diff --git a/src/item/PotionType.php b/src/item/PotionType.php index 7ec0f3876..e7feb0b8e 100644 --- a/src/item/PotionType.php +++ b/src/item/PotionType.php @@ -65,6 +65,7 @@ use pocketmine\utils\EnumTrait; * @method static PotionType STRONG_LEAPING() * @method static PotionType STRONG_POISON() * @method static PotionType STRONG_REGENERATION() + * @method static PotionType STRONG_SLOWNESS() * @method static PotionType STRONG_STRENGTH() * @method static PotionType STRONG_SWIFTNESS() * @method static PotionType STRONG_TURTLE_MASTER() @@ -201,6 +202,9 @@ final class PotionType{ ]), new self("long_slow_falling", "Long Slow Falling", fn() => [ //TODO + ]), + new self("strong_slowness", "Strong Slowness", fn() => [ + new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 3) ]) ); } diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 46b37e3ae..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()); @@ -1506,6 +1508,8 @@ final class StringToItemParser extends StringToTParser{ $result->register("strong_poison_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_POISON())); $result->register("strong_regeneration_potion", fn() => Items::POTION()->setType(PotionType::STRONG_REGENERATION())); $result->register("strong_regeneration_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_REGENERATION())); + $result->register("strong_slowness_potion", fn() => Items::POTION()->setType(PotionType::STRONG_SLOWNESS())); + $result->register("strong_slowness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_SLOWNESS())); $result->register("strong_strength_potion", fn() => Items::POTION()->setType(PotionType::STRONG_STRENGTH())); $result->register("strong_strength_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_STRENGTH())); $result->register("strong_swiftness_potion", fn() => Items::POTION()->setType(PotionType::STRONG_SWIFTNESS())); diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php index e76e71642..cd57eb203 100644 --- a/src/item/enchantment/StringToEnchantmentParser.php +++ b/src/item/enchantment/StringToEnchantmentParser.php @@ -43,6 +43,7 @@ final class StringToEnchantmentParser extends StringToTParser{ $result->register("fire_aspect", fn() => VanillaEnchantments::FIRE_ASPECT()); $result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION()); $result->register("flame", fn() => VanillaEnchantments::FLAME()); + $result->register("fortune", fn() => VanillaEnchantments::FORTUNE()); $result->register("infinity", fn() => VanillaEnchantments::INFINITY()); $result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK()); $result->register("mending", fn() => VanillaEnchantments::MENDING()); diff --git a/src/item/enchantment/VanillaEnchantments.php b/src/item/enchantment/VanillaEnchantments.php index 2be5eed71..ac2f5c57e 100644 --- a/src/item/enchantment/VanillaEnchantments.php +++ b/src/item/enchantment/VanillaEnchantments.php @@ -39,6 +39,7 @@ use pocketmine\utils\RegistryTrait; * @method static FireAspectEnchantment FIRE_ASPECT() * @method static ProtectionEnchantment FIRE_PROTECTION() * @method static Enchantment FLAME() + * @method static Enchantment FORTUNE() * @method static Enchantment INFINITY() * @method static KnockbackEnchantment KNOCKBACK() * @method static Enchantment MENDING() @@ -85,6 +86,7 @@ final class VanillaEnchantments{ self::register("FIRE_ASPECT", new FireAspectEnchantment(KnownTranslationFactory::enchantment_fire(), Rarity::RARE, ItemFlags::SWORD, ItemFlags::NONE, 2)); self::register("EFFICIENCY", new Enchantment(KnownTranslationFactory::enchantment_digging(), Rarity::COMMON, ItemFlags::DIG, ItemFlags::SHEARS, 5)); + self::register("FORTUNE", new Enchantment(KnownTranslationFactory::enchantment_lootBonusDigger(), Rarity::RARE, ItemFlags::DIG, ItemFlags::NONE, 3)); self::register("SILK_TOUCH", new Enchantment(KnownTranslationFactory::enchantment_untouching(), Rarity::MYTHIC, ItemFlags::DIG, ItemFlags::SHEARS, 1)); self::register("UNBREAKING", new Enchantment(KnownTranslationFactory::enchantment_durability(), Rarity::UNCOMMON, ItemFlags::DIG | ItemFlags::ARMOR | ItemFlags::FISHING_ROD | ItemFlags::BOW, ItemFlags::TOOL | ItemFlags::CARROT_STICK | ItemFlags::ELYTRA, 3)); diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index bdd3a892d..254eff910 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -35,7 +35,6 @@ use pocketmine\block\inventory\LoomInventory; use pocketmine\block\inventory\SmithingTableInventory; use pocketmine\block\inventory\StonecutterInventory; use pocketmine\crafting\FurnaceType; -use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\inventory\transaction\InventoryTransaction; @@ -109,7 +108,7 @@ class InventoryManager{ private NetworkSession $session ){ $this->containerOpenCallbacks = new ObjectSet(); - $this->containerOpenCallbacks->add(\Closure::fromCallable([self::class, 'createContainerOpen'])); + $this->containerOpenCallbacks->add(self::createContainerOpen(...)); $this->add(ContainerIds::INVENTORY, $this->player->getInventory()); $this->add(ContainerIds::OFFHAND, $this->player->getOffHandInventory()); @@ -117,9 +116,7 @@ class InventoryManager{ $this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory()); $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid()); - $this->player->getInventory()->getHeldItemIndexChangeListeners()->add(function() : void{ - $this->syncSelectedHotbarSlot(); - }); + $this->player->getInventory()->getHeldItemIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...)); } private function associateIdWithInventory(int $id, Inventory $inventory) : void{ @@ -603,7 +600,7 @@ class InventoryManager{ } public function syncCreative() : void{ - $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache(CreativeInventory::getInstance())); + $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory())); } private function newItemStackId() : int{ diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index a3cebbd73..259a602d4 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -23,28 +23,26 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use FG\ASN1\Exception\ParserException; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\Sequence; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; use function base64_decode; use function base64_encode; +use function bin2hex; +use function chr; use function count; use function explode; -use function gmp_export; -use function gmp_import; -use function gmp_init; -use function gmp_strval; use function is_array; use function json_decode; use function json_encode; use function json_last_error_msg; +use function ltrim; use function openssl_error_string; use function openssl_pkey_get_details; use function openssl_pkey_get_public; use function openssl_sign; use function openssl_verify; +use function ord; use function preg_match; use function rtrim; use function sprintf; @@ -54,8 +52,7 @@ use function str_replace; use function str_split; use function strlen; use function strtr; -use const GMP_BIG_ENDIAN; -use const GMP_MSW_FIRST; +use function substr; use const JSON_THROW_ON_ERROR; use const OPENSSL_ALGO_SHA384; use const STR_PAD_LEFT; @@ -63,6 +60,12 @@ use const STR_PAD_LEFT; final class JwtUtils{ public const BEDROCK_SIGNING_KEY_CURVE_NAME = "secp384r1"; + private const ASN1_INTEGER_TAG = "\x02"; + private const ASN1_SEQUENCE_TAG = "\x30"; + + private const SIGNATURE_PART_LENGTH = 48; + private const SIGNATURE_ALGORITHM = OPENSSL_ALGO_SHA384; + /** * @return string[] * @phpstan-return array{string, string, string} @@ -98,30 +101,84 @@ final class JwtUtils{ return [$header, $body, $signature]; } + private static function signaturePartToAsn1(string $part) : string{ + if(strlen($part) !== self::SIGNATURE_PART_LENGTH){ + throw new JwtException("R and S for a SHA384 signature must each be exactly 48 bytes, but have " . strlen($part) . " bytes"); + } + $part = ltrim($part, "\x00"); + if(ord($part[0]) >= 128){ + //ASN.1 integers with a leading 1 bit are considered negative - add a leading 0 byte to prevent this + //ECDSA signature R and S values are always positive + $part = "\x00" . $part; + } + + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + return self::ASN1_INTEGER_TAG . chr(strlen($part)) . $part; + } + + private static function rawSignatureToDer(string $rawSignature) : string{ + if(strlen($rawSignature) !== self::SIGNATURE_PART_LENGTH * 2){ + throw new JwtException("JWT signature has unexpected length, expected 96, got " . strlen($rawSignature)); + } + + [$rString, $sString] = str_split($rawSignature, self::SIGNATURE_PART_LENGTH); + $sequence = self::signaturePartToAsn1($rString) . self::signaturePartToAsn1($sString); + + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + return self::ASN1_SEQUENCE_TAG . chr(strlen($sequence)) . $sequence; + } + + private static function signaturePartFromAsn1(BinaryStream $stream) : string{ + $prefix = $stream->get(1); + if($prefix !== self::ASN1_INTEGER_TAG){ + throw new \InvalidArgumentException("Expected an ASN.1 INTEGER tag, got " . bin2hex($prefix)); + } + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + $length = $stream->getByte(); + if($length > self::SIGNATURE_PART_LENGTH + 1){ //each part may have an extra leading 0 byte to prevent it being interpreted as a negative number + throw new \InvalidArgumentException("Expected at most 49 bytes for signature R or S, got $length"); + } + $part = $stream->get($length); + return str_pad(ltrim($part, "\x00"), self::SIGNATURE_PART_LENGTH, "\x00", STR_PAD_LEFT); + } + + private static function rawSignatureFromDer(string $derSignature) : string{ + if($derSignature[0] !== self::ASN1_SEQUENCE_TAG){ + throw new \InvalidArgumentException("Invalid DER signature, expected ASN.1 SEQUENCE tag, got " . bin2hex($derSignature[0])); + } + + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + $length = ord($derSignature[1]); + $parts = substr($derSignature, 2, $length); + if(strlen($parts) !== $length){ + throw new \InvalidArgumentException("Invalid DER signature, expected $length sequence bytes, got " . strlen($parts)); + } + + $stream = new BinaryStream($parts); + $rRaw = self::signaturePartFromAsn1($stream); + $sRaw = self::signaturePartFromAsn1($stream); + + if(!$stream->feof()){ + throw new \InvalidArgumentException("Invalid DER signature, unexpected trailing sequence data"); + } + + return $rRaw . $sRaw; + } + /** * @throws JwtException */ public static function verify(string $jwt, \OpenSSLAsymmetricKey $signingKey) : bool{ [$header, $body, $signature] = self::split($jwt); - $plainSignature = self::b64UrlDecode($signature); - if(strlen($plainSignature) !== 96){ - throw new JwtException("JWT signature has unexpected length, expected 96, got " . strlen($plainSignature)); - } - - [$rString, $sString] = str_split($plainSignature, 48); - $convert = fn(string $str) => gmp_strval(gmp_import($str, 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), 10); - - $sequence = new Sequence( - new Integer($convert($rString)), - new Integer($convert($sString)) - ); + $rawSignature = self::b64UrlDecode($signature); + $derSignature = self::rawSignatureToDer($rawSignature); $v = openssl_verify( $header . '.' . $body, - $sequence->getBinary(), + $derSignature, $signingKey, - OPENSSL_ALGO_SHA384 + self::SIGNATURE_ALGORITHM ); switch($v){ case 0: return false; @@ -140,33 +197,13 @@ final class JwtUtils{ openssl_sign( $jwtBody, - $rawDerSig, + $derSignature, $signingKey, - OPENSSL_ALGO_SHA384 + self::SIGNATURE_ALGORITHM ); - try{ - $asnObject = Sequence::fromBinary($rawDerSig); - }catch(ParserException $e){ - throw new AssumptionFailedError("Failed to parse OpenSSL signature: " . $e->getMessage(), 0, $e); - } - if(count($asnObject) !== 2){ - throw new AssumptionFailedError("OpenSSL produced invalid signature, expected exactly 2 parts"); - } - [$r, $s] = [$asnObject[0], $asnObject[1]]; - if(!($r instanceof Integer) || !($s instanceof Integer)){ - throw new AssumptionFailedError("OpenSSL produced invalid signature, expected 2 INTEGER parts"); - } - $rString = $r->getContent(); - $sString = $s->getContent(); - - $toBinary = fn($str) => str_pad( - gmp_export(gmp_init($str, 10), 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), - 48, - "\x00", - STR_PAD_LEFT - ); - $jwtSig = JwtUtils::b64UrlEncode($toBinary($rString) . $toBinary($sString)); + $rawSignature = self::rawSignatureFromDer($derSignature); + $jwtSig = self::b64UrlEncode($rawSignature); return "$jwtBody.$jwtSig"; } diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index b10e7f25a..dbd857b0f 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -197,7 +197,7 @@ class NetworkSession{ $this->setHandler(new SessionStartPacketHandler( $this, - fn() => $this->onSessionStartSuccess() + $this->onSessionStartSuccess(...) )); $this->manager->add($this); @@ -225,13 +225,13 @@ class NetworkSession{ $this->logger->setPrefix($this->getLogPrefix()); $this->manager->markLoginReceived($this); }, - \Closure::fromCallable([$this, "setAuthenticationStatus"]) + $this->setAuthenticationStatus(...) )); } protected function createPlayer() : void{ $this->server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion( - \Closure::fromCallable([$this, 'onPlayerCreated']), + $this->onPlayerCreated(...), function() : void{ //TODO: this should never actually occur... right? $this->logger->error("Failed to create player"); @@ -787,9 +787,7 @@ class NetworkSession{ $this->cipher = EncryptionContext::fakeGCM($encryptionKey); - $this->setHandler(new HandshakePacketHandler(function() : void{ - $this->onServerLoginSuccess(); - })); + $this->setHandler(new HandshakePacketHandler($this->onServerLoginSuccess(...))); $this->logger->debug("Enabled encryption"); })); }else{ @@ -818,9 +816,7 @@ class NetworkSession{ public function notifyTerrainReady() : void{ $this->logger->debug("Sending spawn notification, waiting for spawn response"); $this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::PLAYER_SPAWN)); - $this->setHandler(new SpawnResponsePacketHandler(function() : void{ - $this->onClientSpawnResponse(); - })); + $this->setHandler(new SpawnResponsePacketHandler($this->onClientSpawnResponse(...))); } private function onClientSpawnResponse() : void{ diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php index e6ac6167b..aa70c7d35 100644 --- a/src/network/mcpe/cache/CraftingDataCache.php +++ b/src/network/mcpe/cache/CraftingDataCache.php @@ -25,21 +25,17 @@ namespace pocketmine\network\mcpe\cache; use pocketmine\crafting\CraftingManager; use pocketmine\crafting\FurnaceType; -use pocketmine\crafting\RecipeIngredient; use pocketmine\crafting\ShapedRecipe; use pocketmine\crafting\ShapelessRecipe; use pocketmine\crafting\ShapelessRecipeType; -use pocketmine\item\Item; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\CraftingDataPacket; -use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\recipe\CraftingRecipeBlockName; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipeBlockName; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\PotionContainerChangeRecipe as ProtocolPotionContainerChangeRecipe; use pocketmine\network\mcpe\protocol\types\recipe\PotionTypeRecipe as ProtocolPotionTypeRecipe; -use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient; use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe; use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe; use pocketmine\timings\Timings; @@ -95,12 +91,8 @@ final class CraftingDataCache{ $recipesWithTypeIds[] = new ProtocolShapelessRecipe( CraftingDataPacket::ENTRY_SHAPELESS, Binary::writeInt($index), - array_map(function(RecipeIngredient $item) use ($converter) : ProtocolRecipeIngredient{ - return $converter->coreRecipeIngredientToNet($item); - }, $recipe->getIngredientList()), - array_map(function(Item $item) use ($converter) : ItemStack{ - return $converter->coreItemStackToNet($item); - }, $recipe->getResults()), + array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()), + array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, $typeTag, 50, @@ -118,9 +110,7 @@ final class CraftingDataCache{ CraftingDataPacket::ENTRY_SHAPED, Binary::writeInt($index), $inputs, - array_map(function(Item $item) use ($converter) : ItemStack{ - return $converter->coreItemStackToNet($item); - }, $recipe->getResults()), + array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, CraftingRecipeBlockName::CRAFTING_TABLE, 50, diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 22eb91935..729b5b51b 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -236,6 +236,9 @@ class InGamePacketHandler extends PacketHandler{ if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ $this->player->jump(); } + if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){ + $this->player->missSwing(); + } } if(!$this->forceMoveSync && $hasMoved){ diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index f9532291c..10787f84b 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\handler; -use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; @@ -327,7 +326,7 @@ class ItemStackRequestExecutor{ $this->builder->addAction(new DestroyItemAction($destroyed)); }elseif($action instanceof CreativeCreateStackRequestAction){ - $item = CreativeInventory::getInstance()->getItem($action->getCreativeItemId()); + $item = $this->player->getCreativeInventory()->getItem($action->getCreativeItemId()); if($item === null){ throw new ItemStackRequestProcessException("No such creative item index: " . $action->getCreativeItemId()); } diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 93dff68a2..4bf8ffb15 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -39,6 +39,7 @@ use pocketmine\network\NetworkInterfaceStartException; use pocketmine\network\PacketHandlingException; use pocketmine\player\GameMode; use pocketmine\Server; +use pocketmine\thread\ThreadCrashException; use pocketmine\timings\Timings; use pocketmine\utils\Utils; use raklib\generic\DisconnectReason; @@ -154,7 +155,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ if(!$this->rakLib->isRunning()){ $e = $this->rakLib->getCrashInfo(); if($e !== null){ - throw new \RuntimeException("RakLib crashed: " . $e->makePrettyMessage()); + throw new ThreadCrashException("RakLib crashed", $e); } throw new \Exception("RakLib Thread crashed without crash information"); } diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index a3f7c1609..e59b6971f 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -29,6 +29,7 @@ use pocketmine\snooze\SleeperHandlerEntry; use pocketmine\thread\log\ThreadSafeLogger; use pocketmine\thread\NonThreadSafeValue; use pocketmine\thread\Thread; +use pocketmine\thread\ThreadCrashException; use raklib\generic\SocketException; use raklib\server\ipc\RakLibToUserThreadMessageSender; use raklib\server\ipc\UserToRakLibThreadMessageReceiver; @@ -37,17 +38,12 @@ use raklib\server\ServerSocket; use raklib\server\SimpleProtocolAcceptor; use raklib\utils\ExceptionTraceCleaner; use raklib\utils\InternetAddress; -use function error_get_last; use function gc_enable; use function ini_set; -use function register_shutdown_function; class RakLibServer extends Thread{ - protected bool $cleanShutdown = false; protected bool $ready = false; protected string $mainPath; - /** @phpstan-var NonThreadSafeValue|null */ - public ?NonThreadSafeValue $crashInfo = null; /** @phpstan-var NonThreadSafeValue */ protected NonThreadSafeValue $address; @@ -69,86 +65,51 @@ class RakLibServer extends Thread{ $this->address = new NonThreadSafeValue($address); } - /** - * @return void - */ - public function shutdownHandler(){ - if($this->cleanShutdown !== true && $this->crashInfo === null){ - $error = error_get_last(); - - if($error !== null){ - $this->logger->emergency("Fatal error: " . $error["message"] . " in " . $error["file"] . " on line " . $error["line"]); - $this->setCrashInfo(RakLibThreadCrashInfo::fromLastErrorInfo($error)); - }else{ - $this->logger->emergency("RakLib shutdown unexpectedly"); - } - } - } - - public function getCrashInfo() : ?RakLibThreadCrashInfo{ - return $this->crashInfo?->deserialize(); - } - - private function setCrashInfo(RakLibThreadCrashInfo $info) : void{ - $this->synchronized(function() use ($info) : void{ - $this->crashInfo = new NonThreadSafeValue($info); - $this->notify(); - }); - } - public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{ $this->start($options); $this->synchronized(function() : void{ - while(!$this->ready && $this->crashInfo === null){ + while(!$this->ready && $this->getCrashInfo() === null){ $this->wait(); } - $crashInfo = $this->crashInfo?->deserialize(); + $crashInfo = $this->getCrashInfo(); if($crashInfo !== null){ - if($crashInfo->getClass() === SocketException::class){ + if($crashInfo->getType() === SocketException::class){ throw new SocketException($crashInfo->getMessage()); } - throw new \RuntimeException("RakLib failed to start: " . $crashInfo->makePrettyMessage()); + throw new ThreadCrashException("RakLib failed to start", $crashInfo); } }); } protected function onRun() : void{ - try{ - gc_enable(); - ini_set("display_errors", '1'); - ini_set("display_startup_errors", '1'); + gc_enable(); + ini_set("display_errors", '1'); + ini_set("display_startup_errors", '1'); - register_shutdown_function([$this, "shutdownHandler"]); - - try{ - $socket = new ServerSocket($this->address->deserialize()); - }catch(SocketException $e){ - $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e)); - return; - } - $manager = new Server( - $this->serverId, - $this->logger, - $socket, - $this->maxMtuSize, - new SimpleProtocolAcceptor($this->protocolVersion), - new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)), - new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())), - new ExceptionTraceCleaner($this->mainPath) - ); - $this->synchronized(function() : void{ - $this->ready = true; - $this->notify(); - }); - while(!$this->isKilled){ - $manager->tickProcessor(); - } - $manager->waitShutdown(); - $this->cleanShutdown = true; - }catch(\Throwable $e){ - $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e)); - $this->logger->logException($e); + $socket = new ServerSocket($this->address->deserialize()); + $manager = new Server( + $this->serverId, + $this->logger, + $socket, + $this->maxMtuSize, + new SimpleProtocolAcceptor($this->protocolVersion), + new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)), + new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())), + new ExceptionTraceCleaner($this->mainPath) + ); + $this->synchronized(function() : void{ + $this->ready = true; + $this->notify(); + }); + while(!$this->isKilled){ + $manager->tickProcessor(); } + $manager->waitShutdown(); + } + + protected function onUncaughtException(\Throwable $e) : void{ + parent::onUncaughtException($e); + $this->logger->logException($e); } public function getThreadName() : string{ diff --git a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php deleted file mode 100644 index 60e04b4b4..000000000 --- a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php +++ /dev/null @@ -1,61 +0,0 @@ -getMessage(), $e->getFile(), $e->getLine()); - } - - /** - * @phpstan-param array{message: string, file: string, line: int} $info - */ - public static function fromLastErrorInfo(array $info) : self{ - return new self(null, $info["message"], $info["file"], $info["line"]); - } - - public function getClass() : ?string{ return $this->class; } - - public function getMessage() : string{ return $this->message; } - - public function getFile() : string{ return $this->file; } - - public function getLine() : int{ return $this->line; } - - public function makePrettyMessage() : string{ - return sprintf("%s: \"%s\" in %s on line %d", $this->class ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line); - } -} diff --git a/src/player/Player.php b/src/player/Player.php index ccce2d45e..292ab98b3 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -66,6 +66,7 @@ use pocketmine\event\player\PlayerItemUseEvent; use pocketmine\event\player\PlayerJoinEvent; use pocketmine\event\player\PlayerJumpEvent; use pocketmine\event\player\PlayerKickEvent; +use pocketmine\event\player\PlayerMissedSwingEvent; use pocketmine\event\player\PlayerMoveEvent; use pocketmine\event\player\PlayerPostChunkSendEvent; use pocketmine\event\player\PlayerQuitEvent; @@ -80,6 +81,7 @@ use pocketmine\event\player\PlayerViewDistanceChangeEvent; use pocketmine\form\Form; use pocketmine\form\FormValidationException; use pocketmine\inventory\CallbackInventoryListener; +use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\PlayerCraftingInventory; use pocketmine\inventory\PlayerCursorInventory; @@ -217,6 +219,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected array $permanentWindows = []; protected PlayerCursorInventory $cursorInventory; protected PlayerCraftingInventory $craftingGrid; + protected CreativeInventory $creativeInventory; protected int $messageCounter = 2; @@ -307,6 +310,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->uuid = $this->playerInfo->getUuid(); $this->xuid = $this->playerInfo instanceof XboxLivePlayerInfo ? $this->playerInfo->getXuid() : ""; + $this->creativeInventory = CreativeInventory::getInstance(); + $rootPermissions = [DefaultPermissions::ROOT_USER => true]; if($this->server->isOp($this->username)){ $rootPermissions[DefaultPermissions::ROOT_OPERATOR] = true; @@ -1890,6 +1895,19 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return true; } + /** + * Performs actions associated with the attack action (left-click) without a target entity. + * Under normal circumstances, this will just play the no-damage attack sound and the arm-swing animation. + */ + public function missSwing() : void{ + $ev = new PlayerMissedSwingEvent($this); + $ev->call(); + if(!$ev->isCancelled()){ + $this->broadcastSound(new EntityAttackNoDamageSound()); + $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); + } + } + /** * Interacts with the given entity using the currently-held item. */ @@ -2532,6 +2550,24 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return $this->craftingGrid; } + /** + * Returns the creative inventory shown to the player. + * Unless changed by a plugin, this is usually the same for all players. + */ + public function getCreativeInventory() : CreativeInventory{ + return $this->creativeInventory; + } + + /** + * To set a custom creative inventory, you need to make a clone of a CreativeInventory instance. + */ + public function setCreativeInventory(CreativeInventory $inventory) : void{ + $this->creativeInventory = $inventory; + if($this->spawned && $this->isConnected()){ + $this->getNetworkSession()->getInvManager()?->syncCreative(); + } + } + /** * @internal Called to clean up crafting grid and cursor inventory when it is detected that the player closed their * inventory. diff --git a/src/scheduler/AsyncPool.php b/src/scheduler/AsyncPool.php index a326bc87f..bb79df507 100644 --- a/src/scheduler/AsyncPool.php +++ b/src/scheduler/AsyncPool.php @@ -26,6 +26,7 @@ namespace pocketmine\scheduler; use pmmp\thread\Thread as NativeThread; use pocketmine\snooze\SleeperHandler; use pocketmine\thread\log\ThreadSafeLogger; +use pocketmine\thread\ThreadCrashException; use pocketmine\thread\ThreadSafeClassLoader; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; @@ -215,12 +216,17 @@ class AsyncPool{ } } } - if($crashedTask !== null){ - $message = "Worker $workerId crashed while running task " . get_class($crashedTask) . "#" . spl_object_id($crashedTask); + $info = $entry->worker->getCrashInfo(); + if($info !== null){ + if($crashedTask !== null){ + $message = "Worker $workerId crashed while running task " . get_class($crashedTask) . "#" . spl_object_id($crashedTask); + }else{ + $message = "Worker $workerId crashed while doing unknown work"; + } + throw new ThreadCrashException($message, $info); }else{ - $message = "Worker $workerId crashed for unknown reason"; + throw new \RuntimeException("Worker $workerId crashed for unknown reason"); } - throw new \RuntimeException($message); } } @@ -259,7 +265,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/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index 19a19b102..b26afc29b 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -31,7 +31,6 @@ use pocketmine\thread\Worker; use pocketmine\utils\AssumptionFailedError; use function gc_enable; use function ini_set; -use function set_exception_handler; class AsyncWorker extends Worker{ /** @var mixed[] */ @@ -68,20 +67,17 @@ class AsyncWorker extends Worker{ } $this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier()); + } - set_exception_handler(function(\Throwable $e){ - $this->logger->logException($e); - }); + protected function onUncaughtException(\Throwable $e) : void{ + parent::onUncaughtException($e); + $this->logger->logException($e); } public function getLogger() : ThreadSafeLogger{ return $this->logger; } - public function handleException(\Throwable $e) : void{ - $this->logger->logException($e); - } - public function getThreadName() : string{ return "AsyncWorker#" . $this->id; } diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index c35dd7791..340ce554d 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -26,7 +26,10 @@ namespace pocketmine\thread; use pmmp\thread\ThreadSafeArray; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\Server; +use function error_get_last; use function error_reporting; +use function register_shutdown_function; +use function set_exception_handler; trait CommonThreadPartsTrait{ /** @@ -38,6 +41,8 @@ trait CommonThreadPartsTrait{ protected bool $isKilled = false; + private ?ThreadCrashInfo $crashInfo = null; + /** * @return ThreadSafeClassLoader[] */ @@ -88,12 +93,48 @@ trait CommonThreadPartsTrait{ } } + public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; } + final public function run() : void{ error_reporting(-1); $this->registerClassLoaders(); //set this after the autoloader is registered ErrorToExceptionHandler::set(); + + //this permits adding extra functionality to the exception and shutdown handlers via overriding + set_exception_handler($this->onUncaughtException(...)); + register_shutdown_function($this->onShutdown(...)); + $this->onRun(); + $this->isKilled = true; + } + + /** + * Called by set_exception_handler() when an uncaught exception is thrown. + */ + protected function onUncaughtException(\Throwable $e) : void{ + $this->synchronized(function() use ($e) : void{ + $this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName()); + }); + } + + /** + * Called by register_shutdown_function() when the thread shuts down. This may be because of a benign shutdown, or + * because of a fatal error. Use isKilled to determine which. + */ + protected function onShutdown() : void{ + $this->synchronized(function() : void{ + if(!$this->isKilled && $this->crashInfo === null){ + $last = error_get_last(); + if($last !== null){ + //fatal error + $this->crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName()); + }else{ + //probably misused exit() + $this->crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName()); + } + } + }); } /** diff --git a/src/thread/ThreadCrashException.php b/src/thread/ThreadCrashException.php new file mode 100644 index 000000000..0eba37934 --- /dev/null +++ b/src/thread/ThreadCrashException.php @@ -0,0 +1,38 @@ +crashInfo = $crashInfo; + } + + public function getCrashInfo() : ThreadCrashInfo{ + return $this->crashInfo; + } +} diff --git a/src/thread/ThreadCrashInfo.php b/src/thread/ThreadCrashInfo.php new file mode 100644 index 000000000..66aae927a --- /dev/null +++ b/src/thread/ThreadCrashInfo.php @@ -0,0 +1,89 @@ + */ + private ThreadSafeArray $trace; + + /** + * @param ThreadCrashInfoFrame[] $trace + */ + public function __construct( + private string $type, + private string $message, + private string $file, + private int $line, + array $trace, + private string $threadName + ){ + $this->trace = ThreadSafeArray::fromArray($trace); + } + + public static function fromThrowable(\Throwable $e, string $threadName) : self{ + return new self(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine(), Utils::printableTraceWithMetadata($e->getTrace()), $threadName); + } + + /** + * @phpstan-param array{type: int, message: string, file: string, line: int} $info + */ + public static function fromLastErrorInfo(array $info, string $threadName) : self{ + try{ + $class = ErrorTypeToStringMap::get($info["type"]); + }catch(\InvalidArgumentException){ + $class = "Unknown error type (" . $info["type"] . ")"; + } + return new self($class, $info["message"], $info["file"], $info["line"], Utils::printableTraceWithMetadata(Utils::currentTrace()), $threadName); + } + + public function getType() : string{ return $this->type; } + + public function getMessage() : string{ return $this->message; } + + public function getFile() : string{ return $this->file; } + + public function getLine() : int{ return $this->line; } + + /** + * @return ThreadCrashInfoFrame[] + */ + public function getTrace() : array{ + return (array) $this->trace; + } + + public function getThreadName() : string{ return $this->threadName; } + + public function makePrettyMessage() : string{ + return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line); + } +} diff --git a/src/thread/ThreadCrashInfoFrame.php b/src/thread/ThreadCrashInfoFrame.php new file mode 100644 index 000000000..27f470387 --- /dev/null +++ b/src/thread/ThreadCrashInfoFrame.php @@ -0,0 +1,41 @@ +printableFrame; } + + public function getFile() : ?string{ return $this->file; } + + public function getLine() : int{ return $this->line; } +} diff --git a/src/utils/RegistryTrait.php b/src/utils/RegistryTrait.php index f8ffc1143..573f4737f 100644 --- a/src/utils/RegistryTrait.php +++ b/src/utils/RegistryTrait.php @@ -121,8 +121,6 @@ trait RegistryTrait{ */ private static function _registryGetAll() : array{ self::checkInit(); - return array_map(function(object $o) : object{ - return self::preprocessMember($o); - }, self::$members); + return array_map(self::preprocessMember(...), self::$members); } } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 11548e193..f5ec5f8e4 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -31,6 +31,7 @@ use DaveRandom\CallbackValidator\CallbackType; use pocketmine\entity\Location; use pocketmine\errorhandler\ErrorTypeToStringMap; use pocketmine\math\Vector3; +use pocketmine\thread\ThreadCrashInfoFrame; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function array_combine; @@ -469,6 +470,30 @@ final class Utils{ return $messages; } + /** + * Similar to {@link Utils::printableTrace()}, but associates metadata such as file and line number with each frame. + * This is used to transmit thread-safe information about crash traces to the main thread when a thread crashes. + * + * @param mixed[][] $rawTrace + * @phpstan-param list> $rawTrace + * + * @return ThreadCrashInfoFrame[] + */ + public static function printableTraceWithMetadata(array $rawTrace, int $maxStringLength = 80) : array{ + $printableTrace = self::printableTrace($rawTrace, $maxStringLength); + $safeTrace = []; + foreach($printableTrace as $frameId => $printableFrame){ + $rawFrame = $rawTrace[$frameId]; + $safeTrace[$frameId] = new ThreadCrashInfoFrame( + $printableFrame, + $rawFrame["file"] ?? "unknown", + $rawFrame["line"] ?? 0 + ); + } + + return $safeTrace; + } + /** * @return mixed[][] * @phpstan-return list> diff --git a/src/world/World.php b/src/world/World.php index deb03ef6d..f556d2e1f 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -1610,7 +1610,7 @@ class World implements ChunkManager{ * the current weather and time of day. */ public function getHighestAdjacentFullLightAt(int $x, int $y, int $z) : int{ - return $this->getHighestAdjacentLight($x, $y, $z, \Closure::fromCallable([$this, 'getFullLightAt'])); + return $this->getHighestAdjacentLight($x, $y, $z, $this->getFullLightAt(...)); } /** @@ -1706,7 +1706,7 @@ class World implements ChunkManager{ * Returns the highest potential level of sky light in the positions adjacent to the specified block coordinates. */ public function getHighestAdjacentPotentialBlockSkyLight(int $x, int $y, int $z) : int{ - return $this->getHighestAdjacentLight($x, $y, $z, \Closure::fromCallable([$this, 'getPotentialBlockSkyLightAt'])); + return $this->getHighestAdjacentLight($x, $y, $z, $this->getPotentialBlockSkyLightAt(...)); } /** @@ -1721,7 +1721,7 @@ class World implements ChunkManager{ * Returns the highest block light level available in the positions adjacent to the specified block coordinates. */ public function getHighestAdjacentBlockLight(int $x, int $y, int $z) : int{ - return $this->getHighestAdjacentLight($x, $y, $z, \Closure::fromCallable([$this, 'getBlockLightAt'])); + return $this->getHighestAdjacentLight($x, $y, $z, $this->getBlockLightAt(...)); } private function executeQueuedLightUpdates() : void{ diff --git a/src/world/format/io/WorldProviderManager.php b/src/world/format/io/WorldProviderManager.php index 8a30bcb57..1767c539a 100644 --- a/src/world/format/io/WorldProviderManager.php +++ b/src/world/format/io/WorldProviderManager.php @@ -41,13 +41,13 @@ final class WorldProviderManager{ private WritableWorldProviderManagerEntry $default; public function __construct(){ - $leveldb = new WritableWorldProviderManagerEntry(\Closure::fromCallable([LevelDB::class, 'isValid']), fn(string $path, \Logger $logger) => new LevelDB($path, $logger), \Closure::fromCallable([LevelDB::class, 'generate'])); + $leveldb = new WritableWorldProviderManagerEntry(LevelDB::isValid(...), fn(string $path, \Logger $logger) => new LevelDB($path, $logger), LevelDB::generate(...)); $this->default = $leveldb; $this->addProvider($leveldb, "leveldb"); - $this->addProvider(new ReadOnlyWorldProviderManagerEntry(\Closure::fromCallable([Anvil::class, 'isValid']), fn(string $path, \Logger $logger) => new Anvil($path, $logger)), "anvil"); - $this->addProvider(new ReadOnlyWorldProviderManagerEntry(\Closure::fromCallable([McRegion::class, 'isValid']), fn(string $path, \Logger $logger) => new McRegion($path, $logger)), "mcregion"); - $this->addProvider(new ReadOnlyWorldProviderManagerEntry(\Closure::fromCallable([PMAnvil::class, 'isValid']), fn(string $path, \Logger $logger) => new PMAnvil($path, $logger)), "pmanvil"); + $this->addProvider(new ReadOnlyWorldProviderManagerEntry(Anvil::isValid(...), fn(string $path, \Logger $logger) => new Anvil($path, $logger)), "anvil"); + $this->addProvider(new ReadOnlyWorldProviderManagerEntry(McRegion::isValid(...), fn(string $path, \Logger $logger) => new McRegion($path, $logger)), "mcregion"); + $this->addProvider(new ReadOnlyWorldProviderManagerEntry(PMAnvil::isValid(...), fn(string $path, \Logger $logger) => new PMAnvil($path, $logger)), "pmanvil"); } /** 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 @@ +