diff --git a/changelogs/4.12.md b/changelogs/4.12.md index 16e23dc6a..ab233e6ca 100644 --- a/changelogs/4.12.md +++ b/changelogs/4.12.md @@ -50,3 +50,23 @@ Released 6th January 2023. ## Fixes - Removed a workaround for an old client bug in custom form responses. The code contained a denial-of-service vulnerability. + +# 4.12.6 +Released 7th January 2023. + +## Changes +- Added a new security measure to `NetworkSession` to detect and ban players who flood the server with packets. + +# 4.12.7 +Released 8th January 2023. + +## Fixes +- Fixed players getting kicked when the server lags for too long. +- Fixed players getting kicked when a debugging session is active and a breakpoint is hit. + +# 4.12.8 +Released 9th January 2023. + +## Fixes +- Fixed players getting kicked during PvP. +- Fixed players randomly getting kicked on Windows (improper rate limit handling wrt. 15ms timer resolution). \ No newline at end of file diff --git a/composer.json b/composer.json index 595281d1d..9b5644afd 100644 --- a/composer.json +++ b/composer.json @@ -37,13 +37,13 @@ "pocketmine/bedrock-block-upgrade-schema": "dev-master@dev", "pocketmine/bedrock-data": "dev-modern-world-support@dev", "pocketmine/bedrock-item-upgrade-schema": "dev-master", - "pocketmine/bedrock-protocol": "~17.1.0+bedrock-1.19.50", + "pocketmine/bedrock-protocol": "~18.0.0+bedrock-1.19.50", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.2.0", - "pocketmine/color": "^0.2.0", + "pocketmine/color": "^0.3.0", "pocketmine/errorhandler": "^0.6.0", - "pocketmine/locale-data": "~2.15.0", + "pocketmine/locale-data": "~2.16.0", "pocketmine/log": "^0.4.0", "pocketmine/log-pthreads": "^0.4.0", "pocketmine/math": "^0.4.0", @@ -55,7 +55,7 @@ "symfony/filesystem": "^5.4" }, "require-dev": { - "phpstan/phpstan": "1.9.7", + "phpstan/phpstan": "1.9.11", "phpstan/phpstan-phpunit": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.2.0", "phpunit/phpunit": "^9.2" diff --git a/composer.lock b/composer.lock index 7fbf9957a..c3c76f83e 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": "bf576fbfb1454aecaa4313129d2deadc", + "content-hash": "ecbdb7bda8ed93cb0499bfe486a68ff6", "packages": [ { "name": "adhocore/json-comment", @@ -330,16 +330,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "17.1.0+bedrock-1.19.50", + "version": "18.0.0+bedrock-1.19.50", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "c572706cf5e3202718dd35a35dd30fe08cd671de" + "reference": "b558ec883bd967dd3339f513cba62d2fbcd63349" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c572706cf5e3202718dd35a35dd30fe08cd671de", - "reference": "c572706cf5e3202718dd35a35dd30fe08cd671de", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/b558ec883bd967dd3339f513cba62d2fbcd63349", + "reference": "b558ec883bd967dd3339f513cba62d2fbcd63349", "shasum": "" }, "require": { @@ -347,13 +347,13 @@ "netresearch/jsonmapper": "^4.0", "php": "^8.0", "pocketmine/binaryutils": "^0.2.0", - "pocketmine/color": "^0.2.0", + "pocketmine/color": "^0.2.0 || ^0.3.0", "pocketmine/math": "^0.3.0 || ^0.4.0", "pocketmine/nbt": "^0.3.0", "ramsey/uuid": "^4.1" }, "require-dev": { - "phpstan/phpstan": "1.9.3", + "phpstan/phpstan": "1.9.4", "phpstan/phpstan-phpunit": "^1.0.0", "phpstan/phpstan-strict-rules": "^1.0.0", "phpunit/phpunit": "^9.5" @@ -371,9 +371,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/17.1.0+bedrock-1.19.50" + "source": "https://github.com/pmmp/BedrockProtocol/tree/18.0.0+bedrock-1.19.50" }, - "time": "2022-12-15T20:34:49+00:00" + "time": "2023-01-06T21:47:35+00:00" }, { "name": "pocketmine/binaryutils", @@ -514,24 +514,24 @@ }, { "name": "pocketmine/color", - "version": "0.2.0", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/pmmp/Color.git", - "reference": "09be6ea6d76f2e33d6813c39d29c22c46c17e1d2" + "reference": "8cb346d0a21ad3287cc8d7175e4b643416607249" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Color/zipball/09be6ea6d76f2e33d6813c39d29c22c46c17e1d2", - "reference": "09be6ea6d76f2e33d6813c39d29c22c46c17e1d2", + "url": "https://api.github.com/repos/pmmp/Color/zipball/8cb346d0a21ad3287cc8d7175e4b643416607249", + "reference": "8cb346d0a21ad3287cc8d7175e4b643416607249", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^8.0" }, "require-dev": { - "phpstan/phpstan": "0.12.59", - "phpstan/phpstan-strict-rules": "^0.12.2" + "phpstan/phpstan": "1.9.4", + "phpstan/phpstan-strict-rules": "^1.2.0" }, "type": "library", "autoload": { @@ -546,9 +546,9 @@ "description": "Color handling library used by PocketMine-MP and related projects", "support": { "issues": "https://github.com/pmmp/Color/issues", - "source": "https://github.com/pmmp/Color/tree/0.2.0" + "source": "https://github.com/pmmp/Color/tree/0.3.0" }, - "time": "2020-12-11T01:24:32+00:00" + "time": "2022-12-18T19:49:21+00:00" }, { "name": "pocketmine/errorhandler", @@ -591,16 +591,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.15.0", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "87feaefdd8364730a2350e58fa274b1b493a9d3f" + "reference": "b3bf9029c112414fdb7cd9de778df191565d3038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/87feaefdd8364730a2350e58fa274b1b493a9d3f", - "reference": "87feaefdd8364730a2350e58fa274b1b493a9d3f", + "url": "https://api.github.com/repos/pmmp/Language/zipball/b3bf9029c112414fdb7cd9de778df191565d3038", + "reference": "b3bf9029c112414fdb7cd9de778df191565d3038", "shasum": "" }, "type": "library", @@ -608,9 +608,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.15.0" + "source": "https://github.com/pmmp/Language/tree/2.16.0" }, - "time": "2022-12-27T19:59:39+00:00" + "time": "2023-01-04T20:27:52+00:00" }, { "name": "pocketmine/log", @@ -906,42 +906,53 @@ }, { "name": "ramsey/collection", - "version": "1.2.2", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4", + "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4", "shasum": "" }, "require": { - "php": "^7.3 || ^8", + "php": "^7.4 || ^8.0", "symfony/polyfill-php81": "^1.23" }, "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" }, "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, "autoload": { "psr-4": { "Ramsey\\Collection\\": "src/" @@ -969,7 +980,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.2.2" + "source": "https://github.com/ramsey/collection/tree/1.3.0" }, "funding": [ { @@ -981,27 +992,27 @@ "type": "tidelift" } ], - "time": "2021-10-10T03:01:02+00:00" + "time": "2022-12-27T19:12:24+00:00" }, { "name": "ramsey/uuid", - "version": "4.6.0", + "version": "4.7.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f" + "reference": "433b2014e3979047db08a17a205f410ba3869cf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ad63bc700e7d021039e30ce464eba384c4a1d40f", - "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2", + "reference": "433b2014e3979047db08a17a205f410ba3869cf2", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10", "ext-json": "*", "php": "^8.0", - "ramsey/collection": "^1.0" + "ramsey/collection": "^1.2 || ^2.0" }, "replace": { "rhumsaa/uuid": "self.version" @@ -1061,7 +1072,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.6.0" + "source": "https://github.com/ramsey/uuid/tree/4.7.3" }, "funding": [ { @@ -1073,7 +1084,7 @@ "type": "tidelift" } ], - "time": "2022-11-05T23:03:38+00:00" + "time": "2023-01-12T18:13:24+00:00" }, { "name": "symfony/filesystem", @@ -1470,30 +1481,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -1520,7 +1531,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -1536,7 +1547,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "myclabs/deep-copy", @@ -1766,16 +1777,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.7", + "version": "1.9.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0501435cd342eac7664bd62155b1ef907fc60b6f" + "reference": "60f3d68481eef216199eae7a2603cd5fe124d464" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0501435cd342eac7664bd62155b1ef907fc60b6f", - "reference": "0501435cd342eac7664bd62155b1ef907fc60b6f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/60f3d68481eef216199eae7a2603cd5fe124d464", + "reference": "60f3d68481eef216199eae7a2603cd5fe124d464", "shasum": "" }, "require": { @@ -1805,7 +1816,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.7" + "source": "https://github.com/phpstan/phpstan/tree/1.9.11" }, "funding": [ { @@ -1821,7 +1832,7 @@ "type": "tidelift" } ], - "time": "2023-01-04T21:59:57+00:00" + "time": "2023-01-12T14:04:13+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1877,21 +1888,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.4.4", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6" + "reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6", - "reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d", + "reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.8.6" + "phpstan/phpstan": "^1.9.7" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -1919,22 +1930,22 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.4" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.5" }, - "time": "2022-09-21T11:38:17+00:00" + "time": "2023-01-11T14:16:29+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.22", + "version": "9.2.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "e4bf60d2220b4baaa0572986b5d69870226b06df" + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e4bf60d2220b4baaa0572986b5d69870226b06df", - "reference": "e4bf60d2220b4baaa0572986b5d69870226b06df", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", "shasum": "" }, "require": { @@ -1990,7 +2001,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.22" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" }, "funding": [ { @@ -1998,7 +2009,7 @@ "type": "github" } ], - "time": "2022-12-18T16:40:55+00:00" + "time": "2022-12-28T12:41:10+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index 72318032a..fdfad3b09 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -114,7 +114,14 @@ abstract class BaseBanner extends Transparent{ return SupportType::NONE(); } + private function canBeSupportedBy(Block $block) : bool{ + return $block->isSolid(); + } + 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->getSupportingFace()))){ + return false; + } if($item instanceof ItemBanner){ $this->color = $item->getColor(); $this->setPatterns($item->getPatterns()); @@ -126,7 +133,7 @@ abstract class BaseBanner extends Transparent{ abstract protected function getSupportingFace() : int; public function onNearbyBlockChange() : void{ - if($this->getSide($this->getSupportingFace())->getTypeId() === BlockTypeIds::AIR){ + if(!$this->canBeSupportedBy($this->getSide($this->getSupportingFace()))){ $this->position->getWorld()->useBreakOn($this->position); } } diff --git a/src/block/Bell.php b/src/block/Bell.php index 702a3bac6..f9e61e43d 100644 --- a/src/block/Bell.php +++ b/src/block/Bell.php @@ -88,14 +88,13 @@ final class Bell extends Transparent{ return $this; } - private function canBeSupportedBy(Block $block) : bool{ - //TODO: this isn't the actual logic, but it's the closest approximation we can support for now - return $block->isSolid(); + private function canBeSupportedBy(Block $block, int $face) : bool{ + return !$block->getSupportType($face)->equals(SupportType::NONE()); } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ if($face === Facing::UP){ - if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()))){ + if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()), Facing::UP)){ return false; } if($player !== null){ @@ -103,18 +102,18 @@ final class Bell extends Transparent{ } $this->setAttachmentType(BellAttachmentType::FLOOR()); }elseif($face === Facing::DOWN){ - if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()))){ + 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))))){ + 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)))){ + if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)), Facing::opposite($face))){ $this->setAttachmentType(BellAttachmentType::TWO_WALLS()); } } @@ -123,10 +122,10 @@ final class Bell extends Transparent{ public function onNearbyBlockChange() : void{ if( - ($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP))) || - ($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN))) || - ($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))) || - ($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing))))) + ($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); } @@ -135,21 +134,20 @@ final class Bell extends Transparent{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ $faceHit = Facing::opposite($player->getHorizontalFacing()); - if($this->attachmentType->equals(BellAttachmentType::CEILING())){ - $this->ring($faceHit); - } - if($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)){ - $this->ring($faceHit); - } if( - ($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) && - ($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true)) + $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)) + ) ){ $this->ring($faceHit); + return true; } } - return true; + return false; } public function ring(int $faceHit) : void{ diff --git a/src/block/Button.php b/src/block/Button.php index c9ec492d6..64a953b7b 100644 --- a/src/block/Button.php +++ b/src/block/Button.php @@ -55,7 +55,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($blockClicked, $face)){ + if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } diff --git a/src/block/Ladder.php b/src/block/Ladder.php index ad70405ad..66e5f28cd 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($blockClicked, $face) && Facing::axis($face) !== Axis::Y){ + if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face) && Facing::axis($face) !== Axis::Y){ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } diff --git a/src/block/Lever.php b/src/block/Lever.php index a5a30936f..b82fb04cb 100644 --- a/src/block/Lever.php +++ b/src/block/Lever.php @@ -69,7 +69,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($blockClicked, $face)){ + if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ return false; } diff --git a/src/block/PressurePlate.php b/src/block/PressurePlate.php index 5610b4e1a..7f9403b74 100644 --- a/src/block/PressurePlate.php +++ b/src/block/PressurePlate.php @@ -45,7 +45,7 @@ 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($blockClicked)){ + if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } return false; @@ -55,5 +55,11 @@ abstract class PressurePlate extends Transparent{ return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE()); } + public function onNearbyBlockChange() : void{ + if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ + $this->position->getWorld()->useBreakOn($this->position); + } + } + //TODO } diff --git a/src/block/Thin.php b/src/block/Thin.php index 2c2758841..8de39f4ea 100644 --- a/src/block/Thin.php +++ b/src/block/Thin.php @@ -41,7 +41,7 @@ class Thin extends Transparent{ foreach(Facing::HORIZONTAL as $facing){ $side = $this->getSide($facing); - if($side instanceof Thin || $side->isFullCube()){ + if($side instanceof Thin || $side instanceof Wall || $side->isFullCube()){ $this->connections[$facing] = true; }else{ unset($this->connections[$facing]); diff --git a/src/block/Torch.php b/src/block/Torch.php index b84012839..0542c2be6 100644 --- a/src/block/Torch.php +++ b/src/block/Torch.php @@ -59,7 +59,6 @@ class Torch extends Flowable{ } public function onNearbyBlockChange() : void{ - $below = $this->getSide(Facing::DOWN); $face = Facing::opposite($this->facing); if(!$this->canBeSupportedBy($this->getSide($face), $this->facing)){ @@ -68,10 +67,7 @@ class Torch extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($blockClicked->canBeReplaced() && $this->canBeSupportedBy($blockClicked->getSide(Facing::DOWN), Facing::UP)){ - $this->facing = Facing::UP; - return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); - }elseif($face !== Facing::DOWN && $this->canBeSupportedBy($blockClicked, $face)){ + if($face !== Facing::DOWN && $this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); }else{ diff --git a/src/block/Vine.php b/src/block/Vine.php index 9edeca3c4..0ba00fee6 100644 --- a/src/block/Vine.php +++ b/src/block/Vine.php @@ -105,7 +105,7 @@ class Vine extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$blockClicked->isFullCube() || Facing::axis($face) === Axis::Y){ + if(!$blockReplace->getSide(Facing::opposite($face))->isFullCube() || Facing::axis($face) === Axis::Y){ return false; } diff --git a/src/block/Wall.php b/src/block/Wall.php index d4c116bda..817df1658 100644 --- a/src/block/Wall.php +++ b/src/block/Wall.php @@ -104,7 +104,7 @@ class Wall extends Transparent{ foreach(Facing::HORIZONTAL as $facing){ $block = $this->getSide($facing); - if($block instanceof static || $block instanceof FenceGate || ($block->isSolid() && !$block->isTransparent())){ + if($block instanceof static || $block instanceof FenceGate || $block instanceof Thin || ($block->isSolid() && !$block->isTransparent())){ if(!isset($this->connections[$facing])){ $this->connections[$facing] = WallConnectionType::SHORT(); $changed++; diff --git a/src/block/WallCoralFan.php b/src/block/WallCoralFan.php index 5349f78f2..e100170f4 100644 --- a/src/block/WallCoralFan.php +++ b/src/block/WallCoralFan.php @@ -45,7 +45,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($blockClicked, $face)){ + if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){ return false; } $this->facing = $face; diff --git a/src/block/WaterLily.php b/src/block/WaterLily.php index 39b3c8cee..8263330f6 100644 --- a/src/block/WaterLily.php +++ b/src/block/WaterLily.php @@ -39,19 +39,23 @@ class WaterLily extends Flowable{ return [AxisAlignedBB::one()->contract(1 / 16, 0, 1 / 16)->trim(Facing::UP, 63 / 64)]; } - public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if($blockClicked instanceof Water){ - $up = $blockClicked->getSide(Facing::UP); - if($up->canBeReplaced()){ - return parent::place($tx, $item, $up, $blockClicked, $face, $clickVector, $player); - } - } + public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{ + return !$blockReplace instanceof Water && parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock); + } - return false; + private function canBeSupportedBy(Block $block) : bool{ + return $block instanceof Water; + } + + 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))){ + return false; + } + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } public function onNearbyBlockChange() : void{ - if(!($this->getSide(Facing::DOWN) instanceof Water)){ + if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ $this->position->getWorld()->useBreakOn($this->position); } } diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 80519c609..82e84c18c 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -236,11 +236,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $locatedSerializer = $this->serializers[$typeId][get_class($blockState)] ?? null; if($locatedSerializer === null){ - $parents = class_parents($blockState); - if($parents === false){ - throw new AssumptionFailedError("A block class should always have at least one parent"); - } - foreach($parents as $parent){ + foreach(class_parents($blockState) as $parent){ if(isset($this->serializers[$typeId][$parent])){ $locatedSerializer = $this->serializers[$typeId][$parent]; break; diff --git a/src/data/bedrock/item/ItemSerializer.php b/src/data/bedrock/item/ItemSerializer.php index 152334d75..fbdf2c41b 100644 --- a/src/data/bedrock/item/ItemSerializer.php +++ b/src/data/bedrock/item/ItemSerializer.php @@ -103,13 +103,10 @@ final class ItemSerializer{ $locatedSerializer = $this->itemSerializers[$index][get_class($item)] ?? null; if($locatedSerializer === null){ - $parents = class_parents($item); - if($parents !== false){ - foreach($parents as $parent){ - if(isset($this->itemSerializers[$index][$parent])){ - $locatedSerializer = $this->itemSerializers[$index][$parent]; - break; - } + foreach(class_parents($item) as $parent){ + if(isset($this->itemSerializers[$index][$parent])){ + $locatedSerializer = $this->itemSerializers[$index][$parent]; + break; } } } @@ -162,13 +159,10 @@ final class ItemSerializer{ $locatedSerializer = $this->blockItemSerializers[$index][get_class($block)] ?? null; if($locatedSerializer === null){ - $parents = class_parents($block); - if($parents !== false){ - foreach($parents as $parent){ - if(isset($this->blockItemSerializers[$index][$parent])){ - $locatedSerializer = $this->blockItemSerializers[$index][$parent]; - break; - } + foreach(class_parents($block) as $parent){ + if(isset($this->blockItemSerializers[$index][$parent])){ + $locatedSerializer = $this->blockItemSerializers[$index][$parent]; + break; } } } diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php index 584fb342d..0a690236a 100644 --- a/src/lang/KnownTranslationFactory.php +++ b/src/lang/KnownTranslationFactory.php @@ -1176,6 +1176,10 @@ final class KnownTranslationFactory{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_DIFFICULTY_DESCRIPTION, []); } + public static function pocketmine_command_dumpmemory_description() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_DUMPMEMORY_DESCRIPTION, []); + } + public static function pocketmine_command_effect_description() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_EFFECT_DESCRIPTION, []); } diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php index d69e1db94..d826a5a10 100644 --- a/src/lang/KnownTranslationKeys.php +++ b/src/lang/KnownTranslationKeys.php @@ -260,6 +260,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_COMMAND_DEFAULTGAMEMODE_DESCRIPTION = "pocketmine.command.defaultgamemode.description"; public const POCKETMINE_COMMAND_DEOP_DESCRIPTION = "pocketmine.command.deop.description"; public const POCKETMINE_COMMAND_DIFFICULTY_DESCRIPTION = "pocketmine.command.difficulty.description"; + public const POCKETMINE_COMMAND_DUMPMEMORY_DESCRIPTION = "pocketmine.command.dumpmemory.description"; public const POCKETMINE_COMMAND_EFFECT_DESCRIPTION = "pocketmine.command.effect.description"; public const POCKETMINE_COMMAND_ENCHANT_DESCRIPTION = "pocketmine.command.enchant.description"; public const POCKETMINE_COMMAND_ERROR_PERMISSION = "pocketmine.command.error.permission"; diff --git a/src/lang/Language.php b/src/lang/Language.php index dec450956..c6fc72d7e 100644 --- a/src/lang/Language.php +++ b/src/lang/Language.php @@ -142,8 +142,10 @@ class Language{ * @param (float|int|string|Translatable)[] $params */ public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{ - $baseText = $this->get($str); - $baseText = $this->parseTranslation(($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $baseText : $str, $onlyPrefix); + $baseText = ($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $this->internalGet($str) : null; + if($baseText === null){ //key not found, embedded inside format string, or doesn't match prefix + $baseText = $this->parseTranslation($str, $onlyPrefix); + } foreach($params as $i => $p){ $replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p; @@ -155,7 +157,9 @@ class Language{ public function translate(Translatable $c) : string{ $baseText = $this->internalGet($c->getText()); - $baseText = $this->parseTranslation($baseText ?? $c->getText()); + if($baseText === null){ //key not found or embedded inside format string + $baseText = $this->parseTranslation($c->getText()); + } foreach($c->getParameters() as $i => $p){ $replacement = $p instanceof Translatable ? $this->translate($p) : $p; @@ -181,6 +185,19 @@ class Language{ return $this->lang; } + /** + * Replaces translation keys embedded inside a string with their raw values. + * Embedded translation keys must be prefixed by a "%" character. + * + * This is used to allow the "text" field of a Translatable to contain formatting (e.g. colour codes) and + * multiple embedded translation keys. + * + * Normal translations whose "text" is just a single translation key don't need to use this method, and can be + * processed via get() directly. + * + * @param string|null $onlyPrefix If non-null, only translation keys with this prefix will be replaced. This is + * used to allow a client to do its own translating of vanilla strings. + */ protected function parseTranslation(string $text, ?string $onlyPrefix = null) : string{ $newString = ""; diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 180025c27..93e06e524 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -128,10 +128,14 @@ use function array_values; use function base64_encode; use function bin2hex; use function count; +use function function_exists; use function get_class; +use function hrtime; use function in_array; +use function intdiv; use function json_encode; use function ksort; +use function min; use function random_bytes; use function strcasecmp; use function strlen; @@ -139,10 +143,25 @@ use function strtolower; use function substr; use function time; use function ucfirst; +use function xdebug_is_debugger_active; use const JSON_THROW_ON_ERROR; use const SORT_NUMERIC; class NetworkSession{ + private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions may arrive separately + private const INCOMING_PACKET_BATCH_MAX_BUDGET = 100; //enough to account for a 5-second lag spike + + /** + * At most this many more packets can be received. If this reaches zero, any additional packets received will cause + * the player to be kicked from the server. + * This number is increased every tick up to a maximum limit. + * + * @see self::INCOMING_PACKET_BATCH_PER_TICK + * @see self::INCOMING_PACKET_BATCH_MAX_BUDGET + */ + private int $incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET; + private int $lastPacketBudgetUpdateTimeNs; + private \PrefixedLogger $logger; private ?Player $player = null; private ?PlayerInfo $info = null; @@ -200,6 +219,7 @@ class NetworkSession{ $this->disposeHooks = new ObjectSet(); $this->connectTime = time(); + $this->lastPacketBudgetUpdateTimeNs = hrtime(true); $this->setHandler(new SessionStartPacketHandler( $this, @@ -341,6 +361,17 @@ class NetworkSession{ return; } + if($this->incomingPacketBatchBudget <= 0){ + if(!function_exists('xdebug_is_debugger_active') || !xdebug_is_debugger_active()){ + throw new PacketHandlingException("Receiving packets too fast"); + }else{ + //when a debugging session is active, the server may halt at any point for an indefinite length of time, + //in which time the client will continue to send packets + $this->incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET; + } + } + $this->incomingPacketBatchBudget--; + if($this->cipher !== null){ Timings::$playerNetworkReceiveDecrypt->startTiming(); try{ @@ -962,16 +993,13 @@ class NetworkSession{ public function onChatMessage(Translatable|string $message) : void{ if($message instanceof Translatable){ - //we can't send nested translations to the client, so make sure they are always pre-translated by the server $language = $this->player->getLanguage(); - $parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $message->getParameters()); if(!$this->server->isLanguageForced()){ - foreach($parameters as $i => $p){ - $parameters[$i] = $language->translateString($p, [], "pocketmine."); - } + //we can't send nested translations to the client, so make sure they are always pre-translated by the server + $parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $message->getParameters()); $this->sendDataPacket(TextPacket::translation($language->translateString($message->getText(), $parameters, "pocketmine."), $parameters)); }else{ - $this->sendDataPacket(TextPacket::raw($language->translateString($message->getText(), $parameters))); + $this->sendDataPacket(TextPacket::raw($language->translate($message))); } }else{ $this->sendDataPacket(TextPacket::raw($message)); @@ -1170,5 +1198,16 @@ class NetworkSession{ } $this->flushSendBuffer(); + + $nowNs = hrtime(true); + $timeSinceLastUpdateNs = $nowNs - $this->lastPacketBudgetUpdateTimeNs; + if($timeSinceLastUpdateNs > 50_000_000){ + $ticksSinceLastUpdate = intdiv($timeSinceLastUpdateNs, 50_000_000); + $this->incomingPacketBatchBudget = min( + $this->incomingPacketBatchBudget + (self::INCOMING_PACKET_BATCH_PER_TICK * 2 * $ticksSinceLastUpdate), + self::INCOMING_PACKET_BATCH_MAX_BUDGET + ); + $this->lastPacketBudgetUpdateTimeNs = $nowNs; + } } } diff --git a/src/network/mcpe/handler/DeathPacketHandler.php b/src/network/mcpe/handler/DeathPacketHandler.php index 43deec463..6347d8f65 100644 --- a/src/network/mcpe/handler/DeathPacketHandler.php +++ b/src/network/mcpe/handler/DeathPacketHandler.php @@ -52,17 +52,13 @@ class DeathPacketHandler extends PacketHandler{ /** @var string[] $parameters */ $parameters = []; if($this->deathMessage instanceof Translatable){ - //we can't send nested translations to the client, so make sure they are always pre-translated by the server $language = $this->player->getLanguage(); - $parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $this->deathMessage->getParameters()); if(!$this->player->getServer()->isLanguageForced()){ - foreach($parameters as $i => $p){ - $parameters[$i] = $language->translateString($p, [], "pocketmine."); - } + //we can't send nested translations to the client, so make sure they are always pre-translated by the server + $parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $this->deathMessage->getParameters()); $message = $language->translateString($this->deathMessage->getText(), $parameters, "pocketmine."); }else{ - $message = $language->translateString($this->deathMessage->getText(), $parameters); - $parameters = []; + $message = $language->translate($this->deathMessage); } }else{ $message = $this->deathMessage; diff --git a/src/world/World.php b/src/world/World.php index 1d381bba1..cb34c9292 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -61,6 +61,7 @@ use pocketmine\item\StringToItemParser; use pocketmine\item\VanillaItems; use pocketmine\lang\KnownTranslationFactory; use pocketmine\math\AxisAlignedBB; +use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; @@ -2026,6 +2027,10 @@ class World implements ChunkManager{ if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){ $blockReplace = $blockClicked; + //TODO: while this mimics the vanilla behaviour with replaceable blocks, we should really pass some other + //value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know about + //the vanilla behaviour. + $face = Facing::UP; $hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z); }elseif(!$hand->canBePlacedAt($blockReplace, $clickVector, $face, false)){ return false;