diff --git a/build/php b/build/php index 46604f2f6..ed0bc4d2a 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 46604f2f6a07e3f68a82e4f4d7efdd45629b101e +Subproject commit ed0bc4d2afafd00f9ee92823c6b1bd66789ce4f2 diff --git a/composer.json b/composer.json index 35bcba58f..95253ff40 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-openssl": "*", "ext-pcre": "*", "ext-phar": "*", - "ext-pmmpthread": "^6.0.4", + "ext-pmmpthread": "^6.0.7", "ext-reflection": "*", "ext-simplexml": "*", "ext-sockets": "*", @@ -43,7 +43,7 @@ "pocketmine/errorhandler": "^0.6.0", "pocketmine/locale-data": "~2.19.0", "pocketmine/log": "^0.4.0", - "pocketmine/math": "^0.4.0", + "pocketmine/math": "~1.0.0", "pocketmine/nbt": "^0.3.2", "pocketmine/raklib": "^0.15.0", "pocketmine/raklib-ipc": "^0.2.0", diff --git a/composer.lock b/composer.lock index a97b576ee..de7ccf126 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": "ccd20e7656bc05ec2acd8e28aad9fcf2", + "content-hash": "2d51c1adf069760587b6d36f9c4a5db3", "packages": [ { "name": "adhocore/json-comment", @@ -200,16 +200,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "23.0.2+bedrock-1.20.10", + "version": "23.0.3+bedrock-1.20.10", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "69a309a2dd7dcf3ec8c316385b866397e8c2cbfd" + "reference": "e4157c7af3f91e1b08fe21be171eb73dad7029e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/69a309a2dd7dcf3ec8c316385b866397e8c2cbfd", - "reference": "69a309a2dd7dcf3ec8c316385b866397e8c2cbfd", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e4157c7af3f91e1b08fe21be171eb73dad7029e9", + "reference": "e4157c7af3f91e1b08fe21be171eb73dad7029e9", "shasum": "" }, "require": { @@ -218,8 +218,8 @@ "php": "^8.0", "pocketmine/binaryutils": "^0.2.0", "pocketmine/color": "^0.2.0 || ^0.3.0", - "pocketmine/math": "^0.3.0 || ^0.4.0", - "pocketmine/nbt": "^0.3.0", + "pocketmine/math": "^0.3.0 || ^0.4.0 || ^1.0.0", + "pocketmine/nbt": "^0.3.0 || ^1.0.0", "ramsey/uuid": "^4.1" }, "require-dev": { @@ -241,9 +241,9 @@ "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "support": { "issues": "https://github.com/pmmp/BedrockProtocol/issues", - "source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.2+bedrock-1.20.10" + "source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.3+bedrock-1.20.10" }, - "time": "2023-07-24T15:35:36+00:00" + "time": "2023-08-03T15:30:52+00:00" }, { "name": "pocketmine/binaryutils", @@ -480,16 +480,16 @@ }, { "name": "pocketmine/math", - "version": "0.4.3", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/pmmp/Math.git", - "reference": "47a243d320b01c8099d65309967934c188111549" + "reference": "dc132d93595b32e9f210d78b3c8d43c662a5edbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Math/zipball/47a243d320b01c8099d65309967934c188111549", - "reference": "47a243d320b01c8099d65309967934c188111549", + "url": "https://api.github.com/repos/pmmp/Math/zipball/dc132d93595b32e9f210d78b3c8d43c662a5edbf", + "reference": "dc132d93595b32e9f210d78b3c8d43c662a5edbf", "shasum": "" }, "require": { @@ -498,7 +498,7 @@ }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "1.8.2", + "phpstan/phpstan": "~1.10.3", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^8.5 || ^9.5" }, @@ -515,9 +515,9 @@ "description": "PHP library containing math related code used in PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Math/issues", - "source": "https://github.com/pmmp/Math/tree/0.4.3" + "source": "https://github.com/pmmp/Math/tree/1.0.0" }, - "time": "2022-08-25T18:43:37+00:00" + "time": "2023-08-03T12:56:33+00:00" }, { "name": "pocketmine/nbt", @@ -1541,16 +1541,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.2", + "version": "10.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "db1497ec8dd382e82c962f7abbe0320e4882ee4e" + "reference": "be1fe461fdc917de2a29a452ccf2657d325b443d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/db1497ec8dd382e82c962f7abbe0320e4882ee4e", - "reference": "db1497ec8dd382e82c962f7abbe0320e4882ee4e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/be1fe461fdc917de2a29a452ccf2657d325b443d", + "reference": "be1fe461fdc917de2a29a452ccf2657d325b443d", "shasum": "" }, "require": { @@ -1607,7 +1607,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.3" }, "funding": [ { @@ -1615,7 +1615,7 @@ "type": "github" } ], - "time": "2023-05-22T09:04:27+00:00" + "time": "2023-07-26T13:45:28+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1861,16 +1861,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.2.6", + "version": "10.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd" + "reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c17815c129f133f3019cc18e8d0c8622e6d9bcd", - "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d442ce7c4104d5683c12e67e4dcb5058159e9804", + "reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804", "shasum": "" }, "require": { @@ -1895,7 +1895,7 @@ "sebastian/diff": "^5.0", "sebastian/environment": "^6.0", "sebastian/exporter": "^5.0", - "sebastian/global-state": "^6.0", + "sebastian/global-state": "^6.0.1", "sebastian/object-enumerator": "^5.0", "sebastian/recursion-context": "^5.0", "sebastian/type": "^4.0", @@ -1910,7 +1910,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.2-dev" + "dev-main": "10.3-dev" } }, "autoload": { @@ -1942,7 +1942,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.1" }, "funding": [ { @@ -1958,7 +1958,7 @@ "type": "tidelift" } ], - "time": "2023-07-17T12:08:28+00:00" + "time": "2023-08-04T06:48:08+00:00" }, { "name": "sebastian/cli-parser", @@ -2945,7 +2945,7 @@ "ext-openssl": "*", "ext-pcre": "*", "ext-phar": "*", - "ext-pmmpthread": "^6.0.4", + "ext-pmmpthread": "^6.0.7", "ext-reflection": "*", "ext-simplexml": "*", "ext-sockets": "*", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b3aeaf4f6..1d72511f7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,6 +11,7 @@ includes: rules: - pocketmine\phpstan\rules\DisallowEnumComparisonRule + - pocketmine\phpstan\rules\DisallowForeachByReferenceRule - pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule diff --git a/src/PocketMine.php b/src/PocketMine.php index 2c8ab06de..4c2ff32a7 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -120,8 +120,8 @@ namespace pocketmine { } if(($pmmpthread_version = phpversion("pmmpthread")) !== false){ - if(version_compare($pmmpthread_version, "6.0.4") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){ - $messages[] = "pmmpthread ^6.0.4 is required, while you have $pmmpthread_version."; + if(version_compare($pmmpthread_version, "6.0.7") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){ + $messages[] = "pmmpthread ^6.0.7 is required, while you have $pmmpthread_version."; } } diff --git a/src/block/BaseCoral.php b/src/block/BaseCoral.php index 73b2c48d6..3a6acd5d1 100644 --- a/src/block/BaseCoral.php +++ b/src/block/BaseCoral.php @@ -23,10 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\CoralTypeTrait; use pocketmine\block\utils\SupportType; -use pocketmine\event\block\BlockDeathEvent; use pocketmine\item\Item; use function mt_rand; @@ -46,11 +46,7 @@ abstract class BaseCoral extends Transparent{ public function onScheduledUpdate() : void{ if(!$this->dead && !$this->isCoveredWithWater()){ - $ev = new BlockDeathEvent($this, $this->setDead(true)); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::die($this, (clone $this)->setDead(true)); } } diff --git a/src/block/Cactus.php b/src/block/Cactus.php index 8fff294f6..0176d17bd 100644 --- a/src/block/Cactus.php +++ b/src/block/Cactus.php @@ -23,10 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\item\Item; @@ -111,12 +111,7 @@ class Cactus extends Transparent{ } $b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z); if($b->getTypeId() === BlockTypeIds::AIR){ - $ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS()); - $ev->call(); - if($ev->isCancelled()){ - break; - } - $world->setBlock($b->position, $ev->getNewState()); + BlockEventHelper::grow($b, VanillaBlocks::CACTUS(), null); }else{ break; } diff --git a/src/block/CaveVines.php b/src/block/CaveVines.php index e56b8b720..6ff934881 100644 --- a/src/block/CaveVines.php +++ b/src/block/CaveVines.php @@ -23,10 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Item; use pocketmine\item\VanillaItems; @@ -114,16 +114,12 @@ class CaveVines extends Flowable{ return true; } if($item instanceof Fertilizer){ - $ev = new BlockGrowEvent($this, (clone $this) + $newState = (clone $this) ->setBerries(true) - ->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)) - ); - $ev->call(); - if($ev->isCancelled()){ - return false; + ->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)); + if(BlockEventHelper::grow($this, $newState, $player)){ + $item->pop(); } - $item->pop(); - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); return true; } return false; @@ -141,16 +137,10 @@ class CaveVines extends Flowable{ if($world->isInWorld($growthPos->getFloorX(), $growthPos->getFloorY(), $growthPos->getFloorZ())){ $block = $world->getBlock($growthPos); if($block->getTypeId() === BlockTypeIds::AIR){ - $ev = new BlockGrowEvent($block, VanillaBlocks::CAVE_VINES() + $newState = VanillaBlocks::CAVE_VINES() ->setAge($this->age + 1) - ->setBerries(mt_rand(1, 9) === 1) - ); - - $ev->call(); - - if(!$ev->isCancelled()){ - $world->setBlock($growthPos, $ev->getNewState()); - } + ->setBerries(mt_rand(1, 9) === 1); + BlockEventHelper::grow($block, $newState, null); } } } diff --git a/src/block/CocoaBlock.php b/src/block/CocoaBlock.php index aafce3169..69f94eaf6 100644 --- a/src/block/CocoaBlock.php +++ b/src/block/CocoaBlock.php @@ -23,11 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\block\utils\WoodType; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Item; use pocketmine\item\VanillaItems; @@ -123,12 +123,7 @@ class CocoaBlock extends Transparent{ if($this->age < self::MAX_AGE){ $block = clone $this; $block->age++; - $ev = new BlockGrowEvent($this, $block, $player); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); - return true; - } + return BlockEventHelper::grow($this, $block, $player); } return false; } diff --git a/src/block/ConcretePowder.php b/src/block/ConcretePowder.php index 02fa31adb..635d3ff2c 100644 --- a/src/block/ConcretePowder.php +++ b/src/block/ConcretePowder.php @@ -23,11 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; -use pocketmine\event\block\BlockFormEvent; use pocketmine\math\Facing; class ConcretePowder extends Opaque implements Fallable{ @@ -43,11 +43,7 @@ class ConcretePowder extends Opaque implements Fallable{ public function onNearbyBlockChange() : void{ if(($water = $this->getAdjacentWater()) !== null){ - $ev = new BlockFormEvent($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::form($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water); }else{ $this->startFalling(); } diff --git a/src/block/CoralBlock.php b/src/block/CoralBlock.php index b90246561..3aaf5b752 100644 --- a/src/block/CoralBlock.php +++ b/src/block/CoralBlock.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\CoralTypeTrait; -use pocketmine\event\block\BlockDeathEvent; use pocketmine\item\Item; use function mt_rand; @@ -55,11 +55,7 @@ final class CoralBlock extends Opaque{ } } if(!$hasWater){ - $ev = new BlockDeathEvent($this, $this->setDead(true)); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::die($this, (clone $this)->setDead(true)); } } } diff --git a/src/block/Crops.php b/src/block/Crops.php index 8949e663b..d6e84c424 100644 --- a/src/block/Crops.php +++ b/src/block/Crops.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -68,11 +68,7 @@ abstract class Crops extends Flowable{ if($block->age > self::MAX_AGE){ $block->age = self::MAX_AGE; } - - $ev = new BlockGrowEvent($this, $block, $player); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); + if(BlockEventHelper::grow($this, $block, $player)){ $item->pop(); } @@ -96,11 +92,7 @@ abstract class Crops extends Flowable{ if($this->age < self::MAX_AGE && mt_rand(0, 2) === 1){ $block = clone $this; ++$block->age; - $ev = new BlockGrowEvent($this, $block); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::grow($this, $block, null); } } } diff --git a/src/block/Fire.php b/src/block/Fire.php index 0a3b2e3e3..a39ef6b9c 100644 --- a/src/block/Fire.php +++ b/src/block/Fire.php @@ -23,10 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\event\block\BlockBurnEvent; -use pocketmine\event\block\BlockSpreadEvent; use pocketmine\math\Facing; use pocketmine\world\format\Chunk; use pocketmine\world\World; @@ -225,13 +225,6 @@ class Fire extends BaseFire{ } private function spreadBlock(Block $block, Block $newState) : bool{ - $ev = new BlockSpreadEvent($block, $this, $newState); - $ev->call(); - if(!$ev->isCancelled()){ - $block->position->getWorld()->setBlock($block->position, $ev->getNewState()); - return true; - } - - return false; + return BlockEventHelper::spread($block, $newState, $this); } } diff --git a/src/block/FrostedIce.php b/src/block/FrostedIce.php index 38c552a95..039fe49f3 100644 --- a/src/block/FrostedIce.php +++ b/src/block/FrostedIce.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockMeltEvent; use function mt_rand; class FrostedIce extends Ice{ @@ -97,11 +97,7 @@ class FrostedIce extends Ice{ private function tryMelt() : bool{ $world = $this->position->getWorld(); if($this->age >= self::MAX_AGE){ - $ev = new BlockMeltEvent($this, VanillaBlocks::WATER()); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::melt($this, VanillaBlocks::WATER()); return true; } diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index 39ce512a6..84dec29ce 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockSpreadEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -166,14 +166,7 @@ class GlowLichen extends Transparent{ return false; } - $ev = new BlockSpreadEvent($replacedBlock, $this, $replacementBlock); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($replacedBlock->getPosition(), $ev->getNewState()); - return true; - } - - return false; + return BlockEventHelper::spread($replacedBlock, $replacementBlock, $this); } /** diff --git a/src/block/Grass.php b/src/block/Grass.php index 54975d046..709dc6a9d 100644 --- a/src/block/Grass.php +++ b/src/block/Grass.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\DirtType; -use pocketmine\event\block\BlockSpreadEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Hoe; use pocketmine\item\Item; @@ -58,11 +58,7 @@ class Grass extends Opaque{ $lightAbove = $world->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z); if($lightAbove < 4 && $world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){ //grass dies - $ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT()); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState(), false); - } + BlockEventHelper::spread($this, VanillaBlocks::DIRT(), $this); }elseif($lightAbove >= 9){ //try grass spread for($i = 0; $i < 4; ++$i){ @@ -80,11 +76,7 @@ class Grass extends Opaque{ continue; } - $ev = new BlockSpreadEvent($b, $this, VanillaBlocks::GRASS()); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($b->position, $ev->getNewState(), false); - } + BlockEventHelper::spread($b, VanillaBlocks::GRASS(), $this); } } } diff --git a/src/block/Ice.php b/src/block/Ice.php index 41f60e3f9..ca0302f90 100644 --- a/src/block/Ice.php +++ b/src/block/Ice.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\event\block\BlockMeltEvent; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; use pocketmine\player\Player; @@ -53,11 +53,7 @@ class Ice extends Transparent{ public function onRandomTick() : void{ $world = $this->position->getWorld(); if($world->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){ - $ev = new BlockMeltEvent($this, VanillaBlocks::WATER()); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::melt($this, VanillaBlocks::WATER()); } } diff --git a/src/block/Liquid.php b/src/block/Liquid.php index a56f666a2..e102540ec 100644 --- a/src/block/Liquid.php +++ b/src/block/Liquid.php @@ -23,11 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\MinimumCostFlowCalculator; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; -use pocketmine\event\block\BlockFormEvent; use pocketmine\event\block\BlockSpreadEvent; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -363,12 +363,8 @@ abstract class Liquid extends Transparent{ } protected function liquidCollide(Block $cause, Block $result) : bool{ - $ev = new BlockFormEvent($this, $result, $cause); - $ev->call(); - if(!$ev->isCancelled()){ - $world = $this->position->getWorld(); - $world->setBlock($this->position, $ev->getNewState()); - $world->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8)); + if(BlockEventHelper::form($this, $result, $cause)){ + $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8)); } return true; } diff --git a/src/block/MobHead.php b/src/block/MobHead.php index 1b3f2d88f..96cd1cf34 100644 --- a/src/block/MobHead.php +++ b/src/block/MobHead.php @@ -113,14 +113,15 @@ class MobHead extends Flowable{ * @return AxisAlignedBB[] */ protected function recalculateCollisionBoxes() : array{ - $collisionBox = AxisAlignedBB::one()->contract(0.25, 0, 0.25)->trim(Facing::UP, 0.5); - return match($this->facing){ - Facing::NORTH => [$collisionBox->offset(0, 0.25, 0.25)], - Facing::SOUTH => [$collisionBox->offset(0, 0.25, -0.25)], - Facing::WEST => [$collisionBox->offset(0.25, 0.25, 0)], - Facing::EAST => [$collisionBox->offset(-0.25, 0.25, 0)], - default => [$collisionBox] - }; + $collisionBox = AxisAlignedBB::one() + ->contract(0.25, 0, 0.25) + ->trim(Facing::UP, 0.5); + if($this->facing !== Facing::UP){ + $collisionBox = $collisionBox + ->offsetTowards(Facing::opposite($this->facing), 0.25) + ->offsetTowards(Facing::UP, 0.25); + } + return [$collisionBox]; } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ diff --git a/src/block/Mycelium.php b/src/block/Mycelium.php index c87f89367..08b0b3e75 100644 --- a/src/block/Mycelium.php +++ b/src/block/Mycelium.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\DirtType; -use pocketmine\event\block\BlockSpreadEvent; use pocketmine\item\Item; use pocketmine\math\Facing; use function mt_rand; @@ -54,11 +54,7 @@ class Mycelium extends Opaque{ $block = $world->getBlockAt($x, $y, $z); if($block instanceof Dirt && $block->getDirtType()->equals(DirtType::NORMAL())){ if($block->getSide(Facing::UP) instanceof Transparent){ - $ev = new BlockSpreadEvent($block, $this, VanillaBlocks::MYCELIUM()); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($block->position, $ev->getNewState()); - } + BlockEventHelper::spread($block, VanillaBlocks::MYCELIUM(), $this); } } } diff --git a/src/block/NetherWartPlant.php b/src/block/NetherWartPlant.php index 6a8fe1f7a..d7e587441 100644 --- a/src/block/NetherWartPlant.php +++ b/src/block/NetherWartPlant.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\FortuneDropHelper; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -76,11 +76,7 @@ class NetherWartPlant extends Flowable{ if($this->age < self::MAX_AGE && mt_rand(0, 10) === 0){ //Still growing $block = clone $this; $block->age++; - $ev = new BlockGrowEvent($this, $block); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::grow($this, $block, null); } } diff --git a/src/block/Podzol.php b/src/block/Podzol.php index 4eb720573..33285cc0e 100644 --- a/src/block/Podzol.php +++ b/src/block/Podzol.php @@ -27,6 +27,10 @@ use pocketmine\item\Item; class Podzol extends Opaque{ + public function isAffectedBySilkTouch() : bool{ + return true; + } + public function getDropsForCompatibleTool(Item $item) : array{ return [ VanillaBlocks::DIRT()->asItem() diff --git a/src/block/SnowLayer.php b/src/block/SnowLayer.php index f561c8ff5..05fc88421 100644 --- a/src/block/SnowLayer.php +++ b/src/block/SnowLayer.php @@ -23,11 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockMeltEvent; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\math\AxisAlignedBB; @@ -105,11 +105,7 @@ class SnowLayer extends Flowable implements Fallable{ public function onRandomTick() : void{ $world = $this->position->getWorld(); if($world->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){ - $ev = new BlockMeltEvent($this, VanillaBlocks::AIR()); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::melt($this, VanillaBlocks::AIR()); } } diff --git a/src/block/Stem.php b/src/block/Stem.php index 252b28aeb..7223572dd 100644 --- a/src/block/Stem.php +++ b/src/block/Stem.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Item; use pocketmine\math\Facing; use function array_rand; @@ -64,11 +64,7 @@ abstract class Stem extends Crops{ if($this->age < self::MAX_AGE){ $block = clone $this; ++$block->age; - $ev = new BlockGrowEvent($this, $block); - $ev->call(); - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::grow($this, $block, null); }else{ $grow = $this->getPlant(); foreach(Facing::HORIZONTAL as $side){ @@ -80,12 +76,7 @@ abstract class Stem extends Crops{ $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()); - } + BlockEventHelper::grow($side, $grow, null); } } } diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index 4cc5989a7..3757e4577 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -60,13 +60,11 @@ class Sugarcane extends Flowable{ } $b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z); if($b->getTypeId() === BlockTypeIds::AIR){ - $ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE(), $player); - $ev->call(); - if($ev->isCancelled()){ + if(BlockEventHelper::grow($b, VanillaBlocks::SUGARCANE(), $player)){ + $grew = true; + }else{ break; } - $world->setBlock($b->position, $ev->getNewState()); - $grew = true; }elseif(!$b->hasSameTypeId($this)){ break; } diff --git a/src/block/SweetBerryBush.php b/src/block/SweetBerryBush.php index 5ead39390..ef1169df5 100644 --- a/src/block/SweetBerryBush.php +++ b/src/block/SweetBerryBush.php @@ -23,11 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\FortuneDropHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; use pocketmine\entity\Living; -use pocketmine\event\block\BlockGrowEvent; use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\item\Fertilizer; use pocketmine\item\Item; @@ -87,15 +87,9 @@ class SweetBerryBush extends Flowable{ if($this->age < self::STAGE_MATURE && $item instanceof Fertilizer){ $block = clone $this; $block->age++; - - $ev = new BlockGrowEvent($this, $block, $player); - $ev->call(); - - if(!$ev->isCancelled()){ - $world->setBlock($this->position, $ev->getNewState()); + if(BlockEventHelper::grow($this, $block, $player)){ $item->pop(); } - }elseif(($dropAmount = $this->getBerryDropAmount()) > 0){ $world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES)); $world->dropItem($this->position, $this->asItem()->setCount($dropAmount)); @@ -133,11 +127,7 @@ class SweetBerryBush extends Flowable{ if($this->age < self::STAGE_MATURE && mt_rand(0, 2) === 1){ $block = clone $this; ++$block->age; - $ev = new BlockGrowEvent($this, $block); - $ev->call(); - if(!$ev->isCancelled()){ - $this->position->getWorld()->setBlock($this->position, $ev->getNewState()); - } + BlockEventHelper::grow($this, $block, null); } } diff --git a/src/block/utils/BlockEventHelper.php b/src/block/utils/BlockEventHelper.php new file mode 100644 index 000000000..fc9b06d13 --- /dev/null +++ b/src/block/utils/BlockEventHelper.php @@ -0,0 +1,115 @@ +call(); + if($ev->isCancelled()){ + return false; + } + $newState = $ev->getNewState(); + } + + $position = $oldState->getPosition(); + $position->getWorld()->setBlock($position, $newState); + return true; + } + + public static function spread(Block $oldState, Block $newState, Block $source) : bool{ + if(BlockSpreadEvent::hasHandlers()){ + $ev = new BlockSpreadEvent($oldState, $source, $newState); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $newState = $ev->getNewState(); + } + + $position = $oldState->getPosition(); + $position->getWorld()->setBlock($position, $newState); + return true; + } + + public static function form(Block $oldState, Block $newState, Block $cause) : bool{ + if(BlockFormEvent::hasHandlers()){ + $ev = new BlockFormEvent($oldState, $newState, $cause); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $newState = $ev->getNewState(); + } + + $position = $oldState->getPosition(); + $position->getWorld()->setBlock($position, $newState); + return true; + } + + public static function melt(Block $oldState, Block $newState) : bool{ + if(BlockMeltEvent::hasHandlers()){ + $ev = new BlockMeltEvent($oldState, $newState); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $newState = $ev->getNewState(); + } + + $position = $oldState->getPosition(); + $position->getWorld()->setBlock($position, $newState); + return true; + } + + public static function die(Block $oldState, Block $newState) : bool{ + if(BlockDeathEvent::hasHandlers()){ + $ev = new BlockDeathEvent($oldState, $newState); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $newState = $ev->getNewState(); + } + + $position = $oldState->getPosition(); + $position->getWorld()->setBlock($position, $newState); + return true; + } +} diff --git a/src/event/Event.php b/src/event/Event.php index 5488285b2..21b8ae36a 100644 --- a/src/event/Event.php +++ b/src/event/Event.php @@ -27,6 +27,7 @@ declare(strict_types=1); namespace pocketmine\event; use pocketmine\timings\Timings; +use function count; use function get_class; abstract class Event{ @@ -54,11 +55,11 @@ abstract class Event{ $timings = Timings::getEventTimings($this); $timings->startTiming(); - $handlerList = HandlerListManager::global()->getListFor(get_class($this)); + $handlers = HandlerListManager::global()->getHandlersFor(static::class); ++self::$eventCallDepth; try{ - foreach($handlerList->getListenerList() as $registration){ + foreach($handlers as $registration){ $registration->callEvent($this); } }finally{ @@ -66,4 +67,14 @@ abstract class Event{ $timings->stopTiming(); } } + + /** + * Returns whether the current class context has any registered global handlers. + * This can be used in hot code paths to avoid unnecessary event object creation. + * + * Usage: SomeEventClass::hasHandlers() + */ + public static function hasHandlers() : bool{ + return count(HandlerListManager::global()->getHandlersFor(static::class)) > 0; + } } diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php index 37811e959..74eedf3a4 100644 --- a/src/event/HandlerList.php +++ b/src/event/HandlerList.php @@ -33,8 +33,6 @@ class HandlerList{ /** @var RegisteredListener[][] */ private array $handlerSlots = []; - private RegisteredListenerCache $handlerCache; - /** @var RegisteredListenerCache[] */ private array $affectedHandlerCaches = []; @@ -44,9 +42,9 @@ class HandlerList{ */ public function __construct( private string $class, - private ?HandlerList $parentList + private ?HandlerList $parentList, + private RegisteredListenerCache $handlerCache = new RegisteredListenerCache() ){ - $this->handlerCache = new RegisteredListenerCache(); for($list = $this; $list !== null; $list = $list->parentList){ $list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache; } diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index ab94674cf..047632f54 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -36,6 +36,11 @@ class HandlerListManager{ /** @var HandlerList[] classname => HandlerList */ private array $allLists = []; + /** + * @var RegisteredListenerCache[] event class name => cache + * @phpstan-var array, RegisteredListenerCache> + */ + private array $handlerCaches = []; /** * Unregisters all the listeners @@ -98,7 +103,25 @@ class HandlerListManager{ } $parent = self::resolveNearestHandleableParent($class); - return $this->allLists[$event] = new HandlerList($event, $parent !== null ? $this->getListFor($parent->getName()) : null); + $cache = new RegisteredListenerCache(); + $this->handlerCaches[$event] = $cache; + return $this->allLists[$event] = new HandlerList( + $event, + parentList: $parent !== null ? $this->getListFor($parent->getName()) : null, + handlerCache: $cache + ); + } + + /** + * @phpstan-template TEvent of Event + * @phpstan-param class-string $event + * + * @return RegisteredListener[] + */ + public function getHandlersFor(string $event) : array{ + $cache = $this->handlerCaches[$event] ?? null; + //getListFor() will populate the cache for the next call + return $cache?->list ?? $this->getListFor($event)->getListenerList(); } /** diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index dbd857b0f..100d9c989 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -406,10 +406,12 @@ class NetworkSession{ $timings->startTiming(); try{ - $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer); - $ev->call(); - if($ev->isCancelled()){ - return; + if(DataPacketDecodeEvent::hasHandlers()){ + $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer); + $ev->call(); + if($ev->isCancelled()){ + return; + } } $decodeTimings = Timings::getDecodeDataPacketTimings($packet); @@ -429,19 +431,22 @@ class NetworkSession{ $decodeTimings->stopTiming(); } - $ev = new DataPacketReceiveEvent($this, $packet); - $ev->call(); - if(!$ev->isCancelled()){ - $handlerTimings = Timings::getHandleDataPacketTimings($packet); - $handlerTimings->startTiming(); - try{ - if($this->handler === null || !$packet->handle($this->handler)){ - $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer())); - } - }finally{ - $handlerTimings->stopTiming(); + if(DataPacketReceiveEvent::hasHandlers()){ + $ev = new DataPacketReceiveEvent($this, $packet); + $ev->call(); + if($ev->isCancelled()){ + return; } } + $handlerTimings = Timings::getHandleDataPacketTimings($packet); + $handlerTimings->startTiming(); + try{ + if($this->handler === null || !$packet->handle($this->handler)){ + $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer())); + } + }finally{ + $handlerTimings->stopTiming(); + } }finally{ $timings->stopTiming(); } @@ -459,12 +464,16 @@ class NetworkSession{ $timings = Timings::getSendDataPacketTimings($packet); $timings->startTiming(); try{ - $ev = new DataPacketSendEvent([$this], [$packet]); - $ev->call(); - if($ev->isCancelled()){ - return false; + if(DataPacketSendEvent::hasHandlers()){ + $ev = new DataPacketSendEvent([$this], [$packet]); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $packets = $ev->getPackets(); + }else{ + $packets = [$packet]; } - $packets = $ev->getPackets(); foreach($packets as $evPacket){ $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket)); diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 9de3d214a..c200859fd 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -44,12 +44,14 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ public function broadcastPackets(array $recipients, array $packets) : void{ //TODO: this shouldn't really be called here, since the broadcaster might be replaced by an alternative //implementation that doesn't fire events - $ev = new DataPacketSendEvent($recipients, $packets); - $ev->call(); - if($ev->isCancelled()){ - return; + if(DataPacketSendEvent::hasHandlers()){ + $ev = new DataPacketSendEvent($recipients, $packets); + $ev->call(); + if($ev->isCancelled()){ + return; + } + $packets = $ev->getPackets(); } - $packets = $ev->getPackets(); $compressors = []; diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index e59b6971f..e2d6d9a66 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -85,6 +85,7 @@ class RakLibServer extends Thread{ gc_enable(); ini_set("display_errors", '1'); ini_set("display_startup_errors", '1'); + \GlobalLogger::set($this->logger); $socket = new ServerSocket($this->address->deserialize()); $manager = new Server( @@ -107,11 +108,6 @@ class RakLibServer extends Thread{ $manager->waitShutdown(); } - protected function onUncaughtException(\Throwable $e) : void{ - parent::onUncaughtException($e); - $this->logger->logException($e); - } - public function getThreadName() : string{ return "RakLib"; } diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php index b7e622934..2d8324887 100644 --- a/src/permission/PermissionManager.php +++ b/src/permission/PermissionManager.php @@ -81,9 +81,9 @@ class PermissionManager{ } public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{ - foreach($this->permSubs as $permission => &$subs){ - unset($subs[spl_object_id($permissible)]); - if(count($subs) === 0){ + foreach($this->permSubs as $permission => $subs){ + unset($this->permSubs[$permission][spl_object_id($permissible)]); + if(count($this->permSubs[$permission]) === 0){ unset($this->permSubs[$permission]); } } diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index 6429ac06b..2df6750de 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -154,6 +154,13 @@ class ResourcePackManager{ return $this->serverForceResources; } + /** + * Sets whether players must accept resource packs in order to join. + */ + public function setResourcePacksRequired(bool $value) : void{ + $this->serverForceResources = $value; + } + /** * Returns an array of resource packs in use, sorted in order of priority. * @return ResourcePack[] diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index b26afc29b..919e3eedc 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -69,11 +69,6 @@ class AsyncWorker extends Worker{ $this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier()); } - protected function onUncaughtException(\Throwable $e) : void{ - parent::onUncaughtException($e); - $this->logger->logException($e); - } - public function getLogger() : ThreadSafeLogger{ return $this->logger; } diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index 340ce554d..de2ecbde8 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -28,6 +28,7 @@ use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\Server; use function error_get_last; use function error_reporting; +use function implode; use function register_shutdown_function; use function set_exception_handler; @@ -115,6 +116,7 @@ trait CommonThreadPartsTrait{ protected function onUncaughtException(\Throwable $e) : void{ $this->synchronized(function() use ($e) : void{ $this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName()); + \GlobalLogger::get()->logException($e); }); } @@ -124,15 +126,26 @@ trait CommonThreadPartsTrait{ */ protected function onShutdown() : void{ $this->synchronized(function() : void{ - if(!$this->isKilled && $this->crashInfo === null){ + if($this->isTerminated() && $this->crashInfo === null){ $last = error_get_last(); if($last !== null){ //fatal error - $this->crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName()); + $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()); + $crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName()); } + $this->crashInfo = $crashInfo; + + $lines = []; + //mimic exception printed format + $lines[] = "Fatal error: " . $crashInfo->makePrettyMessage(); + $lines[] = "--- Stack trace ---"; + foreach($crashInfo->getTrace() as $frame){ + $lines[] = " " . $frame->getPrintableFrame(); + } + $lines[] = "--- End of fatal error information ---"; + \GlobalLogger::get()->critical(implode("\n", $lines)); } }); } diff --git a/src/world/World.php b/src/world/World.php index f556d2e1f..7d9311c70 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -200,6 +200,11 @@ class World implements ChunkManager{ * @phpstan-var array> */ private array $blockCache = []; + /** + * @var AxisAlignedBB[][][] chunkHash => [relativeBlockHash => AxisAlignedBB[]] + * @phpstan-var array>> + */ + private array $blockCollisionBoxCache = []; private int $sendTimeTicker = 0; @@ -646,6 +651,7 @@ class World implements ChunkManager{ $this->provider->close(); $this->blockCache = []; + $this->blockCollisionBoxCache = []; $this->unloaded = true; } @@ -1117,6 +1123,7 @@ class World implements ChunkManager{ public function clearCache(bool $force = false) : void{ if($force){ $this->blockCache = []; + $this->blockCollisionBoxCache = []; }else{ $count = 0; foreach($this->blockCache as $list){ @@ -1126,6 +1133,16 @@ class World implements ChunkManager{ break; } } + + $count = 0; + foreach($this->blockCollisionBoxCache as $list){ + $count += count($list); + if($count > 2048){ + //TODO: Is this really the best logic? + $this->blockCollisionBoxCache = []; + break; + } + } } } @@ -1491,25 +1508,53 @@ class World implements ChunkManager{ return $collides; } + /** + * Returns a list of all block AABBs which overlap the full block area at the given coordinates. + * This checks a padding of 1 block around the coordinates to account for oversized AABBs of blocks like fences. + * Larger AABBs (>= 2 blocks on any axis) are not accounted for. + * + * @return AxisAlignedBB[] + */ + private function getCollisionBoxesForCell(int $x, int $y, int $z) : array{ + $block = $this->getBlockAt($x, $y, $z); + $boxes = $block->getCollisionBoxes(); + + $cellBB = AxisAlignedBB::one()->offset($x, $y, $z); + foreach(Facing::ALL as $facing){ + $extraBoxes = $block->getSide($facing)->getCollisionBoxes(); + foreach($extraBoxes as $extraBox){ + if($extraBox->intersectsWith($cellBB)){ + $boxes[] = $extraBox; + } + } + } + + return $boxes; + } + /** * @return AxisAlignedBB[] * @phpstan-return list */ public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entities = true) : array{ - $minX = (int) floor($bb->minX - 1); - $minY = (int) floor($bb->minY - 1); - $minZ = (int) floor($bb->minZ - 1); - $maxX = (int) floor($bb->maxX + 1); - $maxY = (int) floor($bb->maxY + 1); - $maxZ = (int) floor($bb->maxZ + 1); + $minX = (int) floor($bb->minX); + $minY = (int) floor($bb->minY); + $minZ = (int) floor($bb->minZ); + $maxX = (int) floor($bb->maxX); + $maxY = (int) floor($bb->maxY); + $maxZ = (int) floor($bb->maxZ); $collides = []; for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ + $chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE); for($y = $minY; $y <= $maxY; ++$y){ - $block = $this->getBlockAt($x, $y, $z); - foreach($block->getCollisionBoxes() as $blockBB){ + $relativeBlockHash = World::chunkBlockHash($x, $y, $z); + + $boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getCollisionBoxesForCell($x, $y, $z); + + foreach($boxes as $blockBB){ if($blockBB->intersectsWith($bb)){ $collides[] = $blockBB; } @@ -1682,14 +1727,10 @@ class World implements ChunkManager{ */ private function getHighestAdjacentLight(int $x, int $y, int $z, \Closure $lightGetter) : int{ $max = 0; - foreach([ - [$x + 1, $y, $z], - [$x - 1, $y, $z], - [$x, $y + 1, $z], - [$x, $y - 1, $z], - [$x, $y, $z + 1], - [$x, $y, $z - 1] - ] as [$x1, $y1, $z1]){ + foreach(Facing::OFFSET as [$offsetX, $offsetY, $offsetZ]){ + $x1 = $x + $offsetX; + $y1 = $y + $offsetY; + $z1 = $z + $offsetZ; if( !$this->isInWorld($x1, $y1, $z1) || ($chunk = $this->getChunk($x1 >> Chunk::COORD_BIT_SIZE, $z1 >> Chunk::COORD_BIT_SIZE)) === null || @@ -1858,6 +1899,14 @@ class World implements ChunkManager{ $relativeBlockHash = World::chunkBlockHash($x, $y, $z); unset($this->blockCache[$chunkHash][$relativeBlockHash]); + unset($this->blockCollisionBoxCache[$chunkHash][$relativeBlockHash]); + //blocks like fences have collision boxes that reach into neighbouring blocks, so we need to invalidate the + //caches for those blocks as well + foreach(Facing::OFFSET as [$offsetX, $offsetY, $offsetZ]){ + $sideChunkPosHash = World::chunkHash(($x + $offsetX) >> Chunk::COORD_BIT_SIZE, ($z + $offsetZ) >> Chunk::COORD_BIT_SIZE); + $sideChunkBlockHash = World::chunkBlockHash($x + $offsetX, $y + $offsetY, $z + $offsetZ); + unset($this->blockCollisionBoxCache[$sideChunkPosHash][$sideChunkBlockHash]); + } if(!isset($this->changedBlocks[$chunkHash])){ $this->changedBlocks[$chunkHash] = []; @@ -2454,6 +2503,7 @@ class World implements ChunkManager{ $this->chunks[$chunkHash] = $chunk; unset($this->blockCache[$chunkHash]); + unset($this->blockCollisionBoxCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); $chunk->setTerrainDirty(); $this->markTickingChunkForRecheck($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking @@ -2733,6 +2783,7 @@ class World implements ChunkManager{ } $this->chunks[$chunkHash] = $chunk; unset($this->blockCache[$chunkHash]); + unset($this->blockCollisionBoxCache[$chunkHash]); $this->initChunk($x, $z, $chunkData); @@ -2887,6 +2938,7 @@ class World implements ChunkManager{ unset($this->chunks[$chunkHash]); unset($this->blockCache[$chunkHash]); + unset($this->blockCollisionBoxCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); unset($this->registeredTickingChunks[$chunkHash]); $this->markTickingChunkForRecheck($x, $z); @@ -2951,7 +3003,7 @@ class World implements ChunkManager{ * @throws WorldException if the terrain is not generated */ public function getSafeSpawn(?Vector3 $spawn = null) : Position{ - if(!($spawn instanceof Vector3) || $spawn->y < 1){ + if(!($spawn instanceof Vector3) || $spawn->y <= $this->minY){ $spawn = $this->getSpawnLocation(); } diff --git a/src/world/light/LightUpdate.php b/src/world/light/LightUpdate.php index 1a82bdcef..58ac0a9dc 100644 --- a/src/world/light/LightUpdate.php +++ b/src/world/light/LightUpdate.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\world\light; +use pocketmine\math\Facing; use pocketmine\world\format\LightArray; use pocketmine\world\format\SubChunk; use pocketmine\world\utils\SubChunkExplorer; @@ -33,15 +34,6 @@ use function max; //TODO: make light updates asynchronous abstract class LightUpdate{ - private const ADJACENTS = [ - [ 1, 0, 0], - [-1, 0, 0], - [ 0, 1, 0], - [ 0, -1, 0], - [ 0, 0, 1], - [ 0, 0, -1] - ]; - public const BASE_LIGHT_FILTER = 1; /** @@ -78,7 +70,7 @@ abstract class LightUpdate{ protected function getHighestAdjacentLight(int $x, int $y, int $z) : int{ $adjacent = 0; - foreach(self::ADJACENTS as [$ox, $oy, $oz]){ + foreach(Facing::OFFSET as [$ox, $oy, $oz]){ if(($adjacent = max($adjacent, $this->getEffectiveLight($x + $ox, $y + $oy, $z + $oz))) === 15){ break; } @@ -123,7 +115,7 @@ abstract class LightUpdate{ $touched++; [$x, $y, $z, $oldAdjacentLight] = $context->removalQueue->dequeue(); - foreach(self::ADJACENTS as [$ox, $oy, $oz]){ + foreach(Facing::OFFSET as [$ox, $oy, $oz]){ $cx = $x + $ox; $cy = $y + $oy; $cz = $z + $oz; @@ -163,7 +155,7 @@ abstract class LightUpdate{ continue; } - foreach(self::ADJACENTS as [$ox, $oy, $oz]){ + foreach(Facing::OFFSET as [$ox, $oy, $oz]){ $cx = $x + $ox; $cy = $y + $oy; $cz = $z + $oz; diff --git a/tests/phpstan/rules/DisallowForeachByReferenceRule.php b/tests/phpstan/rules/DisallowForeachByReferenceRule.php new file mode 100644 index 000000000..79124d328 --- /dev/null +++ b/tests/phpstan/rules/DisallowForeachByReferenceRule.php @@ -0,0 +1,53 @@ + + */ +final class DisallowForeachByReferenceRule implements Rule{ + + public function getNodeType() : string{ + return Foreach_::class; + } + + public function processNode(Node $node, Scope $scope) : array{ + /** @var Foreach_ $node */ + if($node->byRef){ + return [ + RuleErrorBuilder::message("Foreach by-reference is not allowed, because it has surprising behaviour.") + ->tip("If the value variable is used outside of the foreach construct (e.g. in a second foreach), the iterable's contents will be unexpectedly altered.") + ->build() + ]; + } + + return []; + } +} diff --git a/tests/phpunit/block/regenerate_consistency_check.php b/tests/phpunit/block/regenerate_consistency_check.php index 9930c54f0..ac8a1ad9d 100644 --- a/tests/phpunit/block/regenerate_consistency_check.php +++ b/tests/phpunit/block/regenerate_consistency_check.php @@ -24,6 +24,7 @@ declare(strict_types=1); use pocketmine\block\Block; use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Utils; require dirname(__DIR__, 3) . '/vendor/autoload.php'; @@ -91,8 +92,9 @@ foreach($new as $stateId => $name){ $newTable[$name][] = $stateId; } ksort($newTable, SORT_STRING); -foreach($newTable as &$stateIds){ +foreach(Utils::stringifyKeys($newTable) as $name => $stateIds){ sort($stateIds, SORT_NUMERIC); + $newTable[$name] = $stateIds; } file_put_contents(__DIR__ . '/block_factory_consistency_check.json', json_encode(