From 44771c892d93ab7e6ab3007e5df473cf43b3cd87 Mon Sep 17 00:00:00 2001 From: Muqsit Date: Sun, 1 Dec 2024 21:14:30 +0800 Subject: [PATCH 001/334] Use PlayerAuthInputPacket::SNEAKING flag to test for sneaking (#6544) This binds internal sneaking to whether or not the player is currently pressing the shift key, which fixes #5792 and fixes #5903. However, it does introduce visual issues with sneaking, as explained in #6548. This needs to be worked on separately. For now, it's better we trade 2 functional bugs for 1 visual bug. --- src/network/mcpe/handler/InGamePacketHandler.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index cc1f3eab4..f1cfaec19 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -215,7 +215,10 @@ class InGamePacketHandler extends PacketHandler{ if($inputFlags !== $this->lastPlayerAuthInputFlags){ $this->lastPlayerAuthInputFlags = $inputFlags; - $sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING); + $sneaking = $packet->hasFlag(PlayerAuthInputFlags::SNEAKING); + if($this->player->isSneaking() === $sneaking){ + $sneaking = null; + } $sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING); $swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING); $gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING); From fcef015f32c0b0709cfd4d6f1b3c32f484b25bde Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 2 Dec 2024 00:40:55 +0000 Subject: [PATCH 002/334] L link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3490542c5..799c9d99c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t - Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones. - **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes. - This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers. - - Check out the [issues page]() for something that you could tackle without too much effort. + - Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort. - **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail. - **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE. - **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode. From 49da50659f08472ddc41b6925098006ba95de3f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:36:12 +0000 Subject: [PATCH 003/334] Bump docker/build-push-action from 6.9.0 to 6.10.0 (#6553) --- .github/workflows/build-docker-image.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 4325c63f2..94856fa44 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 with: push: true context: ./pocketmine-mp @@ -66,7 +66,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 with: push: true context: ./pocketmine-mp @@ -79,7 +79,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 with: push: true context: ./pocketmine-mp @@ -92,7 +92,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 with: push: true context: ./pocketmine-mp From 06028aac97d876ce3ef249d64f70013b2f76d1c1 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 3 Dec 2024 02:07:58 +0000 Subject: [PATCH 004/334] issues: don't recommend forums to get help --- .github/ISSUE_TEMPLATE/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d13fb4498..d18b277e7 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,9 +3,6 @@ contact_links: - name: Help & support on Discord url: https://discord.gg/bmSAZBG about: We don't accept support requests on the issue tracker. Please try asking on Discord instead. - - name: Help & support on forums - url: https://forums.pmmp.io - about: We don't accept support requests on the issue tracker. Please try asking on forums instead. - name: Documentation url: https://pmmp.rtfd.io about: PocketMine-MP documentation From c56d4d3e3c965eff18688be9290fc22c3c6ada07 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 3 Dec 2024 14:56:22 +0000 Subject: [PATCH 005/334] dependabot: update github actions deps together, monthly --- .github/dependabot.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 97607ab8f..13721f0ba 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,4 +37,7 @@ updates: - package-ecosystem: github-actions directory: "/" schedule: - interval: weekly + interval: monthly + groups: + github-actions: + patterns: ["*"] From 2d0321ff021d312c33c37a8b085fcd597783fb2b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 3 Dec 2024 15:19:38 +0000 Subject: [PATCH 006/334] Switch back to official JsonMapper the issues that led to the need for a fork have been addressed in the 5.0.0 release. --- composer.json | 2 +- composer.lock | 107 ++++++++++++++++++++++++-------------------------- 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/composer.json b/composer.json index 16bed54b7..c67ffbeeb 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", - "pocketmine/netresearch-jsonmapper": "~v4.4.999", + "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40", "pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40", "pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40", diff --git a/composer.lock b/composer.lock index 7eda66b35..b0ff16095 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": "c57e8f52250edfd03906219fe14fc240", + "content-hash": "119f9b72703588e608a010cefa55db0b", "packages": [ { "name": "adhocore/json-comment", @@ -125,6 +125,57 @@ ], "time": "2023-11-29T23:19:16+00:00" }, + { + "name": "netresearch/jsonmapper", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0" + }, + "time": "2024-09-08T10:20:00+00:00" + }, { "name": "pocketmine/bedrock-block-upgrade-schema", "version": "5.0.0", @@ -565,60 +616,6 @@ }, "time": "2023-07-14T13:01:49+00:00" }, - { - "name": "pocketmine/netresearch-jsonmapper", - "version": "v4.4.999", - "source": { - "type": "git", - "url": "https://github.com/pmmp/netresearch-jsonmapper.git", - "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833", - "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" - }, - "replace": { - "netresearch/jsonmapper": "~4.2.0" - }, - "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Fork of netresearch/jsonmapper with security fixes needed by pocketmine/pocketmine-mp", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999" - }, - "time": "2024-02-23T13:17:01+00:00" - }, { "name": "pocketmine/raklib", "version": "1.1.1", From ba6828c6bd6ad76c0d99f8f8243b95cd13bc2463 Mon Sep 17 00:00:00 2001 From: Dries C Date: Wed, 4 Dec 2024 14:36:52 +0100 Subject: [PATCH 007/334] Release 5.22.0 (Bedrock 1.21.50 support) (#6559) Co-authored-by: Dylan K. Taylor --- changelogs/5.22.md | 16 ++++++++ composer.json | 6 +-- composer.lock | 38 +++++++++---------- src/VersionInfo.php | 4 +- src/data/bedrock/BedrockDataFiles.php | 1 + src/data/bedrock/BiomeIds.php | 1 + src/data/bedrock/block/BlockStateNames.php | 6 +++ .../bedrock/block/BlockStateStringValues.php | 16 ++++++++ src/data/bedrock/block/BlockTypeNames.php | 34 +++++++++++++++++ src/data/bedrock/item/ItemTypeNames.php | 8 ++++ .../mcpe/handler/InGamePacketHandler.php | 15 ++++---- .../mcpe/handler/ItemStackResponseBuilder.php | 1 + .../handler/ResourcePacksPacketHandler.php | 7 +++- 13 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 changelogs/5.22.md diff --git a/changelogs/5.22.md b/changelogs/5.22.md new file mode 100644 index 000000000..6c9ba6d7e --- /dev/null +++ b/changelogs/5.22.md @@ -0,0 +1,16 @@ +# 5.22.0 +Released 4th December 2024. + +**For Minecraft: Bedrock Edition 1.21.50** + +This is a support release for Minecraft: Bedrock Edition 1.21.50. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.50. +- Removed support for earlier versions. diff --git a/composer.json b/composer.json index ed5bb582f..4a6ad5daf 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "pocketmine/netresearch-jsonmapper": "~v4.4.999", "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40", - "pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40", - "pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40", - "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40", + "pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50", + "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", + "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index eb1061ff5..89e7b3af4 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": "b2fbf6e7a9d650341dc71fa4dd124681", + "content-hash": "26d10b9381ab4e19684ca0b7a5a11a42", "packages": [ { "name": "adhocore/json-comment", @@ -153,16 +153,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "2.14.1+bedrock-1.21.40", + "version": "2.15.0+bedrock-1.21.50", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4" + "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/4a41864ed09613ecec6791e2ae076a8ec7089cc4", - "reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad", + "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad", "shasum": "" }, "type": "library", @@ -173,22 +173,22 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/2.14.1+bedrock-1.21.40" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50" }, - "time": "2024-11-12T21:36:20+00:00" + "time": "2024-12-04T12:59:12+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", - "version": "1.13.1", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git", - "reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03" + "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/1cf81305f2ffcf7dde9577c4f16a55c765192b03", - "reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03", + "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb", + "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb", "shasum": "" }, "type": "library", @@ -199,22 +199,22 @@ "description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves", "support": { "issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.13.1" + "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.14.0" }, - "time": "2024-11-12T21:33:17+00:00" + "time": "2024-12-04T12:22:49+00:00" }, { "name": "pocketmine/bedrock-protocol", - "version": "35.0.0+bedrock-1.21.40", + "version": "35.0.0+bedrock-1.21.50", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459" + "reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459", - "reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435", + "reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435", "shasum": "" }, "require": { @@ -245,9 +245,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/35.0.0+bedrock-1.21.40" + "source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.50" }, - "time": "2024-10-24T15:45:43+00:00" + "time": "2024-12-04T13:02:00+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index efb38d71b..460e34bf7 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.21.3"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.22.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 5c476ca1c..3341c0503 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -45,6 +45,7 @@ final class BedrockDataFiles{ public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json'; + public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json'; diff --git a/src/data/bedrock/BiomeIds.php b/src/data/bedrock/BiomeIds.php index 1169a51ea..f3c38d3ed 100644 --- a/src/data/bedrock/BiomeIds.php +++ b/src/data/bedrock/BiomeIds.php @@ -122,4 +122,5 @@ final class BiomeIds{ public const DEEP_DARK = 190; public const MANGROVE_SWAMP = 191; public const CHERRY_GROVE = 192; + public const PALE_GARDEN = 193; } diff --git a/src/data/bedrock/block/BlockStateNames.php b/src/data/bedrock/block/BlockStateNames.php index 0f4b87426..68a3e1b1d 100644 --- a/src/data/bedrock/block/BlockStateNames.php +++ b/src/data/bedrock/block/BlockStateNames.php @@ -93,12 +93,17 @@ final class BlockStateNames{ public const MC_VERTICAL_HALF = "minecraft:vertical_half"; public const MOISTURIZED_AMOUNT = "moisturized_amount"; public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits"; + public const NATURAL = "natural"; public const OCCUPIED_BIT = "occupied_bit"; public const OMINOUS = "ominous"; public const OPEN_BIT = "open_bit"; public const ORIENTATION = "orientation"; public const OUTPUT_LIT_BIT = "output_lit_bit"; public const OUTPUT_SUBTRACT_BIT = "output_subtract_bit"; + public const PALE_MOSS_CARPET_SIDE_EAST = "pale_moss_carpet_side_east"; + public const PALE_MOSS_CARPET_SIDE_NORTH = "pale_moss_carpet_side_north"; + public const PALE_MOSS_CARPET_SIDE_SOUTH = "pale_moss_carpet_side_south"; + public const PALE_MOSS_CARPET_SIDE_WEST = "pale_moss_carpet_side_west"; public const PERSISTENT_BIT = "persistent_bit"; public const PILLAR_AXIS = "pillar_axis"; public const PORTAL_AXIS = "portal_axis"; @@ -116,6 +121,7 @@ final class BlockStateNames{ public const STABILITY_CHECK = "stability_check"; public const STRUCTURE_BLOCK_TYPE = "structure_block_type"; public const SUSPENDED_BIT = "suspended_bit"; + public const TIP = "tip"; public const TOGGLE_BIT = "toggle_bit"; public const TORCH_FACING_DIRECTION = "torch_facing_direction"; public const TRIAL_SPAWNER_STATE = "trial_spawner_state"; diff --git a/src/data/bedrock/block/BlockStateStringValues.php b/src/data/bedrock/block/BlockStateStringValues.php index 9dfdcfb63..e8171e4da 100644 --- a/src/data/bedrock/block/BlockStateStringValues.php +++ b/src/data/bedrock/block/BlockStateStringValues.php @@ -106,6 +106,22 @@ final class BlockStateStringValues{ public const ORIENTATION_UP_WEST = "up_west"; public const ORIENTATION_WEST_UP = "west_up"; + public const PALE_MOSS_CARPET_SIDE_EAST_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_EAST_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_EAST_TALL = "tall"; + + public const PALE_MOSS_CARPET_SIDE_NORTH_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_NORTH_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_NORTH_TALL = "tall"; + + public const PALE_MOSS_CARPET_SIDE_SOUTH_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_SOUTH_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_SOUTH_TALL = "tall"; + + public const PALE_MOSS_CARPET_SIDE_WEST_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_WEST_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_WEST_TALL = "tall"; + public const PILLAR_AXIS_X = "x"; public const PILLAR_AXIS_Y = "y"; public const PILLAR_AXIS_Z = "z"; diff --git a/src/data/bedrock/block/BlockTypeNames.php b/src/data/bedrock/block/BlockTypeNames.php index 029a5f6aa..eec1ab8d1 100644 --- a/src/data/bedrock/block/BlockTypeNames.php +++ b/src/data/bedrock/block/BlockTypeNames.php @@ -192,6 +192,7 @@ final class BlockTypeNames{ public const CAVE_VINES_HEAD_WITH_BERRIES = "minecraft:cave_vines_head_with_berries"; public const CHAIN = "minecraft:chain"; public const CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block"; + public const CHALKBOARD = "minecraft:chalkboard"; public const CHEMICAL_HEAT = "minecraft:chemical_heat"; public const CHERRY_BUTTON = "minecraft:cherry_button"; public const CHERRY_DOOR = "minecraft:cherry_door"; @@ -219,6 +220,7 @@ final class BlockTypeNames{ public const CHISELED_POLISHED_BLACKSTONE = "minecraft:chiseled_polished_blackstone"; public const CHISELED_QUARTZ_BLOCK = "minecraft:chiseled_quartz_block"; public const CHISELED_RED_SANDSTONE = "minecraft:chiseled_red_sandstone"; + public const CHISELED_RESIN_BRICKS = "minecraft:chiseled_resin_bricks"; public const CHISELED_SANDSTONE = "minecraft:chiseled_sandstone"; public const CHISELED_STONE_BRICKS = "minecraft:chiseled_stone_bricks"; public const CHISELED_TUFF = "minecraft:chiseled_tuff"; @@ -227,6 +229,7 @@ final class BlockTypeNames{ public const CHORUS_PLANT = "minecraft:chorus_plant"; public const CLAY = "minecraft:clay"; public const CLIENT_REQUEST_PLACEHOLDER_BLOCK = "minecraft:client_request_placeholder_block"; + public const CLOSED_EYEBLOSSOM = "minecraft:closed_eyeblossom"; public const COAL_BLOCK = "minecraft:coal_block"; public const COAL_ORE = "minecraft:coal_ore"; public const COARSE_DIRT = "minecraft:coarse_dirt"; @@ -262,6 +265,7 @@ final class BlockTypeNames{ public const CRACKED_STONE_BRICKS = "minecraft:cracked_stone_bricks"; public const CRAFTER = "minecraft:crafter"; public const CRAFTING_TABLE = "minecraft:crafting_table"; + public const CREAKING_HEART = "minecraft:creaking_heart"; public const CREEPER_HEAD = "minecraft:creeper_head"; public const CRIMSON_BUTTON = "minecraft:crimson_button"; public const CRIMSON_DOOR = "minecraft:crimson_door"; @@ -831,6 +835,7 @@ final class BlockTypeNames{ public const OBSERVER = "minecraft:observer"; public const OBSIDIAN = "minecraft:obsidian"; public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight"; + public const OPEN_EYEBLOSSOM = "minecraft:open_eyeblossom"; public const ORANGE_CANDLE = "minecraft:orange_candle"; public const ORANGE_CANDLE_CAKE = "minecraft:orange_candle_cake"; public const ORANGE_CARPET = "minecraft:orange_carpet"; @@ -856,6 +861,26 @@ final class BlockTypeNames{ public const OXIDIZED_DOUBLE_CUT_COPPER_SLAB = "minecraft:oxidized_double_cut_copper_slab"; public const PACKED_ICE = "minecraft:packed_ice"; public const PACKED_MUD = "minecraft:packed_mud"; + public const PALE_HANGING_MOSS = "minecraft:pale_hanging_moss"; + public const PALE_MOSS_BLOCK = "minecraft:pale_moss_block"; + public const PALE_MOSS_CARPET = "minecraft:pale_moss_carpet"; + public const PALE_OAK_BUTTON = "minecraft:pale_oak_button"; + public const PALE_OAK_DOOR = "minecraft:pale_oak_door"; + public const PALE_OAK_DOUBLE_SLAB = "minecraft:pale_oak_double_slab"; + public const PALE_OAK_FENCE = "minecraft:pale_oak_fence"; + public const PALE_OAK_FENCE_GATE = "minecraft:pale_oak_fence_gate"; + public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign"; + public const PALE_OAK_LEAVES = "minecraft:pale_oak_leaves"; + public const PALE_OAK_LOG = "minecraft:pale_oak_log"; + public const PALE_OAK_PLANKS = "minecraft:pale_oak_planks"; + public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate"; + public const PALE_OAK_SAPLING = "minecraft:pale_oak_sapling"; + public const PALE_OAK_SLAB = "minecraft:pale_oak_slab"; + public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs"; + public const PALE_OAK_STANDING_SIGN = "minecraft:pale_oak_standing_sign"; + public const PALE_OAK_TRAPDOOR = "minecraft:pale_oak_trapdoor"; + public const PALE_OAK_WALL_SIGN = "minecraft:pale_oak_wall_sign"; + public const PALE_OAK_WOOD = "minecraft:pale_oak_wood"; public const PEARLESCENT_FROGLIGHT = "minecraft:pearlescent_froglight"; public const PEONY = "minecraft:peony"; public const PETRIFIED_OAK_DOUBLE_SLAB = "minecraft:petrified_oak_double_slab"; @@ -994,6 +1019,13 @@ final class BlockTypeNames{ public const REINFORCED_DEEPSLATE = "minecraft:reinforced_deepslate"; public const REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block"; public const RESERVED6 = "minecraft:reserved6"; + public const RESIN_BLOCK = "minecraft:resin_block"; + public const RESIN_BRICK_DOUBLE_SLAB = "minecraft:resin_brick_double_slab"; + public const RESIN_BRICK_SLAB = "minecraft:resin_brick_slab"; + public const RESIN_BRICK_STAIRS = "minecraft:resin_brick_stairs"; + public const RESIN_BRICK_WALL = "minecraft:resin_brick_wall"; + public const RESIN_BRICKS = "minecraft:resin_bricks"; + public const RESIN_CLUMP = "minecraft:resin_clump"; public const RESPAWN_ANCHOR = "minecraft:respawn_anchor"; public const ROSE_BUSH = "minecraft:rose_bush"; public const SAND = "minecraft:sand"; @@ -1096,6 +1128,8 @@ final class BlockTypeNames{ public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood"; public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log"; public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood"; + public const STRIPPED_PALE_OAK_LOG = "minecraft:stripped_pale_oak_log"; + public const STRIPPED_PALE_OAK_WOOD = "minecraft:stripped_pale_oak_wood"; public const STRIPPED_SPRUCE_LOG = "minecraft:stripped_spruce_log"; public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood"; public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae"; diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php index 03b32d482..b0d49fc31 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -75,6 +75,7 @@ final class ItemTypeNames{ public const BLEACH = "minecraft:bleach"; public const BLUE_BUNDLE = "minecraft:blue_bundle"; public const BLUE_DYE = "minecraft:blue_dye"; + public const BOARD = "minecraft:board"; public const BOAT = "minecraft:boat"; public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg"; public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template"; @@ -154,6 +155,7 @@ final class ItemTypeNames{ public const CORAL_FAN = "minecraft:coral_fan"; public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead"; public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg"; + public const CREAKING_SPAWN_EGG = "minecraft:creaking_spawn_egg"; public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern"; public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg"; public const CRIMSON_DOOR = "minecraft:crimson_door"; @@ -398,6 +400,11 @@ final class ItemTypeNames{ public const ORANGE_DYE = "minecraft:orange_dye"; public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door"; public const PAINTING = "minecraft:painting"; + public const PALE_OAK_BOAT = "minecraft:pale_oak_boat"; + public const PALE_OAK_CHEST_BOAT = "minecraft:pale_oak_chest_boat"; + public const PALE_OAK_DOOR = "minecraft:pale_oak_door"; + public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign"; + public const PALE_OAK_SIGN = "minecraft:pale_oak_sign"; public const PANDA_SPAWN_EGG = "minecraft:panda_spawn_egg"; public const PAPER = "minecraft:paper"; public const PARROT_SPAWN_EGG = "minecraft:parrot_spawn_egg"; @@ -448,6 +455,7 @@ final class ItemTypeNames{ public const RED_FLOWER = "minecraft:red_flower"; public const REDSTONE = "minecraft:redstone"; public const REPEATER = "minecraft:repeater"; + public const RESIN_BRICK = "minecraft:resin_brick"; public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template"; public const ROTTEN_FLESH = "minecraft:rotten_flesh"; public const SADDLE = "minecraft:saddle"; diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index f1cfaec19..8a42371e6 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -77,6 +77,7 @@ use pocketmine\network\mcpe\protocol\PlayerHotbarPacket; use pocketmine\network\mcpe\protocol\PlayerInputPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; +use pocketmine\network\mcpe\protocol\serializer\BitSet; use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket; @@ -135,7 +136,7 @@ class InGamePacketHandler extends PacketHandler{ protected ?Vector3 $lastPlayerAuthInputPosition = null; protected ?float $lastPlayerAuthInputYaw = null; protected ?float $lastPlayerAuthInputPitch = null; - protected ?int $lastPlayerAuthInputFlags = null; + protected ?BitSet $lastPlayerAuthInputFlags = null; public bool $forceMoveSync = false; @@ -161,9 +162,9 @@ class InGamePacketHandler extends PacketHandler{ return true; } - private function resolveOnOffInputFlags(int $inputFlags, int $startFlag, int $stopFlag) : ?bool{ - $enabled = ($inputFlags & (1 << $startFlag)) !== 0; - $disabled = ($inputFlags & (1 << $stopFlag)) !== 0; + private function resolveOnOffInputFlags(BitSet $inputFlags, int $startFlag, int $stopFlag) : ?bool{ + $enabled = $inputFlags->get($startFlag); + $disabled = $inputFlags->get($stopFlag); if($enabled !== $disabled){ return $enabled; } @@ -215,7 +216,7 @@ class InGamePacketHandler extends PacketHandler{ if($inputFlags !== $this->lastPlayerAuthInputFlags){ $this->lastPlayerAuthInputFlags = $inputFlags; - $sneaking = $packet->hasFlag(PlayerAuthInputFlags::SNEAKING); + $sneaking = $inputFlags->get(PlayerAuthInputFlags::SNEAKING); if($this->player->isSneaking() === $sneaking){ $sneaking = null; } @@ -233,10 +234,10 @@ class InGamePacketHandler extends PacketHandler{ $this->player->sendData([$this->player]); } - if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ + if($inputFlags->get(PlayerAuthInputFlags::START_JUMPING)){ $this->player->jump(); } - if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){ + if($inputFlags->get(PlayerAuthInputFlags::MISSED_SWING)){ $this->player->missSwing(); } } diff --git a/src/network/mcpe/handler/ItemStackResponseBuilder.php b/src/network/mcpe/handler/ItemStackResponseBuilder.php index 1369e3ba7..faf479ee2 100644 --- a/src/network/mcpe/handler/ItemStackResponseBuilder.php +++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php @@ -93,6 +93,7 @@ final class ItemStackResponseBuilder{ $item->getCount(), $itemStackInfo->getStackId(), $item->getCustomName(), + $item->getCustomName(), $item instanceof Durable ? $item->getDamage() : 0, ); } diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index 5e2671394..a1df394da 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -37,6 +37,7 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType; use pocketmine\resourcepacks\ResourcePack; +use Ramsey\Uuid\Uuid; use function array_keys; use function array_map; use function ceil; @@ -103,7 +104,7 @@ class ResourcePacksPacketHandler extends PacketHandler{ //TODO: more stuff return new ResourcePackInfoEntry( - $pack->getPackId(), + Uuid::fromString($pack->getPackId()), $pack->getPackVersion(), $pack->getPackSize(), $this->encryptionKeys[$pack->getPackId()] ?? "", @@ -117,7 +118,9 @@ class ResourcePacksPacketHandler extends PacketHandler{ resourcePackEntries: $resourcePackEntries, mustAccept: $this->mustAccept, hasAddons: false, - hasScripts: false + hasScripts: false, + worldTemplateId: Uuid::fromString(Uuid::NIL), + worldTemplateVersion: "" )); $this->session->getLogger()->debug("Waiting for client to accept resource packs"); } From a1448bfb88c6662e7a850272a5656162b7551227 Mon Sep 17 00:00:00 2001 From: "pmmp-restrictedactions-bot[bot]" <188621379+pmmp-restrictedactions-bot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:38:41 +0000 Subject: [PATCH 008/334] 5.22.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12160926590 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 460e34bf7..9c1fdf230 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.22.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.22.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 35a90d24ec9fd5055e9d0bef480341017c66fd5b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 5 Dec 2024 12:57:26 +0000 Subject: [PATCH 009/334] AsyncTask: deprecate progress update related stuff --- src/scheduler/AsyncTask.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index 33a0949d4..1175b6fc4 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -68,7 +68,10 @@ abstract class AsyncTask extends Runnable{ */ private static array $threadLocalStorage = []; - /** @phpstan-var ThreadSafeArray|null */ + /** + * @phpstan-var ThreadSafeArray|null + * @deprecated + */ private ?ThreadSafeArray $progressUpdates = null; private ThreadSafe|string|int|bool|null|float $result = null; @@ -161,6 +164,8 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated + * * Call this method from {@link AsyncTask::onRun} (AsyncTask execution thread) to schedule a call to * {@link AsyncTask::onProgressUpdate} from the main thread with the given progress parameter. * @@ -175,6 +180,7 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated * @internal Only call from AsyncPool.php on the main thread */ public function checkProgressUpdates() : void{ @@ -187,6 +193,8 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated + * * Called from the main thread after {@link AsyncTask::publishProgress} is called. * All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before * {@link AsyncTask::onCompletion} is called. From 0aaf4238a8cb6a036c238538351f3a26e4d2b921 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 5 Dec 2024 13:02:09 +0000 Subject: [PATCH 010/334] more deprecations in line with major-next --- src/plugin/DiskResourceProvider.php | 2 ++ src/plugin/ResourceProvider.php | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/plugin/DiskResourceProvider.php b/src/plugin/DiskResourceProvider.php index efdc9cd17..9649f565f 100644 --- a/src/plugin/DiskResourceProvider.php +++ b/src/plugin/DiskResourceProvider.php @@ -36,6 +36,8 @@ use const DIRECTORY_SEPARATOR; /** * Provides resources from the given plugin directory on disk. The path may be prefixed with a specific access protocol * to enable special types of access. + * + * @deprecated */ class DiskResourceProvider implements ResourceProvider{ private string $file; diff --git a/src/plugin/ResourceProvider.php b/src/plugin/ResourceProvider.php index 3594d7eee..5ff8db882 100644 --- a/src/plugin/ResourceProvider.php +++ b/src/plugin/ResourceProvider.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\plugin; +/** + * @deprecated + */ interface ResourceProvider{ /** * Gets an embedded resource on the plugin file. From fa7bc78e7c0ed5e31682de57b65c0edc1e74d50f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 5 Dec 2024 14:29:17 +0000 Subject: [PATCH 011/334] Prepare 5.23.0 release --- changelogs/5.23.md | 103 ++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.23.md diff --git a/changelogs/5.23.md b/changelogs/5.23.md new file mode 100644 index 000000000..4d7ba1808 --- /dev/null +++ b/changelogs/5.23.md @@ -0,0 +1,103 @@ +# 5.23.0 +Released 5th December 2024. + +This is a minor feature release, including new gameplay features, internals improvements, API additions and +deprecations, and improvements to timings. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load. +- Added `/xp` command. +- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors. + +## Gameplay +- Added the following new items: + - End Crystal + - Goat Horn (all variants) + - Recovery Compass +- Added the following enchantments: + - Frost Walker +- Sugarcane now self-destructs when there is no water adjacent to the base block. +- Added basic support for middle-clicking on entities to get their spawn eggs. +- Added sounds when drinking potions. +- Eating food is now allowed in creative mode and in peaceful difficulty. + +## API +### `pocketmine\block` +- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks. +- The following API methods have been deprecated: + - `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work + +### `pocketmine\command` +- The following classes have been added: + - `ClosureCommand` - allows registering a closure to execute a command + +### `pocketmine\event` +- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions. + - This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc. + - If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed). + - The following API methods have been added: + - `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void` + - `public PlayerInteractEvent->setUseItem(bool $useItem) : void` + - `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc). + - `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc). +- The following new classes have been added: + - `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity + +### `pocketmine\inventory\transaction` +- The following API methods have been deprecated: + - `InventoryAction->onAddToTransaction()` + +### `pocketmine\permission` +- The following API methods have been deprecated: + - `PermissionManager->getPermissionSubscriptions()` + - `PermissionManager->subscribeToPermission()` + - `PermissionManager->unsubscribeFromAllPermissions()` + - `PermissionManager->unsubscribeFromPermission()` + +### `pocketmine\plugin` +- The following classes have been deprecated: + - `DiskResourceProvider` + - `ResourceProvider` + +### `pocketmine\promise` +- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array. + +### `pocketmine\scheduler` +- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`. +- The following API methods have been deprecated + - `AsyncTask->publishProgress()` + - `AsyncTask->onProgressUpdate()` + +### `pocketmine\timings` +- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected. + - The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report. + - Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings. + - PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future. +- The following API methods have been added: + - `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`. + - `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset + - `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled + - `public static TimingsHandler::requestPrintTimings() : Promise>` - asynchronously collects timing results from all threads and assembles them into a single report +- The following API methods have been deprecated: + - `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead. + +### `pocketmine\utils` +- The following API methods have been added: + - `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4. + +## Internals +- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins. +- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified. + - While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively. + - It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`. + - In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely. +- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions. +- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies. +- Fixed various deprecation warnings in PHP 8.4. +- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 9c1fdf230..90c1a7df4 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.22.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.23.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From ea068d490781d977cb594654955af2d311b3fec9 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 5 Dec 2024 15:01:49 +0000 Subject: [PATCH 012/334] Update 5.23.md --- changelogs/5.23.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelogs/5.23.md b/changelogs/5.23.md index 4d7ba1808..4cdb81625 100644 --- a/changelogs/5.23.md +++ b/changelogs/5.23.md @@ -19,6 +19,7 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - Added the following new items: - End Crystal - Goat Horn (all variants) + - Ice Bomb (from Education Edition) - Recovery Compass - Added the following enchantments: - Frost Walker From 62e1d87f5e02ff5759c8db88dd822795fbf49534 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 5 Dec 2024 15:47:34 +0000 Subject: [PATCH 013/334] Mention internal timings deprecations plugins shouldn't be using these, but since it's not marked as internal, we can't be sure. --- changelogs/5.23.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelogs/5.23.md b/changelogs/5.23.md index 4cdb81625..f5b08593d 100644 --- a/changelogs/5.23.md +++ b/changelogs/5.23.md @@ -87,6 +87,9 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - `public static TimingsHandler::requestPrintTimings() : Promise>` - asynchronously collects timing results from all threads and assembles them into a single report - The following API methods have been deprecated: - `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead. + - `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed +- The following constants have been deprecated: + - `Timings::GROUP_BREAKDOWN` - no longer used ### `pocketmine\utils` - The following API methods have been added: From 15e8895e5433b66b55307981ad3ec0b4bc8ce518 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:52:16 +0000 Subject: [PATCH 014/334] 5.23.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12183301507 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 90c1a7df4..d983060f4 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.23.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.23.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 2ef02a2c5ec7f9cebdc0c0766c214ad0937cd5fc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 5 Dec 2024 19:57:13 +0000 Subject: [PATCH 015/334] Upgraded block consistency check to detect tile changes --- tests/phpunit/block/BlockTest.php | 70 +- .../block_factory_consistency_check.json | 1478 +++++++++-------- .../block/regenerate_consistency_check.php | 7 +- 3 files changed, 825 insertions(+), 730 deletions(-) diff --git a/tests/phpunit/block/BlockTest.php b/tests/phpunit/block/BlockTest.php index 841917787..971564720 100644 --- a/tests/phpunit/block/BlockTest.php +++ b/tests/phpunit/block/BlockTest.php @@ -27,6 +27,7 @@ use PHPUnit\Framework\TestCase; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; +use function get_debug_type; use function implode; use function is_array; use function is_int; @@ -95,11 +96,12 @@ class BlockTest extends TestCase{ } /** - * @return int[] - * @phpstan-return array + * @return int[][]|string[][] + * @phpstan-return array{array, array} */ public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{ $newTable = []; + $newTileMap = []; $idNameLookup = []; //if we ever split up block registration into multiple registries (e.g. separating chemistry blocks), @@ -118,36 +120,70 @@ class BlockTest extends TestCase{ } $idName = $idNameLookup[$block->getTypeId()]; $newTable[$idName] = ($newTable[$idName] ?? 0) + 1; - } - return $newTable; + $tileClass = $block->getIdInfo()->getTileClass(); + if($tileClass !== null){ + if(isset($newTileMap[$idName]) && $newTileMap[$idName] !== $tileClass){ + throw new AssumptionFailedError("Tile entity $tileClass for $idName is inconsistent"); + } + $newTileMap[$idName] = $tileClass; + } + } + return [$newTable, $newTileMap]; } /** - * @phpstan-param array $actual + * @phpstan-param array $actualStateCounts + * @phpstan-param array $actualTiles * * @return string[] */ - public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{ - $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR); + public static function computeConsistencyCheckDiff(string $expectedFile, array $actualStateCounts, array $actualTiles) : array{ + $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 3, JSON_THROW_ON_ERROR); if(!is_array($expected)){ - throw new AssumptionFailedError("Old table should be array"); + throw new AssumptionFailedError("Old table should be array{stateCounts: array, tiles: array}"); + } + $expectedStates = $expected["stateCounts"] ?? []; + $expectedTiles = $expected["tiles"] ?? []; + if(!is_array($expectedStates)){ + throw new AssumptionFailedError("stateCounts should be an array, but have " . get_debug_type($expectedStates)); + } + if(!is_array($expectedTiles)){ + throw new AssumptionFailedError("tiles should be an array, but have " . get_debug_type($expectedTiles)); } $errors = []; - foreach(Utils::promoteKeys($expected) as $typeName => $numStates){ + foreach(Utils::promoteKeys($expectedStates) as $typeName => $numStates){ if(!is_string($typeName) || !is_int($numStates)){ throw new AssumptionFailedError("Old table should be array"); } - if(!isset($actual[$typeName])){ + if(!isset($actualStateCounts[$typeName])){ $errors[] = "Removed block type $typeName ($numStates permutations)"; - }elseif($actual[$typeName] !== $numStates){ - $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName]; + }elseif($actualStateCounts[$typeName] !== $numStates){ + $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actualStateCounts[$typeName]; } } - foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){ - if(!isset($expected[$typeName])){ - $errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)"; + foreach(Utils::stringifyKeys($actualStateCounts) as $typeName => $numStates){ + if(!isset($expectedStates[$typeName])){ + $errors[] = "Added block type $typeName (" . $actualStateCounts[$typeName] . " permutations)"; + } + } + + foreach(Utils::promoteKeys($expectedTiles) as $typeName => $tile){ + if(!is_string($typeName) || !is_string($tile)){ + throw new AssumptionFailedError("Tile table should be array"); + } + if(isset($actualStateCounts[$typeName])){ + if(!isset($actualTiles[$typeName])){ + $errors[] = "$typeName no longer has a tile"; + }elseif($actualTiles[$typeName] !== $tile){ + $errors[] = "$typeName has changed tile ($tile -> " . $actualTiles[$typeName] . ")"; + } + } + } + foreach(Utils::promoteKeys($actualTiles) as $typeName => $tile){ + if(isset($expectedStates[$typeName]) && !isset($expectedTiles[$typeName])){ + $errors[] = "$typeName has a tile when it previously didn't ($tile)"; } } @@ -155,8 +191,8 @@ class BlockTest extends TestCase{ } public function testConsistency() : void{ - $newTable = self::computeConsistencyCheckTable($this->blockFactory); - $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable); + [$newTable, $newTileMap] = self::computeConsistencyCheckTable($this->blockFactory); + $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable, $newTileMap); self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors)); } diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index 79804d8cb..d14a85fab 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -1,712 +1,770 @@ { - "ACACIA_BUTTON": 12, - "ACACIA_DOOR": 32, - "ACACIA_FENCE": 1, - "ACACIA_FENCE_GATE": 16, - "ACACIA_LEAVES": 4, - "ACACIA_LOG": 6, - "ACACIA_PLANKS": 1, - "ACACIA_PRESSURE_PLATE": 2, - "ACACIA_SAPLING": 2, - "ACACIA_SIGN": 16, - "ACACIA_SLAB": 3, - "ACACIA_STAIRS": 8, - "ACACIA_TRAPDOOR": 16, - "ACACIA_WALL_SIGN": 4, - "ACACIA_WOOD": 6, - "ACTIVATOR_RAIL": 12, - "AIR": 1, - "ALLIUM": 1, - "ALL_SIDED_MUSHROOM_STEM": 1, - "AMETHYST": 1, - "AMETHYST_CLUSTER": 24, - "ANCIENT_DEBRIS": 1, - "ANDESITE": 1, - "ANDESITE_SLAB": 3, - "ANDESITE_STAIRS": 8, - "ANDESITE_WALL": 162, - "ANVIL": 12, - "AZALEA_LEAVES": 4, - "AZURE_BLUET": 1, - "BAMBOO": 12, - "BAMBOO_SAPLING": 2, - "BANNER": 256, - "BARREL": 12, - "BARRIER": 1, - "BASALT": 3, - "BEACON": 1, - "BED": 256, - "BEDROCK": 2, - "BEETROOTS": 8, - "BELL": 16, - "BIG_DRIPLEAF_HEAD": 16, - "BIG_DRIPLEAF_STEM": 4, - "BIRCH_BUTTON": 12, - "BIRCH_DOOR": 32, - "BIRCH_FENCE": 1, - "BIRCH_FENCE_GATE": 16, - "BIRCH_LEAVES": 4, - "BIRCH_LOG": 6, - "BIRCH_PLANKS": 1, - "BIRCH_PRESSURE_PLATE": 2, - "BIRCH_SAPLING": 2, - "BIRCH_SIGN": 16, - "BIRCH_SLAB": 3, - "BIRCH_STAIRS": 8, - "BIRCH_TRAPDOOR": 16, - "BIRCH_WALL_SIGN": 4, - "BIRCH_WOOD": 6, - "BLACKSTONE": 1, - "BLACKSTONE_SLAB": 3, - "BLACKSTONE_STAIRS": 8, - "BLACKSTONE_WALL": 162, - "BLAST_FURNACE": 8, - "BLUE_ICE": 1, - "BLUE_ORCHID": 1, - "BLUE_TORCH": 5, - "BONE_BLOCK": 3, - "BOOKSHELF": 1, - "BREWING_STAND": 8, - "BRICKS": 1, - "BRICK_SLAB": 3, - "BRICK_STAIRS": 8, - "BRICK_WALL": 162, - "BROWN_MUSHROOM": 1, - "BROWN_MUSHROOM_BLOCK": 11, - "BUDDING_AMETHYST": 1, - "CACTUS": 16, - "CAKE": 7, - "CAKE_WITH_CANDLE": 2, - "CAKE_WITH_DYED_CANDLE": 32, - "CALCITE": 1, - "CAMPFIRE": 8, - "CANDLE": 8, - "CARPET": 16, - "CARROTS": 8, - "CARTOGRAPHY_TABLE": 1, - "CARVED_PUMPKIN": 4, - "CAULDRON": 1, - "CAVE_VINES": 104, - "CHAIN": 3, - "CHEMICAL_HEAT": 1, - "CHERRY_BUTTON": 12, - "CHERRY_DOOR": 32, - "CHERRY_FENCE": 1, - "CHERRY_FENCE_GATE": 16, - "CHERRY_LEAVES": 4, - "CHERRY_LOG": 6, - "CHERRY_PLANKS": 1, - "CHERRY_PRESSURE_PLATE": 2, - "CHERRY_SIGN": 16, - "CHERRY_SLAB": 3, - "CHERRY_STAIRS": 8, - "CHERRY_TRAPDOOR": 16, - "CHERRY_WALL_SIGN": 4, - "CHERRY_WOOD": 6, - "CHEST": 4, - "CHISELED_BOOKSHELF": 256, - "CHISELED_COPPER": 8, - "CHISELED_DEEPSLATE": 1, - "CHISELED_NETHER_BRICKS": 1, - "CHISELED_POLISHED_BLACKSTONE": 1, - "CHISELED_QUARTZ": 3, - "CHISELED_RED_SANDSTONE": 1, - "CHISELED_SANDSTONE": 1, - "CHISELED_STONE_BRICKS": 1, - "CHISELED_TUFF": 1, - "CHISELED_TUFF_BRICKS": 1, - "CHORUS_FLOWER": 6, - "CHORUS_PLANT": 1, - "CLAY": 1, - "COAL": 1, - "COAL_ORE": 1, - "COBBLED_DEEPSLATE": 1, - "COBBLED_DEEPSLATE_SLAB": 3, - "COBBLED_DEEPSLATE_STAIRS": 8, - "COBBLED_DEEPSLATE_WALL": 162, - "COBBLESTONE": 1, - "COBBLESTONE_SLAB": 3, - "COBBLESTONE_STAIRS": 8, - "COBBLESTONE_WALL": 162, - "COBWEB": 1, - "COCOA_POD": 12, - "COMPOUND_CREATOR": 4, - "CONCRETE": 16, - "CONCRETE_POWDER": 16, - "COPPER": 8, - "COPPER_BULB": 32, - "COPPER_DOOR": 256, - "COPPER_GRATE": 8, - "COPPER_ORE": 1, - "COPPER_TRAPDOOR": 128, - "CORAL": 10, - "CORAL_BLOCK": 10, - "CORAL_FAN": 20, - "CORNFLOWER": 1, - "CRACKED_DEEPSLATE_BRICKS": 1, - "CRACKED_DEEPSLATE_TILES": 1, - "CRACKED_NETHER_BRICKS": 1, - "CRACKED_POLISHED_BLACKSTONE_BRICKS": 1, - "CRACKED_STONE_BRICKS": 1, - "CRAFTING_TABLE": 1, - "CRIMSON_BUTTON": 12, - "CRIMSON_DOOR": 32, - "CRIMSON_FENCE": 1, - "CRIMSON_FENCE_GATE": 16, - "CRIMSON_HYPHAE": 6, - "CRIMSON_PLANKS": 1, - "CRIMSON_PRESSURE_PLATE": 2, - "CRIMSON_ROOTS": 1, - "CRIMSON_SIGN": 16, - "CRIMSON_SLAB": 3, - "CRIMSON_STAIRS": 8, - "CRIMSON_STEM": 6, - "CRIMSON_TRAPDOOR": 16, - "CRIMSON_WALL_SIGN": 4, - "CRYING_OBSIDIAN": 1, - "CUT_COPPER": 8, - "CUT_COPPER_SLAB": 24, - "CUT_COPPER_STAIRS": 64, - "CUT_RED_SANDSTONE": 1, - "CUT_RED_SANDSTONE_SLAB": 3, - "CUT_SANDSTONE": 1, - "CUT_SANDSTONE_SLAB": 3, - "DANDELION": 1, - "DARK_OAK_BUTTON": 12, - "DARK_OAK_DOOR": 32, - "DARK_OAK_FENCE": 1, - "DARK_OAK_FENCE_GATE": 16, - "DARK_OAK_LEAVES": 4, - "DARK_OAK_LOG": 6, - "DARK_OAK_PLANKS": 1, - "DARK_OAK_PRESSURE_PLATE": 2, - "DARK_OAK_SAPLING": 2, - "DARK_OAK_SIGN": 16, - "DARK_OAK_SLAB": 3, - "DARK_OAK_STAIRS": 8, - "DARK_OAK_TRAPDOOR": 16, - "DARK_OAK_WALL_SIGN": 4, - "DARK_OAK_WOOD": 6, - "DARK_PRISMARINE": 1, - "DARK_PRISMARINE_SLAB": 3, - "DARK_PRISMARINE_STAIRS": 8, - "DAYLIGHT_SENSOR": 32, - "DEAD_BUSH": 1, - "DEEPSLATE": 3, - "DEEPSLATE_BRICKS": 1, - "DEEPSLATE_BRICK_SLAB": 3, - "DEEPSLATE_BRICK_STAIRS": 8, - "DEEPSLATE_BRICK_WALL": 162, - "DEEPSLATE_COAL_ORE": 1, - "DEEPSLATE_COPPER_ORE": 1, - "DEEPSLATE_DIAMOND_ORE": 1, - "DEEPSLATE_EMERALD_ORE": 1, - "DEEPSLATE_GOLD_ORE": 1, - "DEEPSLATE_IRON_ORE": 1, - "DEEPSLATE_LAPIS_LAZULI_ORE": 1, - "DEEPSLATE_REDSTONE_ORE": 2, - "DEEPSLATE_TILES": 1, - "DEEPSLATE_TILE_SLAB": 3, - "DEEPSLATE_TILE_STAIRS": 8, - "DEEPSLATE_TILE_WALL": 162, - "DETECTOR_RAIL": 12, - "DIAMOND": 1, - "DIAMOND_ORE": 1, - "DIORITE": 1, - "DIORITE_SLAB": 3, - "DIORITE_STAIRS": 8, - "DIORITE_WALL": 162, - "DIRT": 3, - "DOUBLE_PITCHER_CROP": 4, - "DOUBLE_TALLGRASS": 2, - "DRAGON_EGG": 1, - "DRIED_KELP": 1, - "DYED_CANDLE": 128, - "DYED_SHULKER_BOX": 16, - "ELEMENT_ACTINIUM": 1, - "ELEMENT_ALUMINUM": 1, - "ELEMENT_AMERICIUM": 1, - "ELEMENT_ANTIMONY": 1, - "ELEMENT_ARGON": 1, - "ELEMENT_ARSENIC": 1, - "ELEMENT_ASTATINE": 1, - "ELEMENT_BARIUM": 1, - "ELEMENT_BERKELIUM": 1, - "ELEMENT_BERYLLIUM": 1, - "ELEMENT_BISMUTH": 1, - "ELEMENT_BOHRIUM": 1, - "ELEMENT_BORON": 1, - "ELEMENT_BROMINE": 1, - "ELEMENT_CADMIUM": 1, - "ELEMENT_CALCIUM": 1, - "ELEMENT_CALIFORNIUM": 1, - "ELEMENT_CARBON": 1, - "ELEMENT_CERIUM": 1, - "ELEMENT_CESIUM": 1, - "ELEMENT_CHLORINE": 1, - "ELEMENT_CHROMIUM": 1, - "ELEMENT_COBALT": 1, - "ELEMENT_CONSTRUCTOR": 4, - "ELEMENT_COPERNICIUM": 1, - "ELEMENT_COPPER": 1, - "ELEMENT_CURIUM": 1, - "ELEMENT_DARMSTADTIUM": 1, - "ELEMENT_DUBNIUM": 1, - "ELEMENT_DYSPROSIUM": 1, - "ELEMENT_EINSTEINIUM": 1, - "ELEMENT_ERBIUM": 1, - "ELEMENT_EUROPIUM": 1, - "ELEMENT_FERMIUM": 1, - "ELEMENT_FLEROVIUM": 1, - "ELEMENT_FLUORINE": 1, - "ELEMENT_FRANCIUM": 1, - "ELEMENT_GADOLINIUM": 1, - "ELEMENT_GALLIUM": 1, - "ELEMENT_GERMANIUM": 1, - "ELEMENT_GOLD": 1, - "ELEMENT_HAFNIUM": 1, - "ELEMENT_HASSIUM": 1, - "ELEMENT_HELIUM": 1, - "ELEMENT_HOLMIUM": 1, - "ELEMENT_HYDROGEN": 1, - "ELEMENT_INDIUM": 1, - "ELEMENT_IODINE": 1, - "ELEMENT_IRIDIUM": 1, - "ELEMENT_IRON": 1, - "ELEMENT_KRYPTON": 1, - "ELEMENT_LANTHANUM": 1, - "ELEMENT_LAWRENCIUM": 1, - "ELEMENT_LEAD": 1, - "ELEMENT_LITHIUM": 1, - "ELEMENT_LIVERMORIUM": 1, - "ELEMENT_LUTETIUM": 1, - "ELEMENT_MAGNESIUM": 1, - "ELEMENT_MANGANESE": 1, - "ELEMENT_MEITNERIUM": 1, - "ELEMENT_MENDELEVIUM": 1, - "ELEMENT_MERCURY": 1, - "ELEMENT_MOLYBDENUM": 1, - "ELEMENT_MOSCOVIUM": 1, - "ELEMENT_NEODYMIUM": 1, - "ELEMENT_NEON": 1, - "ELEMENT_NEPTUNIUM": 1, - "ELEMENT_NICKEL": 1, - "ELEMENT_NIHONIUM": 1, - "ELEMENT_NIOBIUM": 1, - "ELEMENT_NITROGEN": 1, - "ELEMENT_NOBELIUM": 1, - "ELEMENT_OGANESSON": 1, - "ELEMENT_OSMIUM": 1, - "ELEMENT_OXYGEN": 1, - "ELEMENT_PALLADIUM": 1, - "ELEMENT_PHOSPHORUS": 1, - "ELEMENT_PLATINUM": 1, - "ELEMENT_PLUTONIUM": 1, - "ELEMENT_POLONIUM": 1, - "ELEMENT_POTASSIUM": 1, - "ELEMENT_PRASEODYMIUM": 1, - "ELEMENT_PROMETHIUM": 1, - "ELEMENT_PROTACTINIUM": 1, - "ELEMENT_RADIUM": 1, - "ELEMENT_RADON": 1, - "ELEMENT_RHENIUM": 1, - "ELEMENT_RHODIUM": 1, - "ELEMENT_ROENTGENIUM": 1, - "ELEMENT_RUBIDIUM": 1, - "ELEMENT_RUTHENIUM": 1, - "ELEMENT_RUTHERFORDIUM": 1, - "ELEMENT_SAMARIUM": 1, - "ELEMENT_SCANDIUM": 1, - "ELEMENT_SEABORGIUM": 1, - "ELEMENT_SELENIUM": 1, - "ELEMENT_SILICON": 1, - "ELEMENT_SILVER": 1, - "ELEMENT_SODIUM": 1, - "ELEMENT_STRONTIUM": 1, - "ELEMENT_SULFUR": 1, - "ELEMENT_TANTALUM": 1, - "ELEMENT_TECHNETIUM": 1, - "ELEMENT_TELLURIUM": 1, - "ELEMENT_TENNESSINE": 1, - "ELEMENT_TERBIUM": 1, - "ELEMENT_THALLIUM": 1, - "ELEMENT_THORIUM": 1, - "ELEMENT_THULIUM": 1, - "ELEMENT_TIN": 1, - "ELEMENT_TITANIUM": 1, - "ELEMENT_TUNGSTEN": 1, - "ELEMENT_URANIUM": 1, - "ELEMENT_VANADIUM": 1, - "ELEMENT_XENON": 1, - "ELEMENT_YTTERBIUM": 1, - "ELEMENT_YTTRIUM": 1, - "ELEMENT_ZERO": 1, - "ELEMENT_ZINC": 1, - "ELEMENT_ZIRCONIUM": 1, - "EMERALD": 1, - "EMERALD_ORE": 1, - "ENCHANTING_TABLE": 1, - "ENDER_CHEST": 4, - "END_PORTAL_FRAME": 8, - "END_ROD": 6, - "END_STONE": 1, - "END_STONE_BRICKS": 1, - "END_STONE_BRICK_SLAB": 3, - "END_STONE_BRICK_STAIRS": 8, - "END_STONE_BRICK_WALL": 162, - "FAKE_WOODEN_SLAB": 3, - "FARMLAND": 1304, - "FERN": 1, - "FIRE": 16, - "FLETCHING_TABLE": 1, - "FLOWERING_AZALEA_LEAVES": 4, - "FLOWER_POT": 1, - "FROGLIGHT": 9, - "FROSTED_ICE": 4, - "FURNACE": 8, - "GILDED_BLACKSTONE": 1, - "GLASS": 1, - "GLASS_PANE": 1, - "GLAZED_TERRACOTTA": 64, - "GLOWING_ITEM_FRAME": 12, - "GLOWING_OBSIDIAN": 1, - "GLOWSTONE": 1, - "GLOW_LICHEN": 64, - "GOLD": 1, - "GOLD_ORE": 1, - "GRANITE": 1, - "GRANITE_SLAB": 3, - "GRANITE_STAIRS": 8, - "GRANITE_WALL": 162, - "GRASS": 1, - "GRASS_PATH": 1, - "GRAVEL": 1, - "GREEN_TORCH": 5, - "HANGING_ROOTS": 1, - "HARDENED_CLAY": 1, - "HARDENED_GLASS": 1, - "HARDENED_GLASS_PANE": 1, - "HAY_BALE": 3, - "HONEYCOMB": 1, - "HOPPER": 10, - "ICE": 1, - "INFESTED_CHISELED_STONE_BRICK": 1, - "INFESTED_COBBLESTONE": 1, - "INFESTED_CRACKED_STONE_BRICK": 1, - "INFESTED_MOSSY_STONE_BRICK": 1, - "INFESTED_STONE": 1, - "INFESTED_STONE_BRICK": 1, - "INFO_UPDATE": 1, - "INFO_UPDATE2": 1, - "INVISIBLE_BEDROCK": 1, - "IRON": 1, - "IRON_BARS": 1, - "IRON_DOOR": 32, - "IRON_ORE": 1, - "IRON_TRAPDOOR": 16, - "ITEM_FRAME": 12, - "JUKEBOX": 1, - "JUNGLE_BUTTON": 12, - "JUNGLE_DOOR": 32, - "JUNGLE_FENCE": 1, - "JUNGLE_FENCE_GATE": 16, - "JUNGLE_LEAVES": 4, - "JUNGLE_LOG": 6, - "JUNGLE_PLANKS": 1, - "JUNGLE_PRESSURE_PLATE": 2, - "JUNGLE_SAPLING": 2, - "JUNGLE_SIGN": 16, - "JUNGLE_SLAB": 3, - "JUNGLE_STAIRS": 8, - "JUNGLE_TRAPDOOR": 16, - "JUNGLE_WALL_SIGN": 4, - "JUNGLE_WOOD": 6, - "LAB_TABLE": 4, - "LADDER": 4, - "LANTERN": 2, - "LAPIS_LAZULI": 1, - "LAPIS_LAZULI_ORE": 1, - "LARGE_FERN": 2, - "LAVA": 32, - "LAVA_CAULDRON": 6, - "LECTERN": 8, - "LEGACY_STONECUTTER": 1, - "LEVER": 16, - "LIGHT": 16, - "LIGHTNING_ROD": 6, - "LILAC": 2, - "LILY_OF_THE_VALLEY": 1, - "LILY_PAD": 1, - "LIT_PUMPKIN": 4, - "LOOM": 4, - "MAGMA": 1, - "MANGROVE_BUTTON": 12, - "MANGROVE_DOOR": 32, - "MANGROVE_FENCE": 1, - "MANGROVE_FENCE_GATE": 16, - "MANGROVE_LEAVES": 4, - "MANGROVE_LOG": 6, - "MANGROVE_PLANKS": 1, - "MANGROVE_PRESSURE_PLATE": 2, - "MANGROVE_ROOTS": 1, - "MANGROVE_SIGN": 16, - "MANGROVE_SLAB": 3, - "MANGROVE_STAIRS": 8, - "MANGROVE_TRAPDOOR": 16, - "MANGROVE_WALL_SIGN": 4, - "MANGROVE_WOOD": 6, - "MATERIAL_REDUCER": 4, - "MELON": 1, - "MELON_STEM": 40, - "MOB_HEAD": 35, - "MONSTER_SPAWNER": 1, - "MOSSY_COBBLESTONE": 1, - "MOSSY_COBBLESTONE_SLAB": 3, - "MOSSY_COBBLESTONE_STAIRS": 8, - "MOSSY_COBBLESTONE_WALL": 162, - "MOSSY_STONE_BRICKS": 1, - "MOSSY_STONE_BRICK_SLAB": 3, - "MOSSY_STONE_BRICK_STAIRS": 8, - "MOSSY_STONE_BRICK_WALL": 162, - "MUD": 1, - "MUDDY_MANGROVE_ROOTS": 3, - "MUD_BRICKS": 1, - "MUD_BRICK_SLAB": 3, - "MUD_BRICK_STAIRS": 8, - "MUD_BRICK_WALL": 162, - "MUSHROOM_STEM": 1, - "MYCELIUM": 1, - "NETHERITE": 1, - "NETHERRACK": 1, - "NETHER_BRICKS": 1, - "NETHER_BRICK_FENCE": 1, - "NETHER_BRICK_SLAB": 3, - "NETHER_BRICK_STAIRS": 8, - "NETHER_BRICK_WALL": 162, - "NETHER_GOLD_ORE": 1, - "NETHER_PORTAL": 2, - "NETHER_QUARTZ_ORE": 1, - "NETHER_REACTOR_CORE": 1, - "NETHER_WART": 4, - "NETHER_WART_BLOCK": 1, - "NOTE_BLOCK": 1, - "OAK_BUTTON": 12, - "OAK_DOOR": 32, - "OAK_FENCE": 1, - "OAK_FENCE_GATE": 16, - "OAK_LEAVES": 4, - "OAK_LOG": 6, - "OAK_PLANKS": 1, - "OAK_PRESSURE_PLATE": 2, - "OAK_SAPLING": 2, - "OAK_SIGN": 16, - "OAK_SLAB": 3, - "OAK_STAIRS": 8, - "OAK_TRAPDOOR": 16, - "OAK_WALL_SIGN": 4, - "OAK_WOOD": 6, - "OBSIDIAN": 1, - "ORANGE_TULIP": 1, - "OXEYE_DAISY": 1, - "PACKED_ICE": 1, - "PACKED_MUD": 1, - "PEONY": 2, - "PINK_PETALS": 16, - "PINK_TULIP": 1, - "PITCHER_CROP": 3, - "PITCHER_PLANT": 2, - "PODZOL": 1, - "POLISHED_ANDESITE": 1, - "POLISHED_ANDESITE_SLAB": 3, - "POLISHED_ANDESITE_STAIRS": 8, - "POLISHED_BASALT": 3, - "POLISHED_BLACKSTONE": 1, - "POLISHED_BLACKSTONE_BRICKS": 1, - "POLISHED_BLACKSTONE_BRICK_SLAB": 3, - "POLISHED_BLACKSTONE_BRICK_STAIRS": 8, - "POLISHED_BLACKSTONE_BRICK_WALL": 162, - "POLISHED_BLACKSTONE_BUTTON": 12, - "POLISHED_BLACKSTONE_PRESSURE_PLATE": 2, - "POLISHED_BLACKSTONE_SLAB": 3, - "POLISHED_BLACKSTONE_STAIRS": 8, - "POLISHED_BLACKSTONE_WALL": 162, - "POLISHED_DEEPSLATE": 1, - "POLISHED_DEEPSLATE_SLAB": 3, - "POLISHED_DEEPSLATE_STAIRS": 8, - "POLISHED_DEEPSLATE_WALL": 162, - "POLISHED_DIORITE": 1, - "POLISHED_DIORITE_SLAB": 3, - "POLISHED_DIORITE_STAIRS": 8, - "POLISHED_GRANITE": 1, - "POLISHED_GRANITE_SLAB": 3, - "POLISHED_GRANITE_STAIRS": 8, - "POLISHED_TUFF": 1, - "POLISHED_TUFF_SLAB": 3, - "POLISHED_TUFF_STAIRS": 8, - "POLISHED_TUFF_WALL": 162, - "POPPY": 1, - "POTATOES": 8, - "POTION_CAULDRON": 6, - "POWERED_RAIL": 12, - "PRISMARINE": 1, - "PRISMARINE_BRICKS": 1, - "PRISMARINE_BRICKS_SLAB": 3, - "PRISMARINE_BRICKS_STAIRS": 8, - "PRISMARINE_SLAB": 3, - "PRISMARINE_STAIRS": 8, - "PRISMARINE_WALL": 162, - "PUMPKIN": 1, - "PUMPKIN_STEM": 40, - "PURPLE_TORCH": 5, - "PURPUR": 1, - "PURPUR_PILLAR": 3, - "PURPUR_SLAB": 3, - "PURPUR_STAIRS": 8, - "QUARTZ": 1, - "QUARTZ_BRICKS": 1, - "QUARTZ_PILLAR": 3, - "QUARTZ_SLAB": 3, - "QUARTZ_STAIRS": 8, - "RAIL": 10, - "RAW_COPPER": 1, - "RAW_GOLD": 1, - "RAW_IRON": 1, - "REDSTONE": 1, - "REDSTONE_COMPARATOR": 16, - "REDSTONE_LAMP": 2, - "REDSTONE_ORE": 2, - "REDSTONE_REPEATER": 32, - "REDSTONE_TORCH": 10, - "REDSTONE_WIRE": 16, - "RED_MUSHROOM": 1, - "RED_MUSHROOM_BLOCK": 11, - "RED_NETHER_BRICKS": 1, - "RED_NETHER_BRICK_SLAB": 3, - "RED_NETHER_BRICK_STAIRS": 8, - "RED_NETHER_BRICK_WALL": 162, - "RED_SAND": 1, - "RED_SANDSTONE": 1, - "RED_SANDSTONE_SLAB": 3, - "RED_SANDSTONE_STAIRS": 8, - "RED_SANDSTONE_WALL": 162, - "RED_TORCH": 5, - "RED_TULIP": 1, - "REINFORCED_DEEPSLATE": 1, - "RESERVED6": 1, - "ROSE_BUSH": 2, - "SAND": 1, - "SANDSTONE": 1, - "SANDSTONE_SLAB": 3, - "SANDSTONE_STAIRS": 8, - "SANDSTONE_WALL": 162, - "SCULK": 1, - "SEA_LANTERN": 1, - "SEA_PICKLE": 8, - "SHROOMLIGHT": 1, - "SHULKER_BOX": 1, - "SLIME": 1, - "SMALL_DRIPLEAF": 8, - "SMITHING_TABLE": 1, - "SMOKER": 8, - "SMOOTH_BASALT": 1, - "SMOOTH_QUARTZ": 1, - "SMOOTH_QUARTZ_SLAB": 3, - "SMOOTH_QUARTZ_STAIRS": 8, - "SMOOTH_RED_SANDSTONE": 1, - "SMOOTH_RED_SANDSTONE_SLAB": 3, - "SMOOTH_RED_SANDSTONE_STAIRS": 8, - "SMOOTH_SANDSTONE": 1, - "SMOOTH_SANDSTONE_SLAB": 3, - "SMOOTH_SANDSTONE_STAIRS": 8, - "SMOOTH_STONE": 1, - "SMOOTH_STONE_SLAB": 3, - "SNOW": 1, - "SNOW_LAYER": 8, - "SOUL_CAMPFIRE": 8, - "SOUL_FIRE": 1, - "SOUL_LANTERN": 2, - "SOUL_SAND": 1, - "SOUL_SOIL": 1, - "SOUL_TORCH": 5, - "SPONGE": 2, - "SPORE_BLOSSOM": 1, - "SPRUCE_BUTTON": 12, - "SPRUCE_DOOR": 32, - "SPRUCE_FENCE": 1, - "SPRUCE_FENCE_GATE": 16, - "SPRUCE_LEAVES": 4, - "SPRUCE_LOG": 6, - "SPRUCE_PLANKS": 1, - "SPRUCE_PRESSURE_PLATE": 2, - "SPRUCE_SAPLING": 2, - "SPRUCE_SIGN": 16, - "SPRUCE_SLAB": 3, - "SPRUCE_STAIRS": 8, - "SPRUCE_TRAPDOOR": 16, - "SPRUCE_WALL_SIGN": 4, - "SPRUCE_WOOD": 6, - "STAINED_CLAY": 16, - "STAINED_GLASS": 16, - "STAINED_GLASS_PANE": 16, - "STAINED_HARDENED_GLASS": 16, - "STAINED_HARDENED_GLASS_PANE": 16, - "STONE": 1, - "STONECUTTER": 4, - "STONE_BRICKS": 1, - "STONE_BRICK_SLAB": 3, - "STONE_BRICK_STAIRS": 8, - "STONE_BRICK_WALL": 162, - "STONE_BUTTON": 12, - "STONE_PRESSURE_PLATE": 2, - "STONE_SLAB": 3, - "STONE_STAIRS": 8, - "SUGARCANE": 16, - "SUNFLOWER": 2, - "SWEET_BERRY_BUSH": 4, - "TALL_GRASS": 1, - "TINTED_GLASS": 1, - "TNT": 4, - "TORCH": 5, - "TORCHFLOWER": 1, - "TORCHFLOWER_CROP": 2, - "TRAPPED_CHEST": 4, - "TRIPWIRE": 16, - "TRIPWIRE_HOOK": 16, - "TUFF": 1, - "TUFF_BRICKS": 1, - "TUFF_BRICK_SLAB": 3, - "TUFF_BRICK_STAIRS": 8, - "TUFF_BRICK_WALL": 162, - "TUFF_SLAB": 3, - "TUFF_STAIRS": 8, - "TUFF_WALL": 162, - "TWISTING_VINES": 26, - "UNDERWATER_TORCH": 5, - "VINES": 16, - "WALL_BANNER": 64, - "WALL_CORAL_FAN": 40, - "WARPED_BUTTON": 12, - "WARPED_DOOR": 32, - "WARPED_FENCE": 1, - "WARPED_FENCE_GATE": 16, - "WARPED_HYPHAE": 6, - "WARPED_PLANKS": 1, - "WARPED_PRESSURE_PLATE": 2, - "WARPED_ROOTS": 1, - "WARPED_SIGN": 16, - "WARPED_SLAB": 3, - "WARPED_STAIRS": 8, - "WARPED_STEM": 6, - "WARPED_TRAPDOOR": 16, - "WARPED_WALL_SIGN": 4, - "WARPED_WART_BLOCK": 1, - "WATER": 32, - "WATER_CAULDRON": 6, - "WEEPING_VINES": 26, - "WEIGHTED_PRESSURE_PLATE_HEAVY": 16, - "WEIGHTED_PRESSURE_PLATE_LIGHT": 16, - "WHEAT": 8, - "WHITE_TULIP": 1, - "WITHER_ROSE": 1, - "WOOL": 16 + "stateCounts": { + "ACACIA_BUTTON": 12, + "ACACIA_DOOR": 32, + "ACACIA_FENCE": 1, + "ACACIA_FENCE_GATE": 16, + "ACACIA_LEAVES": 4, + "ACACIA_LOG": 6, + "ACACIA_PLANKS": 1, + "ACACIA_PRESSURE_PLATE": 2, + "ACACIA_SAPLING": 2, + "ACACIA_SIGN": 16, + "ACACIA_SLAB": 3, + "ACACIA_STAIRS": 8, + "ACACIA_TRAPDOOR": 16, + "ACACIA_WALL_SIGN": 4, + "ACACIA_WOOD": 6, + "ACTIVATOR_RAIL": 12, + "AIR": 1, + "ALLIUM": 1, + "ALL_SIDED_MUSHROOM_STEM": 1, + "AMETHYST": 1, + "AMETHYST_CLUSTER": 24, + "ANCIENT_DEBRIS": 1, + "ANDESITE": 1, + "ANDESITE_SLAB": 3, + "ANDESITE_STAIRS": 8, + "ANDESITE_WALL": 162, + "ANVIL": 12, + "AZALEA_LEAVES": 4, + "AZURE_BLUET": 1, + "BAMBOO": 12, + "BAMBOO_SAPLING": 2, + "BANNER": 256, + "BARREL": 12, + "BARRIER": 1, + "BASALT": 3, + "BEACON": 1, + "BED": 256, + "BEDROCK": 2, + "BEETROOTS": 8, + "BELL": 16, + "BIG_DRIPLEAF_HEAD": 16, + "BIG_DRIPLEAF_STEM": 4, + "BIRCH_BUTTON": 12, + "BIRCH_DOOR": 32, + "BIRCH_FENCE": 1, + "BIRCH_FENCE_GATE": 16, + "BIRCH_LEAVES": 4, + "BIRCH_LOG": 6, + "BIRCH_PLANKS": 1, + "BIRCH_PRESSURE_PLATE": 2, + "BIRCH_SAPLING": 2, + "BIRCH_SIGN": 16, + "BIRCH_SLAB": 3, + "BIRCH_STAIRS": 8, + "BIRCH_TRAPDOOR": 16, + "BIRCH_WALL_SIGN": 4, + "BIRCH_WOOD": 6, + "BLACKSTONE": 1, + "BLACKSTONE_SLAB": 3, + "BLACKSTONE_STAIRS": 8, + "BLACKSTONE_WALL": 162, + "BLAST_FURNACE": 8, + "BLUE_ICE": 1, + "BLUE_ORCHID": 1, + "BLUE_TORCH": 5, + "BONE_BLOCK": 3, + "BOOKSHELF": 1, + "BREWING_STAND": 8, + "BRICKS": 1, + "BRICK_SLAB": 3, + "BRICK_STAIRS": 8, + "BRICK_WALL": 162, + "BROWN_MUSHROOM": 1, + "BROWN_MUSHROOM_BLOCK": 11, + "BUDDING_AMETHYST": 1, + "CACTUS": 16, + "CAKE": 7, + "CAKE_WITH_CANDLE": 2, + "CAKE_WITH_DYED_CANDLE": 32, + "CALCITE": 1, + "CAMPFIRE": 8, + "CANDLE": 8, + "CARPET": 16, + "CARROTS": 8, + "CARTOGRAPHY_TABLE": 1, + "CARVED_PUMPKIN": 4, + "CAULDRON": 1, + "CAVE_VINES": 104, + "CHAIN": 3, + "CHEMICAL_HEAT": 1, + "CHERRY_BUTTON": 12, + "CHERRY_DOOR": 32, + "CHERRY_FENCE": 1, + "CHERRY_FENCE_GATE": 16, + "CHERRY_LEAVES": 4, + "CHERRY_LOG": 6, + "CHERRY_PLANKS": 1, + "CHERRY_PRESSURE_PLATE": 2, + "CHERRY_SIGN": 16, + "CHERRY_SLAB": 3, + "CHERRY_STAIRS": 8, + "CHERRY_TRAPDOOR": 16, + "CHERRY_WALL_SIGN": 4, + "CHERRY_WOOD": 6, + "CHEST": 4, + "CHISELED_BOOKSHELF": 256, + "CHISELED_COPPER": 8, + "CHISELED_DEEPSLATE": 1, + "CHISELED_NETHER_BRICKS": 1, + "CHISELED_POLISHED_BLACKSTONE": 1, + "CHISELED_QUARTZ": 3, + "CHISELED_RED_SANDSTONE": 1, + "CHISELED_SANDSTONE": 1, + "CHISELED_STONE_BRICKS": 1, + "CHISELED_TUFF": 1, + "CHISELED_TUFF_BRICKS": 1, + "CHORUS_FLOWER": 6, + "CHORUS_PLANT": 1, + "CLAY": 1, + "COAL": 1, + "COAL_ORE": 1, + "COBBLED_DEEPSLATE": 1, + "COBBLED_DEEPSLATE_SLAB": 3, + "COBBLED_DEEPSLATE_STAIRS": 8, + "COBBLED_DEEPSLATE_WALL": 162, + "COBBLESTONE": 1, + "COBBLESTONE_SLAB": 3, + "COBBLESTONE_STAIRS": 8, + "COBBLESTONE_WALL": 162, + "COBWEB": 1, + "COCOA_POD": 12, + "COMPOUND_CREATOR": 4, + "CONCRETE": 16, + "CONCRETE_POWDER": 16, + "COPPER": 8, + "COPPER_BULB": 32, + "COPPER_DOOR": 256, + "COPPER_GRATE": 8, + "COPPER_ORE": 1, + "COPPER_TRAPDOOR": 128, + "CORAL": 10, + "CORAL_BLOCK": 10, + "CORAL_FAN": 20, + "CORNFLOWER": 1, + "CRACKED_DEEPSLATE_BRICKS": 1, + "CRACKED_DEEPSLATE_TILES": 1, + "CRACKED_NETHER_BRICKS": 1, + "CRACKED_POLISHED_BLACKSTONE_BRICKS": 1, + "CRACKED_STONE_BRICKS": 1, + "CRAFTING_TABLE": 1, + "CRIMSON_BUTTON": 12, + "CRIMSON_DOOR": 32, + "CRIMSON_FENCE": 1, + "CRIMSON_FENCE_GATE": 16, + "CRIMSON_HYPHAE": 6, + "CRIMSON_PLANKS": 1, + "CRIMSON_PRESSURE_PLATE": 2, + "CRIMSON_ROOTS": 1, + "CRIMSON_SIGN": 16, + "CRIMSON_SLAB": 3, + "CRIMSON_STAIRS": 8, + "CRIMSON_STEM": 6, + "CRIMSON_TRAPDOOR": 16, + "CRIMSON_WALL_SIGN": 4, + "CRYING_OBSIDIAN": 1, + "CUT_COPPER": 8, + "CUT_COPPER_SLAB": 24, + "CUT_COPPER_STAIRS": 64, + "CUT_RED_SANDSTONE": 1, + "CUT_RED_SANDSTONE_SLAB": 3, + "CUT_SANDSTONE": 1, + "CUT_SANDSTONE_SLAB": 3, + "DANDELION": 1, + "DARK_OAK_BUTTON": 12, + "DARK_OAK_DOOR": 32, + "DARK_OAK_FENCE": 1, + "DARK_OAK_FENCE_GATE": 16, + "DARK_OAK_LEAVES": 4, + "DARK_OAK_LOG": 6, + "DARK_OAK_PLANKS": 1, + "DARK_OAK_PRESSURE_PLATE": 2, + "DARK_OAK_SAPLING": 2, + "DARK_OAK_SIGN": 16, + "DARK_OAK_SLAB": 3, + "DARK_OAK_STAIRS": 8, + "DARK_OAK_TRAPDOOR": 16, + "DARK_OAK_WALL_SIGN": 4, + "DARK_OAK_WOOD": 6, + "DARK_PRISMARINE": 1, + "DARK_PRISMARINE_SLAB": 3, + "DARK_PRISMARINE_STAIRS": 8, + "DAYLIGHT_SENSOR": 32, + "DEAD_BUSH": 1, + "DEEPSLATE": 3, + "DEEPSLATE_BRICKS": 1, + "DEEPSLATE_BRICK_SLAB": 3, + "DEEPSLATE_BRICK_STAIRS": 8, + "DEEPSLATE_BRICK_WALL": 162, + "DEEPSLATE_COAL_ORE": 1, + "DEEPSLATE_COPPER_ORE": 1, + "DEEPSLATE_DIAMOND_ORE": 1, + "DEEPSLATE_EMERALD_ORE": 1, + "DEEPSLATE_GOLD_ORE": 1, + "DEEPSLATE_IRON_ORE": 1, + "DEEPSLATE_LAPIS_LAZULI_ORE": 1, + "DEEPSLATE_REDSTONE_ORE": 2, + "DEEPSLATE_TILES": 1, + "DEEPSLATE_TILE_SLAB": 3, + "DEEPSLATE_TILE_STAIRS": 8, + "DEEPSLATE_TILE_WALL": 162, + "DETECTOR_RAIL": 12, + "DIAMOND": 1, + "DIAMOND_ORE": 1, + "DIORITE": 1, + "DIORITE_SLAB": 3, + "DIORITE_STAIRS": 8, + "DIORITE_WALL": 162, + "DIRT": 3, + "DOUBLE_PITCHER_CROP": 4, + "DOUBLE_TALLGRASS": 2, + "DRAGON_EGG": 1, + "DRIED_KELP": 1, + "DYED_CANDLE": 128, + "DYED_SHULKER_BOX": 16, + "ELEMENT_ACTINIUM": 1, + "ELEMENT_ALUMINUM": 1, + "ELEMENT_AMERICIUM": 1, + "ELEMENT_ANTIMONY": 1, + "ELEMENT_ARGON": 1, + "ELEMENT_ARSENIC": 1, + "ELEMENT_ASTATINE": 1, + "ELEMENT_BARIUM": 1, + "ELEMENT_BERKELIUM": 1, + "ELEMENT_BERYLLIUM": 1, + "ELEMENT_BISMUTH": 1, + "ELEMENT_BOHRIUM": 1, + "ELEMENT_BORON": 1, + "ELEMENT_BROMINE": 1, + "ELEMENT_CADMIUM": 1, + "ELEMENT_CALCIUM": 1, + "ELEMENT_CALIFORNIUM": 1, + "ELEMENT_CARBON": 1, + "ELEMENT_CERIUM": 1, + "ELEMENT_CESIUM": 1, + "ELEMENT_CHLORINE": 1, + "ELEMENT_CHROMIUM": 1, + "ELEMENT_COBALT": 1, + "ELEMENT_CONSTRUCTOR": 4, + "ELEMENT_COPERNICIUM": 1, + "ELEMENT_COPPER": 1, + "ELEMENT_CURIUM": 1, + "ELEMENT_DARMSTADTIUM": 1, + "ELEMENT_DUBNIUM": 1, + "ELEMENT_DYSPROSIUM": 1, + "ELEMENT_EINSTEINIUM": 1, + "ELEMENT_ERBIUM": 1, + "ELEMENT_EUROPIUM": 1, + "ELEMENT_FERMIUM": 1, + "ELEMENT_FLEROVIUM": 1, + "ELEMENT_FLUORINE": 1, + "ELEMENT_FRANCIUM": 1, + "ELEMENT_GADOLINIUM": 1, + "ELEMENT_GALLIUM": 1, + "ELEMENT_GERMANIUM": 1, + "ELEMENT_GOLD": 1, + "ELEMENT_HAFNIUM": 1, + "ELEMENT_HASSIUM": 1, + "ELEMENT_HELIUM": 1, + "ELEMENT_HOLMIUM": 1, + "ELEMENT_HYDROGEN": 1, + "ELEMENT_INDIUM": 1, + "ELEMENT_IODINE": 1, + "ELEMENT_IRIDIUM": 1, + "ELEMENT_IRON": 1, + "ELEMENT_KRYPTON": 1, + "ELEMENT_LANTHANUM": 1, + "ELEMENT_LAWRENCIUM": 1, + "ELEMENT_LEAD": 1, + "ELEMENT_LITHIUM": 1, + "ELEMENT_LIVERMORIUM": 1, + "ELEMENT_LUTETIUM": 1, + "ELEMENT_MAGNESIUM": 1, + "ELEMENT_MANGANESE": 1, + "ELEMENT_MEITNERIUM": 1, + "ELEMENT_MENDELEVIUM": 1, + "ELEMENT_MERCURY": 1, + "ELEMENT_MOLYBDENUM": 1, + "ELEMENT_MOSCOVIUM": 1, + "ELEMENT_NEODYMIUM": 1, + "ELEMENT_NEON": 1, + "ELEMENT_NEPTUNIUM": 1, + "ELEMENT_NICKEL": 1, + "ELEMENT_NIHONIUM": 1, + "ELEMENT_NIOBIUM": 1, + "ELEMENT_NITROGEN": 1, + "ELEMENT_NOBELIUM": 1, + "ELEMENT_OGANESSON": 1, + "ELEMENT_OSMIUM": 1, + "ELEMENT_OXYGEN": 1, + "ELEMENT_PALLADIUM": 1, + "ELEMENT_PHOSPHORUS": 1, + "ELEMENT_PLATINUM": 1, + "ELEMENT_PLUTONIUM": 1, + "ELEMENT_POLONIUM": 1, + "ELEMENT_POTASSIUM": 1, + "ELEMENT_PRASEODYMIUM": 1, + "ELEMENT_PROMETHIUM": 1, + "ELEMENT_PROTACTINIUM": 1, + "ELEMENT_RADIUM": 1, + "ELEMENT_RADON": 1, + "ELEMENT_RHENIUM": 1, + "ELEMENT_RHODIUM": 1, + "ELEMENT_ROENTGENIUM": 1, + "ELEMENT_RUBIDIUM": 1, + "ELEMENT_RUTHENIUM": 1, + "ELEMENT_RUTHERFORDIUM": 1, + "ELEMENT_SAMARIUM": 1, + "ELEMENT_SCANDIUM": 1, + "ELEMENT_SEABORGIUM": 1, + "ELEMENT_SELENIUM": 1, + "ELEMENT_SILICON": 1, + "ELEMENT_SILVER": 1, + "ELEMENT_SODIUM": 1, + "ELEMENT_STRONTIUM": 1, + "ELEMENT_SULFUR": 1, + "ELEMENT_TANTALUM": 1, + "ELEMENT_TECHNETIUM": 1, + "ELEMENT_TELLURIUM": 1, + "ELEMENT_TENNESSINE": 1, + "ELEMENT_TERBIUM": 1, + "ELEMENT_THALLIUM": 1, + "ELEMENT_THORIUM": 1, + "ELEMENT_THULIUM": 1, + "ELEMENT_TIN": 1, + "ELEMENT_TITANIUM": 1, + "ELEMENT_TUNGSTEN": 1, + "ELEMENT_URANIUM": 1, + "ELEMENT_VANADIUM": 1, + "ELEMENT_XENON": 1, + "ELEMENT_YTTERBIUM": 1, + "ELEMENT_YTTRIUM": 1, + "ELEMENT_ZERO": 1, + "ELEMENT_ZINC": 1, + "ELEMENT_ZIRCONIUM": 1, + "EMERALD": 1, + "EMERALD_ORE": 1, + "ENCHANTING_TABLE": 1, + "ENDER_CHEST": 4, + "END_PORTAL_FRAME": 8, + "END_ROD": 6, + "END_STONE": 1, + "END_STONE_BRICKS": 1, + "END_STONE_BRICK_SLAB": 3, + "END_STONE_BRICK_STAIRS": 8, + "END_STONE_BRICK_WALL": 162, + "FAKE_WOODEN_SLAB": 3, + "FARMLAND": 1304, + "FERN": 1, + "FIRE": 16, + "FLETCHING_TABLE": 1, + "FLOWERING_AZALEA_LEAVES": 4, + "FLOWER_POT": 1, + "FROGLIGHT": 9, + "FROSTED_ICE": 4, + "FURNACE": 8, + "GILDED_BLACKSTONE": 1, + "GLASS": 1, + "GLASS_PANE": 1, + "GLAZED_TERRACOTTA": 64, + "GLOWING_ITEM_FRAME": 12, + "GLOWING_OBSIDIAN": 1, + "GLOWSTONE": 1, + "GLOW_LICHEN": 64, + "GOLD": 1, + "GOLD_ORE": 1, + "GRANITE": 1, + "GRANITE_SLAB": 3, + "GRANITE_STAIRS": 8, + "GRANITE_WALL": 162, + "GRASS": 1, + "GRASS_PATH": 1, + "GRAVEL": 1, + "GREEN_TORCH": 5, + "HANGING_ROOTS": 1, + "HARDENED_CLAY": 1, + "HARDENED_GLASS": 1, + "HARDENED_GLASS_PANE": 1, + "HAY_BALE": 3, + "HONEYCOMB": 1, + "HOPPER": 10, + "ICE": 1, + "INFESTED_CHISELED_STONE_BRICK": 1, + "INFESTED_COBBLESTONE": 1, + "INFESTED_CRACKED_STONE_BRICK": 1, + "INFESTED_MOSSY_STONE_BRICK": 1, + "INFESTED_STONE": 1, + "INFESTED_STONE_BRICK": 1, + "INFO_UPDATE": 1, + "INFO_UPDATE2": 1, + "INVISIBLE_BEDROCK": 1, + "IRON": 1, + "IRON_BARS": 1, + "IRON_DOOR": 32, + "IRON_ORE": 1, + "IRON_TRAPDOOR": 16, + "ITEM_FRAME": 12, + "JUKEBOX": 1, + "JUNGLE_BUTTON": 12, + "JUNGLE_DOOR": 32, + "JUNGLE_FENCE": 1, + "JUNGLE_FENCE_GATE": 16, + "JUNGLE_LEAVES": 4, + "JUNGLE_LOG": 6, + "JUNGLE_PLANKS": 1, + "JUNGLE_PRESSURE_PLATE": 2, + "JUNGLE_SAPLING": 2, + "JUNGLE_SIGN": 16, + "JUNGLE_SLAB": 3, + "JUNGLE_STAIRS": 8, + "JUNGLE_TRAPDOOR": 16, + "JUNGLE_WALL_SIGN": 4, + "JUNGLE_WOOD": 6, + "LAB_TABLE": 4, + "LADDER": 4, + "LANTERN": 2, + "LAPIS_LAZULI": 1, + "LAPIS_LAZULI_ORE": 1, + "LARGE_FERN": 2, + "LAVA": 32, + "LAVA_CAULDRON": 6, + "LECTERN": 8, + "LEGACY_STONECUTTER": 1, + "LEVER": 16, + "LIGHT": 16, + "LIGHTNING_ROD": 6, + "LILAC": 2, + "LILY_OF_THE_VALLEY": 1, + "LILY_PAD": 1, + "LIT_PUMPKIN": 4, + "LOOM": 4, + "MAGMA": 1, + "MANGROVE_BUTTON": 12, + "MANGROVE_DOOR": 32, + "MANGROVE_FENCE": 1, + "MANGROVE_FENCE_GATE": 16, + "MANGROVE_LEAVES": 4, + "MANGROVE_LOG": 6, + "MANGROVE_PLANKS": 1, + "MANGROVE_PRESSURE_PLATE": 2, + "MANGROVE_ROOTS": 1, + "MANGROVE_SIGN": 16, + "MANGROVE_SLAB": 3, + "MANGROVE_STAIRS": 8, + "MANGROVE_TRAPDOOR": 16, + "MANGROVE_WALL_SIGN": 4, + "MANGROVE_WOOD": 6, + "MATERIAL_REDUCER": 4, + "MELON": 1, + "MELON_STEM": 40, + "MOB_HEAD": 35, + "MONSTER_SPAWNER": 1, + "MOSSY_COBBLESTONE": 1, + "MOSSY_COBBLESTONE_SLAB": 3, + "MOSSY_COBBLESTONE_STAIRS": 8, + "MOSSY_COBBLESTONE_WALL": 162, + "MOSSY_STONE_BRICKS": 1, + "MOSSY_STONE_BRICK_SLAB": 3, + "MOSSY_STONE_BRICK_STAIRS": 8, + "MOSSY_STONE_BRICK_WALL": 162, + "MUD": 1, + "MUDDY_MANGROVE_ROOTS": 3, + "MUD_BRICKS": 1, + "MUD_BRICK_SLAB": 3, + "MUD_BRICK_STAIRS": 8, + "MUD_BRICK_WALL": 162, + "MUSHROOM_STEM": 1, + "MYCELIUM": 1, + "NETHERITE": 1, + "NETHERRACK": 1, + "NETHER_BRICKS": 1, + "NETHER_BRICK_FENCE": 1, + "NETHER_BRICK_SLAB": 3, + "NETHER_BRICK_STAIRS": 8, + "NETHER_BRICK_WALL": 162, + "NETHER_GOLD_ORE": 1, + "NETHER_PORTAL": 2, + "NETHER_QUARTZ_ORE": 1, + "NETHER_REACTOR_CORE": 1, + "NETHER_WART": 4, + "NETHER_WART_BLOCK": 1, + "NOTE_BLOCK": 1, + "OAK_BUTTON": 12, + "OAK_DOOR": 32, + "OAK_FENCE": 1, + "OAK_FENCE_GATE": 16, + "OAK_LEAVES": 4, + "OAK_LOG": 6, + "OAK_PLANKS": 1, + "OAK_PRESSURE_PLATE": 2, + "OAK_SAPLING": 2, + "OAK_SIGN": 16, + "OAK_SLAB": 3, + "OAK_STAIRS": 8, + "OAK_TRAPDOOR": 16, + "OAK_WALL_SIGN": 4, + "OAK_WOOD": 6, + "OBSIDIAN": 1, + "ORANGE_TULIP": 1, + "OXEYE_DAISY": 1, + "PACKED_ICE": 1, + "PACKED_MUD": 1, + "PEONY": 2, + "PINK_PETALS": 16, + "PINK_TULIP": 1, + "PITCHER_CROP": 3, + "PITCHER_PLANT": 2, + "PODZOL": 1, + "POLISHED_ANDESITE": 1, + "POLISHED_ANDESITE_SLAB": 3, + "POLISHED_ANDESITE_STAIRS": 8, + "POLISHED_BASALT": 3, + "POLISHED_BLACKSTONE": 1, + "POLISHED_BLACKSTONE_BRICKS": 1, + "POLISHED_BLACKSTONE_BRICK_SLAB": 3, + "POLISHED_BLACKSTONE_BRICK_STAIRS": 8, + "POLISHED_BLACKSTONE_BRICK_WALL": 162, + "POLISHED_BLACKSTONE_BUTTON": 12, + "POLISHED_BLACKSTONE_PRESSURE_PLATE": 2, + "POLISHED_BLACKSTONE_SLAB": 3, + "POLISHED_BLACKSTONE_STAIRS": 8, + "POLISHED_BLACKSTONE_WALL": 162, + "POLISHED_DEEPSLATE": 1, + "POLISHED_DEEPSLATE_SLAB": 3, + "POLISHED_DEEPSLATE_STAIRS": 8, + "POLISHED_DEEPSLATE_WALL": 162, + "POLISHED_DIORITE": 1, + "POLISHED_DIORITE_SLAB": 3, + "POLISHED_DIORITE_STAIRS": 8, + "POLISHED_GRANITE": 1, + "POLISHED_GRANITE_SLAB": 3, + "POLISHED_GRANITE_STAIRS": 8, + "POLISHED_TUFF": 1, + "POLISHED_TUFF_SLAB": 3, + "POLISHED_TUFF_STAIRS": 8, + "POLISHED_TUFF_WALL": 162, + "POPPY": 1, + "POTATOES": 8, + "POTION_CAULDRON": 6, + "POWERED_RAIL": 12, + "PRISMARINE": 1, + "PRISMARINE_BRICKS": 1, + "PRISMARINE_BRICKS_SLAB": 3, + "PRISMARINE_BRICKS_STAIRS": 8, + "PRISMARINE_SLAB": 3, + "PRISMARINE_STAIRS": 8, + "PRISMARINE_WALL": 162, + "PUMPKIN": 1, + "PUMPKIN_STEM": 40, + "PURPLE_TORCH": 5, + "PURPUR": 1, + "PURPUR_PILLAR": 3, + "PURPUR_SLAB": 3, + "PURPUR_STAIRS": 8, + "QUARTZ": 1, + "QUARTZ_BRICKS": 1, + "QUARTZ_PILLAR": 3, + "QUARTZ_SLAB": 3, + "QUARTZ_STAIRS": 8, + "RAIL": 10, + "RAW_COPPER": 1, + "RAW_GOLD": 1, + "RAW_IRON": 1, + "REDSTONE": 1, + "REDSTONE_COMPARATOR": 16, + "REDSTONE_LAMP": 2, + "REDSTONE_ORE": 2, + "REDSTONE_REPEATER": 32, + "REDSTONE_TORCH": 10, + "REDSTONE_WIRE": 16, + "RED_MUSHROOM": 1, + "RED_MUSHROOM_BLOCK": 11, + "RED_NETHER_BRICKS": 1, + "RED_NETHER_BRICK_SLAB": 3, + "RED_NETHER_BRICK_STAIRS": 8, + "RED_NETHER_BRICK_WALL": 162, + "RED_SAND": 1, + "RED_SANDSTONE": 1, + "RED_SANDSTONE_SLAB": 3, + "RED_SANDSTONE_STAIRS": 8, + "RED_SANDSTONE_WALL": 162, + "RED_TORCH": 5, + "RED_TULIP": 1, + "REINFORCED_DEEPSLATE": 1, + "RESERVED6": 1, + "ROSE_BUSH": 2, + "SAND": 1, + "SANDSTONE": 1, + "SANDSTONE_SLAB": 3, + "SANDSTONE_STAIRS": 8, + "SANDSTONE_WALL": 162, + "SCULK": 1, + "SEA_LANTERN": 1, + "SEA_PICKLE": 8, + "SHROOMLIGHT": 1, + "SHULKER_BOX": 1, + "SLIME": 1, + "SMALL_DRIPLEAF": 8, + "SMITHING_TABLE": 1, + "SMOKER": 8, + "SMOOTH_BASALT": 1, + "SMOOTH_QUARTZ": 1, + "SMOOTH_QUARTZ_SLAB": 3, + "SMOOTH_QUARTZ_STAIRS": 8, + "SMOOTH_RED_SANDSTONE": 1, + "SMOOTH_RED_SANDSTONE_SLAB": 3, + "SMOOTH_RED_SANDSTONE_STAIRS": 8, + "SMOOTH_SANDSTONE": 1, + "SMOOTH_SANDSTONE_SLAB": 3, + "SMOOTH_SANDSTONE_STAIRS": 8, + "SMOOTH_STONE": 1, + "SMOOTH_STONE_SLAB": 3, + "SNOW": 1, + "SNOW_LAYER": 8, + "SOUL_CAMPFIRE": 8, + "SOUL_FIRE": 1, + "SOUL_LANTERN": 2, + "SOUL_SAND": 1, + "SOUL_SOIL": 1, + "SOUL_TORCH": 5, + "SPONGE": 2, + "SPORE_BLOSSOM": 1, + "SPRUCE_BUTTON": 12, + "SPRUCE_DOOR": 32, + "SPRUCE_FENCE": 1, + "SPRUCE_FENCE_GATE": 16, + "SPRUCE_LEAVES": 4, + "SPRUCE_LOG": 6, + "SPRUCE_PLANKS": 1, + "SPRUCE_PRESSURE_PLATE": 2, + "SPRUCE_SAPLING": 2, + "SPRUCE_SIGN": 16, + "SPRUCE_SLAB": 3, + "SPRUCE_STAIRS": 8, + "SPRUCE_TRAPDOOR": 16, + "SPRUCE_WALL_SIGN": 4, + "SPRUCE_WOOD": 6, + "STAINED_CLAY": 16, + "STAINED_GLASS": 16, + "STAINED_GLASS_PANE": 16, + "STAINED_HARDENED_GLASS": 16, + "STAINED_HARDENED_GLASS_PANE": 16, + "STONE": 1, + "STONECUTTER": 4, + "STONE_BRICKS": 1, + "STONE_BRICK_SLAB": 3, + "STONE_BRICK_STAIRS": 8, + "STONE_BRICK_WALL": 162, + "STONE_BUTTON": 12, + "STONE_PRESSURE_PLATE": 2, + "STONE_SLAB": 3, + "STONE_STAIRS": 8, + "SUGARCANE": 16, + "SUNFLOWER": 2, + "SWEET_BERRY_BUSH": 4, + "TALL_GRASS": 1, + "TINTED_GLASS": 1, + "TNT": 4, + "TORCH": 5, + "TORCHFLOWER": 1, + "TORCHFLOWER_CROP": 2, + "TRAPPED_CHEST": 4, + "TRIPWIRE": 16, + "TRIPWIRE_HOOK": 16, + "TUFF": 1, + "TUFF_BRICKS": 1, + "TUFF_BRICK_SLAB": 3, + "TUFF_BRICK_STAIRS": 8, + "TUFF_BRICK_WALL": 162, + "TUFF_SLAB": 3, + "TUFF_STAIRS": 8, + "TUFF_WALL": 162, + "TWISTING_VINES": 26, + "UNDERWATER_TORCH": 5, + "VINES": 16, + "WALL_BANNER": 64, + "WALL_CORAL_FAN": 40, + "WARPED_BUTTON": 12, + "WARPED_DOOR": 32, + "WARPED_FENCE": 1, + "WARPED_FENCE_GATE": 16, + "WARPED_HYPHAE": 6, + "WARPED_PLANKS": 1, + "WARPED_PRESSURE_PLATE": 2, + "WARPED_ROOTS": 1, + "WARPED_SIGN": 16, + "WARPED_SLAB": 3, + "WARPED_STAIRS": 8, + "WARPED_STEM": 6, + "WARPED_TRAPDOOR": 16, + "WARPED_WALL_SIGN": 4, + "WARPED_WART_BLOCK": 1, + "WATER": 32, + "WATER_CAULDRON": 6, + "WEEPING_VINES": 26, + "WEIGHTED_PRESSURE_PLATE_HEAVY": 16, + "WEIGHTED_PRESSURE_PLATE_LIGHT": 16, + "WHEAT": 8, + "WHITE_TULIP": 1, + "WITHER_ROSE": 1, + "WOOL": 16 + }, + "tiles": { + "ACACIA_SIGN": "pocketmine\\block\\tile\\Sign", + "ACACIA_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "BANNER": "pocketmine\\block\\tile\\Banner", + "BARREL": "pocketmine\\block\\tile\\Barrel", + "BEACON": "pocketmine\\block\\tile\\Beacon", + "BED": "pocketmine\\block\\tile\\Bed", + "BELL": "pocketmine\\block\\tile\\Bell", + "BIRCH_SIGN": "pocketmine\\block\\tile\\Sign", + "BIRCH_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "BLAST_FURNACE": "pocketmine\\block\\tile\\BlastFurnace", + "BREWING_STAND": "pocketmine\\block\\tile\\BrewingStand", + "CAMPFIRE": "pocketmine\\block\\tile\\Campfire", + "CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "CHERRY_SIGN": "pocketmine\\block\\tile\\Sign", + "CHERRY_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "CHEST": "pocketmine\\block\\tile\\Chest", + "CHISELED_BOOKSHELF": "pocketmine\\block\\tile\\ChiseledBookshelf", + "CRIMSON_SIGN": "pocketmine\\block\\tile\\Sign", + "CRIMSON_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "DARK_OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "DARK_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "DAYLIGHT_SENSOR": "pocketmine\\block\\tile\\DaylightSensor", + "DYED_SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", + "ENCHANTING_TABLE": "pocketmine\\block\\tile\\EnchantTable", + "ENDER_CHEST": "pocketmine\\block\\tile\\EnderChest", + "FLOWER_POT": "pocketmine\\block\\tile\\FlowerPot", + "FURNACE": "pocketmine\\block\\tile\\NormalFurnace", + "GLOWING_ITEM_FRAME": "pocketmine\\block\\tile\\GlowingItemFrame", + "HOPPER": "pocketmine\\block\\tile\\Hopper", + "ITEM_FRAME": "pocketmine\\block\\tile\\ItemFrame", + "JUKEBOX": "pocketmine\\block\\tile\\Jukebox", + "JUNGLE_SIGN": "pocketmine\\block\\tile\\Sign", + "JUNGLE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "LAVA_CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "LECTERN": "pocketmine\\block\\tile\\Lectern", + "MANGROVE_SIGN": "pocketmine\\block\\tile\\Sign", + "MANGROVE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "MOB_HEAD": "pocketmine\\block\\tile\\MobHead", + "MONSTER_SPAWNER": "pocketmine\\block\\tile\\MonsterSpawner", + "NOTE_BLOCK": "pocketmine\\block\\tile\\Note", + "OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "REDSTONE_COMPARATOR": "pocketmine\\block\\tile\\Comparator", + "SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", + "SMOKER": "pocketmine\\block\\tile\\Smoker", + "SOUL_CAMPFIRE": "pocketmine\\block\\tile\\Campfire", + "SPRUCE_SIGN": "pocketmine\\block\\tile\\Sign", + "SPRUCE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "TRAPPED_CHEST": "pocketmine\\block\\tile\\Chest", + "WALL_BANNER": "pocketmine\\block\\tile\\Banner", + "WARPED_SIGN": "pocketmine\\block\\tile\\Sign", + "WARPED_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "WATER_CAULDRON": "pocketmine\\block\\tile\\Cauldron" + } } \ No newline at end of file diff --git a/tests/phpunit/block/regenerate_consistency_check.php b/tests/phpunit/block/regenerate_consistency_check.php index e86f70d70..eb4ccf6c8 100644 --- a/tests/phpunit/block/regenerate_consistency_check.php +++ b/tests/phpunit/block/regenerate_consistency_check.php @@ -28,11 +28,11 @@ require dirname(__DIR__, 3) . '/vendor/autoload.php'; /* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */ -$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance()); +[$newTable, $newTiles] = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance()); $oldTablePath = __DIR__ . '/block_factory_consistency_check.json'; if(file_exists($oldTablePath)){ - $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable); + $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable, $newTiles); if(count($errors) > 0){ echo count($errors) . " changes detected:\n"; @@ -47,5 +47,6 @@ if(file_exists($oldTablePath)){ } ksort($newTable, SORT_STRING); +ksort($newTiles, SORT_STRING); -file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); +file_put_contents($oldTablePath, json_encode(["stateCounts" => $newTable, "tiles" => $newTiles], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); From 6b2da15b80f1e70130aef471fdd82ff3ba259303 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 5 Dec 2024 19:58:52 +0000 Subject: [PATCH 016/334] Fixed signs --- src/block/VanillaBlocks.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 54cf90a0c..9d7ed91f4 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -54,6 +54,7 @@ use pocketmine\block\tile\MonsterSpawner as TileMonsterSpawner; use pocketmine\block\tile\NormalFurnace as TileNormalFurnace; use pocketmine\block\tile\Note as TileNote; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; +use pocketmine\block\tile\Sign as TileSign; use pocketmine\block\tile\Smoker as TileSmoker; use pocketmine\block\tile\Tile; use pocketmine\block\utils\AmethystTrait; @@ -1359,8 +1360,8 @@ final class VanillaBlocks{ WoodType::WARPED => VanillaItems::WARPED_SIGN(...), WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...), }; - self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem)); - self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem)); + self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); + self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); } } From 2b0daebc2a72784d6ea1744d6b0b91e7ab66baba Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 5 Dec 2024 20:04:43 +0000 Subject: [PATCH 017/334] 5.23.1 (#6562) --- changelogs/5.23.md | 9 +++++++++ src/VersionInfo.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/changelogs/5.23.md b/changelogs/5.23.md index f5b08593d..7f40b40af 100644 --- a/changelogs/5.23.md +++ b/changelogs/5.23.md @@ -105,3 +105,12 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies. - Fixed various deprecation warnings in PHP 8.4. - `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed. + +# 5.23.1 +Released 5th December 2024. + +## Fixes +- Fixed signs not creating a tile when placed. + +## Internals +- Improved blockstate consistency check to detect tiles disappearing during refactors. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index d983060f4..59c6919bb 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.23.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 8efdf501adcacdf34046682285e45eb1c1e7e770 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:05:36 +0000 Subject: [PATCH 018/334] 5.23.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12187209543 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 59c6919bb..1eca900cf 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.23.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.23.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 1481977f35b69b5b77551107584dc6503e1e5286 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 5 Dec 2024 20:47:46 +0000 Subject: [PATCH 019/334] Create pr-stale.yml --- .github/workflows/pr-stale.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/pr-stale.yml diff --git a/.github/workflows/pr-stale.yml b/.github/workflows/pr-stale.yml new file mode 100644 index 000000000..23518b2cf --- /dev/null +++ b/.github/workflows/pr-stale.yml @@ -0,0 +1,29 @@ +name: 'Clean up stale PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + days-before-issue-stale: -1 + days-before-issue-close: -1 + stale-pr-message: | + This PR has been marked as "Waiting on Author", but we haven't seen any activity in 7 days. + + If there is no further activity, it will be closed in 28 days. + + Note for maintainers: Adding an assignee to the PR will prevent it from being marked as stale. + + close-pr-message: | + As this PR hasn't been updated for a while, unfortunately we'll have to close it. + + days-before-pr-stale: 7 + days-before-pr-close: 28 + only-labels: "Status: Waiting on Author" + close-pr-label: "Resolution: Abandoned" + exempt-all-assignees: true + From 45917d495c14f3ce37bbf0848262ad8a470030f3 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 8 Dec 2024 16:52:33 +0000 Subject: [PATCH 020/334] Fixed CrashDump incorrectly detecting phar core crashes as plugin crashes (#6564) fixes #6563 Since #6217 was merged, \pocketmine\PATH no longer includes the path of the original phar. This means that the frame originating from the phar stub would not get its path cleaned up, leading to it being incorrectly detected as a plugin frame. We should probably explore better methods of detecting plugin crashes in the future; however this fix should solve the immediate issue. --- build/server-phar-stub.php | 2 ++ src/PocketMine.php | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/build/server-phar-stub.php b/build/server-phar-stub.php index b4018e3a7..c713636c4 100644 --- a/build/server-phar-stub.php +++ b/build/server-phar-stub.php @@ -25,6 +25,7 @@ namespace pocketmine\server_phar_stub; use function clearstatcache; use function copy; +use function define; use function fclose; use function fflush; use function flock; @@ -165,4 +166,5 @@ $start = hrtime(true); $cacheName = preparePharCache($tmpDir, __FILE__); echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n"; +define('pocketmine\ORIGINAL_PHAR_PATH', __FILE__); require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php'; diff --git a/src/PocketMine.php b/src/PocketMine.php index b2e1cd046..ffcfd91db 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -282,6 +282,11 @@ JIT_WARNING exit(0); } + if(defined('pocketmine\ORIGINAL_PHAR_PATH')){ + //if we're inside a phar cache, \pocketmine\PATH will not include the original phar + Filesystem::addCleanedPath(ORIGINAL_PHAR_PATH, Filesystem::CLEAN_PATH_SRC_PREFIX); + } + $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd()))); $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd; $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins"; From fe7c282052af55f4ed23d9e6629e1e7c0a501121 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:03:09 +0000 Subject: [PATCH 021/334] Bump pocketmine/locale-data in the production-patch-updates group (#6568) --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 1cf141588..54f65014f 100644 --- a/composer.lock +++ b/composer.lock @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.22.0", + "version": "2.22.1", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d" + "reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d", - "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d", + "url": "https://api.github.com/repos/pmmp/Language/zipball/fa4e377c437391cfcfdedd08eea3a848eabd1b49", + "reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.22.0" + "source": "https://github.com/pmmp/Language/tree/2.22.1" }, - "time": "2024-11-16T13:28:01+00:00" + "time": "2024-12-06T14:44:17+00:00" }, { "name": "pocketmine/log", From a8eaa43bc8beaf273ce806ff314d1eecf1545fef Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 9 Dec 2024 16:36:26 +0000 Subject: [PATCH 022/334] Recombine release workflows having two different workflows able to trigger releases is a pain for build number continuity. perhaps longer term we should source the build number a different way, but these workflows needed restructuring anyway. --- .github/workflows/draft-release-from-pr.yml | 65 -------------- .github/workflows/draft-release-from-tag.yml | 13 --- .github/workflows/draft-release.yml | 94 +++++++++++++++----- 3 files changed, 70 insertions(+), 102 deletions(-) delete mode 100644 .github/workflows/draft-release-from-pr.yml delete mode 100644 .github/workflows/draft-release-from-tag.yml diff --git a/.github/workflows/draft-release-from-pr.yml b/.github/workflows/draft-release-from-pr.yml deleted file mode 100644 index 8a347853b..000000000 --- a/.github/workflows/draft-release-from-pr.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Draft release from PR - -on: - #presume that pull_request_target is safe at this point, since the PR was approved and merged - #we need write access to prepare the release & create comments - pull_request_target: - types: - - closed - branches: - - stable - - minor-next - - major-next - - "legacy/*" - paths: - - "src/VersionInfo.php" - -jobs: - check: - name: Check release - uses: ./.github/workflows/draft-release-pr-check.yml - - draft: - name: Create GitHub draft release - needs: [check] - if: needs.check.outputs.valid == 'true' - - uses: ./.github/workflows/draft-release.yml - - post-draft-url-comment: - name: Post draft release URL as comment - needs: [draft] - - runs-on: ubuntu-20.04 - - steps: - - name: Post draft release URL on PR - uses: thollander/actions-comment-pull-request@v3 - with: - message: "[Draft release ${{ needs.draft.outputs.version }}](${{ needs.draft.outputs.draft-url }}) has been created for commit ${{ github.sha }}. Please review and publish it." - - trigger-post-release-workflow: - name: Trigger post-release RestrictedActions workflow - # Not sure if needs is actually needed here - needs: [check] - if: needs.check.outputs.valid == 'true' - - runs-on: ubuntu-20.04 - - steps: - - name: Generate access token - id: generate-token - uses: actions/create-github-app-token@v1 - with: - app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} - private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} - owner: ${{ github.repository_owner }} - repositories: RestrictedActions - - - name: Dispatch post-release restricted action - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ steps.generate-token.outputs.token }} - repository: ${{ github.repository_owner }}/RestrictedActions - event-type: pocketmine_mp_post_release - client-payload: '{"branch": "${{ github.ref }}"}' diff --git a/.github/workflows/draft-release-from-tag.yml b/.github/workflows/draft-release-from-tag.yml deleted file mode 100644 index f7a5df544..000000000 --- a/.github/workflows/draft-release-from-tag.yml +++ /dev/null @@ -1,13 +0,0 @@ -#Allows creating a release by pushing a tag -#This might be useful for retroactive releases -name: Draft release from git tag - -on: - push: - tags: "*" - -jobs: - draft: - name: Create GitHub draft release - if: "startsWith(github.event.head_commit.message, 'Release ')" - uses: ./.github/workflows/draft-release.yml diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index cd1841e4f..3374ff68f 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -1,28 +1,61 @@ name: Draft release on: - workflow_call: - outputs: - draft-url: - description: 'The URL of the draft release' - value: ${{ jobs.draft.outputs.draft-url }} - version: - description: 'PocketMine-MP version' - value: ${{ jobs.draft.outputs.version }} + #presume that pull_request_target is safe at this point, since the PR was approved and merged + #we need write access to prepare the release & create comments + pull_request_target: + types: + - closed + branches: + - stable + - minor-next + - major-next + - "legacy/*" + paths: + - "src/VersionInfo.php" + push: + tags: + - "*" + +env: + PHP_VERSION: "8.2" jobs: - draft: - name: Create GitHub draft release + check: + name: Check release + uses: ./.github/workflows/draft-release-pr-check.yml + + trigger-post-release-workflow: + name: Trigger post-release RestrictedActions workflow + needs: [check] + if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - php-version: [8.2] - outputs: - draft-url: ${{ steps.create-draft.outputs.html_url }} - version: ${{ steps.get-pm-version.outputs.PM_VERSION }} + steps: + - name: Generate access token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} + private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} + owner: ${{ github.repository_owner }} + repositories: RestrictedActions + + - name: Dispatch post-release restricted action + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.repository_owner }}/RestrictedActions + event-type: pocketmine_mp_post_release + client-payload: '{"branch": "${{ github.ref }}"}' + + draft: + name: Create GitHub draft release + needs: [check] + if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags + + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 @@ -32,7 +65,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@2.31.1 with: - php-version: ${{ matrix.php-version }} + php-version: ${{ env.PHP_VERSION }} - name: Restore Composer package cache uses: actions/cache@v4 @@ -50,7 +83,7 @@ jobs: - name: Calculate build number id: build-number run: | - BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins + BUILD_NUMBER=$((2300+$GITHUB_RUN_NUMBER)) #to stay above jenkins echo "Build number: $BUILD_NUMBER" echo BUILD_NUMBER=$BUILD_NUMBER >> $GITHUB_OUTPUT @@ -63,23 +96,31 @@ jobs: - name: Get PocketMine-MP release version id: get-pm-version run: | - echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT + PM_VERSION=$(php build/dump-version-info.php base_version) + echo PM_VERSION=$PM_VERSION >> $GITHUB_OUTPUT echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT echo PRERELEASE=$(php build/dump-version-info.php prerelease) >> $GITHUB_OUTPUT + if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)" + else + tag="$PM_VERSION" + fi + echo TAG_NAME=$tag >> $GITHUB_OUTPUT + - name: Generate PHP binary download URL id: php-binary-url run: | - echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT + echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ env.PHP_VERSION }}-latest" >> $GITHUB_OUTPUT - name: Generate build info run: | php build/generate-build-info-json.php \ ${{ github.sha }} \ - ${{ steps.get-pm-version.outputs.PM_VERSION }} \ + ${{ steps.get-pm-version.outputs.TAG_NAME }} \ ${{ github.repository }} \ ${{ steps.build-number.outputs.BUILD_NUMBER }} \ ${{ github.run_id }} \ @@ -108,12 +149,17 @@ jobs: draft: true prerelease: ${{ steps.get-pm-version.outputs.PRERELEASE }} name: PocketMine-MP ${{ steps.get-pm-version.outputs.PM_VERSION }} - tag: ${{ steps.get-pm-version.outputs.PM_VERSION }} + tag: ${{ steps.get-pm-version.outputs.TAG_NAME }} token: ${{ secrets.GITHUB_TOKEN }} - skipIfReleaseExists: true #for release PRs, tags will be created on release publish and trigger the tag release workflow - don't create a second draft body: | **For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}** Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.CHANGELOG_FILE_NAME }}#${{ steps.get-pm-version.outputs.CHANGELOG_MD_HEADER }}) for details. :information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}). + + - name: Post draft release URL on PR + if: github.event_name == 'pull_request_target' + uses: thollander/actions-comment-pull-request@v3 + with: + message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it." From ad6d34f1a61b8392e5541c94e3a35413c68aeb2e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 9 Dec 2024 16:44:07 +0000 Subject: [PATCH 023/334] Remove legacy make-release script we no longer use this release workflow, all releases should now be done via pull request --- build/make-release.php | 174 --------------------- tests/phpstan/configs/actual-problems.neon | 5 - 2 files changed, 179 deletions(-) delete mode 100644 build/make-release.php diff --git a/build/make-release.php b/build/make-release.php deleted file mode 100644 index 741f9d787..000000000 --- a/build/make-release.php +++ /dev/null @@ -1,174 +0,0 @@ - "Version to insert and tag", - "next" => "Version to put in the file after tagging", - "channel" => "Release channel to post this build into" -]; - -function systemWrapper(string $command, string $errorMessage) : void{ - system($command, $result); - if($result !== 0){ - echo "error: $errorMessage; aborting\n"; - exit(1); - } -} - -function main() : void{ - $filteredOpts = []; - $postCommitOnly = false; - foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help", "post"])) as $optName => $optValue){ - if($optName === "help"){ - fwrite(STDOUT, "Options:\n"); - - $maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS))); - foreach(ACCEPTED_OPTS as $acceptedName => $description){ - fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n"); - } - exit(0); - } - if($optName === "post"){ - $postCommitOnly = true; - continue; - } - if(!is_string($optValue)){ - fwrite(STDERR, "--$optName expects exactly 1 value\n"); - exit(1); - } - $filteredOpts[$optName] = $optValue; - } - - $channel = $filteredOpts["channel"] ?? null; - if(isset($filteredOpts["current"])){ - $currentVer = new VersionString($filteredOpts["current"]); - }else{ - $currentVer = new VersionString(VersionInfo::BASE_VERSION); - } - - $nextVer = isset($filteredOpts["next"]) ? new VersionString($filteredOpts["next"]) : null; - - $suffix = $currentVer->getSuffix(); - if($suffix !== ""){ - if($channel === "stable"){ - fwrite(STDERR, "error: cannot release a suffixed build into the stable channel\n"); - exit(1); - } - if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){ - echo "error: invalid current version suffix \"$suffix\"; aborting\n"; - exit(1); - } - $nextVer ??= new VersionString(sprintf( - "%u.%u.%u-%s%u", - $currentVer->getMajor(), - $currentVer->getMinor(), - $currentVer->getPatch(), - $matches[1], - ((int) $matches[2]) + 1 - )); - $channel ??= strtolower($matches[1]); - }else{ - $nextVer ??= new VersionString(sprintf( - "%u.%u.%u", - $currentVer->getMajor(), - $currentVer->getMinor(), - $currentVer->getPatch() + 1 - )); - $channel ??= "stable"; - } - - $versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php'; - - if($postCommitOnly){ - echo "Skipping release commit & tag. Bumping to next version $nextVer directly.\n"; - }else{ - echo "About to tag version $currentVer. Next version will be $nextVer.\n"; - echo "$currentVer will be published on release channel \"$channel\".\n"; - echo "please add appropriate notes to the changelog and press enter..."; - fgets(STDIN); - systemWrapper('git add "' . dirname(__DIR__) . '/changelogs"', "failed to stage changelog changes"); - system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result); - if($result === 0){ - echo "error: no changelog changes detected; aborting\n"; - exit(1); - } - replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel); - systemWrapper('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"', "failed to create release commit"); - systemWrapper('git tag ' . $currentVer->getBaseVersion(), "failed to create release tag"); - } - - replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel); - systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit"); - systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit"); -} - -main(); diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index f15dc9d53..b071d2a7e 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" - count: 2 - path: ../../../build/make-release.php - - message: "#^Parameter \\#1 \\$strings of function pocketmine\\\\build\\\\server_phar\\\\preg_quote_array expects array\\, array\\ given\\.$#" count: 1 From bba525da02e9c0d8878927578ae4a25ec9984934 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 9 Dec 2024 16:44:25 +0000 Subject: [PATCH 024/334] Remove dead PHPStan ignored errors --- tests/phpstan/configs/actual-problems.neon | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index b071d2a7e..1bb9329f0 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -455,26 +455,6 @@ parameters: count: 1 path: ../../../src/command/defaults/TimeCommand.php - - - message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#" - count: 2 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$stream of function fseek expects resource, resource\\|false given\\.$#" - count: 1 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$stream of function fwrite expects resource, resource\\|false given\\.$#" - count: 1 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$stream of function stream_get_contents expects resource, resource\\|false given\\.$#" - count: 1 - path: ../../../src/command/defaults/TimingsCommand.php - - message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#" count: 1 From 6f197bc1bb90c248a4361669a02f11f3ee4fa86f Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 9 Dec 2024 16:51:41 +0000 Subject: [PATCH 025/334] 5.23.2 (#6569) --- changelogs/5.23.md | 13 +++++++++++++ src/VersionInfo.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelogs/5.23.md b/changelogs/5.23.md index 7f40b40af..6e72e9403 100644 --- a/changelogs/5.23.md +++ b/changelogs/5.23.md @@ -114,3 +114,16 @@ Released 5th December 2024. ## Internals - Improved blockstate consistency check to detect tiles disappearing during refactors. + +# 5.23.2 +Released 9th December 2024. + +## General +- Updated translations for Russian and Korean. + +## Fixes +- Fixed server build number. +- Fixed some crashes being misreported as plugin-involved. + +## Internals +- Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 1eca900cf..ba46bd63b 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.23.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 67b9d6222d17e77c8000a52bdfe61de7e51ebd5c Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:52:50 +0000 Subject: [PATCH 026/334] 5.23.3 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12240364052 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index ba46bd63b..48ce7dc9e 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.23.2"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.23.3"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 68172156833e2b969dd7917c438902f2f8bc50f0 Mon Sep 17 00:00:00 2001 From: Maxence <40174895+roimee6@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:40:03 +0100 Subject: [PATCH 027/334] TextFormat: Added new material colours for armor trims (#5838) Unfortunately, these new formatting codes conflict with the Java strikethrough and underline, so we can't support these anymore. A TextFormat::javaToBedrock() is provided to strip these codes, or (if these formats become supported via different codes) to convert them to Bedrock variants. Co-authored-by: Dylan T. --- src/utils/Terminal.php | 60 ++++++++++++++++++++----- src/utils/TextFormat.php | 96 +++++++++++++++++++++++++++++++++------- 2 files changed, 130 insertions(+), 26 deletions(-) diff --git a/src/utils/Terminal.php b/src/utils/Terminal.php index 49b4224ec..2abdbc357 100644 --- a/src/utils/Terminal.php +++ b/src/utils/Terminal.php @@ -59,6 +59,16 @@ abstract class Terminal{ public static string $COLOR_YELLOW = ""; public static string $COLOR_WHITE = ""; public static string $COLOR_MINECOIN_GOLD = ""; + public static string $COLOR_MATERIAL_QUARTZ = ""; + public static string $COLOR_MATERIAL_IRON = ""; + public static string $COLOR_MATERIAL_NETHERITE = ""; + public static string $COLOR_MATERIAL_REDSTONE = ""; + public static string $COLOR_MATERIAL_COPPER = ""; + public static string $COLOR_MATERIAL_GOLD = ""; + public static string $COLOR_MATERIAL_EMERALD = ""; + public static string $COLOR_MATERIAL_DIAMOND = ""; + public static string $COLOR_MATERIAL_LAPIS = ""; + public static string $COLOR_MATERIAL_AMETHYST = ""; private static ?bool $formattingCodes = null; @@ -111,6 +121,16 @@ abstract class Terminal{ self::$COLOR_YELLOW = $color(227); self::$COLOR_WHITE = $color(231); self::$COLOR_MINECOIN_GOLD = $color(184); + self::$COLOR_MATERIAL_QUARTZ = $color(188); + self::$COLOR_MATERIAL_IRON = $color(251); + self::$COLOR_MATERIAL_NETHERITE = $color(237); + self::$COLOR_MATERIAL_REDSTONE = $color(88); + self::$COLOR_MATERIAL_COPPER = $color(131); + self::$COLOR_MATERIAL_GOLD = $color(178); + self::$COLOR_MATERIAL_EMERALD = $color(35); + self::$COLOR_MATERIAL_DIAMOND = $color(37); + self::$COLOR_MATERIAL_LAPIS = $color(24); + self::$COLOR_MATERIAL_AMETHYST = $color(98); } protected static function getEscapeCodes() : void{ @@ -144,15 +164,25 @@ abstract class Terminal{ self::$COLOR_YELLOW = $colors >= 256 ? $setaf(227) : $setaf(11); self::$COLOR_WHITE = $colors >= 256 ? $setaf(231) : $setaf(15); self::$COLOR_MINECOIN_GOLD = $colors >= 256 ? $setaf(184) : $setaf(11); + self::$COLOR_MATERIAL_QUARTZ = $colors >= 256 ? $setaf(188) : $setaf(7); + self::$COLOR_MATERIAL_IRON = $colors >= 256 ? $setaf(251) : $setaf(7); + self::$COLOR_MATERIAL_NETHERITE = $colors >= 256 ? $setaf(237) : $setaf(1); + self::$COLOR_MATERIAL_REDSTONE = $colors >= 256 ? $setaf(88) : $setaf(9); + self::$COLOR_MATERIAL_COPPER = $colors >= 256 ? $setaf(131) : $setaf(3); + self::$COLOR_MATERIAL_GOLD = $colors >= 256 ? $setaf(178) : $setaf(11); + self::$COLOR_MATERIAL_EMERALD = $colors >= 256 ? $setaf(35) : $setaf(2); + self::$COLOR_MATERIAL_DIAMOND = $colors >= 256 ? $setaf(37) : $setaf(14); + self::$COLOR_MATERIAL_LAPIS = $colors >= 256 ? $setaf(24) : $setaf(12); + self::$COLOR_MATERIAL_AMETHYST = $colors >= 256 ? $setaf(98) : $setaf(13); }else{ - self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = $setaf(0); - self::$COLOR_RED = self::$COLOR_DARK_RED = $setaf(1); - self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = $setaf(2); - self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = $setaf(3); - self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = $setaf(4); - self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = $setaf(5); - self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = $setaf(6); - self::$COLOR_GRAY = self::$COLOR_WHITE = $setaf(7); + self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = self::$COLOR_MATERIAL_NETHERITE = $setaf(0); + self::$COLOR_RED = self::$COLOR_DARK_RED = self::$COLOR_MATERIAL_REDSTONE = self::$COLOR_MATERIAL_COPPER = $setaf(1); + self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = self::$COLOR_MATERIAL_EMERALD = $setaf(2); + self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = $setaf(3); + self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = self::$COLOR_MATERIAL_LAPIS = $setaf(4); + self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = self::$COLOR_MATERIAL_AMETHYST = $setaf(5); + self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = self::$COLOR_MATERIAL_DIAMOND = $setaf(6); + self::$COLOR_GRAY = self::$COLOR_WHITE = self::$COLOR_MATERIAL_QUARTZ = self::$COLOR_MATERIAL_IRON = $setaf(7); } } @@ -191,12 +221,10 @@ abstract class Terminal{ public static function toANSI(string $string) : string{ $newString = ""; foreach(TextFormat::tokenize($string) as $token){ - $newString .= match($token){ + $newString .= match ($token) { TextFormat::BOLD => Terminal::$FORMAT_BOLD, TextFormat::OBFUSCATED => Terminal::$FORMAT_OBFUSCATED, TextFormat::ITALIC => Terminal::$FORMAT_ITALIC, - TextFormat::UNDERLINE => Terminal::$FORMAT_UNDERLINE, - TextFormat::STRIKETHROUGH => Terminal::$FORMAT_STRIKETHROUGH, TextFormat::RESET => Terminal::$FORMAT_RESET, TextFormat::BLACK => Terminal::$COLOR_BLACK, TextFormat::DARK_BLUE => Terminal::$COLOR_DARK_BLUE, @@ -215,6 +243,16 @@ abstract class Terminal{ TextFormat::YELLOW => Terminal::$COLOR_YELLOW, TextFormat::WHITE => Terminal::$COLOR_WHITE, TextFormat::MINECOIN_GOLD => Terminal::$COLOR_MINECOIN_GOLD, + TextFormat::MATERIAL_QUARTZ => Terminal::$COLOR_MATERIAL_QUARTZ, + TextFormat::MATERIAL_IRON => Terminal::$COLOR_MATERIAL_IRON, + TextFormat::MATERIAL_NETHERITE => Terminal::$COLOR_MATERIAL_NETHERITE, + TextFormat::MATERIAL_REDSTONE => Terminal::$COLOR_MATERIAL_REDSTONE, + TextFormat::MATERIAL_COPPER => Terminal::$COLOR_MATERIAL_COPPER, + TextFormat::MATERIAL_GOLD => Terminal::$COLOR_MATERIAL_GOLD, + TextFormat::MATERIAL_EMERALD => Terminal::$COLOR_MATERIAL_EMERALD, + TextFormat::MATERIAL_DIAMOND => Terminal::$COLOR_MATERIAL_DIAMOND, + TextFormat::MATERIAL_LAPIS => Terminal::$COLOR_MATERIAL_LAPIS, + TextFormat::MATERIAL_AMETHYST => Terminal::$COLOR_MATERIAL_AMETHYST, default => $token, }; } diff --git a/src/utils/TextFormat.php b/src/utils/TextFormat.php index dfd6a359a..56aca3f8a 100644 --- a/src/utils/TextFormat.php +++ b/src/utils/TextFormat.php @@ -63,6 +63,16 @@ abstract class TextFormat{ public const YELLOW = TextFormat::ESCAPE . "e"; public const WHITE = TextFormat::ESCAPE . "f"; public const MINECOIN_GOLD = TextFormat::ESCAPE . "g"; + public const MATERIAL_QUARTZ = TextFormat::ESCAPE . "h"; + public const MATERIAL_IRON = TextFormat::ESCAPE . "i"; + public const MATERIAL_NETHERITE = TextFormat::ESCAPE . "j"; + public const MATERIAL_REDSTONE = TextFormat::ESCAPE . "m"; + public const MATERIAL_COPPER = TextFormat::ESCAPE . "n"; + public const MATERIAL_GOLD = TextFormat::ESCAPE . "p"; + public const MATERIAL_EMERALD = TextFormat::ESCAPE . "q"; + public const MATERIAL_DIAMOND = TextFormat::ESCAPE . "s"; + public const MATERIAL_LAPIS = TextFormat::ESCAPE . "t"; + public const MATERIAL_AMETHYST = TextFormat::ESCAPE . "u"; public const COLORS = [ self::BLACK => self::BLACK, @@ -82,19 +92,29 @@ abstract class TextFormat{ self::YELLOW => self::YELLOW, self::WHITE => self::WHITE, self::MINECOIN_GOLD => self::MINECOIN_GOLD, + self::MATERIAL_QUARTZ => self::MATERIAL_QUARTZ, + self::MATERIAL_IRON => self::MATERIAL_IRON, + self::MATERIAL_NETHERITE => self::MATERIAL_NETHERITE, + self::MATERIAL_REDSTONE => self::MATERIAL_REDSTONE, + self::MATERIAL_COPPER => self::MATERIAL_COPPER, + self::MATERIAL_GOLD => self::MATERIAL_GOLD, + self::MATERIAL_EMERALD => self::MATERIAL_EMERALD, + self::MATERIAL_DIAMOND => self::MATERIAL_DIAMOND, + self::MATERIAL_LAPIS => self::MATERIAL_LAPIS, + self::MATERIAL_AMETHYST => self::MATERIAL_AMETHYST, ]; public const OBFUSCATED = TextFormat::ESCAPE . "k"; public const BOLD = TextFormat::ESCAPE . "l"; - public const STRIKETHROUGH = TextFormat::ESCAPE . "m"; - public const UNDERLINE = TextFormat::ESCAPE . "n"; + /** @deprecated */ + public const STRIKETHROUGH = ""; + /** @deprecated */ + public const UNDERLINE = ""; public const ITALIC = TextFormat::ESCAPE . "o"; public const FORMATS = [ self::OBFUSCATED => self::OBFUSCATED, self::BOLD => self::BOLD, - self::STRIKETHROUGH => self::STRIKETHROUGH, - self::UNDERLINE => self::UNDERLINE, self::ITALIC => self::ITALIC, ]; @@ -130,7 +150,7 @@ abstract class TextFormat{ * @return string[] */ public static function tokenize(string $string) : array{ - $result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-gk-or])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-u])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); if($result === false) throw self::makePcreError(); return $result; } @@ -144,7 +164,7 @@ abstract class TextFormat{ $string = mb_scrub($string, 'UTF-8'); $string = self::preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console) if($removeFormat){ - $string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-gk-or]/u", "", $string)); + $string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-u]/u", "", $string)); } return str_replace("\x1b", "", self::preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string)); } @@ -155,7 +175,7 @@ abstract class TextFormat{ * @param string $placeholder default "&" */ public static function colorize(string $string, string $placeholder = "&") : string{ - return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-gk-or])/u', TextFormat::ESCAPE . '$1', $string); + return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-u])/u', TextFormat::ESCAPE . '$1', $string); } /** @@ -183,6 +203,20 @@ abstract class TextFormat{ return $baseFormat . str_replace(TextFormat::RESET, $baseFormat, $string); } + /** + * Converts any Java formatting codes in the given string to Bedrock. + * + * As of 1.21.50, strikethrough (§m) and underline (§n) are not supported by Bedrock, and these symbols are instead + * used to represent additional colours in Bedrock. To avoid unintended formatting, this function currently strips + * those formatting codes to prevent unintended colour display in formatted text. + * + * If Bedrock starts to support these formats in the future, this function will be updated to translate them rather + * than removing them. + */ + public static function javaToBedrock(string $string) : string{ + return str_replace([TextFormat::ESCAPE . "m", TextFormat::ESCAPE . "n"], "", $string); + } + /** * Returns an HTML-formatted string with colors/markup */ @@ -203,14 +237,6 @@ abstract class TextFormat{ $newString .= ""; ++$tokens; break; - case TextFormat::UNDERLINE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::STRIKETHROUGH: - $newString .= ""; - ++$tokens; - break; case TextFormat::RESET: $newString .= str_repeat("", $tokens); $tokens = 0; @@ -285,6 +311,46 @@ abstract class TextFormat{ $newString .= ""; ++$tokens; break; + case TextFormat::MATERIAL_QUARTZ: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_IRON: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_NETHERITE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_REDSTONE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_COPPER: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_GOLD: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_EMERALD: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_DIAMOND: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_LAPIS: + $newString .= ""; + ++$tokens; + break; + case TextFormat::MATERIAL_AMETHYST: + $newString .= ""; + ++$tokens; + break; default: $newString .= $token; break; From ba93665fe797f7376b8b556c3387025b48299bc1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 10 Dec 2024 14:11:11 +0000 Subject: [PATCH 028/334] TextFormat: reduce hella duplicated code in toHTML() --- src/utils/TextFormat.php | 170 +++++++++------------------------------ 1 file changed, 40 insertions(+), 130 deletions(-) diff --git a/src/utils/TextFormat.php b/src/utils/TextFormat.php index 56aca3f8a..fadf6ea4e 100644 --- a/src/utils/TextFormat.php +++ b/src/utils/TextFormat.php @@ -224,136 +224,46 @@ abstract class TextFormat{ $newString = ""; $tokens = 0; foreach(self::tokenize($string) as $token){ - switch($token){ - case TextFormat::BOLD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::OBFUSCATED: - //$newString .= ""; - //++$tokens; - break; - case TextFormat::ITALIC: - $newString .= ""; - ++$tokens; - break; - case TextFormat::RESET: - $newString .= str_repeat("", $tokens); - $tokens = 0; - break; - - //Colors - case TextFormat::BLACK: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_BLUE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_GREEN: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_AQUA: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_RED: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_PURPLE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::GOLD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::GRAY: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_GRAY: - $newString .= ""; - ++$tokens; - break; - case TextFormat::BLUE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::GREEN: - $newString .= ""; - ++$tokens; - break; - case TextFormat::AQUA: - $newString .= ""; - ++$tokens; - break; - case TextFormat::RED: - $newString .= ""; - ++$tokens; - break; - case TextFormat::LIGHT_PURPLE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::YELLOW: - $newString .= ""; - ++$tokens; - break; - case TextFormat::WHITE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MINECOIN_GOLD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_QUARTZ: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_IRON: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_NETHERITE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_REDSTONE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_COPPER: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_GOLD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_EMERALD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_DIAMOND: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_LAPIS: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MATERIAL_AMETHYST: - $newString .= ""; - ++$tokens; - break; - default: - $newString .= $token; - break; + $formatString = match($token){ + TextFormat::BLACK => "color:#000", + TextFormat::DARK_BLUE => "color:#00A", + TextFormat::DARK_GREEN => "color:#0A0", + TextFormat::DARK_AQUA => "color:#0AA", + TextFormat::DARK_RED => "color:#A00", + TextFormat::DARK_PURPLE => "color:#A0A", + TextFormat::GOLD => "color:#FA0", + TextFormat::GRAY => "color:#AAA", + TextFormat::DARK_GRAY => "color:#555", + TextFormat::BLUE => "color:#55F", + TextFormat::GREEN => "color:#5F5", + TextFormat::AQUA => "color:#5FF", + TextFormat::RED => "color:#F55", + TextFormat::LIGHT_PURPLE => "color:#F5F", + TextFormat::YELLOW => "color:#FF5", + TextFormat::WHITE => "color:#FFF", + TextFormat::MINECOIN_GOLD => "color:#dd0", + TextFormat::MATERIAL_QUARTZ => "color:#e2d3d1", + TextFormat::MATERIAL_IRON => "color:#cec9c9", + TextFormat::MATERIAL_NETHERITE => "color:#44393a", + TextFormat::MATERIAL_REDSTONE => "color:#961506", + TextFormat::MATERIAL_COPPER => "color:#b4684d", + TextFormat::MATERIAL_GOLD => "color:#deb02c", + TextFormat::MATERIAL_EMERALD => "color:#119f36", + TextFormat::MATERIAL_DIAMOND => "color:#2cb9a8", + TextFormat::MATERIAL_LAPIS => "color:#20487a", + TextFormat::MATERIAL_AMETHYST => "color:#9a5cc5", + TextFormat::BOLD => "font-weight:bold", + TextFormat::ITALIC => "font-style:italic", + default => null + }; + if($formatString !== null){ + $newString .= ""; + ++$tokens; + }elseif($token === TextFormat::RESET){ + $newString .= str_repeat("", $tokens); + $tokens = 0; + }else{ + $newString .= $token; } } From f7687af337d001ddbcc47b8e773f014a33faa662 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 12 Dec 2024 13:11:48 +0000 Subject: [PATCH 029/334] Fixed draft release being created on release publish --- .github/workflows/draft-release.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 3374ff68f..6000dd5a8 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -21,7 +21,31 @@ env: PHP_VERSION: "8.2" jobs: + skip: + name: Check whether to ignore this tag + runs-on: ubuntu-20.04 + + outputs: + skip: ${{ steps.exists.outputs.exists == 'true' }} + + steps: + - name: Check if release already exists + id: exists + env: + GH_TOKEN: ${{ github.token }} + run: | + exists=false + if [[ "${{ github.ref_type }}" == "tag" ]]; then + tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)" + if gh release view "$tag" --repo "${{ github.repository }}"; then + exists=true + fi + fi + echo exists=$exists >> $GITHUB_OUTPUT + check: + needs: [skip] + if: needs.skip.outputs.skip != 'true' name: Check release uses: ./.github/workflows/draft-release-pr-check.yml From b3410787659c56ffe02ca00ab610b43fb5126e64 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:53:52 +0300 Subject: [PATCH 030/334] Implement new pale oak blocks (#6570) --- src/block/BlockTypeIds.php | 16 +++++++++++++++- src/block/Leaves.php | 1 + src/block/VanillaBlocks.php | 15 +++++++++++++++ src/block/utils/LeavesType.php | 4 +++- src/block/utils/WoodType.php | 2 ++ .../convert/BlockObjectToStateSerializer.php | 15 +++++++++++++++ .../convert/BlockStateToObjectDeserializer.php | 15 +++++++++++++++ .../item/ItemSerializerDeserializerRegistrar.php | 2 ++ src/item/ItemTypeIds.php | 3 ++- src/item/StringToItemParser.php | 15 +++++++++++++++ src/item/VanillaItems.php | 2 ++ tests/phpstan/configs/phpstan-bugs.neon | 5 +++++ .../block/block_factory_consistency_check.json | 16 ++++++++++++++++ 13 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 3914a4b74..033985179 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -765,8 +765,22 @@ final class BlockTypeIds{ public const COPPER_TRAPDOOR = 10735; public const CHISELED_COPPER = 10736; public const COPPER_GRATE = 10737; + public const PALE_OAK_BUTTON = 10738; + public const PALE_OAK_DOOR = 10739; + public const PALE_OAK_FENCE = 10740; + public const PALE_OAK_FENCE_GATE = 10741; + public const PALE_OAK_LEAVES = 10742; + public const PALE_OAK_LOG = 10743; + public const PALE_OAK_PLANKS = 10744; + public const PALE_OAK_PRESSURE_PLATE = 10745; + public const PALE_OAK_SIGN = 10746; + public const PALE_OAK_SLAB = 10747; + public const PALE_OAK_STAIRS = 10748; + public const PALE_OAK_TRAPDOOR = 10749; + public const PALE_OAK_WALL_SIGN = 10750; + public const PALE_OAK_WOOD = 10751; - public const FIRST_UNUSED_BLOCK_ID = 10738; + public const FIRST_UNUSED_BLOCK_ID = 10752; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/Leaves.php b/src/block/Leaves.php index 7fe9eae74..847536557 100644 --- a/src/block/Leaves.php +++ b/src/block/Leaves.php @@ -157,6 +157,7 @@ class Leaves extends Transparent{ LeavesType::MANGROVE, //TODO: mangrove propagule LeavesType::AZALEA, LeavesType::FLOWERING_AZALEA => null, //TODO: azalea LeavesType::CHERRY => null, //TODO: cherry + LeavesType::PALE_OAK => null, //TODO: pale oak })?->asItem(); if($sapling !== null){ $drops[] = $sapling; diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 9d7ed91f4..9755f9eda 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -590,6 +590,20 @@ use function strtolower; * @method static Flower OXEYE_DAISY() * @method static PackedIce PACKED_ICE() * @method static Opaque PACKED_MUD() + * @method static WoodenButton PALE_OAK_BUTTON() + * @method static WoodenDoor PALE_OAK_DOOR() + * @method static WoodenFence PALE_OAK_FENCE() + * @method static FenceGate PALE_OAK_FENCE_GATE() + * @method static Leaves PALE_OAK_LEAVES() + * @method static Wood PALE_OAK_LOG() + * @method static Planks PALE_OAK_PLANKS() + * @method static WoodenPressurePlate PALE_OAK_PRESSURE_PLATE() + * @method static FloorSign PALE_OAK_SIGN() + * @method static WoodenSlab PALE_OAK_SLAB() + * @method static WoodenStairs PALE_OAK_STAIRS() + * @method static WoodenTrapdoor PALE_OAK_TRAPDOOR() + * @method static WallSign PALE_OAK_WALL_SIGN() + * @method static Wood PALE_OAK_WOOD() * @method static DoublePlant PEONY() * @method static PinkPetals PINK_PETALS() * @method static Flower PINK_TULIP() @@ -1359,6 +1373,7 @@ final class VanillaBlocks{ WoodType::CRIMSON => VanillaItems::CRIMSON_SIGN(...), WoodType::WARPED => VanillaItems::WARPED_SIGN(...), WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...), + WoodType::PALE_OAK => VanillaItems::PALE_OAK_SIGN(...), }; self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); diff --git a/src/block/utils/LeavesType.php b/src/block/utils/LeavesType.php index 975551ad6..4846feed0 100644 --- a/src/block/utils/LeavesType.php +++ b/src/block/utils/LeavesType.php @@ -53,6 +53,7 @@ enum LeavesType{ case AZALEA; case FLOWERING_AZALEA; case CHERRY; + case PALE_OAK; public function getDisplayName() : string{ return match($this){ @@ -65,7 +66,8 @@ enum LeavesType{ self::MANGROVE => "Mangrove", self::AZALEA => "Azalea", self::FLOWERING_AZALEA => "Flowering Azalea", - self::CHERRY => "Cherry" + self::CHERRY => "Cherry", + self::PALE_OAK => "Pale Oak", }; } } diff --git a/src/block/utils/WoodType.php b/src/block/utils/WoodType.php index f6195b9f9..c83a4ab00 100644 --- a/src/block/utils/WoodType.php +++ b/src/block/utils/WoodType.php @@ -53,6 +53,7 @@ enum WoodType{ case CRIMSON; case WARPED; case CHERRY; + case PALE_OAK; public function getDisplayName() : string{ return match($this){ @@ -66,6 +67,7 @@ enum WoodType{ self::CRIMSON => "Crimson", self::WARPED => "Warped", self::CHERRY => "Cherry", + self::PALE_OAK => "Pale Oak", }; } diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index aebfd67ff..99e756576 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -704,6 +704,20 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSlab(Blocks::OAK_SLAB(), Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB); $this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS); + $this->map(Blocks::PALE_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::PALE_OAK_BUTTON))); + $this->map(Blocks::PALE_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::PALE_OAK_DOOR))); + $this->map(Blocks::PALE_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::PALE_OAK_FENCE_GATE))); + $this->map(Blocks::PALE_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::PALE_OAK_PRESSURE_PLATE))); + $this->map(Blocks::PALE_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::PALE_OAK_STANDING_SIGN))); + $this->map(Blocks::PALE_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::PALE_OAK_TRAPDOOR))); + $this->map(Blocks::PALE_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::PALE_OAK_WALL_SIGN))); + $this->mapLog(Blocks::PALE_OAK_LOG(), Ids::PALE_OAK_LOG, Ids::STRIPPED_PALE_OAK_LOG); + $this->mapLog(Blocks::PALE_OAK_WOOD(), Ids::PALE_OAK_WOOD, Ids::STRIPPED_PALE_OAK_WOOD); + $this->mapSimple(Blocks::PALE_OAK_FENCE(), Ids::PALE_OAK_FENCE); + $this->mapSimple(Blocks::PALE_OAK_PLANKS(), Ids::PALE_OAK_PLANKS); + $this->mapSlab(Blocks::PALE_OAK_SLAB(), Ids::PALE_OAK_SLAB, Ids::PALE_OAK_DOUBLE_SLAB); + $this->mapStairs(Blocks::PALE_OAK_STAIRS(), Ids::PALE_OAK_STAIRS); + $this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON))); $this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR))); $this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE))); @@ -740,6 +754,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::CHERRY_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::CHERRY_LEAVES))); $this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED))); $this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES))); + $this->map(Blocks::PALE_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::PALE_OAK_LEAVES))); //legacy mess $this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::ACACIA_LEAVES))); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 5c0a427cc..998561ffc 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -608,6 +608,20 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSlab(Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB, fn() => Blocks::OAK_SLAB()); $this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS()); + $this->map(Ids::PALE_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::PALE_OAK_BUTTON(), $in)); + $this->map(Ids::PALE_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::PALE_OAK_DOOR(), $in)); + $this->map(Ids::PALE_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::PALE_OAK_FENCE_GATE(), $in)); + $this->map(Ids::PALE_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::PALE_OAK_PRESSURE_PLATE(), $in)); + $this->map(Ids::PALE_OAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::PALE_OAK_SIGN(), $in)); + $this->map(Ids::PALE_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::PALE_OAK_TRAPDOOR(), $in)); + $this->map(Ids::PALE_OAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::PALE_OAK_WALL_SIGN(), $in)); + $this->mapLog(Ids::PALE_OAK_LOG, Ids::STRIPPED_PALE_OAK_LOG, fn() => Blocks::PALE_OAK_LOG()); + $this->mapLog(Ids::PALE_OAK_WOOD, Ids::STRIPPED_PALE_OAK_WOOD, fn() => Blocks::PALE_OAK_WOOD()); + $this->mapSimple(Ids::PALE_OAK_FENCE, fn() => Blocks::PALE_OAK_FENCE()); + $this->mapSimple(Ids::PALE_OAK_PLANKS, fn() => Blocks::PALE_OAK_PLANKS()); + $this->mapSlab(Ids::PALE_OAK_SLAB, Ids::PALE_OAK_DOUBLE_SLAB, fn() => Blocks::PALE_OAK_SLAB()); + $this->mapStairs(Ids::PALE_OAK_STAIRS, fn() => Blocks::PALE_OAK_STAIRS()); + $this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in)); $this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in)); $this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in)); @@ -647,6 +661,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map(Ids::JUNGLE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::JUNGLE_LEAVES(), $in)); $this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in)); $this->map(Ids::OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::OAK_LEAVES(), $in)); + $this->map(Ids::PALE_OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::PALE_OAK_LEAVES(), $in)); $this->map(Ids::SPRUCE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::SPRUCE_LEAVES(), $in)); } diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index df1db4211..30c774af7 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -151,6 +151,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Block(Ids::JUNGLE_DOOR, Blocks::JUNGLE_DOOR()); $this->map1to1Block(Ids::MANGROVE_DOOR, Blocks::MANGROVE_DOOR()); $this->map1to1Block(Ids::NETHER_WART, Blocks::NETHER_WART()); + $this->map1to1Block(Ids::PALE_OAK_DOOR, Blocks::PALE_OAK_DOOR()); $this->map1to1Block(Ids::REPEATER, Blocks::REDSTONE_REPEATER()); $this->map1to1Block(Ids::SOUL_CAMPFIRE, Blocks::SOUL_CAMPFIRE()); $this->map1to1Block(Ids::SPRUCE_DOOR, Blocks::SPRUCE_DOOR()); @@ -331,6 +332,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::OAK_BOAT, Items::OAK_BOAT()); $this->map1to1Item(Ids::OAK_SIGN, Items::OAK_SIGN()); $this->map1to1Item(Ids::PAINTING, Items::PAINTING()); + $this->map1to1Item(Ids::PALE_OAK_SIGN, Items::PALE_OAK_SIGN()); $this->map1to1Item(Ids::PAPER, Items::PAPER()); $this->map1to1Item(Ids::PHANTOM_MEMBRANE, Items::PHANTOM_MEMBRANE()); $this->map1to1Item(Ids::PITCHER_POD, Items::PITCHER_POD()); diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index c93c23e81..acb0275c6 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -328,8 +328,9 @@ final class ItemTypeIds{ public const END_CRYSTAL = 20289; public const ICE_BOMB = 20290; public const RECOVERY_COMPASS = 20291; + public const PALE_OAK_SIGN = 20292; - public const FIRST_UNUSED_ITEM_ID = 20292; + public const FIRST_UNUSED_ITEM_ID = 20293; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 09c93d5d9..b58b98154 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -872,6 +872,19 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("oxeye_daisy", fn() => Blocks::OXEYE_DAISY()); $result->registerBlock("packed_ice", fn() => Blocks::PACKED_ICE()); $result->registerBlock("packed_mud", fn() => Blocks::PACKED_MUD()); + $result->registerBlock("pale_oak_button", fn() => Blocks::PALE_OAK_BUTTON()); + $result->registerBlock("pale_oak_door", fn() => Blocks::PALE_OAK_DOOR()); + $result->registerBlock("pale_oak_fence", fn() => Blocks::PALE_OAK_FENCE()); + $result->registerBlock("pale_oak_fence_gate", fn() => Blocks::PALE_OAK_FENCE_GATE()); + $result->registerBlock("pale_oak_leaves", fn() => Blocks::PALE_OAK_LEAVES()); + $result->registerBlock("pale_oak_log", fn() => Blocks::PALE_OAK_LOG()->setStripped(false)); + $result->registerBlock("pale_oak_planks", fn() => Blocks::PALE_OAK_PLANKS()); + $result->registerBlock("pale_oak_pressure_plate", fn() => Blocks::PALE_OAK_PRESSURE_PLATE()); + $result->registerBlock("pale_oak_sign", fn() => Blocks::PALE_OAK_SIGN()); + $result->registerBlock("pale_oak_slab", fn() => Blocks::PALE_OAK_SLAB()); + $result->registerBlock("pale_oak_stairs", fn() => Blocks::PALE_OAK_STAIRS()); + $result->registerBlock("pale_oak_trapdoor", fn() => Blocks::PALE_OAK_TRAPDOOR()); + $result->registerBlock("pale_oak_wood", fn() => Blocks::PALE_OAK_WOOD()->setStripped(false)); $result->registerBlock("peony", fn() => Blocks::PEONY()); $result->registerBlock("pink_petals", fn() => Blocks::PINK_PETALS()); $result->registerBlock("pink_tulip", fn() => Blocks::PINK_TULIP()); @@ -1084,6 +1097,8 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("stripped_mangrove_wood", fn() => Blocks::MANGROVE_WOOD()->setStripped(true)); $result->registerBlock("stripped_oak_log", fn() => Blocks::OAK_LOG()->setStripped(true)); $result->registerBlock("stripped_oak_wood", fn() => Blocks::OAK_WOOD()->setStripped(true)); + $result->registerBlock("stripped_pale_oak_log", fn() => Blocks::PALE_OAK_LOG()->setStripped(true)); + $result->registerBlock("stripped_pale_oak_wood", fn() => Blocks::PALE_OAK_WOOD()->setStripped(true)); $result->registerBlock("stripped_spruce_log", fn() => Blocks::SPRUCE_LOG()->setStripped(true)); $result->registerBlock("stripped_spruce_wood", fn() => Blocks::SPRUCE_WOOD()->setStripped(true)); $result->registerBlock("stripped_warped_hyphae", fn() => Blocks::WARPED_HYPHAE()->setStripped(true)); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 6768ed8f0..e2e4ee450 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -243,6 +243,7 @@ use function strtolower; * @method static Boat OAK_BOAT() * @method static ItemBlockWallOrFloor OAK_SIGN() * @method static PaintingItem PAINTING() + * @method static ItemBlockWallOrFloor PALE_OAK_SIGN() * @method static Item PAPER() * @method static Item PHANTOM_MEMBRANE() * @method static PitcherPod PITCHER_POD() @@ -535,6 +536,7 @@ final class VanillaItems{ }); self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN())); self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting")); + self::register("pale_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::PALE_OAK_SIGN(), Blocks::PALE_OAK_WALL_SIGN())); self::register("paper", fn(IID $id) => new Item($id, "Paper")); self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane")); self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod")); diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index ea5e1c62a..1ae740d66 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -60,6 +60,11 @@ parameters: count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:PALE_OAK_SIGN\\(\\)\\.$#" + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:SPRUCE_SIGN\\(\\)\\.$#" count: 1 diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index d14a85fab..9f5414c6e 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -511,6 +511,20 @@ "OXEYE_DAISY": 1, "PACKED_ICE": 1, "PACKED_MUD": 1, + "PALE_OAK_BUTTON": 12, + "PALE_OAK_DOOR": 32, + "PALE_OAK_FENCE": 1, + "PALE_OAK_FENCE_GATE": 16, + "PALE_OAK_LEAVES": 4, + "PALE_OAK_LOG": 6, + "PALE_OAK_PLANKS": 1, + "PALE_OAK_PRESSURE_PLATE": 2, + "PALE_OAK_SIGN": 16, + "PALE_OAK_SLAB": 3, + "PALE_OAK_STAIRS": 8, + "PALE_OAK_TRAPDOOR": 16, + "PALE_OAK_WALL_SIGN": 4, + "PALE_OAK_WOOD": 6, "PEONY": 2, "PINK_PETALS": 16, "PINK_TULIP": 1, @@ -754,6 +768,8 @@ "NOTE_BLOCK": "pocketmine\\block\\tile\\Note", "OAK_SIGN": "pocketmine\\block\\tile\\Sign", "OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "PALE_OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "PALE_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron", "REDSTONE_COMPARATOR": "pocketmine\\block\\tile\\Comparator", "SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", From 42094e676827d9adc0ced960aaa5db5778b6ac8f Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:21:41 +0300 Subject: [PATCH 031/334] Implement resin blocks & items (#6571) --- src/block/BlockTypeIds.php | 9 +++- src/block/ResinClump.php | 54 +++++++++++++++++++ src/block/VanillaBlocks.php | 20 +++++++ .../convert/BlockObjectToStateSerializer.php | 11 ++++ .../BlockStateToObjectDeserializer.php | 7 +++ .../ItemSerializerDeserializerRegistrar.php | 1 + src/item/ItemTypeIds.php | 3 +- src/item/StringToItemParser.php | 9 ++++ src/item/VanillaItems.php | 2 + .../block_factory_consistency_check.json | 7 +++ 10 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/block/ResinClump.php diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 033985179..c440cefdc 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -779,8 +779,15 @@ final class BlockTypeIds{ public const PALE_OAK_TRAPDOOR = 10749; public const PALE_OAK_WALL_SIGN = 10750; public const PALE_OAK_WOOD = 10751; + public const RESIN = 10752; + public const RESIN_BRICK_SLAB = 10753; + public const RESIN_BRICK_STAIRS = 10754; + public const RESIN_BRICK_WALL = 10755; + public const RESIN_BRICKS = 10756; + public const RESIN_CLUMP = 10757; + public const CHISELED_RESIN_BRICKS = 10758; - public const FIRST_UNUSED_BLOCK_ID = 10752; + public const FIRST_UNUSED_BLOCK_ID = 10759; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/ResinClump.php b/src/block/ResinClump.php new file mode 100644 index 000000000..75126edf3 --- /dev/null +++ b/src/block/ResinClump.php @@ -0,0 +1,54 @@ +faces : []; + } + + protected function recalculateCollisionBoxes() : array{ + return []; + } +} diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 9755f9eda..ce3087a9b 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -191,6 +191,7 @@ use function strtolower; * @method static Opaque CHISELED_POLISHED_BLACKSTONE() * @method static SimplePillar CHISELED_QUARTZ() * @method static Opaque CHISELED_RED_SANDSTONE() + * @method static Opaque CHISELED_RESIN_BRICKS() * @method static Opaque CHISELED_SANDSTONE() * @method static Opaque CHISELED_STONE_BRICKS() * @method static Opaque CHISELED_TUFF() @@ -687,6 +688,12 @@ use function strtolower; * @method static Flower RED_TULIP() * @method static Opaque REINFORCED_DEEPSLATE() * @method static Reserved6 RESERVED6() + * @method static Opaque RESIN() + * @method static Opaque RESIN_BRICKS() + * @method static Slab RESIN_BRICK_SLAB() + * @method static Stair RESIN_BRICK_STAIRS() + * @method static Wall RESIN_BRICK_WALL() + * @method static ResinClump RESIN_CLUMP() * @method static DoublePlant ROSE_BUSH() * @method static Sand SAND() * @method static Opaque SANDSTONE() @@ -1326,6 +1333,7 @@ final class VanillaBlocks{ self::registerBlocksR17(); self::registerBlocksR18(); self::registerMudBlocks(); + self::registerResinBlocks(); self::registerTuffBlocks(); self::registerCraftingTables(); @@ -1743,6 +1751,18 @@ final class VanillaBlocks{ self::register("mud_brick_wall", fn(BID $id) => new Wall($id, "Mud Brick Wall", $mudBricksBreakInfo)); } + private static function registerResinBlocks() : void{ + self::register("resin", fn(BID $id) => new Opaque($id, "Block of Resin", new Info(BreakInfo::instant()))); + self::register("resin_clump", fn(BID $id) => new ResinClump($id, "Resin Clump", new Info(BreakInfo::instant()))); + + $resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD)); + self::register("resin_brick_slab", fn(BID $id) => new Slab($id, "Resin Brick", $resinBricksInfo)); + self::register("resin_brick_stairs", fn(BID $id) => new Stair($id, "Resin Brick Stairs", $resinBricksInfo)); + self::register("resin_brick_wall", fn(BID $id) => new Wall($id, "Resin Brick Wall", $resinBricksInfo)); + self::register("resin_bricks", fn(BID $id) => new Opaque($id, "Resin Bricks", $resinBricksInfo)); + self::register("chiseled_resin_bricks", fn(BID $id) => new Opaque($id, "Chiseled Resin Bricks", $resinBricksInfo)); + } + private static function registerTuffBlocks() : void{ $tuffBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)); diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 99e756576..e41e82054 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -121,6 +121,7 @@ use pocketmine\block\RedstoneOre; use pocketmine\block\RedstoneRepeater; use pocketmine\block\RedstoneTorch; use pocketmine\block\RedstoneWire; +use pocketmine\block\ResinClump; use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\block\Sapling; use pocketmine\block\SeaPickle; @@ -810,6 +811,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::CHISELED_NETHER_BRICKS(), Ids::CHISELED_NETHER_BRICKS); $this->mapSimple(Blocks::CHISELED_POLISHED_BLACKSTONE(), Ids::CHISELED_POLISHED_BLACKSTONE); $this->mapSimple(Blocks::CHISELED_RED_SANDSTONE(), Ids::CHISELED_RED_SANDSTONE); + $this->mapSimple(Blocks::CHISELED_RESIN_BRICKS(), Ids::CHISELED_RESIN_BRICKS); $this->mapSimple(Blocks::CHISELED_SANDSTONE(), Ids::CHISELED_SANDSTONE); $this->mapSimple(Blocks::CHISELED_STONE_BRICKS(), Ids::CHISELED_STONE_BRICKS); $this->mapSimple(Blocks::CHISELED_TUFF(), Ids::CHISELED_TUFF); @@ -1051,6 +1053,8 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::RED_SANDSTONE(), Ids::RED_SANDSTONE); $this->mapSimple(Blocks::REINFORCED_DEEPSLATE(), Ids::REINFORCED_DEEPSLATE); $this->mapSimple(Blocks::RESERVED6(), Ids::RESERVED6); + $this->mapSimple(Blocks::RESIN(), Ids::RESIN_BLOCK); + $this->mapSimple(Blocks::RESIN_BRICKS(), Ids::RESIN_BRICKS); $this->mapSimple(Blocks::SAND(), Ids::SAND); $this->mapSimple(Blocks::SANDSTONE(), Ids::SANDSTONE); $this->mapSimple(Blocks::SCULK(), Ids::SCULK); @@ -1735,6 +1739,13 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapStairs(Blocks::RED_SANDSTONE_STAIRS(), Ids::RED_SANDSTONE_STAIRS); $this->map(Blocks::RED_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_SANDSTONE_WALL))); $this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_RED))); + $this->mapSlab(Blocks::RESIN_BRICK_SLAB(), Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB); + $this->map(Blocks::RESIN_BRICK_STAIRS(), fn(Stair $block) => Helper::encodeStairs($block, new Writer(Ids::RESIN_BRICK_STAIRS))); + $this->map(Blocks::RESIN_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RESIN_BRICK_WALL))); + $this->map(Blocks::RESIN_CLUMP(), function(ResinClump $block) : Writer{ + return Writer::create(Ids::RESIN_CLUMP) + ->writeFacingFlags($block->getFaces()); + }); $this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH))); $this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB); $this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 998561ffc..cb9a6e7ae 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -735,6 +735,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::CHISELED_NETHER_BRICKS, fn() => Blocks::CHISELED_NETHER_BRICKS()); $this->mapSimple(Ids::CHISELED_POLISHED_BLACKSTONE, fn() => Blocks::CHISELED_POLISHED_BLACKSTONE()); $this->mapSimple(Ids::CHISELED_RED_SANDSTONE, fn() => Blocks::CHISELED_RED_SANDSTONE()); + $this->mapSimple(Ids::CHISELED_RESIN_BRICKS, fn() => Blocks::CHISELED_RESIN_BRICKS()); $this->mapSimple(Ids::CHISELED_SANDSTONE, fn() => Blocks::CHISELED_SANDSTONE()); $this->mapSimple(Ids::CHISELED_STONE_BRICKS, fn() => Blocks::CHISELED_STONE_BRICKS()); $this->mapSimple(Ids::CHISELED_TUFF, fn() => Blocks::CHISELED_TUFF()); @@ -972,6 +973,8 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::REDSTONE_BLOCK, fn() => Blocks::REDSTONE()); $this->mapSimple(Ids::REINFORCED_DEEPSLATE, fn() => Blocks::REINFORCED_DEEPSLATE()); $this->mapSimple(Ids::RESERVED6, fn() => Blocks::RESERVED6()); + $this->mapSimple(Ids::RESIN_BLOCK, fn() => Blocks::RESIN()); + $this->mapSimple(Ids::RESIN_BRICKS, fn() => Blocks::RESIN_BRICKS()); $this->mapSimple(Ids::SAND, fn() => Blocks::SAND()); $this->mapSimple(Ids::SANDSTONE, fn() => Blocks::SANDSTONE()); $this->mapSimple(Ids::SCULK, fn() => Blocks::SCULK()); @@ -1582,6 +1585,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return Blocks::SUGARCANE() ->setAge($in->readBoundedInt(StateNames::AGE, 0, 15)); }); + $this->mapSlab(Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB, fn() => Blocks::RESIN_BRICK_SLAB()); + $this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS()); + $this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in)); + $this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags())); $this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB()); $this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS()); $this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in)); diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 30c774af7..e72e2fe4e 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -356,6 +356,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON()); $this->map1to1Item(Ids::RECOVERY_COMPASS, Items::RECOVERY_COMPASS()); $this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST()); + $this->map1to1Item(Ids::RESIN_BRICK, Items::RESIN_BRICK()); $this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH()); $this->map1to1Item(Ids::SALMON, Items::RAW_SALMON()); diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index acb0275c6..fb3a08161 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -329,8 +329,9 @@ final class ItemTypeIds{ public const ICE_BOMB = 20290; public const RECOVERY_COMPASS = 20291; public const PALE_OAK_SIGN = 20292; + public const RESIN_BRICK = 20293; - public const FIRST_UNUSED_ITEM_ID = 20293; + public const FIRST_UNUSED_ITEM_ID = 20294; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index b58b98154..19def35e7 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -243,6 +243,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("chiseled_polished_blackstone", fn() => Blocks::CHISELED_POLISHED_BLACKSTONE()); $result->registerBlock("chiseled_quartz", fn() => Blocks::CHISELED_QUARTZ()); $result->registerBlock("chiseled_red_sandstone", fn() => Blocks::CHISELED_RED_SANDSTONE()); + $result->registerBlock("chiseled_resin_bricks", fn() => Blocks::CHISELED_RESIN_BRICKS()); $result->registerBlock("chiseled_sandstone", fn() => Blocks::CHISELED_SANDSTONE()); $result->registerBlock("chiseled_stone_bricks", fn() => Blocks::CHISELED_STONE_BRICKS()); $result->registerBlock("chiseled_tuff", fn() => Blocks::CHISELED_TUFF()); @@ -985,6 +986,13 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("repeater", fn() => Blocks::REDSTONE_REPEATER()); $result->registerBlock("repeater_block", fn() => Blocks::REDSTONE_REPEATER()); $result->registerBlock("reserved6", fn() => Blocks::RESERVED6()); + $result->registerBlock("resin", fn() => Blocks::RESIN()); + $result->registerBlock("resin_block", fn() => Blocks::RESIN()); + $result->registerBlock("resin_brick_slab", fn() => Blocks::RESIN_BRICK_SLAB()); + $result->registerBlock("resin_brick_stairs", fn() => Blocks::RESIN_BRICK_STAIRS()); + $result->registerBlock("resin_brick_wall", fn() => Blocks::RESIN_BRICK_WALL()); + $result->registerBlock("resin_bricks", fn() => Blocks::RESIN_BRICKS()); + $result->registerBlock("resin_clump", fn() => Blocks::RESIN_CLUMP()); $result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED)); $result->registerBlock("rose", fn() => Blocks::POPPY()); $result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH()); @@ -1499,6 +1507,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("recovery_compass", fn() => Items::RECOVERY_COMPASS()); $result->register("redstone", fn() => Items::REDSTONE_DUST()); $result->register("redstone_dust", fn() => Items::REDSTONE_DUST()); + $result->register("resin_brick", fn() => Items::RESIN_BRICK()); $result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("rotten_flesh", fn() => Items::ROTTEN_FLESH()); $result->register("salmon", fn() => Items::RAW_SALMON()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index e2e4ee450..adc89259e 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -287,6 +287,7 @@ use function strtolower; * @method static Record RECORD_WARD() * @method static Item RECOVERY_COMPASS() * @method static Redstone REDSTONE_DUST() + * @method static Item RESIN_BRICK() * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static RottenFlesh ROTTEN_FLESH() * @method static Item SCUTE() @@ -579,6 +580,7 @@ final class VanillaItems{ self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward")); self::register("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass")); self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone")); + self::register("resin_brick", fn(IID $id) => new Item($id, "Resin Brick")); self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh")); self::register("scute", fn(IID $id) => new Item($id, "Scute")); self::register("shears", fn(IID $id) => new Shears($id, "Shears", [EnchantmentTags::SHEARS])); diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index 9f5414c6e..0b9150988 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -112,6 +112,7 @@ "CHISELED_POLISHED_BLACKSTONE": 1, "CHISELED_QUARTZ": 3, "CHISELED_RED_SANDSTONE": 1, + "CHISELED_RESIN_BRICKS": 1, "CHISELED_SANDSTONE": 1, "CHISELED_STONE_BRICKS": 1, "CHISELED_TUFF": 1, @@ -608,6 +609,12 @@ "RED_TULIP": 1, "REINFORCED_DEEPSLATE": 1, "RESERVED6": 1, + "RESIN": 1, + "RESIN_BRICKS": 1, + "RESIN_BRICK_SLAB": 3, + "RESIN_BRICK_STAIRS": 8, + "RESIN_BRICK_WALL": 162, + "RESIN_CLUMP": 64, "ROSE_BUSH": 2, "SAND": 1, "SANDSTONE": 1, From de66d84d29c56cf566eb67cd1d8660ba679c0852 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Fri, 13 Dec 2024 21:10:34 +0300 Subject: [PATCH 032/334] Implement new 1.20 and 1.21 records (#6572) --- src/block/utils/RecordType.php | 8 ++++++++ .../bedrock/item/ItemSerializerDeserializerRegistrar.php | 4 ++++ src/item/ItemTypeIds.php | 6 +++++- src/item/StringToItemParser.php | 4 ++++ src/item/VanillaItems.php | 8 ++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/block/utils/RecordType.php b/src/block/utils/RecordType.php index e63cee920..0757db09b 100644 --- a/src/block/utils/RecordType.php +++ b/src/block/utils/RecordType.php @@ -59,11 +59,15 @@ enum RecordType{ case DISK_CAT; case DISK_BLOCKS; case DISK_CHIRP; + case DISK_CREATOR; + case DISK_CREATOR_MUSIC_BOX; case DISK_FAR; case DISK_MALL; case DISK_MELLOHI; case DISK_OTHERSIDE; case DISK_PIGSTEP; + case DISK_PRECIPICE; + case DISK_RELIC; case DISK_STAL; case DISK_STRAD; case DISK_WARD; @@ -83,11 +87,15 @@ enum RecordType{ self::DISK_CAT => ["C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()], self::DISK_BLOCKS => ["C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()], self::DISK_CHIRP => ["C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()], + self::DISK_CREATOR => ["Lena Raine - Creator", LevelSoundEvent::RECORD_CREATOR, KnownTranslationFactory::item_record_creator_desc()], + self::DISK_CREATOR_MUSIC_BOX => ["Lena Raine - Creator (Music Box)", LevelSoundEvent::RECORD_CREATOR_MUSIC_BOX, KnownTranslationFactory::item_record_creator_music_box_desc()], self::DISK_FAR => ["C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()], self::DISK_MALL => ["C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()], self::DISK_MELLOHI => ["C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()], self::DISK_OTHERSIDE => ["Lena Raine - otherside", LevelSoundEvent::RECORD_OTHERSIDE, KnownTranslationFactory::item_record_otherside_desc()], self::DISK_PIGSTEP => ["Lena Raine - Pigstep", LevelSoundEvent::RECORD_PIGSTEP, KnownTranslationFactory::item_record_pigstep_desc()], + self::DISK_PRECIPICE => ["Aaron Cherof - Precipice", LevelSoundEvent::RECORD_PRECIPICE, KnownTranslationFactory::item_record_precipice_desc()], + self::DISK_RELIC => ["Aaron Cherof - Relic", LevelSoundEvent::RECORD_RELIC, KnownTranslationFactory::item_record_relic_desc()], self::DISK_STAL => ["C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()], self::DISK_STRAD => ["C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()], self::DISK_WARD => ["C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()], diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index e72e2fe4e..771154d46 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -303,11 +303,15 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::MUSIC_DISC_BLOCKS, Items::RECORD_BLOCKS()); $this->map1to1Item(Ids::MUSIC_DISC_CAT, Items::RECORD_CAT()); $this->map1to1Item(Ids::MUSIC_DISC_CHIRP, Items::RECORD_CHIRP()); + $this->map1to1Item(Ids::MUSIC_DISC_CREATOR, Items::RECORD_CREATOR()); + $this->map1to1Item(Ids::MUSIC_DISC_CREATOR_MUSIC_BOX, Items::RECORD_CREATOR_MUSIC_BOX()); $this->map1to1Item(Ids::MUSIC_DISC_FAR, Items::RECORD_FAR()); $this->map1to1Item(Ids::MUSIC_DISC_MALL, Items::RECORD_MALL()); $this->map1to1Item(Ids::MUSIC_DISC_MELLOHI, Items::RECORD_MELLOHI()); $this->map1to1Item(Ids::MUSIC_DISC_OTHERSIDE, Items::RECORD_OTHERSIDE()); $this->map1to1Item(Ids::MUSIC_DISC_PIGSTEP, Items::RECORD_PIGSTEP()); + $this->map1to1Item(Ids::MUSIC_DISC_PRECIPICE, Items::RECORD_PRECIPICE()); + $this->map1to1Item(Ids::MUSIC_DISC_RELIC, Items::RECORD_RELIC()); $this->map1to1Item(Ids::MUSIC_DISC_STAL, Items::RECORD_STAL()); $this->map1to1Item(Ids::MUSIC_DISC_STRAD, Items::RECORD_STRAD()); $this->map1to1Item(Ids::MUSIC_DISC_WAIT, Items::RECORD_WAIT()); diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index fb3a08161..c63046c6b 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -330,8 +330,12 @@ final class ItemTypeIds{ public const RECOVERY_COMPASS = 20291; public const PALE_OAK_SIGN = 20292; public const RESIN_BRICK = 20293; + public const RECORD_RELIC = 20294; + public const RECORD_CREATOR = 20295; + public const RECORD_CREATOR_MUSIC_BOX = 20296; + public const RECORD_PRECIPICE = 20297; - public const FIRST_UNUSED_ITEM_ID = 20294; + public const FIRST_UNUSED_ITEM_ID = 20298; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 19def35e7..a3bd7b872 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1495,11 +1495,15 @@ final class StringToItemParser extends StringToTParser{ $result->register("record_blocks", fn() => Items::RECORD_BLOCKS()); $result->register("record_cat", fn() => Items::RECORD_CAT()); $result->register("record_chirp", fn() => Items::RECORD_CHIRP()); + $result->register("record_creator", fn() => Items::RECORD_CREATOR()); + $result->register("record_creator_music_box", fn() => Items::RECORD_CREATOR_MUSIC_BOX()); $result->register("record_far", fn() => Items::RECORD_FAR()); $result->register("record_mall", fn() => Items::RECORD_MALL()); $result->register("record_mellohi", fn() => Items::RECORD_MELLOHI()); $result->register("record_otherside", fn() => Items::RECORD_OTHERSIDE()); $result->register("record_pigstep", fn() => Items::RECORD_PIGSTEP()); + $result->register("record_precipice", fn() => Items::RECORD_PRECIPICE()); + $result->register("record_relic", fn() => Items::RECORD_RELIC()); $result->register("record_stal", fn() => Items::RECORD_STAL()); $result->register("record_strad", fn() => Items::RECORD_STRAD()); $result->register("record_wait", fn() => Items::RECORD_WAIT()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index adc89259e..f76cf369f 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -276,11 +276,15 @@ use function strtolower; * @method static Record RECORD_BLOCKS() * @method static Record RECORD_CAT() * @method static Record RECORD_CHIRP() + * @method static Record RECORD_CREATOR() + * @method static Record RECORD_CREATOR_MUSIC_BOX() * @method static Record RECORD_FAR() * @method static Record RECORD_MALL() * @method static Record RECORD_MELLOHI() * @method static Record RECORD_OTHERSIDE() * @method static Record RECORD_PIGSTEP() + * @method static Record RECORD_PRECIPICE() + * @method static Record RECORD_RELIC() * @method static Record RECORD_STAL() * @method static Record RECORD_STRAD() * @method static Record RECORD_WAIT() @@ -569,11 +573,15 @@ final class VanillaItems{ self::register("record_blocks", fn(IID $id) => new Record($id, RecordType::DISK_BLOCKS, "Record Blocks")); self::register("record_cat", fn(IID $id) => new Record($id, RecordType::DISK_CAT, "Record Cat")); self::register("record_chirp", fn(IID $id) => new Record($id, RecordType::DISK_CHIRP, "Record Chirp")); + self::register("record_creator", fn(IID $id) => new Record($id, RecordType::DISK_CREATOR, "Record Creator")); + self::register("record_creator_music_box", fn(IID $id) => new Record($id, RecordType::DISK_CREATOR_MUSIC_BOX, "Record Creator (Music Box)")); self::register("record_far", fn(IID $id) => new Record($id, RecordType::DISK_FAR, "Record Far")); self::register("record_mall", fn(IID $id) => new Record($id, RecordType::DISK_MALL, "Record Mall")); self::register("record_mellohi", fn(IID $id) => new Record($id, RecordType::DISK_MELLOHI, "Record Mellohi")); self::register("record_otherside", fn(IID $id) => new Record($id, RecordType::DISK_OTHERSIDE, "Record Otherside")); self::register("record_pigstep", fn(IID $id) => new Record($id, RecordType::DISK_PIGSTEP, "Record Pigstep")); + self::register("record_precipice", fn(IID $id) => new Record($id, RecordType::DISK_PRECIPICE, "Record Precipice")); + self::register("record_relic", fn(IID $id) => new Record($id, RecordType::DISK_RELIC, "Record Relic")); self::register("record_stal", fn(IID $id) => new Record($id, RecordType::DISK_STAL, "Record Stal")); self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad")); self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait")); From b10caf74376c00f0f0c31f797fb4f6307e142c03 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Sat, 14 Dec 2024 00:54:48 +0300 Subject: [PATCH 033/334] Remove tool tier of some blocks to match vanilla (#6573) --- src/block/VanillaBlocks.php | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index ce3087a9b..231004dfa 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -879,12 +879,12 @@ final class VanillaBlocks{ self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible()))); self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant()))); - self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))), TileBell::class); + self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0))), TileBell::class); self::register("blue_ice", fn(BID $id) => new BlueIce($id, "Blue Ice", new Info(BreakInfo::pickaxe(2.8)))); self::register("bone_block", fn(BID $id) => new BoneBlock($id, "Bone Block", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD)))); self::register("bookshelf", fn(BID $id) => new Bookshelf($id, "Bookshelf", new Info(BreakInfo::axe(1.5)))); self::register("chiseled_bookshelf", fn(BID $id) => new ChiseledBookshelf($id, "Chiseled Bookshelf", new Info(BreakInfo::axe(1.5))), TileChiseledBookshelf::class); - self::register("brewing_stand", fn(BID $id) => new BrewingStand($id, "Brewing Stand", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))), TileBrewingStand::class); + self::register("brewing_stand", fn(BID $id) => new BrewingStand($id, "Brewing Stand", new Info(BreakInfo::pickaxe(0.5))), TileBrewingStand::class); $bricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); self::register("brick_stairs", fn(BID $id) => new Stair($id, "Brick Stairs", $bricksBreakInfo)); @@ -942,7 +942,7 @@ final class VanillaBlocks{ self::register("end_stone_bricks", fn(BID $id) => new Opaque($id, "End Stone Bricks", $endBrickBreakInfo)); self::register("end_stone_brick_stairs", fn(BID $id) => new Stair($id, "End Stone Brick Stairs", $endBrickBreakInfo)); - self::register("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, ToolTier::WOOD, 3000.0))), TileEnderChest::class); + self::register("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, blastResistance: 3000.0))), TileEnderChest::class); self::register("farmland", fn(BID $id) => new Farmland($id, "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT]))); self::register("fire", fn(BID $id) => new Fire($id, "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE]))); @@ -998,9 +998,9 @@ final class VanillaBlocks{ $ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE, 30.0)); self::register("iron", fn(BID $id) => new Opaque($id, "Iron Block", $ironBreakInfo)); self::register("iron_bars", fn(BID $id) => new Thin($id, "Iron Bars", $ironBreakInfo)); - $ironDoorBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 25.0)); - self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", $ironDoorBreakInfo)); - self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", $ironDoorBreakInfo)); + + self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", new Info(BreakInfo::pickaxe(5.0)))); + self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD)))); $itemFrameInfo = new Info(new BreakInfo(0.25)); self::register("item_frame", fn(BID $id) => new ItemFrame($id, "Item Frame", $itemFrameInfo), TileItemFrame::class); @@ -1009,7 +1009,7 @@ final class VanillaBlocks{ self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(0.8))), TileJukebox::class); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not self::register("ladder", fn(BID $id) => new Ladder($id, "Ladder", new Info(BreakInfo::axe(0.4)))); - $lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD)); + $lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0)); self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15)); self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10)); @@ -1144,7 +1144,7 @@ final class VanillaBlocks{ self::register("mossy_stone_brick_stairs", fn(BID $id) => new Stair($id, "Mossy Stone Brick Stairs", $stoneBreakInfo)); self::register("stone_button", fn(BID $id) => new StoneButton($id, "Stone Button", new Info(BreakInfo::pickaxe(0.5)))); self::register("stonecutter", fn(BID $id) => new Stonecutter($id, "Stonecutter", new Info(BreakInfo::pickaxe(3.5)))); - self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)))); + self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5)))); //TODO: in the future this won't be the same for all the types $stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); @@ -1200,7 +1200,7 @@ final class VanillaBlocks{ self::register("water", fn(BID $id) => new Water($id, "Water", new Info(BreakInfo::indestructible(500.0)))); self::register("lily_pad", fn(BID $id) => new WaterLily($id, "Lily Pad", new Info(BreakInfo::instant()))); - $weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)); + $weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5)); self::register("weighted_pressure_plate_heavy", fn(BID $id) => new WeightedPressurePlateHeavy( $id, "Weighted Pressure Plate Heavy", @@ -1606,7 +1606,7 @@ final class VanillaBlocks{ $prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : ""); self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $blackstoneBreakInfo)); self::register("polished_blackstone_button", fn(BID $id) => new StoneButton($id, $prefix("Button"), new Info(BreakInfo::pickaxe(0.5)))); - self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)), 20)); + self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5)), 20)); self::register("polished_blackstone_slab", fn(BID $id) => new Slab($id, $prefix(""), $slabBreakInfo)); self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo)); self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo)); @@ -1713,9 +1713,8 @@ final class VanillaBlocks{ self::register("cut_copper_stairs", fn(BID $id) => new CopperStairs($id, "Cut Copper Stairs", $copperBreakInfo)); self::register("copper_bulb", fn(BID $id) => new CopperBulb($id, "Copper Bulb", $copperBreakInfo)); - $copperDoorBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0)); - self::register("copper_door", fn(BID $id) => new CopperDoor($id, "Copper Door", $copperDoorBreakInfo)); - self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", $copperDoorBreakInfo)); + self::register("copper_door", fn(BID $id) => new CopperDoor($id, "Copper Door", new Info(BreakInfo::pickaxe(3.0, blastResistance: 30.0)))); + self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0)))); $candleBreakInfo = new Info(new BreakInfo(0.1)); self::register("candle", fn(BID $id) => new Candle($id, "Candle", $candleBreakInfo)); From 8f8fe948c1ebbbe07f1451ff4505c1cd7ab614d5 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 15 Dec 2024 16:26:39 +0000 Subject: [PATCH 034/334] MemoryManager: Control when cycle garbage collection is run (#6554) This PR replicates the mechanism by which PHP's own GC is triggered: using a dynamically adjusted threshold based on the number of roots and the number of destroyed cycles. This approach was chosen to minimize behavioural changes. This currently only applies to the main thread. Doing this for other threads is a bit more complicated (and in the case of RakLib, possibly not necessary anyway). By doing this, we can get more accurate performance profiling. Instead of GC happening in random pathways and throwing off GC numbers, we trigger it in a predictable place, where timings can record it. This change may also produce minor performance improvements in code touching lots of objects (such as `CraftingDataPacket` encoding`), which previously might've triggered multiple GC runs within a single tick. Now that GC runs wait for `MemoryManager`, it can touch as many objects as it wants during a tick without paying a performance penalty. While working on this change I came across a number of issues that should probably be addressed in the future: 1) Objects like Server, World and Player that can't possibly be GC'd repeatedly end up in the GC root buffer because the refcounts fluctuate frequently. Because of the dependency chains in these objects, they all drag each other into GC, causing an almost guaranteed parasitic performance cost to GC. This is discussed in php/php-src#17131, as the proper solution to this is probably generational GC, or perhaps some way to explicitly mark objects to be ignored by GC. 2) World's `blockCache` blows up the GC root threshold due to poor size management. This leads to infrequent, but extremely expensive GC runs due to the sheer number of objects being scanned. We could avoid a lot of this cost by managing caches like this more effectively. 3) StringToItemParser and many of the pocketmine\data classes which make heavy use of closures are afflicted by thousands of reference cycles. This doesn't present a major performance issue in most cases because the cycles are simple, but this could easily be fixed with some simple refactors. --- src/MemoryManager.php | 47 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/MemoryManager.php b/src/MemoryManager.php index 4308167d3..638a2613a 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -41,11 +41,12 @@ use function fopen; use function fwrite; use function gc_collect_cycles; use function gc_disable; -use function gc_enable; use function gc_mem_caches; +use function gc_status; use function get_class; use function get_declared_classes; use function get_defined_functions; +use function hrtime; use function ini_get; use function ini_set; use function intdiv; @@ -55,9 +56,11 @@ use function is_object; use function is_resource; use function is_string; use function json_encode; +use function max; use function mb_strtoupper; use function min; use function mkdir; +use function number_format; use function preg_match; use function print_r; use function round; @@ -75,6 +78,14 @@ class MemoryManager{ private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2; private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND; + //These constants are copied from Zend/zend_gc.c as of PHP 8.3.14 + //TODO: These values could be adjusted to better suit PM, but for now we just want to mirror PHP GC to minimize + //behavioural changes. + private const GC_THRESHOLD_TRIGGER = 100; + private const GC_THRESHOLD_MAX = 1_000_000_000; + private const GC_THRESHOLD_DEFAULT = 10_001; + private const GC_THRESHOLD_STEP = 10_000; + private int $memoryLimit; private int $globalMemoryLimit; private int $checkRate; @@ -91,6 +102,9 @@ class MemoryManager{ private bool $garbageCollectionTrigger; private bool $garbageCollectionAsync; + private int $cycleCollectionThreshold = self::GC_THRESHOLD_DEFAULT; + private int $cycleCollectionTimeTotalNs = 0; + private int $lowMemChunkRadiusOverride; private bool $lowMemChunkGC; @@ -107,6 +121,7 @@ class MemoryManager{ $this->logger = new \PrefixedLogger($server->getLogger(), "Memory Manager"); $this->init($server->getConfigGroup()); + gc_disable(); } private function init(ServerConfigGroup $config) : void{ @@ -152,7 +167,6 @@ class MemoryManager{ $this->lowMemClearWorldCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER, true); $this->dumpWorkers = $config->getPropertyBool(Yml::MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER, true); - gc_enable(); } public function isLowMemory() : bool{ @@ -204,6 +218,18 @@ class MemoryManager{ $this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2))); } + private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{ + //TODO Very simple heuristic for dynamic GC buffer resizing: + //If there are "too few" collections, increase the collection threshold + //by a fixed step + //Adapted from zend_gc.c/gc_adjust_threshold() as of PHP 8.3.14 + if($cyclesCollected < self::GC_THRESHOLD_TRIGGER || $rootsAfterGC >= $this->cycleCollectionThreshold){ + $this->cycleCollectionThreshold = min(self::GC_THRESHOLD_MAX, $this->cycleCollectionThreshold + self::GC_THRESHOLD_STEP); + }elseif($this->cycleCollectionThreshold > self::GC_THRESHOLD_DEFAULT){ + $this->cycleCollectionThreshold = max(self::GC_THRESHOLD_DEFAULT, $this->cycleCollectionThreshold - self::GC_THRESHOLD_STEP); + } + } + /** * Called every tick to update the memory manager state. */ @@ -236,9 +262,25 @@ class MemoryManager{ } } + $rootsBefore = gc_status()["roots"]; if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){ $this->garbageCollectionTicker = 0; $this->triggerGarbageCollector(); + }elseif($rootsBefore >= $this->cycleCollectionThreshold){ + Timings::$garbageCollector->startTiming(); + + $start = hrtime(true); + $cycles = gc_collect_cycles(); + $end = hrtime(true); + + $rootsAfter = gc_status()["roots"]; + $this->adjustGcThreshold($cycles, $rootsAfter); + + Timings::$garbageCollector->stopTiming(); + + $time = $end - $start; + $this->cycleCollectionTimeTotalNs += $time; + $this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->cycleCollectionTimeTotalNs) . " ns"); } Timings::$memoryManager->stopTiming(); @@ -465,7 +507,6 @@ class MemoryManager{ $logger->info("Finished!"); ini_set('memory_limit', $hardLimit); - gc_enable(); } /** From 0aa6cde259e550cd3259e7bdfdaa660031e1a752 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 16:41:54 +0000 Subject: [PATCH 035/334] Remove stupid MemoryManager settings No one in their right mind is going to change the defaults for these anyway. All this crap does is overwhelm users with stuff they don't understand. Most of this stuff has no business being modified by non-developers anyway. --- resources/pocketmine.yml | 16 ------------ src/MemoryManager.php | 50 ++++++++++++------------------------- src/YmlServerProperties.php | 6 ----- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/resources/pocketmine.yml b/resources/pocketmine.yml index 408b5b95b..531924973 100644 --- a/resources/pocketmine.yml +++ b/resources/pocketmine.yml @@ -54,12 +54,6 @@ memory: #This only affects the main thread. Other threads should fire their own collections period: 36000 - #Fire asynchronous tasks to collect garbage from workers - collect-async-worker: true - - #Trigger on low memory - low-memory-trigger: true - #Settings controlling memory dump handling. memory-dump: #Dump memory from async workers as well as the main thread. If you have issues with segfaults when dumping memory, disable this setting. @@ -69,16 +63,6 @@ memory: #Cap maximum render distance per player when low memory is triggered. Set to 0 to disable cap. chunk-radius: 4 - #Do chunk garbage collection on trigger - trigger-chunk-collect: true - - world-caches: - #Disallow adding to world chunk-packet caches when memory is low - disable-chunk-cache: true - #Clear world caches when memory is low - low-memory-trigger: true - - network: #Threshold for batching packets, in bytes. Only these packets will be compressed #Set to 0 to compress everything, -1 to disable. diff --git a/src/MemoryManager.php b/src/MemoryManager.php index 638a2613a..da520d077 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -99,17 +99,11 @@ class MemoryManager{ private int $garbageCollectionPeriod; private int $garbageCollectionTicker = 0; - private bool $garbageCollectionTrigger; - private bool $garbageCollectionAsync; private int $cycleCollectionThreshold = self::GC_THRESHOLD_DEFAULT; private int $cycleCollectionTimeTotalNs = 0; private int $lowMemChunkRadiusOverride; - private bool $lowMemChunkGC; - - private bool $lowMemDisableChunkCache; - private bool $lowMemClearWorldCache; private bool $dumpWorkers = true; @@ -157,14 +151,8 @@ class MemoryManager{ $this->continuousTriggerRate = $config->getPropertyInt(Yml::MEMORY_CONTINUOUS_TRIGGER_RATE, self::DEFAULT_CONTINUOUS_TRIGGER_RATE); $this->garbageCollectionPeriod = $config->getPropertyInt(Yml::MEMORY_GARBAGE_COLLECTION_PERIOD, self::DEFAULT_TICKS_PER_GC); - $this->garbageCollectionTrigger = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER, true); - $this->garbageCollectionAsync = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER, true); $this->lowMemChunkRadiusOverride = $config->getPropertyInt(Yml::MEMORY_MAX_CHUNKS_CHUNK_RADIUS, 4); - $this->lowMemChunkGC = $config->getPropertyBool(Yml::MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT, true); - - $this->lowMemDisableChunkCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE, true); - $this->lowMemClearWorldCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER, true); $this->dumpWorkers = $config->getPropertyBool(Yml::MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER, true); } @@ -177,8 +165,11 @@ class MemoryManager{ return $this->globalMemoryLimit; } + /** + * @deprecated + */ public function canUseChunkCache() : bool{ - return !$this->lowMemory || !$this->lowMemDisableChunkCache; + return !$this->lowMemory; } /** @@ -194,26 +185,19 @@ class MemoryManager{ public function trigger(int $memory, int $limit, bool $global = false, int $triggerCount = 0) : void{ $this->logger->debug(sprintf("%sLow memory triggered, limit %gMB, using %gMB", $global ? "Global " : "", round(($limit / 1024) / 1024, 2), round(($memory / 1024) / 1024, 2))); - if($this->lowMemClearWorldCache){ - foreach($this->server->getWorldManager()->getWorlds() as $world){ - $world->clearCache(true); - } - ChunkCache::pruneCaches(); + foreach($this->server->getWorldManager()->getWorlds() as $world){ + $world->clearCache(true); } + ChunkCache::pruneCaches(); - if($this->lowMemChunkGC){ - foreach($this->server->getWorldManager()->getWorlds() as $world){ - $world->doChunkGarbageCollection(); - } + foreach($this->server->getWorldManager()->getWorlds() as $world){ + $world->doChunkGarbageCollection(); } $ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount); $ev->call(); - $cycles = 0; - if($this->garbageCollectionTrigger){ - $cycles = $this->triggerGarbageCollector(); - } + $cycles = $this->triggerGarbageCollector(); $this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2))); } @@ -289,14 +273,12 @@ class MemoryManager{ public function triggerGarbageCollector() : int{ Timings::$garbageCollector->startTiming(); - if($this->garbageCollectionAsync){ - $pool = $this->server->getAsyncPool(); - if(($w = $pool->shutdownUnusedWorkers()) > 0){ - $this->logger->debug("Shut down $w idle async pool workers"); - } - foreach($pool->getRunningWorkers() as $i){ - $pool->submitTaskToWorker(new GarbageCollectionTask(), $i); - } + $pool = $this->server->getAsyncPool(); + if(($w = $pool->shutdownUnusedWorkers()) > 0){ + $this->logger->debug("Shut down $w idle async pool workers"); + } + foreach($pool->getRunningWorkers() as $i){ + $pool->submitTaskToWorker(new GarbageCollectionTask(), $i); } $cycles = gc_collect_cycles(); diff --git a/src/YmlServerProperties.php b/src/YmlServerProperties.php index 9bd203eef..282b0b3cd 100644 --- a/src/YmlServerProperties.php +++ b/src/YmlServerProperties.php @@ -75,20 +75,14 @@ final class YmlServerProperties{ public const MEMORY_CONTINUOUS_TRIGGER = 'memory.continuous-trigger'; public const MEMORY_CONTINUOUS_TRIGGER_RATE = 'memory.continuous-trigger-rate'; public const MEMORY_GARBAGE_COLLECTION = 'memory.garbage-collection'; - public const MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER = 'memory.garbage-collection.collect-async-worker'; - public const MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER = 'memory.garbage-collection.low-memory-trigger'; public const MEMORY_GARBAGE_COLLECTION_PERIOD = 'memory.garbage-collection.period'; public const MEMORY_GLOBAL_LIMIT = 'memory.global-limit'; public const MEMORY_MAIN_HARD_LIMIT = 'memory.main-hard-limit'; public const MEMORY_MAIN_LIMIT = 'memory.main-limit'; public const MEMORY_MAX_CHUNKS = 'memory.max-chunks'; public const MEMORY_MAX_CHUNKS_CHUNK_RADIUS = 'memory.max-chunks.chunk-radius'; - public const MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT = 'memory.max-chunks.trigger-chunk-collect'; public const MEMORY_MEMORY_DUMP = 'memory.memory-dump'; public const MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER = 'memory.memory-dump.dump-async-worker'; - public const MEMORY_WORLD_CACHES = 'memory.world-caches'; - public const MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE = 'memory.world-caches.disable-chunk-cache'; - public const MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER = 'memory.world-caches.low-memory-trigger'; public const NETWORK = 'network'; public const NETWORK_ASYNC_COMPRESSION = 'network.async-compression'; public const NETWORK_ASYNC_COMPRESSION_THRESHOLD = 'network.async-compression-threshold'; From cf1b360a62d986661ea4a9b5e00a7d5ec202ced2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 18:40:32 +0000 Subject: [PATCH 036/334] World: Prevent block cache from getting too big This has been a long-standing issue since at least 2016, and probably longer. Heavy use of getBlock(At) could cause the cache to blow up and use all available memory. Recently, it's become clear that unmanaged cache size is also a problem for GC, because the large number of objects blows up the GC root buffer. At first, this causes more frequent GC runs; later, the frequency of GC runs drops, but the performance cost of them goes up substantially because of the sheer number of objects. We can avoid this by trimming the cache when we detect that it's exceeded limits. I've implemented this in such a way that failing to update blockCacheSize in new code won't have lasting impacts, since the cache count will be recalculated during scheduled cache cleaning anyway. Closes #152. --- src/world/World.php | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/world/World.php b/src/world/World.php index ff65377c0..c2d153921 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -167,6 +167,9 @@ class World implements ChunkManager{ public const DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK = 3; + //TODO: this could probably do with being a lot bigger + private const BLOCK_CACHE_SIZE_CAP = 2048; + /** * @var Player[] entity runtime ID => Player * @phpstan-var array @@ -202,6 +205,7 @@ class World implements ChunkManager{ * @phpstan-var array> */ private array $blockCache = []; + private int $blockCacheSize = 0; /** * @var AxisAlignedBB[][][] chunkHash => [relativeBlockHash => AxisAlignedBB[]] * @phpstan-var array>> @@ -653,6 +657,7 @@ class World implements ChunkManager{ $this->provider->close(); $this->blockCache = []; + $this->blockCacheSize = 0; $this->blockCollisionBoxCache = []; $this->unloaded = true; @@ -1138,13 +1143,16 @@ class World implements ChunkManager{ public function clearCache(bool $force = false) : void{ if($force){ $this->blockCache = []; + $this->blockCacheSize = 0; $this->blockCollisionBoxCache = []; }else{ - $count = 0; + //Recalculate this when we're asked - blockCacheSize may be higher than the real size + $this->blockCacheSize = 0; foreach($this->blockCache as $list){ - $count += count($list); - if($count > 2048){ + $this->blockCacheSize += count($list); + if($this->blockCacheSize > self::BLOCK_CACHE_SIZE_CAP){ $this->blockCache = []; + $this->blockCacheSize = 0; break; } } @@ -1152,7 +1160,7 @@ class World implements ChunkManager{ $count = 0; foreach($this->blockCollisionBoxCache as $list){ $count += count($list); - if($count > 2048){ + if($count > self::BLOCK_CACHE_SIZE_CAP){ //TODO: Is this really the best logic? $this->blockCollisionBoxCache = []; break; @@ -1161,6 +1169,19 @@ class World implements ChunkManager{ } } + private function trimBlockCache() : void{ + $before = $this->blockCacheSize; + //Since PHP maintains key order, earliest in foreach should be the oldest entries + //Older entries are less likely to be hot, so destroying these should usually have the lowest impact on performance + foreach($this->blockCache as $chunkHash => $blocks){ + unset($this->blockCache[$chunkHash]); + $this->blockCacheSize -= count($blocks); + if($this->blockCacheSize < self::BLOCK_CACHE_SIZE_CAP){ + break; + } + } + } + /** * @return true[] fullID => dummy * @phpstan-return array @@ -1921,6 +1942,10 @@ class World implements ChunkManager{ if($addToCache && $relativeBlockHash !== null){ $this->blockCache[$chunkHash][$relativeBlockHash] = $block; + + if(++$this->blockCacheSize >= self::BLOCK_CACHE_SIZE_CAP){ + $this->trimBlockCache(); + } } return $block; @@ -1967,6 +1992,7 @@ class World implements ChunkManager{ $relativeBlockHash = World::chunkBlockHash($x, $y, $z); unset($this->blockCache[$chunkHash][$relativeBlockHash]); + $this->blockCacheSize--; 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 @@ -2570,6 +2596,7 @@ class World implements ChunkManager{ $this->chunks[$chunkHash] = $chunk; + $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); @@ -2854,6 +2881,8 @@ class World implements ChunkManager{ $this->logger->debug("Chunk $x $z has been upgraded, will be saved at the next autosave opportunity"); } $this->chunks[$chunkHash] = $chunk; + + $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); @@ -3013,6 +3042,7 @@ class World implements ChunkManager{ } unset($this->chunks[$chunkHash]); + $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); From 742aa46b88b4446d7922f8f1a30bd8db672516fd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 20:44:00 +0000 Subject: [PATCH 037/334] Separate memory dumping utilities from MemoryManager --- src/MemoryDump.php | 299 +++++++++++++++++++++++++ src/MemoryManager.php | 264 +--------------------- src/scheduler/DumpWorkerMemoryTask.php | 4 +- 3 files changed, 305 insertions(+), 262 deletions(-) create mode 100644 src/MemoryDump.php diff --git a/src/MemoryDump.php b/src/MemoryDump.php new file mode 100644 index 000000000..5f721e041 --- /dev/null +++ b/src/MemoryDump.php @@ -0,0 +1,299 @@ +getProperties() as $property){ + if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){ + continue; + } + + if(!$property->isInitialized()){ + continue; + } + + $staticCount++; + $staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + + if(count($staticProperties[$className]) === 0){ + unset($staticProperties[$className]); + } + + foreach($reflection->getMethods() as $method){ + if($method->getDeclaringClass()->getName() !== $reflection->getName()){ + continue; + } + $methodStatics = []; + foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){ + $methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + if(count($methodStatics) > 0){ + $functionStaticVars[$className . "::" . $method->getName()] = $methodStatics; + $functionStaticVarsCount += count($functionStaticVars); + } + } + } + + file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + $logger->info("Wrote $staticCount static properties"); + + $globalVariables = []; + $globalCount = 0; + + $ignoredGlobals = [ + 'GLOBALS' => true, + '_SERVER' => true, + '_REQUEST' => true, + '_POST' => true, + '_GET' => true, + '_FILES' => true, + '_ENV' => true, + '_COOKIE' => true, + '_SESSION' => true + ]; + + foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){ + if(isset($ignoredGlobals[$varName])){ + continue; + } + + $globalCount++; + $globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + + file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + $logger->info("Wrote $globalCount global variables"); + + foreach(get_defined_functions()["user"] as $function){ + $reflect = new \ReflectionFunction($function); + + $vars = []; + foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){ + $vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + if(count($vars) > 0){ + $functionStaticVars[$function] = $vars; + $functionStaticVarsCount += count($vars); + } + } + file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + $logger->info("Wrote $functionStaticVarsCount function static variables"); + + $data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + + do{ + $continue = false; + foreach(Utils::stringifyKeys($objects) as $hash => $object){ + if(!is_object($object)){ + continue; + } + $continue = true; + + $className = get_class($object); + if(!isset($instanceCounts[$className])){ + $instanceCounts[$className] = 1; + }else{ + $instanceCounts[$className]++; + } + + $objects[$hash] = true; + $info = [ + "information" => "$hash@$className", + ]; + if($object instanceof \Closure){ + $info["definition"] = Utils::getNiceClosureName($object); + $info["referencedVars"] = []; + $reflect = new \ReflectionFunction($object); + if(($closureThis = $reflect->getClosureThis()) !== null){ + $info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + + foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){ + $info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + }else{ + $reflection = new \ReflectionObject($object); + + $info["properties"] = []; + + for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){ + foreach($reflection->getProperties() as $property){ + if($property->isStatic()){ + continue; + } + + $name = $property->getName(); + if($reflection !== $original){ + if($property->isPrivate()){ + $name = $reflection->getName() . ":" . $name; + }else{ + continue; + } + } + if(!$property->isInitialized($object)){ + continue; + } + + $info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + } + } + + fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n"); + } + + }while($continue); + + $logger->info("Wrote " . count($objects) . " objects"); + + fclose($obData); + + file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + + arsort($instanceCounts, SORT_NUMERIC); + file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + + $logger->info("Finished!"); + + ini_set('memory_limit', $hardLimit); + } + + /** + * @param object[] $objects reference parameter + * @param int[] $refCounts reference parameter + * + * @phpstan-param array $objects + * @phpstan-param array $refCounts + * @phpstan-param-out array $objects + * @phpstan-param-out array $refCounts + */ + private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{ + if($maxNesting <= 0){ + return "(error) NESTING LIMIT REACHED"; + } + + --$maxNesting; + + if(is_object($from)){ + if(!isset($objects[$hash = spl_object_hash($from)])){ + $objects[$hash] = $from; + $refCounts[$hash] = 0; + } + + ++$refCounts[$hash]; + + $data = "(object) $hash"; + }elseif(is_array($from)){ + if($recursion >= 5){ + return "(error) ARRAY RECURSION LIMIT REACHED"; + } + $data = []; + $numeric = 0; + foreach(Utils::promoteKeys($from) as $key => $value){ + $data[$numeric] = [ + "k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), + "v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), + ]; + $numeric++; + } + }elseif(is_string($from)){ + $data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize); + }elseif(is_resource($from)){ + $data = "(resource) " . print_r($from, true); + }elseif(is_float($from)){ + $data = "(float) $from"; + }else{ + $data = $from; + } + + return $data; + } +} diff --git a/src/MemoryManager.php b/src/MemoryManager.php index da520d077..b83e99fa5 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -29,49 +29,21 @@ use pocketmine\scheduler\DumpWorkerMemoryTask; use pocketmine\scheduler\GarbageCollectionTask; use pocketmine\timings\Timings; use pocketmine\utils\Process; -use pocketmine\utils\Utils; use pocketmine\YmlServerProperties as Yml; -use Symfony\Component\Filesystem\Path; -use function arsort; -use function count; -use function fclose; -use function file_exists; -use function file_put_contents; -use function fopen; -use function fwrite; use function gc_collect_cycles; use function gc_disable; use function gc_mem_caches; use function gc_status; -use function get_class; -use function get_declared_classes; -use function get_defined_functions; use function hrtime; -use function ini_get; use function ini_set; use function intdiv; -use function is_array; -use function is_float; -use function is_object; -use function is_resource; -use function is_string; -use function json_encode; use function max; use function mb_strtoupper; use function min; -use function mkdir; use function number_format; use function preg_match; -use function print_r; use function round; -use function spl_object_hash; use function sprintf; -use function strlen; -use function substr; -use const JSON_PRETTY_PRINT; -use const JSON_THROW_ON_ERROR; -use const JSON_UNESCAPED_SLASHES; -use const SORT_NUMERIC; class MemoryManager{ private const DEFAULT_CHECK_RATE = Server::TARGET_TICKS_PER_SECOND; @@ -295,7 +267,7 @@ class MemoryManager{ public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize) : void{ $logger = new \PrefixedLogger($this->server->getLogger(), "Memory Dump"); $logger->notice("After the memory dump is done, the server might crash"); - self::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger); + MemoryDump::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger); if($this->dumpWorkers){ $pool = $this->server->getAsyncPool(); @@ -307,238 +279,10 @@ class MemoryManager{ /** * Static memory dumper accessible from any thread. + * @deprecated + * @see MemoryDump */ public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{ - $hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist"); - ini_set('memory_limit', '-1'); - gc_disable(); - - if(!file_exists($outputFolder)){ - mkdir($outputFolder, 0777, true); - } - - $obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+")); - - $objects = []; - - $refCounts = []; - - $instanceCounts = []; - - $staticProperties = []; - $staticCount = 0; - - $functionStaticVars = []; - $functionStaticVarsCount = 0; - - foreach(get_declared_classes() as $className){ - $reflection = new \ReflectionClass($className); - $staticProperties[$className] = []; - foreach($reflection->getProperties() as $property){ - if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){ - continue; - } - - if(!$property->isInitialized()){ - continue; - } - - $staticCount++; - $staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - - if(count($staticProperties[$className]) === 0){ - unset($staticProperties[$className]); - } - - foreach($reflection->getMethods() as $method){ - if($method->getDeclaringClass()->getName() !== $reflection->getName()){ - continue; - } - $methodStatics = []; - foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){ - $methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - if(count($methodStatics) > 0){ - $functionStaticVars[$className . "::" . $method->getName()] = $methodStatics; - $functionStaticVarsCount += count($functionStaticVars); - } - } - } - - file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - $logger->info("Wrote $staticCount static properties"); - - $globalVariables = []; - $globalCount = 0; - - $ignoredGlobals = [ - 'GLOBALS' => true, - '_SERVER' => true, - '_REQUEST' => true, - '_POST' => true, - '_GET' => true, - '_FILES' => true, - '_ENV' => true, - '_COOKIE' => true, - '_SESSION' => true - ]; - - foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){ - if(isset($ignoredGlobals[$varName])){ - continue; - } - - $globalCount++; - $globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - - file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - $logger->info("Wrote $globalCount global variables"); - - foreach(get_defined_functions()["user"] as $function){ - $reflect = new \ReflectionFunction($function); - - $vars = []; - foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){ - $vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - if(count($vars) > 0){ - $functionStaticVars[$function] = $vars; - $functionStaticVarsCount += count($vars); - } - } - file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - $logger->info("Wrote $functionStaticVarsCount function static variables"); - - $data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - - do{ - $continue = false; - foreach(Utils::stringifyKeys($objects) as $hash => $object){ - if(!is_object($object)){ - continue; - } - $continue = true; - - $className = get_class($object); - if(!isset($instanceCounts[$className])){ - $instanceCounts[$className] = 1; - }else{ - $instanceCounts[$className]++; - } - - $objects[$hash] = true; - $info = [ - "information" => "$hash@$className", - ]; - if($object instanceof \Closure){ - $info["definition"] = Utils::getNiceClosureName($object); - $info["referencedVars"] = []; - $reflect = new \ReflectionFunction($object); - if(($closureThis = $reflect->getClosureThis()) !== null){ - $info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - - foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){ - $info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - }else{ - $reflection = new \ReflectionObject($object); - - $info["properties"] = []; - - for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){ - foreach($reflection->getProperties() as $property){ - if($property->isStatic()){ - continue; - } - - $name = $property->getName(); - if($reflection !== $original){ - if($property->isPrivate()){ - $name = $reflection->getName() . ":" . $name; - }else{ - continue; - } - } - if(!$property->isInitialized($object)){ - continue; - } - - $info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - } - } - - fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n"); - } - - }while($continue); - - $logger->info("Wrote " . count($objects) . " objects"); - - fclose($obData); - - file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - - arsort($instanceCounts, SORT_NUMERIC); - file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - - $logger->info("Finished!"); - - ini_set('memory_limit', $hardLimit); - } - - /** - * @param object[] $objects reference parameter - * @param int[] $refCounts reference parameter - * - * @phpstan-param array $objects - * @phpstan-param array $refCounts - * @phpstan-param-out array $objects - * @phpstan-param-out array $refCounts - */ - private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{ - if($maxNesting <= 0){ - return "(error) NESTING LIMIT REACHED"; - } - - --$maxNesting; - - if(is_object($from)){ - if(!isset($objects[$hash = spl_object_hash($from)])){ - $objects[$hash] = $from; - $refCounts[$hash] = 0; - } - - ++$refCounts[$hash]; - - $data = "(object) $hash"; - }elseif(is_array($from)){ - if($recursion >= 5){ - return "(error) ARRAY RECURSION LIMIT REACHED"; - } - $data = []; - $numeric = 0; - foreach(Utils::promoteKeys($from) as $key => $value){ - $data[$numeric] = [ - "k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), - "v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), - ]; - $numeric++; - } - }elseif(is_string($from)){ - $data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize); - }elseif(is_resource($from)){ - $data = "(resource) " . print_r($from, true); - }elseif(is_float($from)){ - $data = "(float) $from"; - }else{ - $data = $from; - } - - return $data; + MemoryDump::dumpMemory($startingObject, $outputFolder, $maxNesting, $maxStringSize, $logger); } } diff --git a/src/scheduler/DumpWorkerMemoryTask.php b/src/scheduler/DumpWorkerMemoryTask.php index 5ef787b5b..fac8d6368 100644 --- a/src/scheduler/DumpWorkerMemoryTask.php +++ b/src/scheduler/DumpWorkerMemoryTask.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\scheduler; use pmmp\thread\Thread as NativeThread; -use pocketmine\MemoryManager; +use pocketmine\MemoryDump; use Symfony\Component\Filesystem\Path; use function assert; @@ -41,7 +41,7 @@ class DumpWorkerMemoryTask extends AsyncTask{ public function onRun() : void{ $worker = NativeThread::getCurrentThread(); assert($worker instanceof AsyncWorker); - MemoryManager::dumpMemory( + MemoryDump::dumpMemory( $worker, Path::join($this->outputFolder, "AsyncWorker#" . $worker->getAsyncWorkerId()), $this->maxNesting, From 45482e868de8100d4eeaf27ec44f3f2d0ed4a5e9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 20:45:51 +0000 Subject: [PATCH 038/334] Fixed AsyncWorker GC not getting re-enabled after memory dump async workers still use automatic GC for now. We should probably switch to manual GC at some point, but it's not a priority right now. --- src/MemoryDump.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/MemoryDump.php b/src/MemoryDump.php index 5f721e041..0503f747c 100644 --- a/src/MemoryDump.php +++ b/src/MemoryDump.php @@ -33,6 +33,7 @@ use function file_put_contents; use function fopen; use function fwrite; use function gc_disable; +use function gc_enabled; use function get_class; use function get_declared_classes; use function get_defined_functions; @@ -66,6 +67,7 @@ final class MemoryDump{ public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{ $hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist"); ini_set('memory_limit', '-1'); + $gcEnabled = gc_enabled(); gc_disable(); if(!file_exists($outputFolder)){ @@ -244,6 +246,9 @@ final class MemoryDump{ $logger->info("Finished!"); ini_set('memory_limit', $hardLimit); + if($gcEnabled){ + gc_enable(); + } } /** From 8f536e6f214c7192ca1bbc2b3182613ed99e9a2a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 20:46:10 +0000 Subject: [PATCH 039/334] always the CS --- src/MemoryDump.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MemoryDump.php b/src/MemoryDump.php index 0503f747c..5a9c274cb 100644 --- a/src/MemoryDump.php +++ b/src/MemoryDump.php @@ -33,6 +33,7 @@ use function file_put_contents; use function fopen; use function fwrite; use function gc_disable; +use function gc_enable; use function gc_enabled; use function get_class; use function get_declared_classes; From 42f90e94ffb7cdd30162eb450be073e2be6e5a06 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 21:25:32 +0000 Subject: [PATCH 040/334] AsyncWorker now manually triggers GC at the end of each task run, similar to the main thread this avoids costly GC runs during hot code. --- src/GarbageCollectorManager.php | 102 +++++++++++++++++++++++ src/MemoryManager.php | 48 +---------- src/network/mcpe/raklib/RakLibServer.php | 4 + src/scheduler/AsyncTask.php | 1 + src/scheduler/AsyncWorker.php | 13 ++- 5 files changed, 121 insertions(+), 47 deletions(-) create mode 100644 src/GarbageCollectorManager.php diff --git a/src/GarbageCollectorManager.php b/src/GarbageCollectorManager.php new file mode 100644 index 000000000..c5cd1f9ed --- /dev/null +++ b/src/GarbageCollectorManager.php @@ -0,0 +1,102 @@ +logger = new \PrefixedLogger($logger, "Cyclic Garbage Collector"); + $this->timings = new TimingsHandler("Cyclic Garbage Collector"); + } + + private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{ + //TODO Very simple heuristic for dynamic GC buffer resizing: + //If there are "too few" collections, increase the collection threshold + //by a fixed step + //Adapted from zend_gc.c/gc_adjust_threshold() as of PHP 8.3.14 + if($cyclesCollected < self::GC_THRESHOLD_TRIGGER || $rootsAfterGC >= $this->threshold){ + $this->threshold = min(self::GC_THRESHOLD_MAX, $this->threshold + self::GC_THRESHOLD_STEP); + }elseif($this->threshold > self::GC_THRESHOLD_DEFAULT){ + $this->threshold = max(self::GC_THRESHOLD_DEFAULT, $this->threshold - self::GC_THRESHOLD_STEP); + } + } + + public function getThreshold() : int{ return $this->threshold; } + + public function getCollectionTimeTotalNs() : int{ return $this->collectionTimeTotalNs; } + + public function maybeCollectCycles() : int{ + $rootsBefore = gc_status()["roots"]; + if($rootsBefore < $this->threshold){ + return 0; + } + + $this->timings->startTiming(); + + $start = hrtime(true); + $cycles = gc_collect_cycles(); + $end = hrtime(true); + + $rootsAfter = gc_status()["roots"]; + $this->adjustGcThreshold($cycles, $rootsAfter); + + $this->timings->stopTiming(); + + $time = $end - $start; + $this->collectionTimeTotalNs += $time; + $this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->collectionTimeTotalNs) . " ns"); + + return $cycles; + } +} diff --git a/src/MemoryManager.php b/src/MemoryManager.php index b83e99fa5..f541514dd 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -31,16 +31,11 @@ use pocketmine\timings\Timings; use pocketmine\utils\Process; use pocketmine\YmlServerProperties as Yml; use function gc_collect_cycles; -use function gc_disable; use function gc_mem_caches; -use function gc_status; -use function hrtime; use function ini_set; use function intdiv; -use function max; use function mb_strtoupper; use function min; -use function number_format; use function preg_match; use function round; use function sprintf; @@ -50,13 +45,7 @@ class MemoryManager{ private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2; private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND; - //These constants are copied from Zend/zend_gc.c as of PHP 8.3.14 - //TODO: These values could be adjusted to better suit PM, but for now we just want to mirror PHP GC to minimize - //behavioural changes. - private const GC_THRESHOLD_TRIGGER = 100; - private const GC_THRESHOLD_MAX = 1_000_000_000; - private const GC_THRESHOLD_DEFAULT = 10_001; - private const GC_THRESHOLD_STEP = 10_000; + private GarbageCollectorManager $cycleGcManager; private int $memoryLimit; private int $globalMemoryLimit; @@ -72,9 +61,6 @@ class MemoryManager{ private int $garbageCollectionPeriod; private int $garbageCollectionTicker = 0; - private int $cycleCollectionThreshold = self::GC_THRESHOLD_DEFAULT; - private int $cycleCollectionTimeTotalNs = 0; - private int $lowMemChunkRadiusOverride; private bool $dumpWorkers = true; @@ -85,9 +71,9 @@ class MemoryManager{ private Server $server ){ $this->logger = new \PrefixedLogger($server->getLogger(), "Memory Manager"); + $this->cycleGcManager = new GarbageCollectorManager($this->logger); $this->init($server->getConfigGroup()); - gc_disable(); } private function init(ServerConfigGroup $config) : void{ @@ -174,18 +160,6 @@ class MemoryManager{ $this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2))); } - private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{ - //TODO Very simple heuristic for dynamic GC buffer resizing: - //If there are "too few" collections, increase the collection threshold - //by a fixed step - //Adapted from zend_gc.c/gc_adjust_threshold() as of PHP 8.3.14 - if($cyclesCollected < self::GC_THRESHOLD_TRIGGER || $rootsAfterGC >= $this->cycleCollectionThreshold){ - $this->cycleCollectionThreshold = min(self::GC_THRESHOLD_MAX, $this->cycleCollectionThreshold + self::GC_THRESHOLD_STEP); - }elseif($this->cycleCollectionThreshold > self::GC_THRESHOLD_DEFAULT){ - $this->cycleCollectionThreshold = max(self::GC_THRESHOLD_DEFAULT, $this->cycleCollectionThreshold - self::GC_THRESHOLD_STEP); - } - } - /** * Called every tick to update the memory manager state. */ @@ -218,25 +192,11 @@ class MemoryManager{ } } - $rootsBefore = gc_status()["roots"]; if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){ $this->garbageCollectionTicker = 0; $this->triggerGarbageCollector(); - }elseif($rootsBefore >= $this->cycleCollectionThreshold){ - Timings::$garbageCollector->startTiming(); - - $start = hrtime(true); - $cycles = gc_collect_cycles(); - $end = hrtime(true); - - $rootsAfter = gc_status()["roots"]; - $this->adjustGcThreshold($cycles, $rootsAfter); - - Timings::$garbageCollector->stopTiming(); - - $time = $end - $start; - $this->cycleCollectionTimeTotalNs += $time; - $this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->cycleCollectionTimeTotalNs) . " ns"); + }else{ + $this->cycleGcManager->maybeCollectCycles(); } Timings::$memoryManager->stopTiming(); diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index 5137b94ba..3b4b8da74 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -82,7 +82,11 @@ class RakLibServer extends Thread{ } protected function onRun() : void{ + //TODO: switch to manually triggered GC + //the best time to do it is between ticks when the server would otherwise be sleeping, but RakLib's current + //design doesn't allow this as of 1.1.1 gc_enable(); + ini_set("display_errors", '1'); ini_set("display_startup_errors", '1'); \GlobalLogger::set($this->logger); diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index 1175b6fc4..a9b466f40 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -93,6 +93,7 @@ abstract class AsyncTask extends Runnable{ $this->finished = true; AsyncWorker::getNotifier()->wakeupSleeper(); + AsyncWorker::maybeCollectCycles(); } /** diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index 5fdfb1ebb..5f911bc1c 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -24,12 +24,12 @@ declare(strict_types=1); namespace pocketmine\scheduler; use pmmp\thread\Thread as NativeThread; +use pocketmine\GarbageCollectorManager; use pocketmine\snooze\SleeperHandlerEntry; use pocketmine\snooze\SleeperNotifier; use pocketmine\thread\log\ThreadSafeLogger; use pocketmine\thread\Worker; use pocketmine\utils\AssumptionFailedError; -use function gc_enable; use function ini_set; class AsyncWorker extends Worker{ @@ -37,6 +37,7 @@ class AsyncWorker extends Worker{ private static array $store = []; private static ?SleeperNotifier $notifier = null; + private static ?GarbageCollectorManager $cycleGcManager = null; public function __construct( private ThreadSafeLogger $logger, @@ -52,11 +53,16 @@ class AsyncWorker extends Worker{ throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage"); } + public static function maybeCollectCycles() : void{ + if(self::$cycleGcManager === null){ + throw new AssumptionFailedError("GarbageCollectorManager not found in thread-local storage"); + } + self::$cycleGcManager->maybeCollectCycles(); + } + protected function onRun() : void{ \GlobalLogger::set($this->logger); - gc_enable(); - if($this->memoryLimit > 0){ ini_set('memory_limit', $this->memoryLimit . 'M'); $this->logger->debug("Set memory limit to " . $this->memoryLimit . " MB"); @@ -66,6 +72,7 @@ class AsyncWorker extends Worker{ } self::$notifier = $this->sleeperEntry->createNotifier(); + self::$cycleGcManager = new GarbageCollectorManager($this->logger); } public function getLogger() : ThreadSafeLogger{ From 80899ea72c0b6a8bdedb606205b83c6775fb8f69 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 15 Dec 2024 21:34:16 +0000 Subject: [PATCH 041/334] Make sure timings are counted under the proper parents --- src/GarbageCollectorManager.php | 3 ++- src/MemoryManager.php | 2 +- src/scheduler/AsyncWorker.php | 3 ++- src/timings/Timings.php | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/GarbageCollectorManager.php b/src/GarbageCollectorManager.php index c5cd1f9ed..a8912a90d 100644 --- a/src/GarbageCollectorManager.php +++ b/src/GarbageCollectorManager.php @@ -54,10 +54,11 @@ final class GarbageCollectorManager{ public function __construct( \Logger $logger, + ?TimingsHandler $parentTimings, ){ gc_disable(); $this->logger = new \PrefixedLogger($logger, "Cyclic Garbage Collector"); - $this->timings = new TimingsHandler("Cyclic Garbage Collector"); + $this->timings = new TimingsHandler("Cyclic Garbage Collector", $parentTimings); } private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{ diff --git a/src/MemoryManager.php b/src/MemoryManager.php index f541514dd..2b7f5f1d3 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -71,7 +71,7 @@ class MemoryManager{ private Server $server ){ $this->logger = new \PrefixedLogger($server->getLogger(), "Memory Manager"); - $this->cycleGcManager = new GarbageCollectorManager($this->logger); + $this->cycleGcManager = new GarbageCollectorManager($this->logger, Timings::$memoryManager); $this->init($server->getConfigGroup()); } diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index 5f911bc1c..e208d427c 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -29,6 +29,7 @@ use pocketmine\snooze\SleeperHandlerEntry; use pocketmine\snooze\SleeperNotifier; use pocketmine\thread\log\ThreadSafeLogger; use pocketmine\thread\Worker; +use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; use function ini_set; @@ -72,7 +73,7 @@ class AsyncWorker extends Worker{ } self::$notifier = $this->sleeperEntry->createNotifier(); - self::$cycleGcManager = new GarbageCollectorManager($this->logger); + self::$cycleGcManager = new GarbageCollectorManager($this->logger, Timings::$asyncTaskWorkers); } public function getLogger() : ThreadSafeLogger{ diff --git a/src/timings/Timings.php b/src/timings/Timings.php index e71700023..ee737f537 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -131,7 +131,7 @@ abstract class Timings{ /** @var TimingsHandler[] */ private static array $asyncTaskError = []; - private static TimingsHandler $asyncTaskWorkers; + public static TimingsHandler $asyncTaskWorkers; /** @var TimingsHandler[] */ private static array $asyncTaskRun = []; From aee358d3296550d777383c7791423f072d6e2cba Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 16 Dec 2024 03:11:07 +0000 Subject: [PATCH 042/334] This timings handler management is a crap design This has bitten me on the ass so many times now --- src/scheduler/AsyncWorker.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index e208d427c..528d632d1 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -73,6 +73,7 @@ class AsyncWorker extends Worker{ } self::$notifier = $this->sleeperEntry->createNotifier(); + Timings::init(); self::$cycleGcManager = new GarbageCollectorManager($this->logger, Timings::$asyncTaskWorkers); } From fea17fa4a99bb7d98a1489100797d21c58b8d95f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 19 Dec 2024 20:33:40 +0000 Subject: [PATCH 043/334] RakLibServer: disable GC GC is not required for RakLib as it doesn't generate any unmanaged cycles. Cycles in general do exist (e.g. Server <-> ServerSession), but these are explicitly cleaned up, so GC wouldn't have any useful work to do. --- src/network/mcpe/raklib/RakLibServer.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index 3b4b8da74..d5e825bee 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -38,7 +38,7 @@ use raklib\server\ServerSocket; use raklib\server\SimpleProtocolAcceptor; use raklib\utils\ExceptionTraceCleaner; use raklib\utils\InternetAddress; -use function gc_enable; +use function gc_disable; use function ini_set; class RakLibServer extends Thread{ @@ -82,10 +82,9 @@ class RakLibServer extends Thread{ } protected function onRun() : void{ - //TODO: switch to manually triggered GC - //the best time to do it is between ticks when the server would otherwise be sleeping, but RakLib's current - //design doesn't allow this as of 1.1.1 - gc_enable(); + //RakLib has cycles (e.g. ServerSession <-> Server) but these cycles are explicitly cleaned up anyway, and are + //very few, so it's pointless to waste CPU time on GC + gc_disable(); ini_set("display_errors", '1'); ini_set("display_startup_errors", '1'); From 506cfe0362b181c16b860aa3b42f3bf619901b4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:39:39 +0000 Subject: [PATCH 044/334] Bump build/php from `5016e0a` to `b1eaaa4` (#6579) Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `5016e0a` to `b1eaaa4`. - [Release notes](https://github.com/pmmp/php-build-scripts/releases) - [Commits](https://github.com/pmmp/php-build-scripts/compare/5016e0a3d54c714c12b331ea0474a6f500ffc0a3...b1eaaa48ecd5bbbcefcf07f7fc183a09845c91c3) --- updated-dependencies: - dependency-name: build/php dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 5016e0a3d..b1eaaa48e 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 5016e0a3d54c714c12b331ea0474a6f500ffc0a3 +Subproject commit b1eaaa48ecd5bbbcefcf07f7fc183a09845c91c3 From 6a1d80e021d0e5d2c8734663c5469b411cb5e53f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Dec 2024 17:41:01 +0000 Subject: [PATCH 045/334] tools/convert-world: fixed some UI issues --- tools/convert-world.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/convert-world.php b/tools/convert-world.php index d4d15ce57..828ccb470 100644 --- a/tools/convert-world.php +++ b/tools/convert-world.php @@ -52,7 +52,7 @@ $writableFormats = array_filter($providerManager->getAvailableProviders(), fn(Wo $requiredOpts = [ "world" => "path to the input world for conversion", "backup" => "path to back up the original files", - "format" => "desired output format (can be one of: " . implode(",", array_keys($writableFormats)) . ")" + "format" => "desired output format (can be one of: " . implode(", ", array_keys($writableFormats)) . ")" ]; $usageMessage = "Options:\n"; foreach($requiredOpts as $_opt => $_desc){ @@ -89,7 +89,7 @@ if(count($oldProviderClasses) === 0){ exit(1); } if(count($oldProviderClasses) > 1){ - fwrite(STDERR, "Ambiguous input world format: matched " . count($oldProviderClasses) . " (" . implode(array_keys($oldProviderClasses)) . ")" . PHP_EOL); + fwrite(STDERR, "Ambiguous input world format: matched " . count($oldProviderClasses) . " (" . implode(", ", array_keys($oldProviderClasses)) . ")" . PHP_EOL); exit(1); } $oldProviderClass = array_shift($oldProviderClasses); From ada3acdba4a0a2cef5f4de284a66e42cc6dcf23e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Dec 2024 17:43:59 +0000 Subject: [PATCH 046/334] FormatConverter: ensure we don't get stalled due to stdout buffer flood this can happen due to very noisy outputs during conversion, e.g. if there were many unknown blocks. --- src/world/format/io/FormatConverter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/world/format/io/FormatConverter.php b/src/world/format/io/FormatConverter.php index 1d485afa2..ea5af221c 100644 --- a/src/world/format/io/FormatConverter.php +++ b/src/world/format/io/FormatConverter.php @@ -33,6 +33,7 @@ use function basename; use function crc32; use function file_exists; use function floor; +use function flush; use function microtime; use function mkdir; use function random_bytes; @@ -150,6 +151,7 @@ class FormatConverter{ $diff = $time - $thisRound; $thisRound = $time; $this->logger->info("Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) . " chunks/sec)"); + flush(); } } $total = microtime(true) - $start; From 306623e890b046cf195e57860d1043d99cf986fb Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Dec 2024 17:47:16 +0000 Subject: [PATCH 047/334] FormatConverter: do periodic GC this reduces the risk of OOM during conversion of large worlds we probably ought to limit the size of region caches for regionized worlds, but that's a problem for another time. --- src/world/format/io/FormatConverter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/world/format/io/FormatConverter.php b/src/world/format/io/FormatConverter.php index ea5af221c..421d707fa 100644 --- a/src/world/format/io/FormatConverter.php +++ b/src/world/format/io/FormatConverter.php @@ -153,6 +153,9 @@ class FormatConverter{ $this->logger->info("Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) . " chunks/sec)"); flush(); } + if(($counter % (2 ** 16)) === 0){ + $new->doGarbageCollection(); + } } $total = microtime(true) - $start; $this->logger->info("Converted $counter / $counter chunks in " . round($total, 3) . " seconds (" . floor($counter / $total) . " chunks/sec)"); From 8a5eb71432ebf4036624ffc97b65c4db1e6fd6f4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 23 Dec 2024 21:04:33 +0000 Subject: [PATCH 048/334] ChunkCache: track strings in cache directly instead of CompressBatchPromise this reduces memory footprint slightly, but more importantly reduces GC workload. Since it also reduces the work done on cache hit, it might *slightly* improve performance, but any improvement is likely to be minimal. --- src/network/mcpe/NetworkSession.php | 30 +++++++++----- src/network/mcpe/cache/ChunkCache.php | 57 ++++++++++++++++----------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 78b11e27e..98460bef3 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -115,6 +115,7 @@ use pocketmine\utils\ObjectSet; use pocketmine\utils\TextFormat; use pocketmine\world\format\io\GlobalItemDataHandlers; use pocketmine\world\Position; +use pocketmine\world\World; use pocketmine\YmlServerProperties; use function array_map; use function array_values; @@ -1178,6 +1179,19 @@ class NetworkSession{ $this->sendDataPacket(ClientboundCloseFormPacket::create()); } + /** + * @phpstan-param \Closure() : void $onCompletion + */ + private function sendChunkPacket(string $chunkPacket, \Closure $onCompletion, World $world) : void{ + $world->timings->syncChunkSend->startTiming(); + try{ + $this->queueCompressed($chunkPacket); + $onCompletion(); + }finally{ + $world->timings->syncChunkSend->stopTiming(); + } + } + /** * Instructs the networksession to start using the chunk at the given coordinates. This may occur asynchronously. * @param \Closure $onCompletion To be called when chunk sending has completed. @@ -1185,8 +1199,12 @@ class NetworkSession{ */ public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{ $world = $this->player->getLocation()->getWorld(); - ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve( - + $promiseOrPacket = ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ); + if(is_string($promiseOrPacket)){ + $this->sendChunkPacket($promiseOrPacket, $onCompletion, $world); + return; + } + $promiseOrPacket->onResolve( //this callback may be called synchronously or asynchronously, depending on whether the promise is resolved yet function(CompressBatchPromise $promise) use ($world, $onCompletion, $chunkX, $chunkZ) : void{ if(!$this->isConnected()){ @@ -1204,13 +1222,7 @@ class NetworkSession{ //to NEEDED if they want to be resent. return; } - $world->timings->syncChunkSend->startTiming(); - try{ - $this->queueCompressed($promise); - $onCompletion(); - }finally{ - $world->timings->syncChunkSend->stopTiming(); - } + $this->sendChunkPacket($promise->getResult(), $onCompletion, $world); } ); } diff --git a/src/network/mcpe/cache/ChunkCache.php b/src/network/mcpe/cache/ChunkCache.php index 12e769776..2c5f67f08 100644 --- a/src/network/mcpe/cache/ChunkCache.php +++ b/src/network/mcpe/cache/ChunkCache.php @@ -32,6 +32,7 @@ use pocketmine\world\ChunkListener; use pocketmine\world\ChunkListenerNoOpTrait; use pocketmine\world\format\Chunk; use pocketmine\world\World; +use function is_string; use function spl_object_id; use function strlen; @@ -69,7 +70,7 @@ class ChunkCache implements ChunkListener{ foreach(self::$instances as $compressorMap){ foreach($compressorMap as $chunkCache){ foreach($chunkCache->caches as $chunkHash => $promise){ - if($promise->hasResult()){ + if(is_string($promise)){ //Do not clear promises that are not yet fulfilled; they will have requesters waiting on them unset($chunkCache->caches[$chunkHash]); } @@ -79,8 +80,8 @@ class ChunkCache implements ChunkListener{ } /** - * @var CompressBatchPromise[] - * @phpstan-var array + * @var CompressBatchPromise[]|string[] + * @phpstan-var array */ private array $caches = []; @@ -92,29 +93,17 @@ class ChunkCache implements ChunkListener{ private Compressor $compressor ){} - /** - * Requests asynchronous preparation of the chunk at the given coordinates. - * - * @return CompressBatchPromise a promise of resolution which will contain a compressed chunk packet. - */ - public function request(int $chunkX, int $chunkZ) : CompressBatchPromise{ + private function prepareChunkAsync(int $chunkX, int $chunkZ, int $chunkHash) : CompressBatchPromise{ $this->world->registerChunkListener($this, $chunkX, $chunkZ); $chunk = $this->world->getChunk($chunkX, $chunkZ); if($chunk === null){ throw new \InvalidArgumentException("Cannot request an unloaded chunk"); } - $chunkHash = World::chunkHash($chunkX, $chunkZ); - - if(isset($this->caches[$chunkHash])){ - ++$this->hits; - return $this->caches[$chunkHash]; - } - ++$this->misses; $this->world->timings->syncChunkSendPrepare->startTiming(); try{ - $this->caches[$chunkHash] = new CompressBatchPromise(); + $promise = new CompressBatchPromise(); $this->world->getServer()->getAsyncPool()->submitTask( new ChunkRequestTask( @@ -122,17 +111,39 @@ class ChunkCache implements ChunkListener{ $chunkZ, DimensionIds::OVERWORLD, //TODO: not hardcode this $chunk, - $this->caches[$chunkHash], + $promise, $this->compressor ) ); + $this->caches[$chunkHash] = $promise; + $promise->onResolve(function(CompressBatchPromise $promise) use ($chunkHash) : void{ + //the promise may have been discarded or replaced if the chunk was unloaded or modified in the meantime + if(($this->caches[$chunkHash] ?? null) === $promise){ + $this->caches[$chunkHash] = $promise->getResult(); + } + }); - return $this->caches[$chunkHash]; + return $promise; }finally{ $this->world->timings->syncChunkSendPrepare->stopTiming(); } } + /** + * Requests asynchronous preparation of the chunk at the given coordinates. + * + * @return CompressBatchPromise|string Compressed chunk packet, or a promise for one to be resolved asynchronously. + */ + public function request(int $chunkX, int $chunkZ) : CompressBatchPromise|string{ + $chunkHash = World::chunkHash($chunkX, $chunkZ); + if(isset($this->caches[$chunkHash])){ + ++$this->hits; + return $this->caches[$chunkHash]; + } + + return $this->prepareChunkAsync($chunkX, $chunkZ, $chunkHash); + } + private function destroy(int $chunkX, int $chunkZ) : bool{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $existing = $this->caches[$chunkHash] ?? null; @@ -148,12 +159,12 @@ class ChunkCache implements ChunkListener{ $chunkPosHash = World::chunkHash($chunkX, $chunkZ); $cache = $this->caches[$chunkPosHash] ?? null; if($cache !== null){ - if(!$cache->hasResult()){ + if(!is_string($cache)){ //some requesters are waiting for this chunk, so their request needs to be fulfilled $cache->cancel(); unset($this->caches[$chunkPosHash]); - $this->request($chunkX, $chunkZ)->onResolve(...$cache->getResolveCallbacks()); + $this->prepareChunkAsync($chunkX, $chunkZ, $chunkPosHash)->onResolve(...$cache->getResolveCallbacks()); }else{ //dump the cache, it'll be regenerated the next time it's requested $this->destroy($chunkX, $chunkZ); @@ -199,8 +210,8 @@ class ChunkCache implements ChunkListener{ public function calculateCacheSize() : int{ $result = 0; foreach($this->caches as $cache){ - if($cache->hasResult()){ - $result += strlen($cache->getResult()); + if(is_string($cache)){ + $result += strlen($cache); } } return $result; From 81e3730b99944413188e0c5db6eb5ab1f4c762ec Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 24 Dec 2024 14:20:16 +0000 Subject: [PATCH 049/334] Fixed crashes containing PHP internal stack frames being flagged as plugin-caused --- src/utils/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Utils.php b/src/utils/Utils.php index c8be174d6..9317aff3c 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -490,7 +490,7 @@ final class Utils{ $rawFrame = $rawTrace[$frameId]; $safeTrace[$frameId] = new ThreadCrashInfoFrame( $printableFrame, - $rawFrame["file"] ?? "unknown", + $rawFrame["file"] ?? null, $rawFrame["line"] ?? 0 ); } From debf8d18fc85b44d334251966502b3401e33fc37 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 24 Dec 2024 16:27:40 +0000 Subject: [PATCH 050/334] Upgrade issue templates --- .github/ISSUE_TEMPLATE/api-change-request.md | 19 ----- .github/ISSUE_TEMPLATE/bug-report.yml | 84 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 37 --------- .github/ISSUE_TEMPLATE/crash.md | 16 ---- .github/ISSUE_TEMPLATE/crash.yml | 24 ++++++ .github/ISSUE_TEMPLATE/feature-proposal.yml | 19 +++++ 6 files changed, 127 insertions(+), 72 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/api-change-request.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/crash.md create mode 100644 .github/ISSUE_TEMPLATE/crash.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-proposal.yml diff --git a/.github/ISSUE_TEMPLATE/api-change-request.md b/.github/ISSUE_TEMPLATE/api-change-request.md deleted file mode 100644 index e3d24ea0f..000000000 --- a/.github/ISSUE_TEMPLATE/api-change-request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: API change request -about: Suggest a change, addition or removal to the plugin API -title: '' -labels: '' -assignees: '' - ---- - - -## Problem description - - - -## Proposed solution - - - -## Alternative solutions that don't require API changes diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 000000000..3a4e49100 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,84 @@ +name: Bug report +description: Report a feature of PocketMine-MP not working as expected +body: + - type: markdown + attributes: + value: | + ## Plugin information + + > [!IMPORTANT] + > It's strongly recommended to test for bugs without plugins before reporting an issue. + > This helps avoid wasting maintainers' time on bugs that are not actually caused by PocketMine-MP. + > + > If you're not sure whether a plugin might be causing your issue, please seek help on our [Discord](https://discord.gg/bmSAZBG) before writing an issue. + + - type: dropdown + attributes: + label: Plugin information + options: + - "I haven't tested without plugins" + - Bug happens without plugins + - Bug only happens with certain plugins (describe below) + validations: + required: true + + - type: markdown + attributes: + value: | + ## Bug description + + > [!TIP] + > Helpful information to include: + > - Steps to reproduce the issue + > - Error backtraces + > - Crashdumps + > - Plugin code that triggers the issue + > - List of installed plugins (use /plugins) + + > [!IMPORTANT] + > **Steps to reproduce are critical to finding the cause of the problem!** + > Without reproducing steps, the issue will probably not be solvable and may be closed. + + - type: textarea + attributes: + label: Problem description + description: Describe the problem, and how you encountered it + placeholder: e.g. Steps to reproduce the issue + validations: + required: true + - type: textarea + attributes: + label: Expected behaviour + description: What did you expect to happen? + validations: + required: true + + - type: markdown + attributes: + value: | + ## Version, OS and game info + + - type: input + attributes: + label: PocketMine-MP version + placeholder: Use the /version command in PocketMine-MP + validations: + required: true + - type: input + attributes: + label: PHP version + placeholder: Use the /version command in PocketMine-MP + validations: + required: true + - type: input + attributes: + label: Server OS + placeholder: Use the /version command in PocketMine-MP + validations: + required: true + - type: input + attributes: + label: Game version (if applicable) + placeholder: e.g. Android, iOS, Windows, Xbox, PS4, Switch + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 730d6e811..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: Bug report -about: Unexpected non-crash behaviour (except missing gameplay features) -title: '' -labels: '' -assignees: '' - ---- - -### Issue description - -- Expected result: What were you expecting to happen? -- Actual result: What actually happened? - -### Steps to reproduce the issue -1. ... -2. ... - -### OS and versions - -* PocketMine-MP: -* PHP: -* Using JIT: yes/no (delete as appropriate) -* Server OS: -* Game version: Android/iOS/Win10/Xbox/PS4/Switch (delete as appropriate) - -### Plugins - - -- If you remove all plugins, does the issue still occur? -- If the issue is **not** reproducible without plugins: - - Have you asked for help on our forums before creating an issue? - - Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section - -### Crashdump, backtrace or other files - - diff --git a/.github/ISSUE_TEMPLATE/crash.md b/.github/ISSUE_TEMPLATE/crash.md deleted file mode 100644 index ee91d230e..000000000 --- a/.github/ISSUE_TEMPLATE/crash.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Crash -about: Report a crash in PocketMine-MP (not plugins) -title: Server crashed -labels: '' -assignees: '' - ---- - - - - -Link to crashdump: - - -### Additional comments (optional) diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml new file mode 100644 index 000000000..d6d46c406 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash.yml @@ -0,0 +1,24 @@ +name: Crash +description: Report a crash in PocketMine-MP (not plugins) +title: Server crashed +body: + - type: markdown + attributes: + value: | + > [!TIP] + > Submit crashdump `.log` files to the [Crash Archive](https://crash.pmmp.io/submit). + > If you can't submit the crashdump to the Crash Archive, paste it on a site like [GitHub Gist](https://gist.github.com) or [Pastebin](https://pastebin.com). + + > [!CAUTION] + > DON'T paste the crashdump data directly into an issue. + + - type: input + attributes: + label: Link to crashdump + validations: + required: true + + - type: textarea + attributes: + label: Additional comments (optional) + description: Any other information that might help us solve the problem diff --git a/.github/ISSUE_TEMPLATE/feature-proposal.yml b/.github/ISSUE_TEMPLATE/feature-proposal.yml new file mode 100644 index 000000000..e0d37ef06 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-proposal.yml @@ -0,0 +1,19 @@ +name: Feature addition, change, or removal +description: Propose adding new features, or changing/removing existing ones +body: + - type: textarea + attributes: + label: Problem description + description: Explain why a change is needed + validations: + required: true + - type: textarea + attributes: + label: Proposed solution + description: Describe what changes you think should be made + validations: + required: true + - type: textarea + attributes: + label: "Alternative solutions or workarounds" + description: "Describe other ways you've explored to achieve your goal" From dc2e82df7f092ece19c700697e15d1d565f9a791 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 24 Dec 2024 16:37:18 +0000 Subject: [PATCH 051/334] crash.yml: add field ID for crash archive "report github issue" button --- .github/ISSUE_TEMPLATE/crash.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml index d6d46c406..735255de2 100644 --- a/.github/ISSUE_TEMPLATE/crash.yml +++ b/.github/ISSUE_TEMPLATE/crash.yml @@ -13,6 +13,7 @@ body: > DON'T paste the crashdump data directly into an issue. - type: input + id: crashdump-url attributes: label: Link to crashdump validations: From d634a5fa3dea366037e24f1b154f25fa668a526f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 13:06:46 +0000 Subject: [PATCH 052/334] Bump build/php from `b1eaaa4` to `9728fa5` (#6587) Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `b1eaaa4` to `9728fa5`. - [Release notes](https://github.com/pmmp/php-build-scripts/releases) - [Commits](https://github.com/pmmp/php-build-scripts/compare/b1eaaa48ecd5bbbcefcf07f7fc183a09845c91c3...9728fa57f72b9c935b7d3206055715f5afcda7ae) --- updated-dependencies: - dependency-name: build/php dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index b1eaaa48e..9728fa57f 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit b1eaaa48ecd5bbbcefcf07f7fc183a09845c91c3 +Subproject commit 9728fa57f72b9c935b7d3206055715f5afcda7ae From fbaa125d0ce21ffef98fc1630881a92bedfbaa73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:36:17 +0000 Subject: [PATCH 053/334] Bump build/php from `9728fa5` to `56cec11` (#6588) Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `9728fa5` to `56cec11`. - [Release notes](https://github.com/pmmp/php-build-scripts/releases) - [Commits](https://github.com/pmmp/php-build-scripts/compare/9728fa57f72b9c935b7d3206055715f5afcda7ae...56cec11745bbd87719a303a0a1de41ee1d1d69c2) --- updated-dependencies: - dependency-name: build/php dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 9728fa57f..56cec1174 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 9728fa57f72b9c935b7d3206055715f5afcda7ae +Subproject commit 56cec11745bbd87719a303a0a1de41ee1d1d69c2 From da62eb9f3314daf4be83674ce7323850e1498601 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 3 Jan 2025 19:26:24 +0000 Subject: [PATCH 054/334] ... --- src/world/light/LightPopulationTask.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/world/light/LightPopulationTask.php b/src/world/light/LightPopulationTask.php index 5aa0ead65..7a4f5071d 100644 --- a/src/world/light/LightPopulationTask.php +++ b/src/world/light/LightPopulationTask.php @@ -90,7 +90,7 @@ class LightPopulationTask extends AsyncTask{ /** * @var \Closure - * @phpstan-var \Closure(array $blockLight, array $skyLight, array $heightMap>) : void + * @phpstan-var \Closure(array $blockLight, array $skyLight, array $heightMap) : void */ $callback = $this->fetchLocal(self::TLS_KEY_COMPLETION_CALLBACK); $callback($blockLightArrays, $skyLightArrays, $heightMapArray); From 4a4572131f27ab967701ceaaf2020cfbe26e375c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:26:14 +0000 Subject: [PATCH 055/334] Bump shivammathur/setup-php in the github-actions group (#6591) --- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release-pr-check.yml | 2 +- .github/workflows/draft-release.yml | 2 +- .github/workflows/main.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 8d0add224..fde5e3099 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index 4c8d0f685..131c0dde2 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: php-version: 8.2 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 6000dd5a8..445a1f7a6 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -87,7 +87,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: php-version: ${{ env.PHP_VERSION }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5a9740b5..571868747 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: php-version: 8.2 tools: php-cs-fixer:3.49 From 8b2323153734c680c2b4cae17af3b271beb38670 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:37:11 +0000 Subject: [PATCH 056/334] Fixup PHPDoc for blocks --- src/block/Anvil.php | 3 --- src/block/Bamboo.php | 3 --- src/block/BaseBanner.php | 4 ---- src/block/BaseSign.php | 4 ---- src/block/Bed.php | 3 --- src/block/Block.php | 7 ++++++- src/block/Cactus.php | 3 --- src/block/Cake.php | 3 --- src/block/CakeWithCandle.php | 3 --- src/block/Carpet.php | 3 --- src/block/Chest.php | 3 --- src/block/CocoaBlock.php | 3 --- src/block/DaylightSensor.php | 3 --- src/block/Door.php | 3 --- src/block/EnchantingTable.php | 3 --- src/block/EndPortalFrame.php | 3 --- src/block/EndRod.php | 3 --- src/block/EnderChest.php | 3 --- src/block/Farmland.php | 3 --- src/block/Fence.php | 4 ---- src/block/FenceGate.php | 3 --- src/block/Flowable.php | 4 ---- src/block/FlowerPot.php | 3 --- src/block/GlowLichen.php | 4 ---- src/block/GrassPath.php | 3 --- src/block/Ladder.php | 3 --- src/block/Lantern.php | 3 --- src/block/Liquid.php | 4 ---- src/block/MobHead.php | 3 --- src/block/NetherPortal.php | 4 ---- src/block/RedstoneComparator.php | 3 --- src/block/RedstoneRepeater.php | 3 --- src/block/SeaPickle.php | 4 ---- src/block/Slab.php | 3 --- src/block/SnowLayer.php | 3 --- src/block/SoulSand.php | 3 --- src/block/Thin.php | 1 - src/block/Trapdoor.php | 3 --- src/block/WaterLily.php | 3 --- src/block/utils/CandleTrait.php | 5 ++++- src/block/utils/CopperTrait.php | 5 +++++ 41 files changed, 15 insertions(+), 122 deletions(-) diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 0a1a47070..2c48f9a7c 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -70,9 +70,6 @@ class Anvil extends Transparent implements Fallable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->squash(Facing::axis(Facing::rotateY($this->facing, false)), 1 / 8)]; } diff --git a/src/block/Bamboo.php b/src/block/Bamboo.php index 9f605bca6..fd64e10ef 100644 --- a/src/block/Bamboo.php +++ b/src/block/Bamboo.php @@ -87,9 +87,6 @@ class Bamboo extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //this places the BB at the northwest corner, not the center $inset = 1 - (($this->thick ? 3 : 2) / 16); diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index 6b9e493d1..b56323453 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -30,7 +30,6 @@ use pocketmine\block\utils\SupportType; use pocketmine\item\Banner as ItemBanner; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; @@ -97,9 +96,6 @@ abstract class BaseBanner extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 5a905f8b8..0f5d77d58 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -34,7 +34,6 @@ use pocketmine\event\block\SignChangeEvent; use pocketmine\item\Dye; use pocketmine\item\Item; use pocketmine\item\ItemTypeIds; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\utils\TextFormat; @@ -95,9 +94,6 @@ abstract class BaseSign extends Transparent{ return 16; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/Bed.php b/src/block/Bed.php index 8efbdfe01..133c4a9cc 100644 --- a/src/block/Bed.php +++ b/src/block/Bed.php @@ -76,9 +76,6 @@ class Bed extends Transparent{ } } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 16)]; } diff --git a/src/block/Block.php b/src/block/Block.php index 89fe39265..36e08fc0b 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -75,7 +75,10 @@ class Block{ protected BlockTypeInfo $typeInfo; protected Position $position; - /** @var AxisAlignedBB[]|null */ + /** + * @var AxisAlignedBB[]|null + * @phpstan-var list|null + */ protected ?array $collisionBoxes = null; private int $requiredBlockItemStateDataBits; @@ -907,6 +910,7 @@ class Block{ * - anti-cheat checks in plugins * * @return AxisAlignedBB[] + * @phpstan-return list */ final public function getCollisionBoxes() : array{ if($this->collisionBoxes === null){ @@ -931,6 +935,7 @@ class Block{ /** * @return AxisAlignedBB[] + * @phpstan-return list */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()]; diff --git a/src/block/Cactus.php b/src/block/Cactus.php index 6f2b04c8a..67b15b946 100644 --- a/src/block/Cactus.php +++ b/src/block/Cactus.php @@ -43,9 +43,6 @@ class Cactus extends Transparent{ return true; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $shrinkSize = 1 / 16; return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)]; diff --git a/src/block/Cake.php b/src/block/Cake.php index 073fc62ac..e8c6dc93e 100644 --- a/src/block/Cake.php +++ b/src/block/Cake.php @@ -40,9 +40,6 @@ class Cake extends BaseCake{ $w->boundedIntAuto(0, self::MAX_BITES, $this->bites); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php index 380d080c5..546843d6c 100644 --- a/src/block/CakeWithCandle.php +++ b/src/block/CakeWithCandle.php @@ -36,9 +36,6 @@ class CakeWithCandle extends BaseCake{ onInteract as onInteractCandle; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/Carpet.php b/src/block/Carpet.php index 1ee7240c5..2d8e7ea47 100644 --- a/src/block/Carpet.php +++ b/src/block/Carpet.php @@ -36,9 +36,6 @@ class Carpet extends Flowable{ return true; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)]; } diff --git a/src/block/Chest.php b/src/block/Chest.php index dca21576a..7d2650007 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -36,9 +36,6 @@ use pocketmine\player\Player; class Chest extends Transparent{ use FacesOppositePlacingPlayerTrait; - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //these are slightly bigger than in PC return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)]; diff --git a/src/block/CocoaBlock.php b/src/block/CocoaBlock.php index 6d8ce1adc..83e1de34b 100644 --- a/src/block/CocoaBlock.php +++ b/src/block/CocoaBlock.php @@ -50,9 +50,6 @@ class CocoaBlock extends Flowable{ $w->boundedIntAuto(0, self::MAX_AGE, $this->age); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/DaylightSensor.php b/src/block/DaylightSensor.php index 4141a2b7e..5720af529 100644 --- a/src/block/DaylightSensor.php +++ b/src/block/DaylightSensor.php @@ -62,9 +62,6 @@ class DaylightSensor extends Transparent{ return 300; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 10 / 16)]; } diff --git a/src/block/Door.php b/src/block/Door.php index 82ddaab51..fa88267e1 100644 --- a/src/block/Door.php +++ b/src/block/Door.php @@ -95,9 +95,6 @@ class Door extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //TODO: doors are 0.1825 blocks thick, instead of 0.1875 like JE (https://bugs.mojang.com/browse/MCPE-19214) return [AxisAlignedBB::one()->trim($this->open ? Facing::rotateY($this->facing, !$this->hingeRight) : $this->facing, 327 / 400)]; diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php index 6a6c936b2..53573d064 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -33,9 +33,6 @@ use pocketmine\player\Player; class EnchantingTable extends Transparent{ - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 0.25)]; } diff --git a/src/block/EndPortalFrame.php b/src/block/EndPortalFrame.php index 612cf3723..ed5b77433 100644 --- a/src/block/EndPortalFrame.php +++ b/src/block/EndPortalFrame.php @@ -50,9 +50,6 @@ class EndPortalFrame extends Opaque{ return 1; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 3 / 16)]; } diff --git a/src/block/EndRod.php b/src/block/EndRod.php index f0b28c26d..a6770f370 100644 --- a/src/block/EndRod.php +++ b/src/block/EndRod.php @@ -52,9 +52,6 @@ class EndRod extends Flowable{ return 14; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $myAxis = Facing::axis($this->facing); diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 9004f7c79..6a8cf108c 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -40,9 +40,6 @@ class EnderChest extends Transparent{ return 7; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //these are slightly bigger than in PC return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)]; diff --git a/src/block/Farmland.php b/src/block/Farmland.php index b7a2500a8..83bc34561 100644 --- a/src/block/Farmland.php +++ b/src/block/Farmland.php @@ -94,9 +94,6 @@ class Farmland extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)]; } diff --git a/src/block/Fence.php b/src/block/Fence.php index 30caaa4cf..52256d9f0 100644 --- a/src/block/Fence.php +++ b/src/block/Fence.php @@ -54,13 +54,9 @@ class Fence extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $inset = 0.5 - $this->getThickness() / 2; - /** @var AxisAlignedBB[] $bbs */ $bbs = []; $connectWest = isset($this->connections[Facing::WEST]); diff --git a/src/block/FenceGate.php b/src/block/FenceGate.php index 735456449..2bbfdf892 100644 --- a/src/block/FenceGate.php +++ b/src/block/FenceGate.php @@ -64,9 +64,6 @@ class FenceGate extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return $this->open ? [] : [AxisAlignedBB::one()->extend(Facing::UP, 0.5)->squash(Facing::axis($this->facing), 6 / 16)]; } diff --git a/src/block/Flowable.php b/src/block/Flowable.php index 0328bcd74..355c9caea 100644 --- a/src/block/Flowable.php +++ b/src/block/Flowable.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\SupportType; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; /** @@ -46,9 +45,6 @@ abstract class Flowable extends Transparent{ parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/FlowerPot.php b/src/block/FlowerPot.php index fb3e78d82..79fb73b12 100644 --- a/src/block/FlowerPot.php +++ b/src/block/FlowerPot.php @@ -83,9 +83,6 @@ class FlowerPot extends Flowable{ return $block->hasTypeTag(BlockTypeTags::POTTABLE_PLANTS); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->contract(3 / 16, 0, 3 / 16)->trim(Facing::UP, 5 / 8)]; } diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index d30e25395..a44c4d035 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -28,7 +28,6 @@ use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Fertilizer; use pocketmine\item\Item; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -47,9 +46,6 @@ class GlowLichen extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/GrassPath.php b/src/block/GrassPath.php index 5b11bd374..ea56e4b95 100644 --- a/src/block/GrassPath.php +++ b/src/block/GrassPath.php @@ -29,9 +29,6 @@ use pocketmine\math\Facing; class GrassPath extends Transparent{ - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)]; } diff --git a/src/block/Ladder.php b/src/block/Ladder.php index 58f133f6e..09c0b8f6b 100644 --- a/src/block/Ladder.php +++ b/src/block/Ladder.php @@ -58,9 +58,6 @@ class Ladder extends Transparent{ return true; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim($this->facing, 13 / 16)]; } diff --git a/src/block/Lantern.php b/src/block/Lantern.php index e9cbcc3fe..302e69fd7 100644 --- a/src/block/Lantern.php +++ b/src/block/Lantern.php @@ -59,9 +59,6 @@ class Lantern extends Transparent{ return $this->lightLevel; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/Liquid.php b/src/block/Liquid.php index a37019d65..813d76904 100644 --- a/src/block/Liquid.php +++ b/src/block/Liquid.php @@ -30,7 +30,6 @@ use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; use pocketmine\event\block\BlockSpreadEvent; use pocketmine\item\Item; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\utils\Utils; @@ -89,9 +88,6 @@ abstract class Liquid extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/MobHead.php b/src/block/MobHead.php index f4e945841..41e816c55 100644 --- a/src/block/MobHead.php +++ b/src/block/MobHead.php @@ -104,9 +104,6 @@ class MobHead extends Flowable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $collisionBox = AxisAlignedBB::one() ->contract(0.25, 0, 0.25) diff --git a/src/block/NetherPortal.php b/src/block/NetherPortal.php index 6a45fb7a0..1b199c603 100644 --- a/src/block/NetherPortal.php +++ b/src/block/NetherPortal.php @@ -28,7 +28,6 @@ use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; use pocketmine\item\Item; use pocketmine\math\Axis; -use pocketmine\math\AxisAlignedBB; class NetherPortal extends Transparent{ @@ -62,9 +61,6 @@ class NetherPortal extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/RedstoneComparator.php b/src/block/RedstoneComparator.php index ee63a77a9..40e1ef510 100644 --- a/src/block/RedstoneComparator.php +++ b/src/block/RedstoneComparator.php @@ -79,9 +79,6 @@ class RedstoneComparator extends Flowable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)]; } diff --git a/src/block/RedstoneRepeater.php b/src/block/RedstoneRepeater.php index 7e6e73da8..bf9d0c5da 100644 --- a/src/block/RedstoneRepeater.php +++ b/src/block/RedstoneRepeater.php @@ -62,9 +62,6 @@ class RedstoneRepeater extends Flowable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)]; } diff --git a/src/block/SeaPickle.php b/src/block/SeaPickle.php index 627af9bac..34f5c3e9e 100644 --- a/src/block/SeaPickle.php +++ b/src/block/SeaPickle.php @@ -26,7 +26,6 @@ namespace pocketmine\block; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; @@ -70,9 +69,6 @@ class SeaPickle extends Transparent{ return $this->underwater ? ($this->count + 1) * 3 : 0; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/Slab.php b/src/block/Slab.php index 6000bec39..2bbb7528c 100644 --- a/src/block/Slab.php +++ b/src/block/Slab.php @@ -93,9 +93,6 @@ class Slab extends Transparent{ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ if($this->slabType === SlabType::DOUBLE){ return [AxisAlignedBB::one()]; diff --git a/src/block/SnowLayer.php b/src/block/SnowLayer.php index cca8424a9..8549f0b31 100644 --- a/src/block/SnowLayer.php +++ b/src/block/SnowLayer.php @@ -65,9 +65,6 @@ class SnowLayer extends Flowable implements Fallable{ return $this->layers < self::MAX_LAYERS; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //TODO: this zero-height BB is intended to stay in lockstep with a MCPE bug return [AxisAlignedBB::one()->trim(Facing::UP, $this->layers >= 4 ? 0.5 : 1)]; diff --git a/src/block/SoulSand.php b/src/block/SoulSand.php index 2c6453b6c..e1285d095 100644 --- a/src/block/SoulSand.php +++ b/src/block/SoulSand.php @@ -28,9 +28,6 @@ use pocketmine\math\Facing; class SoulSand extends Opaque{ - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 8)]; } diff --git a/src/block/Thin.php b/src/block/Thin.php index dde2d7d84..82010697a 100644 --- a/src/block/Thin.php +++ b/src/block/Thin.php @@ -56,7 +56,6 @@ class Thin extends Transparent{ protected function recalculateCollisionBoxes() : array{ $inset = 7 / 16; - /** @var AxisAlignedBB[] $bbs */ $bbs = []; if(isset($this->connections[Facing::WEST]) || isset($this->connections[Facing::EAST])){ diff --git a/src/block/Trapdoor.php b/src/block/Trapdoor.php index 20b6af2ab..a903e1b5e 100644 --- a/src/block/Trapdoor.php +++ b/src/block/Trapdoor.php @@ -62,9 +62,6 @@ class Trapdoor extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim($this->open ? $this->facing : ($this->top ? Facing::DOWN : Facing::UP), 13 / 16)]; } diff --git a/src/block/WaterLily.php b/src/block/WaterLily.php index 5dfb0d74a..b04b1baed 100644 --- a/src/block/WaterLily.php +++ b/src/block/WaterLily.php @@ -33,9 +33,6 @@ class WaterLily extends Flowable{ canBePlacedAt as supportedWhenPlacedAt; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->contract(1 / 16, 0, 1 / 16)->trim(Facing::UP, 63 / 64)]; } diff --git a/src/block/utils/CandleTrait.php b/src/block/utils/CandleTrait.php index c9da97ee0..0cbd13044 100644 --- a/src/block/utils/CandleTrait.php +++ b/src/block/utils/CandleTrait.php @@ -43,7 +43,10 @@ trait CandleTrait{ return $this->lit ? 3 : 0; } - /** @see Block::onInteract() */ + /** + * @param Item[] &$returnedItems + * @see Block::onInteract() + */ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE || $item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){ if($this->lit){ diff --git a/src/block/utils/CopperTrait.php b/src/block/utils/CopperTrait.php index 5ad8aa82d..2ed06b798 100644 --- a/src/block/utils/CopperTrait.php +++ b/src/block/utils/CopperTrait.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block\utils; +use pocketmine\block\Block; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Axe; use pocketmine\item\Item; @@ -58,6 +59,10 @@ trait CopperTrait{ return $this; } + /** + * @param Item[] &$returnedItems + * @see Block::onInteract() + */ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if(!$this->waxed && $item->getTypeId() === ItemTypeIds::HONEYCOMB){ $this->waxed = true; From 5c905d9a95656aed463da44c2e6a24af78057757 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:38:32 +0000 Subject: [PATCH 057/334] BlockBreakInfo: use strict comparison weak compare isn't needed here since this can be float/float --- src/block/BlockBreakInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/BlockBreakInfo.php b/src/block/BlockBreakInfo.php index b49cb6f13..8cfa425dc 100644 --- a/src/block/BlockBreakInfo.php +++ b/src/block/BlockBreakInfo.php @@ -95,7 +95,7 @@ class BlockBreakInfo{ * Returns whether this block can be instantly broken. */ public function breaksInstantly() : bool{ - return $this->hardness == 0.0; + return $this->hardness === 0.0; } /** From 8ee70b209e14cff13239d95594dca58ec4e11be9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:38:54 +0000 Subject: [PATCH 058/334] MemoryDump: fix PHPDoc types --- src/MemoryDump.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MemoryDump.php b/src/MemoryDump.php index 5a9c274cb..bd1e0fc9a 100644 --- a/src/MemoryDump.php +++ b/src/MemoryDump.php @@ -253,12 +253,12 @@ final class MemoryDump{ } /** - * @param object[] $objects reference parameter - * @param int[] $refCounts reference parameter + * @param object[]|true[] $objects reference parameter + * @param int[] $refCounts reference parameter * - * @phpstan-param array $objects + * @phpstan-param array $objects * @phpstan-param array $refCounts - * @phpstan-param-out array $objects + * @phpstan-param-out array $objects * @phpstan-param-out array $refCounts */ private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{ From 90f0b85d2e599681298e44bdce7e40e2f0342367 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:41:00 +0000 Subject: [PATCH 059/334] Eliminate weak comparisons in entity package Weak comparisons were used in cases when we were worried about comparing int and float. In some cases (particularly involving Vector3) we do need to be wary of this, so floatval() is used to avoid incorrect type comparisons. In other cases, we were already exclusively comparing float-float, so weak compare wasn't needed anyway. --- src/entity/Attribute.php | 6 +++--- src/entity/Entity.php | 31 ++++++++++++++++--------------- src/entity/Location.php | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/entity/Attribute.php b/src/entity/Attribute.php index 829e3d26c..3e9b7d7c2 100644 --- a/src/entity/Attribute.php +++ b/src/entity/Attribute.php @@ -76,7 +76,7 @@ class Attribute{ throw new \InvalidArgumentException("Minimum $minValue is greater than the maximum $max"); } - if($this->minValue != $minValue){ + if($this->minValue !== $minValue){ $this->desynchronized = true; $this->minValue = $minValue; } @@ -95,7 +95,7 @@ class Attribute{ throw new \InvalidArgumentException("Maximum $maxValue is less than the minimum $min"); } - if($this->maxValue != $maxValue){ + if($this->maxValue !== $maxValue){ $this->desynchronized = true; $this->maxValue = $maxValue; } @@ -140,7 +140,7 @@ class Attribute{ $value = min(max($value, $this->getMinValue()), $this->getMaxValue()); } - if($this->currentValue != $value){ + if($this->currentValue !== $value){ $this->desynchronized = true; $this->currentValue = $value; }elseif($forceSend){ diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 9bd0de9ea..7f0f6028b 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -72,6 +72,7 @@ use function assert; use function cos; use function count; use function deg2rad; +use function floatval; use function floor; use function fmod; use function get_class; @@ -591,7 +592,7 @@ abstract class Entity{ * Sets the health of the Entity. This won't send any update to the players */ public function setHealth(float $amount) : void{ - if($amount == $this->health){ + if($amount === $this->health){ return; } @@ -760,8 +761,8 @@ abstract class Entity{ $diffMotion = $this->motion->subtractVector($this->lastMotion)->lengthSquared(); - $still = $this->motion->lengthSquared() == 0.0; - $wasStill = $this->lastMotion->lengthSquared() == 0.0; + $still = $this->motion->lengthSquared() === 0.0; + $wasStill = $this->lastMotion->lengthSquared() === 0.0; if($wasStill !== $still){ //TODO: hack for client-side AI interference: prevent client sided movement when motion is 0 $this->setNoClientPredictions($still); @@ -1004,7 +1005,7 @@ abstract class Entity{ abs($this->motion->z) <= self::MOTION_THRESHOLD ? 0 : null ); - if($this->motion->x != 0 || $this->motion->y != 0 || $this->motion->z != 0){ + if(floatval($this->motion->x) !== 0.0 || floatval($this->motion->y) !== 0.0 || floatval($this->motion->z) !== 0.0){ $this->move($this->motion->x, $this->motion->y, $this->motion->z); } @@ -1058,9 +1059,9 @@ abstract class Entity{ public function hasMovementUpdate() : bool{ return ( $this->forceMovementUpdate || - $this->motion->x != 0 || - $this->motion->y != 0 || - $this->motion->z != 0 || + floatval($this->motion->x) !== 0.0 || + floatval($this->motion->y) !== 0.0 || + floatval($this->motion->z) !== 0.0 || !$this->onGround ); } @@ -1163,7 +1164,7 @@ abstract class Entity{ $moveBB->offset(0, $dy, 0); - $fallingFlag = ($this->onGround || ($dy != $wantedY && $wantedY < 0)); + $fallingFlag = ($this->onGround || ($dy !== $wantedY && $wantedY < 0)); foreach($list as $bb){ $dx = $bb->calculateXOffset($moveBB, $dx); @@ -1177,7 +1178,7 @@ abstract class Entity{ $moveBB->offset(0, 0, $dz); - if($this->stepHeight > 0 && $fallingFlag && ($wantedX != $dx || $wantedZ != $dz)){ + if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; @@ -1242,9 +1243,9 @@ abstract class Entity{ $postFallVerticalVelocity = $this->updateFallState($dy, $this->onGround); $this->motion = $this->motion->withComponents( - $wantedX != $dx ? 0 : null, - $postFallVerticalVelocity ?? ($wantedY != $dy ? 0 : null), - $wantedZ != $dz ? 0 : null + $wantedX !== $dx ? 0 : null, + $postFallVerticalVelocity ?? ($wantedY !== $dy ? 0 : null), + $wantedZ !== $dz ? 0 : null ); //TODO: vehicle collision events (first we need to spawn them!) @@ -1253,10 +1254,10 @@ abstract class Entity{ } protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{ - $this->isCollidedVertically = $wantedY != $dy; - $this->isCollidedHorizontally = ($wantedX != $dx || $wantedZ != $dz); + $this->isCollidedVertically = $wantedY !== $dy; + $this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz); $this->isCollided = ($this->isCollidedHorizontally || $this->isCollidedVertically); - $this->onGround = ($wantedY != $dy && $wantedY < 0); + $this->onGround = ($wantedY !== $dy && $wantedY < 0); } /** diff --git a/src/entity/Location.php b/src/entity/Location.php index 990108ac4..d9c101882 100644 --- a/src/entity/Location.php +++ b/src/entity/Location.php @@ -66,7 +66,7 @@ class Location extends Position{ public function equals(Vector3 $v) : bool{ if($v instanceof Location){ - return parent::equals($v) && $v->yaw == $this->yaw && $v->pitch == $this->pitch; + return parent::equals($v) && $v->yaw === $this->yaw && $v->pitch === $this->pitch; } return parent::equals($v); } From 59f6c8510577539785c60a43fa293c7f7839d1af Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:42:57 +0000 Subject: [PATCH 060/334] Command: mark execute $args as being list --- src/command/Command.php | 1 + src/command/FormattedCommandAlias.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/command/Command.php b/src/command/Command.php index 4c2c6815b..63276a123 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -80,6 +80,7 @@ abstract class Command{ /** * @param string[] $args + * @phpstan-param list $args * * @return mixed * @throws CommandException diff --git a/src/command/FormattedCommandAlias.php b/src/command/FormattedCommandAlias.php index 5086672f6..b47363397 100644 --- a/src/command/FormattedCommandAlias.php +++ b/src/command/FormattedCommandAlias.php @@ -121,6 +121,7 @@ class FormattedCommandAlias extends Command{ /** * @param string[] $args + * @phpstan-param list $args */ private function buildCommand(string $formatString, array $args) : ?string{ $index = 0; From e30ae487dc9fa2798f39f37a8c14afb940619ecf Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:43:34 +0000 Subject: [PATCH 061/334] SimpleCommandMap: ensure we always pass a list to Command::setAliases() some offsets may have been removed if the alias failed to be registered. --- src/command/SimpleCommandMap.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index e0d8e6565..9f5441746 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -73,6 +73,7 @@ use pocketmine\timings\Timings; use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; use function array_shift; +use function array_values; use function count; use function implode; use function str_contains; @@ -163,7 +164,7 @@ class SimpleCommandMap implements CommandMap{ unset($aliases[$index]); } } - $command->setAliases($aliases); + $command->setAliases(array_values($aliases)); if(!$registered){ $command->setLabel($fallbackPrefix . ":" . $label); From c5a1c153898af48e476e59aaf06d0764b0f3f042 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:44:04 +0000 Subject: [PATCH 062/334] TimingsCommand: beware crash on invalid timings server response --- src/command/defaults/TimingsCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php index ec26f3efe..08a8b82aa 100644 --- a/src/command/defaults/TimingsCommand.php +++ b/src/command/defaults/TimingsCommand.php @@ -46,6 +46,8 @@ use function fwrite; use function http_build_query; use function implode; use function is_array; +use function is_int; +use function is_string; use function json_decode; use function mkdir; use function strtolower; @@ -178,7 +180,7 @@ class TimingsCommand extends VanillaCommand{ return; } $response = json_decode($result->getBody(), true); - if(is_array($response) && isset($response["id"])){ + if(is_array($response) && isset($response["id"]) && (is_int($response["id"]) || is_string($response["id"]))){ Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead( "https://" . $host . "/?id=" . $response["id"])); }else{ From b6bd3ef30cb88afb078d8f69ae8c6993ed24f1df Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:46:16 +0000 Subject: [PATCH 063/334] Improve PHPDocs in world package --- src/world/World.php | 4 +++- src/world/format/Chunk.php | 8 +++++++- src/world/format/HeightArray.php | 4 ++-- src/world/format/io/FastChunkSerializer.php | 1 - src/world/format/io/region/RegionWorldProvider.php | 8 ++++++-- src/world/light/LightPopulationTask.php | 9 ++++++--- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/world/World.php b/src/world/World.php index c2d153921..134b1f091 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -1562,6 +1562,7 @@ class World implements ChunkManager{ * Larger AABBs (>= 2 blocks on any axis) are not accounted for. * * @return AxisAlignedBB[] + * @phpstan-return list */ private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{ $block = $this->getBlockAt($x, $y, $z); @@ -2040,7 +2041,6 @@ class World implements ChunkManager{ * @phpstan-return list */ public function dropExperience(Vector3 $pos, int $amount) : array{ - /** @var ExperienceOrb[] $orbs */ $orbs = []; foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){ @@ -3083,6 +3083,7 @@ class World implements ChunkManager{ * @phpstan-return Promise */ public function requestSafeSpawn(?Vector3 $spawn = null) : Promise{ + /** @phpstan-var PromiseResolver $resolver */ $resolver = new PromiseResolver(); $spawn ??= $this->getSpawnLocation(); /* @@ -3254,6 +3255,7 @@ class World implements ChunkManager{ private function enqueuePopulationRequest(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $this->addChunkHashToPopulationRequestQueue($chunkHash); + /** @phpstan-var PromiseResolver $resolver */ $resolver = $this->chunkPopulationRequestMap[$chunkHash] = new PromiseResolver(); if($associatedChunkLoader === null){ $temporaryLoader = new class implements ChunkLoader{}; diff --git a/src/world/format/Chunk.php b/src/world/format/Chunk.php index e4c877dc9..9ea5d3f8e 100644 --- a/src/world/format/Chunk.php +++ b/src/world/format/Chunk.php @@ -57,7 +57,10 @@ class Chunk{ */ protected \SplFixedArray $subChunks; - /** @var Tile[] */ + /** + * @var Tile[] + * @phpstan-var array + */ protected array $tiles = []; protected HeightArray $heightMap; @@ -210,6 +213,7 @@ class Chunk{ /** * @return Tile[] + * @phpstan-return array */ public function getTiles() : array{ return $this->tiles; @@ -237,6 +241,7 @@ class Chunk{ /** * @return int[] + * @phpstan-return non-empty-list */ public function getHeightMapArray() : array{ return $this->heightMap->getValues(); @@ -244,6 +249,7 @@ class Chunk{ /** * @param int[] $values + * @phpstan-param non-empty-list $values */ public function setHeightMapArray(array $values) : void{ $this->heightMap = new HeightArray($values); diff --git a/src/world/format/HeightArray.php b/src/world/format/HeightArray.php index 27f9cecb7..03094c3c8 100644 --- a/src/world/format/HeightArray.php +++ b/src/world/format/HeightArray.php @@ -36,7 +36,7 @@ final class HeightArray{ /** * @param int[] $values ZZZZXXXX key bit order - * @phpstan-param list $values + * @phpstan-param non-empty-list $values */ public function __construct(array $values){ if(count($values) !== 256){ @@ -66,7 +66,7 @@ final class HeightArray{ /** * @return int[] ZZZZXXXX key bit order - * @phpstan-return list + * @phpstan-return non-empty-list */ public function getValues() : array{ return $this->array->toArray(); diff --git a/src/world/format/io/FastChunkSerializer.php b/src/world/format/io/FastChunkSerializer.php index 6e18f27ac..35a8ff42f 100644 --- a/src/world/format/io/FastChunkSerializer.php +++ b/src/world/format/io/FastChunkSerializer.php @@ -112,7 +112,6 @@ final class FastChunkSerializer{ $y = Binary::signByte($stream->getByte()); $airBlockId = $stream->getInt(); - /** @var PalettedBlockArray[] $layers */ $layers = []; for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){ $layers[] = self::deserializePalettedArray($stream); diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 75fcfd083..0ab70300e 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -93,10 +93,12 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ } /** - * @param int $regionX reference parameter - * @param int $regionZ reference parameter + * @param int|null $regionX reference parameter + * @param int|null $regionZ reference parameter * @phpstan-param-out int $regionX * @phpstan-param-out int $regionZ + * + * TODO: make this private */ public static function getRegionIndex(int $chunkX, int $chunkZ, &$regionX, &$regionZ) : void{ $regionX = $chunkX >> 5; @@ -154,6 +156,8 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ /** * @return CompoundTag[] + * @phpstan-return list + * * @throws CorruptedChunkException */ protected static function getCompoundList(string $context, ListTag $list) : array{ diff --git a/src/world/light/LightPopulationTask.php b/src/world/light/LightPopulationTask.php index 7a4f5071d..29d957831 100644 --- a/src/world/light/LightPopulationTask.php +++ b/src/world/light/LightPopulationTask.php @@ -44,7 +44,7 @@ class LightPopulationTask extends AsyncTask{ private string $resultBlockLightArrays; /** - * @phpstan-param \Closure(array $blockLight, array $skyLight, array $heightMap) : void $onCompletion + * @phpstan-param \Closure(array $blockLight, array $skyLight, non-empty-list $heightMap) : void $onCompletion */ public function __construct(Chunk $chunk, \Closure $onCompletion){ $this->chunk = FastChunkSerializer::serializeTerrain($chunk); @@ -80,7 +80,10 @@ class LightPopulationTask extends AsyncTask{ } public function onCompletion() : void{ - /** @var int[] $heightMapArray */ + /** + * @var int[] $heightMapArray + * @phpstan-var non-empty-list $heightMapArray + */ $heightMapArray = igbinary_unserialize($this->resultHeightMap); /** @var LightArray[] $skyLightArrays */ @@ -90,7 +93,7 @@ class LightPopulationTask extends AsyncTask{ /** * @var \Closure - * @phpstan-var \Closure(array $blockLight, array $skyLight, array $heightMap) : void + * @phpstan-var \Closure(array $blockLight, array $skyLight, non-empty-list $heightMap) : void */ $callback = $this->fetchLocal(self::TLS_KEY_COMPLETION_CALLBACK); $callback($blockLightArrays, $skyLightArrays, $heightMapArray); From 20849d6268f229660efa23311fd03151af8247b3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:48:22 +0000 Subject: [PATCH 064/334] Fixed potential crashes in type ID tests if the constants had any non-stringable values, these would blow up. this would still be fine in the sense that the tests would fail, but better that they fail gracefully if possible. --- tests/phpunit/block/BlockTypeIdsTest.php | 6 +++++- tests/phpunit/item/ItemTypeIdsTest.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/block/BlockTypeIdsTest.php b/tests/phpunit/block/BlockTypeIdsTest.php index ce21a89ab..cbfc07eaf 100644 --- a/tests/phpunit/block/BlockTypeIdsTest.php +++ b/tests/phpunit/block/BlockTypeIdsTest.php @@ -35,8 +35,12 @@ class BlockTypeIdsTest extends TestCase{ $constants = $reflect->getConstants(); unset($constants['FIRST_UNUSED_BLOCK_ID']); + self::assertNotEmpty($constants, "We should never have zero type IDs"); - self::assertSame($reflect->getConstant('FIRST_UNUSED_BLOCK_ID'), max($constants) + 1, "FIRST_UNUSED_BLOCK_ID must be one higher than the highest fixed type ID"); + $max = max($constants); + self::assertIsInt($max, "Max type ID should always be an integer"); + + self::assertSame($reflect->getConstant('FIRST_UNUSED_BLOCK_ID'), $max + 1, "FIRST_UNUSED_BLOCK_ID must be one higher than the highest fixed type ID"); } public function testNoDuplicates() : void{ diff --git a/tests/phpunit/item/ItemTypeIdsTest.php b/tests/phpunit/item/ItemTypeIdsTest.php index 7336780b3..a30489f07 100644 --- a/tests/phpunit/item/ItemTypeIdsTest.php +++ b/tests/phpunit/item/ItemTypeIdsTest.php @@ -35,8 +35,12 @@ class ItemTypeIdsTest extends TestCase{ $constants = $reflect->getConstants(); unset($constants['FIRST_UNUSED_ITEM_ID']); + self::assertNotEmpty($constants, "We should never have zero type IDs"); - self::assertSame($reflect->getConstant('FIRST_UNUSED_ITEM_ID'), max($constants) + 1, "FIRST_UNUSED_ITEM_ID must be one higher than the highest fixed type ID"); + $max = max($constants); + self::assertIsInt($max, "Max type ID should always be an integer"); + + self::assertSame($reflect->getConstant('FIRST_UNUSED_ITEM_ID'), $max + 1, "FIRST_UNUSED_ITEM_ID must be one higher than the highest fixed type ID"); } public function testNoDuplicates() : void{ From 9a130bce320860031a7cda02a6d14f48ebbc288b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:50:25 +0000 Subject: [PATCH 065/334] Config: remove bad assumptions about string root keys these could just as easily be integers and the code should still work. --- src/utils/Config.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/utils/Config.php b/src/utils/Config.php index cabc8fe16..dbccc892e 100644 --- a/src/utils/Config.php +++ b/src/utils/Config.php @@ -72,7 +72,7 @@ class Config{ /** * @var mixed[] - * @phpstan-var array + * @phpstan-var array */ private array $config = []; @@ -450,7 +450,7 @@ class Config{ /** * @return mixed[] - * @phpstan-return list|array + * @phpstan-return list|array */ public function getAll(bool $keys = false) : array{ return ($keys ? array_keys($this->config) : $this->config); @@ -458,7 +458,6 @@ class Config{ /** * @param mixed[] $defaults - * @phpstan-param array $defaults */ public function setDefaults(array $defaults) : void{ $this->fillDefaults($defaults, $this->config); @@ -467,13 +466,11 @@ class Config{ /** * @param mixed[] $default * @param mixed[] $data reference parameter - * @phpstan-param array $default - * @phpstan-param array $data - * @phpstan-param-out array $data + * @phpstan-param-out array $data */ private function fillDefaults(array $default, array &$data) : int{ $changed = 0; - foreach(Utils::stringifyKeys($default) as $k => $v){ + foreach(Utils::promoteKeys($default) as $k => $v){ if(is_array($v)){ if(!isset($data[$k]) || !is_array($data[$k])){ $data[$k] = []; @@ -560,7 +557,7 @@ class Config{ }; break; } - $result[(string) $k] = $v; + $result[$k] = $v; } } From 97c5902ae2fea587faaee7487bbe14fa6100d67e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:52:05 +0000 Subject: [PATCH 066/334] Internet: make postURL() error reporting behaviour more predictable err is now always set to null when doing a new operation. previously, if the same var was used multiple times and a previous one failed, code might think that a previous error belonged to the current operation. --- src/utils/Internet.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/utils/Internet.php b/src/utils/Internet.php index 07b3c7a33..f3dad2271 100644 --- a/src/utils/Internet.php +++ b/src/utils/Internet.php @@ -157,22 +157,21 @@ class Internet{ * POSTs data to an URL * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. * - * @phpstan-template TErrorVar of mixed - * * @param string[]|string $args * @param string[] $extraHeaders * @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation. * @phpstan-param string|array $args * @phpstan-param list $extraHeaders - * @phpstan-param TErrorVar $err - * @phpstan-param-out TErrorVar|string $err + * @phpstan-param-out string|null $err */ public static function postURL(string $page, array|string $args, int $timeout = 10, array $extraHeaders = [], &$err = null) : ?InternetRequestResult{ try{ - return self::simpleCurl($page, $timeout, $extraHeaders, [ + $result = self::simpleCurl($page, $timeout, $extraHeaders, [ CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $args ]); + $err = null; + return $result; }catch(InternetException $ex){ $err = $ex->getMessage(); return null; From 0358b7dce4d4c6d74a4388aaf931168eb5d7a56b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 22:53:35 +0000 Subject: [PATCH 067/334] utils: avoid weak comparisons --- src/utils/Internet.php | 2 +- src/utils/Timezone.php | 8 ++++---- src/utils/Utils.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/Internet.php b/src/utils/Internet.php index f3dad2271..1811ce51e 100644 --- a/src/utils/Internet.php +++ b/src/utils/Internet.php @@ -100,7 +100,7 @@ class Internet{ } $ip = self::getURL("http://ifconfig.me/ip"); - if($ip !== null && ($addr = trim($ip->getBody())) != ""){ + if($ip !== null && ($addr = trim($ip->getBody())) !== ""){ return self::$ip = $addr; } diff --git a/src/utils/Timezone.php b/src/utils/Timezone.php index 7f1dcf0e4..6723b12eb 100644 --- a/src/utils/Timezone.php +++ b/src/utils/Timezone.php @@ -134,7 +134,7 @@ abstract class Timezone{ $offset = $matches[2]; - if($offset == ""){ + if($offset === ""){ return "UTC"; } @@ -156,7 +156,7 @@ abstract class Timezone{ $offset = trim(exec('date +%:z')); - if($offset == "+00:00"){ + if($offset === "+00:00"){ return "UTC"; } @@ -195,7 +195,7 @@ abstract class Timezone{ $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; //After date_parse is done, put the sign back - if($negative_offset == true){ + if($negative_offset){ $offset = -abs($offset); } @@ -204,7 +204,7 @@ abstract class Timezone{ //That's been a bug in PHP since 2008! foreach(timezone_abbreviations_list() as $zones){ foreach($zones as $timezone){ - if($timezone['timezone_id'] !== null && $timezone['offset'] == $offset){ + if($timezone['timezone_id'] !== null && $timezone['offset'] === $offset){ return $timezone['timezone_id']; } } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 9317aff3c..cc33e60e0 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -220,7 +220,7 @@ final class Utils{ $mac = implode("\n", $mac); if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches) > 0){ foreach($matches[1] as $i => $v){ - if($v == "00-00-00-00-00-00"){ + if($v === "00-00-00-00-00-00"){ unset($matches[1][$i]); } } @@ -234,7 +234,7 @@ final class Utils{ $mac = implode("\n", $mac); if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches) > 0){ foreach($matches[1] as $i => $v){ - if($v == "00:00:00:00:00:00"){ + if($v === "00:00:00:00:00:00"){ unset($matches[1][$i]); } } From 357dfb5c7e54f3585d604f9b1258240f7d04c723 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:01:14 +0000 Subject: [PATCH 068/334] Fixed build --- src/utils/Config.php | 10 +++++----- src/world/World.php | 2 +- tests/phpstan/configs/actual-problems.neon | 2 +- tests/phpstan/configs/spl-fixed-array-sucks.neon | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/Config.php b/src/utils/Config.php index dbccc892e..7b6da6389 100644 --- a/src/utils/Config.php +++ b/src/utils/Config.php @@ -506,8 +506,8 @@ class Config{ } /** - * @param string[] $entries - * @phpstan-param list $entries + * @param string[]|int[] $entries + * @phpstan-param list $entries */ public static function writeList(array $entries) : string{ return implode("\n", $entries); @@ -515,11 +515,11 @@ class Config{ /** * @param string[]|int[]|float[]|bool[] $config - * @phpstan-param array $config + * @phpstan-param array $config */ public static function writeProperties(array $config) : string{ $content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n"; - foreach(Utils::stringifyKeys($config) as $k => $v){ + foreach(Utils::promoteKeys($config) as $k => $v){ if(is_bool($v)){ $v = $v ? "on" : "off"; } @@ -531,7 +531,7 @@ class Config{ /** * @return string[]|int[]|float[]|bool[] - * @phpstan-return array + * @phpstan-return array */ public static function parseProperties(string $content) : array{ $result = []; diff --git a/src/world/World.php b/src/world/World.php index 134b1f091..b6df7f2a4 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -1364,7 +1364,7 @@ class World implements ChunkManager{ * TODO: phpstan can't infer these types yet :( * @phpstan-var array $blockLight * @phpstan-var array $skyLight - * @phpstan-var array $heightMap + * @phpstan-var non-empty-list $heightMap */ if($this->unloaded || ($chunk = $this->getChunk($chunkX, $chunkZ)) === null || $chunk->isLightPopulated() === true){ return; diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 1bb9329f0..f55fe1cbc 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -791,7 +791,7 @@ parameters: path: ../../../src/utils/Config.php - - message: "#^Parameter \\#1 \\$config of static method pocketmine\\\\utils\\\\Config\\:\\:writeProperties\\(\\) expects array\\, array\\ given\\.$#" + message: "#^Parameter \\#1 \\$config of static method pocketmine\\\\utils\\\\Config\\:\\:writeProperties\\(\\) expects array\\, array\\ given\\.$#" count: 1 path: ../../../src/utils/Config.php diff --git a/tests/phpstan/configs/spl-fixed-array-sucks.neon b/tests/phpstan/configs/spl-fixed-array-sucks.neon index daa6361dd..7c2b2b91a 100644 --- a/tests/phpstan/configs/spl-fixed-array-sucks.neon +++ b/tests/phpstan/configs/spl-fixed-array-sucks.neon @@ -16,7 +16,7 @@ parameters: path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\HeightArray\\:\\:getValues\\(\\) should return array\\ but returns array\\\\.$#" + message: "#^Method pocketmine\\\\world\\\\format\\\\HeightArray\\:\\:getValues\\(\\) should return non\\-empty\\-array\\ but returns array\\\\.$#" count: 1 path: ../../../src/world/format/HeightArray.php From 84ec8b7abe99220528bae9020ba8b4a529907ff7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:02:18 +0000 Subject: [PATCH 069/334] Removed dead error patterns I do think these are PHPStan bugs, since the trait should inherit the parent class's doc comment But for the sake of catching more bugs, these doc comments have been manually added anyway. --- tests/phpstan/configs/phpstan-bugs.neon | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 1ae740d66..0ee2b68a0 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -1,20 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Method pocketmine\\\\block\\\\CakeWithCandle\\:\\:onInteractCandle\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" - count: 1 - path: ../../../src/block/CakeWithCandle.php - - - - message: "#^Method pocketmine\\\\block\\\\CopperDoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" - count: 1 - path: ../../../src/block/CopperDoor.php - - - - message: "#^Method pocketmine\\\\block\\\\CopperTrapdoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" - count: 1 - path: ../../../src/block/CopperTrapdoor.php - - message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 From 1b2d2a3fe19e933eeb10f54d71dc5dc8037fd949 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:04:00 +0000 Subject: [PATCH 070/334] plugin: improve PHPDocs and type compliance --- src/plugin/PluginDescription.php | 28 ++++++++++++++++++++-------- src/plugin/PluginLoadTriage.php | 4 ++-- src/plugin/PluginManager.php | 10 +++++----- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/plugin/PluginDescription.php b/src/plugin/PluginDescription.php index 35ae2ba32..89ac19e05 100644 --- a/src/plugin/PluginDescription.php +++ b/src/plugin/PluginDescription.php @@ -84,11 +84,20 @@ class PluginDescription{ * @phpstan-var array> */ private array $extensions = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $depend = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $softDepend = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $loadBefore = []; private string $version; /** @@ -173,7 +182,7 @@ class PluginDescription{ } if(isset($plugin[self::KEY_DEPEND])){ - $this->depend = (array) $plugin[self::KEY_DEPEND]; + $this->depend = array_values((array) $plugin[self::KEY_DEPEND]); } if(isset($plugin[self::KEY_EXTENSIONS])){ $extensions = (array) $plugin[self::KEY_EXTENSIONS]; @@ -183,13 +192,13 @@ class PluginDescription{ $k = $v; $v = "*"; } - $this->extensions[(string) $k] = array_map('strval', is_array($v) ? $v : [$v]); + $this->extensions[(string) $k] = array_values(array_map('strval', is_array($v) ? $v : [$v])); } } - $this->softDepend = (array) ($plugin[self::KEY_SOFTDEPEND] ?? $this->softDepend); + $this->softDepend = array_values((array) ($plugin[self::KEY_SOFTDEPEND] ?? $this->softDepend)); - $this->loadBefore = (array) ($plugin[self::KEY_LOADBEFORE] ?? $this->loadBefore); + $this->loadBefore = array_values((array) ($plugin[self::KEY_LOADBEFORE] ?? $this->loadBefore)); $this->website = (string) ($plugin[self::KEY_WEBSITE] ?? $this->website); @@ -210,7 +219,7 @@ class PluginDescription{ $this->authors = []; if(isset($plugin[self::KEY_AUTHOR])){ if(is_array($plugin[self::KEY_AUTHOR])){ - $this->authors = $plugin[self::KEY_AUTHOR]; + $this->authors = array_values($plugin[self::KEY_AUTHOR]); }else{ $this->authors[] = $plugin[self::KEY_AUTHOR]; } @@ -284,6 +293,7 @@ class PluginDescription{ /** * @return string[] + * @phpstan-return list */ public function getDepend() : array{ return $this->depend; @@ -295,6 +305,7 @@ class PluginDescription{ /** * @return string[] + * @phpstan-return list */ public function getLoadBefore() : array{ return $this->loadBefore; @@ -324,6 +335,7 @@ class PluginDescription{ /** * @return string[] + * @phpstan-return list */ public function getSoftDepend() : array{ return $this->softDepend; diff --git a/src/plugin/PluginLoadTriage.php b/src/plugin/PluginLoadTriage.php index 77d102668..fcf32751e 100644 --- a/src/plugin/PluginLoadTriage.php +++ b/src/plugin/PluginLoadTriage.php @@ -31,12 +31,12 @@ final class PluginLoadTriage{ public array $plugins = []; /** * @var string[][] - * @phpstan-var array> + * @phpstan-var array> */ public array $dependencies = []; /** * @var string[][] - * @phpstan-var array> + * @phpstan-var array> */ public array $softDependencies = []; } diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index f84698aa0..a8440f04f 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -327,12 +327,12 @@ class PluginManager{ * @param string[][] $dependencyLists * @param Plugin[] $loadedPlugins * - * @phpstan-param array> $dependencyLists - * @phpstan-param-out array> $dependencyLists + * @phpstan-param array> $dependencyLists + * @phpstan-param-out array> $dependencyLists */ private function checkDepsForTriage(string $pluginName, string $dependencyType, array &$dependencyLists, array $loadedPlugins, PluginLoadTriage $triage) : void{ if(isset($dependencyLists[$pluginName])){ - foreach($dependencyLists[$pluginName] as $key => $dependency){ + foreach(Utils::promoteKeys($dependencyLists[$pluginName]) as $key => $dependency){ if(isset($loadedPlugins[$dependency]) || $this->getPlugin($dependency) instanceof Plugin){ $this->server->getLogger()->debug("Successfully resolved $dependencyType dependency \"$dependency\" for plugin \"$pluginName\""); unset($dependencyLists[$pluginName][$key]); @@ -399,7 +399,7 @@ class PluginManager{ //check for skippable soft dependencies first, in case the dependents could resolve hard dependencies foreach(Utils::stringifyKeys($triage->plugins) as $name => $file){ if(isset($triage->softDependencies[$name]) && !isset($triage->dependencies[$name])){ - foreach($triage->softDependencies[$name] as $k => $dependency){ + foreach(Utils::promoteKeys($triage->softDependencies[$name]) as $k => $dependency){ if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){ $this->server->getLogger()->debug("Skipping resolution of missing soft dependency \"$dependency\" for plugin \"$name\""); unset($triage->softDependencies[$name][$k]); @@ -416,7 +416,7 @@ class PluginManager{ if(isset($triage->dependencies[$name])){ $unknownDependencies = []; - foreach($triage->dependencies[$name] as $k => $dependency){ + foreach($triage->dependencies[$name] as $dependency){ if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){ //assume that the plugin is never going to be loaded //by this point all soft dependencies have been ignored if they were able to be, so From db9ba83001e2a2e6aa49e1e250d211c3f1e72fcf Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:05:06 +0000 Subject: [PATCH 071/334] Make some assumptions about proc_open() --- src/utils/Git.php | 2 +- src/utils/Process.php | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/utils/Git.php b/src/utils/Git.php index 041d795a1..2b9e242bc 100644 --- a/src/utils/Git.php +++ b/src/utils/Git.php @@ -39,7 +39,7 @@ final class Git{ * @param bool $dirty reference parameter, set to whether the repo has local changes */ public static function getRepositoryState(string $dir, bool &$dirty) : ?string{ - if(Process::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 && $out !== false && strlen($out = trim($out)) === 40){ + if(Process::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 && strlen($out = trim($out)) === 40){ if(Process::execute("git -C \"$dir\" diff --quiet") === 1 || Process::execute("git -C \"$dir\" diff --cached --quiet") === 1){ //Locally-modified $dirty = true; } diff --git a/src/utils/Process.php b/src/utils/Process.php index 1370ab27c..90149870a 100644 --- a/src/utils/Process.php +++ b/src/utils/Process.php @@ -174,8 +174,17 @@ final class Process{ return -1; } - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); + $out = stream_get_contents($pipes[1]); + if($out === false){ + throw new AssumptionFailedError("Presume this can't happen for proc_open ... ???"); + } + $stdout = $out; + + $err = stream_get_contents($pipes[2]); + if($err === false){ + throw new AssumptionFailedError("Presume this can't happen for proc_open ... ???"); + } + $stderr = $err; foreach($pipes as $p){ fclose($p); From 9592f066f338c517d985ee4cf30877bbf135863d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:05:49 +0000 Subject: [PATCH 072/334] PHPDoc: Restrict ReversePriorityQueue to numeric priorities --- src/utils/ReversePriorityQueue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ReversePriorityQueue.php b/src/utils/ReversePriorityQueue.php index 53fd0f08a..03f1aea8d 100644 --- a/src/utils/ReversePriorityQueue.php +++ b/src/utils/ReversePriorityQueue.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\utils; /** - * @phpstan-template TPriority + * @phpstan-template TPriority of numeric * @phpstan-template TValue * @phpstan-extends \SplPriorityQueue */ From 73edb8799dad7d6dc8fbc029060a58da6be29bec Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:06:19 +0000 Subject: [PATCH 073/334] SignalHandler: fixed dodgy setup logic --- src/utils/SignalHandler.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/SignalHandler.php b/src/utils/SignalHandler.php index 82ae02da6..75d38ff97 100644 --- a/src/utils/SignalHandler.php +++ b/src/utils/SignalHandler.php @@ -36,14 +36,12 @@ use const SIGTERM; final class SignalHandler{ /** @phpstan-var (\Closure(int) : void)|null */ - private ?\Closure $interruptCallback; + private ?\Closure $interruptCallback = null; /** * @phpstan-param \Closure() : void $interruptCallback */ public function __construct(\Closure $interruptCallback){ - $this->interruptCallback = $interruptCallback; - if(function_exists('sapi_windows_set_ctrl_handler')){ sapi_windows_set_ctrl_handler($this->interruptCallback = function(int $signo) use ($interruptCallback) : void{ if($signo === PHP_WINDOWS_EVENT_CTRL_C || $signo === PHP_WINDOWS_EVENT_CTRL_BREAK){ From a1ba8bc3da8f726f24e43fc70059441d2f3eae05 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:07:54 +0000 Subject: [PATCH 074/334] NetworkSession: improve PHPDoc types --- src/network/mcpe/NetworkSession.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 98460bef3..21fa38ea7 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -164,7 +164,10 @@ class NetworkSession{ private ?EncryptionContext $cipher = null; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $sendBuffer = []; /** * @var PromiseResolver[] @@ -544,6 +547,7 @@ class NetworkSession{ * @phpstan-return Promise */ public function sendDataPacketWithReceipt(ClientboundPacket $packet, bool $immediate = false) : Promise{ + /** @phpstan-var PromiseResolver $resolver */ $resolver = new PromiseResolver(); if(!$this->sendDataPacketInternal($packet, $immediate, $resolver)){ From d1fa6edc50d53c2f2b0280580f04370ff67749bc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:08:18 +0000 Subject: [PATCH 075/334] InGamePacketHandler: fix weak comparisons --- src/network/mcpe/handler/InGamePacketHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 88b4ba1a0..dba16e1e6 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -416,7 +416,7 @@ class InGamePacketHandler extends PacketHandler{ $droppedCount = null; foreach($data->getActions() as $networkInventoryAction){ - if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot == NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){ + if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot === NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){ $droppedCount = $networkInventoryAction->newItem->getItemStack()->getCount(); if($droppedCount <= 0){ throw new PacketHandlingException("Expected positive count for dropped item"); @@ -578,7 +578,7 @@ class InGamePacketHandler extends PacketHandler{ private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{ $this->player->selectHotbarSlot($data->getHotbarSlot()); - if($data->getActionType() == ReleaseItemTransactionData::ACTION_RELEASE){ + if($data->getActionType() === ReleaseItemTransactionData::ACTION_RELEASE){ $this->player->releaseHeldItem(); return true; } From 2e32c5067029e1040ec431cd6b817470129c37ef Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:08:48 +0000 Subject: [PATCH 076/334] NetworkSession: apparently aliases are already a list at this point??? --- src/network/mcpe/NetworkSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 21fa38ea7..80a7e4ddf 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1110,7 +1110,7 @@ class NetworkSession{ //work around a client bug which makes the original name not show when aliases are used $aliases[] = $lname; } - $aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", array_values($aliases)); + $aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", $aliases); } $description = $command->getDescription(); From 601be3fb3325204ea6cdf32e6007722bf6b786ba Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:09:26 +0000 Subject: [PATCH 077/334] stfu --- src/network/mcpe/NetworkSession.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 80a7e4ddf..511d0dece 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -118,7 +118,6 @@ use pocketmine\world\Position; use pocketmine\world\World; use pocketmine\YmlServerProperties; use function array_map; -use function array_values; use function base64_encode; use function bin2hex; use function count; From a17512de9388439c9dd6364e15873d4a67263e3a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 6 Jan 2025 23:26:13 +0000 Subject: [PATCH 078/334] Command: don't trust plugins not to pass junk --- src/command/Command.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/command/Command.php b/src/command/Command.php index 63276a123..aea57e6a2 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -33,6 +33,7 @@ use pocketmine\permission\PermissionManager; use pocketmine\Server; use pocketmine\utils\BroadcastLoggerForwarder; use pocketmine\utils\TextFormat; +use function array_values; use function explode; use function implode; use function str_replace; @@ -213,6 +214,7 @@ abstract class Command{ * @phpstan-param list $aliases */ public function setAliases(array $aliases) : void{ + $aliases = array_values($aliases); //because plugins can and will pass crap $this->aliases = $aliases; if(!$this->isRegistered()){ $this->activeAliases = $aliases; From 28d31c97f81ff82481e9b4f3e6c9f8a66ac45834 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:05:01 +0000 Subject: [PATCH 079/334] Server: fixup PHPStan 2.x reported issues --- src/Server.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Server.php b/src/Server.php index 9003a7f9c..5a72ad048 100644 --- a/src/Server.php +++ b/src/Server.php @@ -138,6 +138,7 @@ use function file_put_contents; use function filemtime; use function fopen; use function get_class; +use function gettype; use function ini_set; use function is_array; use function is_dir; @@ -918,6 +919,7 @@ class Server{ TimingsHandler::getCollectCallbacks()->add(function() : array{ $promises = []; foreach($this->asyncPool->getRunningWorkers() as $workerId){ + /** @phpstan-var PromiseResolver> $resolver */ $resolver = new PromiseResolver(); $this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId); @@ -1013,7 +1015,11 @@ class Server{ copy(Path::join(\pocketmine\RESOURCE_PATH, 'plugin_list.yml'), $graylistFile); } try{ - $pluginGraylist = PluginGraylist::fromArray(yaml_parse(Filesystem::fileGetContents($graylistFile))); + $array = yaml_parse(Filesystem::fileGetContents($graylistFile)); + if(!is_array($array)){ + throw new \InvalidArgumentException("Expected array for root, but have " . gettype($array)); + } + $pluginGraylist = PluginGraylist::fromArray($array); }catch(\InvalidArgumentException $e){ $this->logger->emergency("Failed to load $graylistFile: " . $e->getMessage()); $this->forceShutdownExit(); @@ -1174,7 +1180,7 @@ class Server{ if($this->worldManager->getDefaultWorld() === null){ $default = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world"); - if(trim($default) == ""){ + if(trim($default) === ""){ $this->logger->warning("level-name cannot be null, using default"); $default = "world"; $this->configGroup->setConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world"); From 7b1b35ab1f0bcfb2df1acf0670d286e9a19a130f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:07:38 +0000 Subject: [PATCH 080/334] generator: fixup issues reported by PHPStan 2.0 --- src/world/generator/FlatGeneratorOptions.php | 3 +-- src/world/generator/normal/Normal.php | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/world/generator/FlatGeneratorOptions.php b/src/world/generator/FlatGeneratorOptions.php index ab10c8a47..ba89cefb7 100644 --- a/src/world/generator/FlatGeneratorOptions.php +++ b/src/world/generator/FlatGeneratorOptions.php @@ -75,8 +75,7 @@ final class FlatGeneratorOptions{ $y = 0; $itemParser = LegacyStringToItemParser::getInstance(); foreach($split as $line){ - preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches); - if(count($matches) !== 3){ + if(preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches) !== 1){ throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\""); } diff --git a/src/world/generator/normal/Normal.php b/src/world/generator/normal/Normal.php index 1d4805e16..a440f1e5f 100644 --- a/src/world/generator/normal/Normal.php +++ b/src/world/generator/normal/Normal.php @@ -126,10 +126,10 @@ class Normal extends Generator{ $hash = (int) $hash; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; - if($xNoise == 3){ + if($xNoise === 3){ $xNoise = 1; } - if($zNoise == 3){ + if($zNoise === 3){ $zNoise = 1; } From cd59e272bcabcbcf97fdd380625bcf39969043f0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:10:42 +0000 Subject: [PATCH 081/334] PHPStan 2.0 fixes --- src/crafting/CraftingManager.php | 2 -- .../block/upgrade/BlockStateUpgradeSchemaUtils.php | 7 +++---- src/data/runtime/RuntimeEnumMetadata.php | 3 +-- src/event/HandlerListManager.php | 2 +- src/inventory/transaction/InventoryTransaction.php | 2 +- src/item/WritableBookBase.php | 5 +++-- src/item/enchantment/ItemEnchantmentTagRegistry.php | 6 ++++-- src/player/Player.php | 8 +++----- src/resourcepacks/ResourcePackManager.php | 11 +++++++++-- src/scheduler/BulkCurlTask.php | 5 ++++- src/timings/TimingsHandler.php | 2 ++ src/timings/TimingsRecord.php | 2 +- src/world/World.php | 11 +++++------ src/world/format/io/region/RegionWorldProvider.php | 10 ++++++---- 14 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index ff2be6926..895eeaccc 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -110,7 +110,6 @@ class CraftingManager{ /** * @param Item[] $items - * @phpstan-param list $items * * @return Item[] * @phpstan-return list @@ -135,7 +134,6 @@ class CraftingManager{ /** * @param Item[] $outputs - * @phpstan-param list $outputs */ private static function hashOutputs(array $outputs) : string{ $outputs = self::pack($outputs); diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php index 08eba8978..b4ed0dd26 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php @@ -37,7 +37,6 @@ use pocketmine\utils\Utils; use Symfony\Component\Filesystem\Path; use function array_key_last; use function array_map; -use function array_values; use function assert; use function count; use function get_debug_type; @@ -138,8 +137,8 @@ final class BlockStateUpgradeSchemaUtils{ $convertedRemappedValuesIndex = []; foreach(Utils::stringifyKeys($model->remappedPropertyValuesIndex ?? []) as $mappingKey => $mappingValues){ - foreach($mappingValues as $k => $oldNew){ - $convertedRemappedValuesIndex[$mappingKey][$k] = new BlockStateUpgradeSchemaValueRemap( + foreach($mappingValues as $oldNew){ + $convertedRemappedValuesIndex[$mappingKey][] = new BlockStateUpgradeSchemaValueRemap( self::jsonModelToTag($oldNew->old), self::jsonModelToTag($oldNew->new) ); @@ -361,7 +360,7 @@ final class BlockStateUpgradeSchemaUtils{ //remaps with the same number of criteria should be sorted alphabetically, but this is not strictly necessary return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []); }); - $result->remappedStates[$oldBlockName] = array_values($keyedRemaps); + $result->remappedStates[$oldBlockName] = $keyedRemaps; //usort strips keys, so this is already a list } if(isset($result->remappedStates)){ ksort($result->remappedStates); diff --git a/src/data/runtime/RuntimeEnumMetadata.php b/src/data/runtime/RuntimeEnumMetadata.php index 261b7a1bc..45f831b19 100644 --- a/src/data/runtime/RuntimeEnumMetadata.php +++ b/src/data/runtime/RuntimeEnumMetadata.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\data\runtime; -use function array_values; use function ceil; use function count; use function log; @@ -60,7 +59,7 @@ final class RuntimeEnumMetadata{ usort($members, fn(\UnitEnum $a, \UnitEnum $b) => $a->name <=> $b->name); //sort by name to ensure consistent ordering (and thus consistent bit assignments) $this->bits = (int) ceil(log(count($members), 2)); - $this->intToEnum = array_values($members); + $this->intToEnum = $members; //usort strips keys so this is already a list $reversed = []; foreach($this->intToEnum as $int => $enum){ diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index 605a38747..9437df37f 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -119,7 +119,7 @@ class HandlerListManager{ 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(); + return $cache->list ?? $this->getListFor($event)->getListenerList(); } /** diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 47290e401..8f7b57610 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -232,7 +232,7 @@ class InventoryTransaction{ /** * @param SlotChangeAction[] $possibleActions - * @phpstan-param list $possibleActions + * @phpstan-param array $possibleActions */ protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{ assert(count($possibleActions) > 0); diff --git a/src/item/WritableBookBase.php b/src/item/WritableBookBase.php index 6b7e55468..d3b9b7061 100644 --- a/src/item/WritableBookBase.php +++ b/src/item/WritableBookBase.php @@ -101,8 +101,9 @@ abstract class WritableBookBase extends Item{ * @return $this */ public function deletePage(int $pageId) : self{ - unset($this->pages[$pageId]); - $this->pages = array_values($this->pages); + $newPages = $this->pages; + unset($newPages[$pageId]); + $this->pages = array_values($newPages); return $this; } diff --git a/src/item/enchantment/ItemEnchantmentTagRegistry.php b/src/item/enchantment/ItemEnchantmentTagRegistry.php index 210cd8e86..b239f18a2 100644 --- a/src/item/enchantment/ItemEnchantmentTagRegistry.php +++ b/src/item/enchantment/ItemEnchantmentTagRegistry.php @@ -32,6 +32,7 @@ use function array_merge; use function array_search; use function array_shift; use function array_unique; +use function array_values; use function count; /** @@ -103,7 +104,8 @@ final class ItemEnchantmentTagRegistry{ foreach(Utils::stringifyKeys($this->tagMap) as $key => $nestedTags){ if(($nestedKey = array_search($tag, $nestedTags, true)) !== false){ - unset($this->tagMap[$key][$nestedKey]); + unset($nestedTags[$nestedKey]); + $this->tagMap[$key] = array_values($nestedTags); } } } @@ -115,7 +117,7 @@ final class ItemEnchantmentTagRegistry{ */ public function removeNested(string $tag, array $nestedTags) : void{ $this->assertNotInternalTag($tag); - $this->tagMap[$tag] = array_diff($this->tagMap[$tag], $nestedTags); + $this->tagMap[$tag] = array_values(array_diff($this->tagMap[$tag], $nestedTags)); } /** diff --git a/src/player/Player.php b/src/player/Player.php index bf911a2ff..66ba6f376 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -147,7 +147,6 @@ use function count; use function explode; use function floor; use function get_class; -use function is_int; use function max; use function mb_strlen; use function microtime; @@ -826,7 +825,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $X = null; $Z = null; World::getXZ($index, $X, $Z); - assert(is_int($X) && is_int($Z)); ++$count; @@ -1346,7 +1344,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->nextChunkOrderRun = 0; } - if(!$revert && $distanceSquared != 0){ + if(!$revert && $distanceSquared !== 0.0){ $dx = $newPos->x - $oldPos->x; $dy = $newPos->y - $oldPos->y; $dz = $newPos->z - $oldPos->z; @@ -2319,7 +2317,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $ev = new PlayerQuitEvent($this, $quitMessage ?? $this->getLeaveMessage(), $reason); $ev->call(); - if(($quitMessage = $ev->getQuitMessage()) != ""){ + if(($quitMessage = $ev->getQuitMessage()) !== ""){ $this->server->broadcastMessage($quitMessage); } $this->save(); @@ -2460,7 +2458,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->xpManager->setXpAndProgress(0, 0.0); } - if($ev->getDeathMessage() != ""){ + if($ev->getDeathMessage() !== ""){ $this->server->broadcastMessage($ev->getDeathMessage()); } diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index baf16ca20..c4668eb2a 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -47,10 +47,16 @@ class ResourcePackManager{ private string $path; private bool $serverForceResources = false; - /** @var ResourcePack[] */ + /** + * @var ResourcePack[] + * @phpstan-var list + */ private array $resourcePacks = []; - /** @var ResourcePack[] */ + /** + * @var ResourcePack[] + * @phpstan-var array + */ private array $uuidList = []; /** @@ -165,6 +171,7 @@ class ResourcePackManager{ /** * Returns an array of resource packs in use, sorted in order of priority. * @return ResourcePack[] + * @phpstan-return list */ public function getResourceStack() : array{ return $this->resourcePacks; diff --git a/src/scheduler/BulkCurlTask.php b/src/scheduler/BulkCurlTask.php index ccc1b2466..21f144702 100644 --- a/src/scheduler/BulkCurlTask.php +++ b/src/scheduler/BulkCurlTask.php @@ -77,7 +77,10 @@ class BulkCurlTask extends AsyncTask{ * @phpstan-var \Closure(list) : void */ $callback = $this->fetchLocal(self::TLS_KEY_COMPLETION_CALLBACK); - /** @var InternetRequestResult[]|InternetException[] $results */ + /** + * @var InternetRequestResult[]|InternetException[] $results + * @phpstan-var list $results + */ $results = $this->getResult(); $callback($results); } diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php index 95f7dbacc..25f139d91 100644 --- a/src/timings/TimingsHandler.php +++ b/src/timings/TimingsHandler.php @@ -123,6 +123,7 @@ class TimingsHandler{ /** * @return string[] + * @phpstan-return list */ private static function printFooter() : array{ $result = []; @@ -173,6 +174,7 @@ class TimingsHandler{ } } + /** @phpstan-var PromiseResolver> $resolver */ $resolver = new PromiseResolver(); Promise::all($otherThreadRecordPromises)->onCompletion( function(array $promisedRecords) use ($resolver, $thisThreadRecords) : void{ diff --git a/src/timings/TimingsRecord.php b/src/timings/TimingsRecord.php index 2e4928d8a..390ab74e5 100644 --- a/src/timings/TimingsRecord.php +++ b/src/timings/TimingsRecord.php @@ -131,7 +131,7 @@ final class TimingsRecord{ } public function stopTiming(int $now) : void{ - if($this->start == 0){ + if($this->start === 0){ return; } if(self::$currentRecord !== $this){ diff --git a/src/world/World.php b/src/world/World.php index b6df7f2a4..7395afa78 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -113,6 +113,7 @@ use function array_keys; use function array_map; use function array_merge; use function array_sum; +use function array_values; use function assert; use function cos; use function count; @@ -678,7 +679,6 @@ class World implements ChunkManager{ * Used for broadcasting sounds and particles with specific targets. * * @param Player[] $allowed - * @phpstan-param list $allowed * * @return array */ @@ -1089,7 +1089,6 @@ class World implements ChunkManager{ /** * @param Vector3[] $blocks - * @phpstan-param list $blocks * * @return ClientboundPacket[] * @phpstan-return list @@ -1456,8 +1455,8 @@ class World implements ChunkManager{ $this->provider->saveChunk($chunkX, $chunkZ, new ChunkData( $chunk->getSubChunks(), $chunk->isPopulated(), - array_map(fn(Entity $e) => $e->saveNBT(), array_filter($this->getChunkEntities($chunkX, $chunkZ), fn(Entity $e) => $e->canSaveWithChunk())), - array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()), + array_map(fn(Entity $e) => $e->saveNBT(), array_values(array_filter($this->getChunkEntities($chunkX, $chunkZ), fn(Entity $e) => $e->canSaveWithChunk()))), + array_map(fn(Tile $t) => $t->saveNBT(), array_values($chunk->getTiles())), ), $chunk->getTerrainDirtyFlags()); $chunk->clearTerrainDirtyFlags(); } @@ -3019,8 +3018,8 @@ class World implements ChunkManager{ $this->provider->saveChunk($x, $z, new ChunkData( $chunk->getSubChunks(), $chunk->isPopulated(), - array_map(fn(Entity $e) => $e->saveNBT(), array_filter($this->getChunkEntities($x, $z), fn(Entity $e) => $e->canSaveWithChunk())), - array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()), + array_map(fn(Entity $e) => $e->saveNBT(), array_values(array_filter($this->getChunkEntities($x, $z), fn(Entity $e) => $e->canSaveWithChunk()))), + array_map(fn(Tile $t) => $t->saveNBT(), array_values($chunk->getTiles())), ), $chunk->getTerrainDirtyFlags()); }finally{ $this->timings->syncChunkSave->stopTiming(); diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 0ab70300e..7a4463f5d 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -33,10 +33,8 @@ use pocketmine\world\format\io\exception\CorruptedChunkException; use pocketmine\world\format\io\LoadedChunkData; use pocketmine\world\format\io\WorldData; use Symfony\Component\Filesystem\Path; -use function assert; use function file_exists; use function is_dir; -use function is_int; use function morton2d_encode; use function rename; use function scandir; @@ -60,7 +58,12 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ public static function isValid(string $path) : bool{ if(file_exists(Path::join($path, "level.dat")) && is_dir($regionPath = Path::join($path, "region"))){ - foreach(scandir($regionPath, SCANDIR_SORT_NONE) as $file){ + $files = scandir($regionPath, SCANDIR_SORT_NONE); + if($files === false){ + //we can't tell the type if we don't have read perms + return false; + } + foreach($files as $file){ $extPos = strrpos($file, "."); if($extPos !== false && substr($file, $extPos + 1) === static::getRegionFileExtension()){ //we don't care if other region types exist, we only care if this format is possible @@ -199,7 +202,6 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ public function loadChunk(int $chunkX, int $chunkZ) : ?LoadedChunkData{ $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); - assert(is_int($regionX) && is_int($regionZ)); if(!file_exists($this->pathToRegion($regionX, $regionZ))){ return null; From b1c7fc017a9f68b0eefde15380ec6c67ddfa59aa Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:13:20 +0000 Subject: [PATCH 082/334] CS --- src/world/generator/FlatGeneratorOptions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/world/generator/FlatGeneratorOptions.php b/src/world/generator/FlatGeneratorOptions.php index ba89cefb7..b52d64658 100644 --- a/src/world/generator/FlatGeneratorOptions.php +++ b/src/world/generator/FlatGeneratorOptions.php @@ -27,7 +27,6 @@ use pocketmine\data\bedrock\BiomeIds; use pocketmine\item\LegacyStringToItemParser; use pocketmine\item\LegacyStringToItemParserException; use function array_map; -use function count; use function explode; use function preg_match; use function preg_match_all; From 47cb04f6a65eeed7e91e85d428f83b108bce218a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:15:50 +0000 Subject: [PATCH 083/334] tools: fix PHPStan 2.0 issues --- tools/blockstate-upgrade-schema-utils.php | 9 ++++++--- tools/compact-regions.php | 10 ++++++++-- tools/generate-bedrock-data-from-packets.php | 6 +++--- tools/generate-item-upgrade-schema.php | 15 +++++++++++---- tools/simulate-chunk-selector.php | 16 ++++++++++++++++ 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index b7a9a4169..4f5c8740c 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -523,10 +523,12 @@ function processRemappedStates(array $upgradeTable) : array{ } } } + $orderedUnchanged = []; foreach(Utils::stringifyKeys($unchangedStatesByNewName) as $newName => $unchangedStates){ - ksort($unchangedStates); - $unchangedStatesByNewName[$newName] = $unchangedStates; + sort($unchangedStates); + $orderedUnchanged[$newName] = $unchangedStates; } + $unchangedStatesByNewName = $orderedUnchanged; $notFlattenedProperties = []; @@ -656,7 +658,8 @@ function processRemappedStates(array $upgradeTable) : array{ usort($list, function(BlockStateUpgradeSchemaBlockRemap $a, BlockStateUpgradeSchemaBlockRemap $b) : int{ return count($b->oldState) <=> count($a->oldState); }); - return array_values($list); + //usort discards keys, so this is already a list + return $list; } /** diff --git a/tools/compact-regions.php b/tools/compact-regions.php index 04ac3f0c9..ab80792d3 100644 --- a/tools/compact-regions.php +++ b/tools/compact-regions.php @@ -76,7 +76,12 @@ function find_regions_recursive(string $dir, array &$files) : void{ in_array(pathinfo($fullPath, PATHINFO_EXTENSION), SUPPORTED_EXTENSIONS, true) && is_file($fullPath) ){ - $files[$fullPath] = filesize($fullPath); + $size = filesize($fullPath); + if($size === false){ + //If we can't get the size of the file, we probably don't have perms to read it, so ignore it + continue; + } + $files[$fullPath] = $size; }elseif(is_dir($fullPath)){ find_regions_recursive($fullPath, $files); } @@ -165,7 +170,8 @@ function main(array $argv) : int{ clearstatcache(); $newSize = 0; foreach(Utils::stringifyKeys($files) as $file => $oldSize){ - $newSize += file_exists($file) ? filesize($file) : 0; + $size = file_exists($file) ? filesize($file) : 0; + $newSize += $size !== false ? $size : 0; } $diff = $currentSize - $newSize; $logger->info("Finished compaction of " . count($files) . " files. Freed " . number_format($diff) . " bytes of space (" . round(($diff / $currentSize) * 100, 2) . "% reduction)."); diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 2c20e6099..9aac5185e 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -454,7 +454,7 @@ class ParserPacketHandler extends PacketHandler{ //this sorts the data into a canonical order to make diffs between versions reliable //how the data is ordered doesn't matter as long as it's reproducible - foreach($recipes as $_type => $entries){ + foreach(Utils::promoteKeys($recipes) as $_type => $entries){ $_sortedRecipes = []; $_seen = []; foreach($entries as $entry){ @@ -475,10 +475,10 @@ class ParserPacketHandler extends PacketHandler{ } ksort($recipes, SORT_STRING); - foreach($recipes as $type => $entries){ + foreach(Utils::promoteKeys($recipes) as $type => $entries){ echo "$type: " . count($entries) . "\n"; } - foreach($recipes as $type => $entries){ + foreach(Utils::promoteKeys($recipes) as $type => $entries){ file_put_contents(Path::join($recipesPath, $type . '.json'), json_encode($entries, JSON_PRETTY_PRINT) . "\n"); } diff --git a/tools/generate-item-upgrade-schema.php b/tools/generate-item-upgrade-schema.php index 4eee92539..7ad473b23 100644 --- a/tools/generate-item-upgrade-schema.php +++ b/tools/generate-item-upgrade-schema.php @@ -31,10 +31,12 @@ namespace pocketmine\tools\generate_item_upgrade_schema; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\utils\Filesystem; +use pocketmine\utils\Utils; use Symfony\Component\Filesystem\Path; use function count; use function dirname; use function file_put_contents; +use function fwrite; use function is_array; use function json_decode; use function json_encode; @@ -45,6 +47,7 @@ use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; use const SCANDIR_SORT_ASCENDING; use const SORT_STRING; +use const STDERR; require dirname(__DIR__) . '/vendor/autoload.php'; @@ -56,7 +59,7 @@ if(count($argv) !== 4){ [, $mappingTableFile, $upgradeSchemasDir, $outputFile] = $argv; $target = json_decode(Filesystem::fileGetContents($mappingTableFile), true, JSON_THROW_ON_ERROR); -if(!is_array($target)){ +if(!is_array($target) || !isset($target["simple"]) || !is_array($target["simple"]) || !isset($target["complex"]) || !is_array($target["complex"])){ \GlobalLogger::get()->error("Invalid mapping table file"); exit(1); } @@ -93,7 +96,7 @@ foreach($files as $file){ $newDiff = []; -foreach($target["simple"] as $oldId => $newId){ +foreach(Utils::promoteKeys($target["simple"]) as $oldId => $newId){ $previousNewId = $merged["simple"][$oldId] ?? null; if( $previousNewId === $newId || //if previous schemas already accounted for this @@ -107,8 +110,12 @@ if(isset($newDiff["renamedIds"])){ ksort($newDiff["renamedIds"], SORT_STRING); } -foreach($target["complex"] as $oldId => $mappings){ - foreach($mappings as $meta => $newId){ +foreach(Utils::promoteKeys($target["complex"]) as $oldId => $mappings){ + if(!is_array($mappings)){ + fwrite(STDERR, "Complex mapping for $oldId is not an array\n"); + exit(1); + } + foreach(Utils::promoteKeys($mappings) as $meta => $newId){ if(($merged["complex"][$oldId][$meta] ?? null) !== $newId){ if($oldId === "minecraft:spawn_egg" && $meta === 130 && ($newId === "minecraft:axolotl_bucket" || $newId === "minecraft:axolotl_spawn_egg")){ //TODO: hack for vanilla bug workaround diff --git a/tools/simulate-chunk-selector.php b/tools/simulate-chunk-selector.php index 0b279268a..3d5e167cf 100644 --- a/tools/simulate-chunk-selector.php +++ b/tools/simulate-chunk-selector.php @@ -53,6 +53,10 @@ use const STR_PAD_LEFT; require dirname(__DIR__) . '/vendor/autoload.php'; +/** + * @phpstan-param positive-int $scale + * @phpstan-param positive-int $radius + */ function newImage(int $scale, int $radius) : \GdImage{ $image = Utils::assumeNotFalse(imagecreatetruecolor($scale * $radius * 2, $scale * $radius * 2)); imagesavealpha($image, true); @@ -149,6 +153,18 @@ if($radius === null){ fwrite(STDERR, "Please specify a radius using --radius\n"); exit(1); } +if($radius < 1){ + fwrite(STDERR, "Radius cannot be less than 1\n"); + exit(1); +} +if($scale < 1){ + fwrite(STDERR, "Scale cannot be less than 1\n"); + exit(1); +} +if($nChunksPerStep < 1){ + fwrite(STDERR, "Chunks per step cannot be less than 1\n"); + exit(1); +} $outputDirectory = null; if(isset($opts["output"])){ From 38441a6ba3818b7e3b96995c048a28fb1c77dd89 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:23:16 +0000 Subject: [PATCH 084/334] build: avoid weak comparison --- build/server-phar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/server-phar.php b/build/server-phar.php index f6bb29d51..7560fa5da 100644 --- a/build/server-phar.php +++ b/build/server-phar.php @@ -129,7 +129,7 @@ function buildPhar(string $pharPath, string $basePath, array $includedPaths, arr } function main() : void{ - if(ini_get("phar.readonly") == 1){ + if(ini_get("phar.readonly") === "1"){ echo "Set phar.readonly to 0 with -dphar.readonly=0" . PHP_EOL; exit(1); } From d69a887b0d40320d18fa9d0950fbeb1a54d38ec2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:24:26 +0000 Subject: [PATCH 085/334] Utils: fix parameter doc for printableExceptionInfo() --- src/utils/Utils.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/Utils.php b/src/utils/Utils.php index cc33e60e0..37cf54390 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -406,6 +406,7 @@ final class Utils{ /** * @param mixed[][] $trace + * @phpstan-param list>|null $trace * @return string[] */ public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{ From d25ec58a6fd7151ad4a5dbf4035c9630caa1061b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:25:37 +0000 Subject: [PATCH 086/334] AsyncPoolTest: phpdoc --- tests/phpunit/scheduler/AsyncPoolTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/scheduler/AsyncPoolTest.php b/tests/phpunit/scheduler/AsyncPoolTest.php index 53ec15c12..88985cc39 100644 --- a/tests/phpunit/scheduler/AsyncPoolTest.php +++ b/tests/phpunit/scheduler/AsyncPoolTest.php @@ -71,6 +71,7 @@ class AsyncPoolTest extends TestCase{ } public function testThreadSafeSetResult() : void{ + /** @phpstan-var PromiseResolver> $resolver */ $resolver = new PromiseResolver(); $resolver->getPromise()->onCompletion( function(ThreadSafeArray $result) : void{ From 9633b7d8a79b90fcd7510ddb7bc765ad649361f3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:34:43 +0000 Subject: [PATCH 087/334] Update to PHPStan 2.x --- composer.json | 6 +- composer.lock | 58 +- phpstan.neon.dist | 2 + src/command/utils/CommandStringHelper.php | 3 +- src/utils/Utils.php | 9 +- .../format/io/region/RegionWorldProvider.php | 3 + src/world/generator/FlatGeneratorOptions.php | 2 +- tests/phpstan/DummyPluginOwned.php | 28 + tests/phpstan/configs/actual-problems.neon | 773 ++++++++++++------ .../phpstan/configs/dependency-problems.neon | 73 ++ .../phpstan/configs/impossible-generics.neon | 6 +- tests/phpstan/configs/phpstan-bugs.neon | 192 ++++- .../configs/spl-fixed-array-sucks.neon | 18 +- .../rules/DeprecatedLegacyEnumAccessRule.php | 27 +- .../rules/DisallowEnumComparisonRule.php | 15 +- .../rules/DisallowForeachByReferenceRule.php | 1 + .../rules/UnsafeForeachArrayOfStringRule.php | 2 +- tests/phpunit/utils/fixtures/TestTrait.php | 2 +- 18 files changed, 876 insertions(+), 344 deletions(-) create mode 100644 tests/phpstan/DummyPluginOwned.php create mode 100644 tests/phpstan/configs/dependency-problems.neon diff --git a/composer.json b/composer.json index e2ae641ca..a40f3733e 100644 --- a/composer.json +++ b/composer.json @@ -52,9 +52,9 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "1.11.11", - "phpstan/phpstan-phpunit": "^1.1.0", - "phpstan/phpstan-strict-rules": "^1.2.0", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "^2.0.0", + "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" }, "autoload": { diff --git a/composer.lock b/composer.lock index 54f65014f..8df6c329d 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": "732102eca72dc1d29e7b67dfbce07653", + "content-hash": "994ccffe45f066768542019f6f9d237b", "packages": [ { "name": "adhocore/json-comment", @@ -1386,20 +1386,20 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.11", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1440,34 +1440,33 @@ "type": "github" } ], - "time": "2024-08-19T14:37:29+00:00" + "time": "2025-01-05T16:43:48+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.4.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + "reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e32ac656788a5bf3dedda89e6a2cad5643bf1a18", + "reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -1490,34 +1489,33 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.3" }, - "time": "2024-04-20T06:39:00+00:00" + "time": "2024-12-19T09:14:43+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3", + "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -1539,9 +1537,9 @@ "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.6.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.1" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2024-12-12T20:21:10+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 6e8578652..dfaa964e4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,6 +1,7 @@ includes: - tests/phpstan/analyse-for-current-php-version.neon.php - tests/phpstan/configs/actual-problems.neon + - tests/phpstan/configs/dependency-problems.neon - tests/phpstan/configs/impossible-generics.neon - tests/phpstan/configs/php-bugs.neon - tests/phpstan/configs/phpstan-bugs.neon @@ -31,6 +32,7 @@ parameters: paths: - build - src + - tests/phpstan/DummyPluginOwned.php - tests/phpstan/rules - tests/phpunit - tests/plugins/TesterPlugin diff --git a/src/command/utils/CommandStringHelper.php b/src/command/utils/CommandStringHelper.php index eacc5d3d8..76d70a9bb 100644 --- a/src/command/utils/CommandStringHelper.php +++ b/src/command/utils/CommandStringHelper.php @@ -51,9 +51,8 @@ final class CommandStringHelper{ foreach($matches[0] as $k => $_){ for($i = 1; $i <= 2; ++$i){ if($matches[$i][$k] !== ""){ - /** @var string $match */ //phpstan can't understand preg_match and friends by itself :( $match = $matches[$i][$k]; - $args[(int) $k] = preg_replace('/\\\\([\\\\"])/u', '$1', $match) ?? throw new AssumptionFailedError(preg_last_error_msg()); + $args[] = preg_replace('/\\\\([\\\\"])/u', '$1', $match) ?? throw new AssumptionFailedError(preg_last_error_msg()); break; } } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 37cf54390..f557562c9 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -167,6 +167,7 @@ final class Utils{ /** * @phpstan-return \Closure(object) : object + * @deprecated */ public static function cloneCallback() : \Closure{ return static function(object $o){ @@ -179,15 +180,13 @@ final class Utils{ * @phpstan-template TValue of object * * @param object[] $array - * @phpstan-param array $array + * @phpstan-param array|list $array * * @return object[] - * @phpstan-return array + * @phpstan-return ($array is list ? list : array) */ public static function cloneObjectArray(array $array) : array{ - /** @phpstan-var \Closure(TValue) : TValue $callback */ - $callback = self::cloneCallback(); - return array_map($callback, $array); + return array_map(fn(object $o) => clone $o, $array); } /** diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 7a4463f5d..8fe7928b8 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -215,6 +215,9 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ return null; } + /** + * @phpstan-return \RegexIterator + */ private function createRegionIterator() : \RegexIterator{ return new \RegexIterator( new \FilesystemIterator( diff --git a/src/world/generator/FlatGeneratorOptions.php b/src/world/generator/FlatGeneratorOptions.php index b52d64658..563297b00 100644 --- a/src/world/generator/FlatGeneratorOptions.php +++ b/src/world/generator/FlatGeneratorOptions.php @@ -117,7 +117,7 @@ final class FlatGeneratorOptions{ } } } - $options[(string) $option] = $params; + $options[$option] = $params; } return new self($structure, $biomeId, $options); } diff --git a/tests/phpstan/DummyPluginOwned.php b/tests/phpstan/DummyPluginOwned.php new file mode 100644 index 000000000..b63975dcf --- /dev/null +++ b/tests/phpstan/DummyPluginOwned.php @@ -0,0 +1,28 @@ +, array\\ given\\.$#" + message: '#^Parameter \#1 \$strings of function pocketmine\\build\\server_phar\\preg_quote_array expects array\, array\ given\.$#' + identifier: argument.type count: 1 path: ../../../build/server-phar.php - - message: "#^Do\\-while loop condition is always false\\.$#" + message: '#^Do\-while loop condition is always false\.$#' + identifier: doWhile.alwaysFalse count: 1 path: ../../../src/PocketMine.php - - message: "#^Parameter \\#1 \\$array of static method pocketmine\\\\plugin\\\\PluginGraylist\\:\\:fromArray\\(\\) expects array, mixed given\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/Server.php - - message: "#^Cannot cast mixed to int\\.$#" + message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\\> but returns array\\>\.$#' + identifier: return.type + count: 1 + path: ../../../src/Server.php + + - + message: '#^Parameter \#1 \$generatorName of closure expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/Server.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int count: 2 path: ../../../src/ServerConfigGroup.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 2 path: ../../../src/ServerConfigGroup.php - - message: "#^Cannot access offset 'git' on mixed\\.$#" + message: '#^Cannot access offset ''git'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 1 path: ../../../src/VersionInfo.php - - message: "#^Method pocketmine\\\\VersionInfo\\:\\:GIT_HASH\\(\\) should return string but returns mixed\\.$#" + message: '#^Method pocketmine\\VersionInfo\:\:GIT_HASH\(\) should return string but returns mixed\.$#' + identifier: return.type count: 1 path: ../../../src/VersionInfo.php - - message: "#^Static property pocketmine\\\\VersionInfo\\:\\:\\$gitHash \\(string\\|null\\) does not accept mixed\\.$#" + message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/VersionInfo.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Block.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Block.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:setBlockStateId\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\format\\Chunk\:\:setBlockStateId\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Block.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Block.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/block/ChorusFlower.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/block/ChorusFlower.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/block/ChorusFlower.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getRealBlockSkyLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getRealBlockSkyLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DaylightSensor.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getRealBlockSkyLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getRealBlockSkyLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DaylightSensor.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getRealBlockSkyLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getRealBlockSkyLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DaylightSensor.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#1 \\$xDiff of class pocketmine\\\\world\\\\particle\\\\DragonEggTeleportParticle constructor expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$xDiff of class pocketmine\\world\\particle\\DragonEggTeleportParticle constructor expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int\\<\\-64, 319\\> given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int\<\-64, 319\> given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#2 \\$yDiff of class pocketmine\\\\world\\\\particle\\\\DragonEggTeleportParticle constructor expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$yDiff of class pocketmine\\world\\particle\\DragonEggTeleportParticle constructor expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#3 \\$zDiff of class pocketmine\\\\world\\\\particle\\\\DragonEggTeleportParticle constructor expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$zDiff of class pocketmine\\world\\particle\\DragonEggTeleportParticle constructor expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getHighestAdjacentFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getHighestAdjacentFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getHighestAdjacentFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#1 \\$min of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$min of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#2 \\$max of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$max of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentBlockLight\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getHighestAdjacentBlockLight\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Ice.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentBlockLight\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getHighestAdjacentBlockLight\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Ice.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentBlockLight\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getHighestAdjacentBlockLight\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Ice.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Leaves.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Leaves.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Leaves.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Liquid.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Liquid.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Liquid.php - - message: "#^Parameter \\#1 \\$min of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$min of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Mycelium.php - - message: "#^Parameter \\#2 \\$max of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$max of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Mycelium.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/RedMushroom.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/RedMushroom.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/RedMushroom.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/SnowLayer.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/SnowLayer.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/SnowLayer.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#1 \\$x of class pocketmine\\\\math\\\\Vector3 constructor expects float\\|int, int\\|null given\\.$#" + message: '#^Parameter \#1 \$x of class pocketmine\\math\\Vector3 constructor expects float\|int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, int\\|null given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#2 \\$value of method pocketmine\\\\nbt\\\\tag\\\\CompoundTag\\:\\:setInt\\(\\) expects int, int\\|null given\\.$#" + message: '#^Parameter \#2 \$value of method pocketmine\\nbt\\tag\\CompoundTag\:\:setInt\(\) expects int, int\|null given\.$#' + identifier: argument.type count: 4 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#3 \\$z of class pocketmine\\\\math\\\\Vector3 constructor expects float\\|int, int\\|null given\\.$#" + message: '#^Parameter \#3 \$z of class pocketmine\\math\\Vector3 constructor expects float\|int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, int\\|null given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Property pocketmine\\\\block\\\\tile\\\\Chest\\:\\:\\$pairX \\(int\\|null\\) does not accept float\\|int\\.$#" + message: '#^Property pocketmine\\block\\tile\\Chest\:\:\$pairX \(int\|null\) does not accept float\|int\.$#' + identifier: assign.propertyType count: 2 path: ../../../src/block/tile/Chest.php - - message: "#^Property pocketmine\\\\block\\\\tile\\\\Chest\\:\\:\\$pairZ \\(int\\|null\\) does not accept float\\|int\\.$#" + message: '#^Property pocketmine\\block\\tile\\Chest\:\:\$pairZ \(int\|null\) does not accept float\|int\.$#' + identifier: assign.propertyType count: 2 path: ../../../src/block/tile/Chest.php - - message: "#^Constant pocketmine\\\\block\\\\tile\\\\MobHead\\:\\:TAG_MOUTH_MOVING is unused\\.$#" + message: '#^Constant pocketmine\\block\\tile\\MobHead\:\:TAG_MOUTH_MOVING is unused\.$#' + identifier: classConstant.unused count: 1 path: ../../../src/block/tile/MobHead.php - - message: "#^Constant pocketmine\\\\block\\\\tile\\\\MobHead\\:\\:TAG_MOUTH_TICK_COUNT is unused\\.$#" + message: '#^Constant pocketmine\\block\\tile\\MobHead\:\:TAG_MOUTH_TICK_COUNT is unused\.$#' + identifier: classConstant.unused count: 1 path: ../../../src/block/tile/MobHead.php - - message: "#^Parameter \\#2 \\$value of method pocketmine\\\\nbt\\\\tag\\\\CompoundTag\\:\\:setInt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$value of method pocketmine\\nbt\\tag\\CompoundTag\:\:setInt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/tile/Spawnable.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/utils/CropGrowthHelper.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/utils/CropGrowthHelper.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/utils/CropGrowthHelper.php - - message: "#^Cannot call method addParticle\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method addParticle\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/ParticleCommand.php - - message: "#^Cannot call method getSeed\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method getSeed\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/SeedCommand.php - - message: "#^Cannot call method setSpawnLocation\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method setSpawnLocation\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/SetWorldSpawnCommand.php - - message: "#^Cannot call method getTime\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method getTime\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/TimeCommand.php - - message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$path of static method pocketmine\\utils\\Filesystem\:\:cleanPath\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/crash/CrashDump.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Binary operation "\." between ''Error\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Binary operation "\." between ''File\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Binary operation "\." between ''Line\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Binary operation "\." between ''Type\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Parameter \#1 \$blockToItemId of class pocketmine\\data\\bedrock\\item\\BlockItemIdMap constructor expects array\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/data/bedrock/item/BlockItemIdMap.php + + - + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/Living.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/Living.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/Living.php - - message: "#^Parameter \\#2 \\$x of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/FallingBlock.php - - message: "#^Parameter \\#3 \\$y of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/FallingBlock.php - - message: "#^Parameter \\#4 \\$z of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/FallingBlock.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/Painting.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/Painting.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/Painting.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Parameter \\#2 \\$recipe of class pocketmine\\\\event\\\\inventory\\\\CraftItemEvent constructor expects pocketmine\\\\crafting\\\\CraftingRecipe, pocketmine\\\\crafting\\\\CraftingRecipe\\|null given\\.$#" + message: '#^Parameter \#2 \$recipe of class pocketmine\\event\\inventory\\CraftItemEvent constructor expects pocketmine\\crafting\\CraftingRecipe, pocketmine\\crafting\\CraftingRecipe\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/inventory/transaction/CraftingTransaction.php - - message: "#^Parameter \\#3 \\$repetitions of class pocketmine\\\\event\\\\inventory\\\\CraftItemEvent constructor expects int, int\\|null given\\.$#" + message: '#^Parameter \#3 \$repetitions of class pocketmine\\event\\inventory\\CraftItemEvent constructor expects int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/inventory/transaction/CraftingTransaction.php - - message: "#^Cannot cast mixed to int\\.$#" + message: '#^Parameter &\$haveItems @param\-out type of method pocketmine\\inventory\\transaction\\InventoryTransaction\:\:matchItems\(\) expects list\, array\, pocketmine\\item\\Item\> given\.$#' + identifier: paramOut.type + count: 1 + path: ../../../src/inventory/transaction/InventoryTransaction.php + + - + message: '#^Parameter &\$needItems @param\-out type of method pocketmine\\inventory\\transaction\\InventoryTransaction\:\:matchItems\(\) expects list\, array\, pocketmine\\item\\Item\> given\.$#' + identifier: paramOut.type + count: 1 + path: ../../../src/inventory/transaction/InventoryTransaction.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int count: 2 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$buffer of method pocketmine\\\\nbt\\\\BaseNbtSerializer\\:\\:read\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$buffer of method pocketmine\\nbt\\BaseNbtSerializer\:\:read\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$string of function base64_decode expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$string of function base64_decode expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$string of function hex2bin expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$string of function hex2bin expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$result of method pocketmine\\\\network\\\\mcpe\\\\compression\\\\CompressBatchPromise\\:\\:resolve\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$result of method pocketmine\\network\\mcpe\\compression\\CompressBatchPromise\:\:resolve\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/ChunkRequestTask.php - - message: "#^Cannot call method doFirstSpawn\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method doFirstSpawn\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getAttributeMap\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getAttributeMap\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getLanguage\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getLanguage\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 4 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getLocation\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getLocation\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getUsedChunkStatus\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getUsedChunkStatus\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getUsername\\(\\) on pocketmine\\\\player\\\\PlayerInfo\\|null\\.$#" + message: '#^Cannot call method getUsername\(\) on pocketmine\\player\\PlayerInfo\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getUuid\\(\\) on pocketmine\\\\player\\\\PlayerInfo\\|null\\.$#" + message: '#^Cannot call method getUuid\(\) on pocketmine\\player\\PlayerInfo\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method sendData\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method sendData\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method setNoClientPredictions\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method setNoClientPredictions\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method syncAll\\(\\) on pocketmine\\\\network\\\\mcpe\\\\InventoryManager\\|null\\.$#" + message: '#^Cannot call method syncAll\(\) on pocketmine\\network\\mcpe\\InventoryManager\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$clientPub of class pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask constructor expects string, string\\|null given\\.$#" + message: '#^Parameter \#1 \$clientPub of class pocketmine\\network\\mcpe\\encryption\\PrepareEncryptionTask constructor expects string, string\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAbilities\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$for of method pocketmine\\network\\mcpe\\NetworkSession\:\:syncAbilities\(\) expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\DeathPacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$player of class pocketmine\\network\\mcpe\\handler\\DeathPacketHandler constructor expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\InGamePacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$player of class pocketmine\\network\\mcpe\\handler\\InGamePacketHandler constructor expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$playerInfo of class pocketmine\\\\event\\\\player\\\\PlayerResourcePackOfferEvent constructor expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#" + message: '#^Parameter \#1 \$playerInfo of class pocketmine\\event\\player\\PlayerResourcePackOfferEvent constructor expects pocketmine\\player\\PlayerInfo, pocketmine\\player\\PlayerInfo\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$target of method pocketmine\\\\command\\\\Command\\:\\:testPermissionSilent\\(\\) expects pocketmine\\\\command\\\\CommandSender, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$target of method pocketmine\\command\\Command\:\:testPermissionSilent\(\) expects pocketmine\\command\\CommandSender, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectAdded\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$entity of method pocketmine\\network\\mcpe\\EntityEventBroadcaster\:\:onEntityEffectAdded\(\) expects pocketmine\\entity\\Living, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectRemoved\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$entity of method pocketmine\\network\\mcpe\\EntityEventBroadcaster\:\:onEntityEffectRemoved\(\) expects pocketmine\\entity\\Living, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$entity of method pocketmine\\network\\mcpe\\EntityEventBroadcaster\:\:syncAttributes\(\) expects pocketmine\\entity\\Living, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\PreSpawnPacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$player of class pocketmine\\network\\mcpe\\handler\\PreSpawnPacketHandler constructor expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$playerInfo of method pocketmine\\\\Server\\:\\:createPlayer\\(\\) expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#" + message: '#^Parameter \#2 \$playerInfo of method pocketmine\\Server\:\:createPlayer\(\) expects pocketmine\\player\\PlayerInfo, pocketmine\\player\\PlayerInfo\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#3 \\$inventoryManager of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\InGamePacketHandler constructor expects pocketmine\\\\network\\\\mcpe\\\\InventoryManager, pocketmine\\\\network\\\\mcpe\\\\InventoryManager\\|null given\\.$#" + message: '#^Parameter \#3 \$inventoryManager of class pocketmine\\network\\mcpe\\handler\\InGamePacketHandler constructor expects pocketmine\\network\\mcpe\\InventoryManager, pocketmine\\network\\mcpe\\InventoryManager\|null given\.$#' + identifier: argument.type count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#4 \\$inventoryManager of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\PreSpawnPacketHandler constructor expects pocketmine\\\\network\\\\mcpe\\\\InventoryManager, pocketmine\\\\network\\\\mcpe\\\\InventoryManager\\|null given\\.$#" + message: '#^Parameter \#4 \$inventoryManager of class pocketmine\\network\\mcpe\\handler\\PreSpawnPacketHandler constructor expects pocketmine\\network\\mcpe\\InventoryManager, pocketmine\\network\\mcpe\\InventoryManager\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\auth\\\\ProcessLoginTask\\:\\:\\$chain \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\network\\mcpe\\auth\\ProcessLoginTask\:\:\$chain \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/network/mcpe/auth/ProcessLoginTask.php - - message: "#^Parameter \\#1 \\$result of method pocketmine\\\\network\\\\mcpe\\\\compression\\\\CompressBatchPromise\\:\\:resolve\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$result of method pocketmine\\network\\mcpe\\compression\\CompressBatchPromise\:\:resolve\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/compression/CompressBatchTask.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask\\:\\:\\$serverPrivateKey \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\network\\mcpe\\encryption\\PrepareEncryptionTask\:\:\$serverPrivateKey \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/network/mcpe/encryption/PrepareEncryptionTask.php - - message: "#^Method pocketmine\\\\permission\\\\DefaultPermissions\\:\\:registerPermission\\(\\) should return pocketmine\\\\permission\\\\Permission but returns pocketmine\\\\permission\\\\Permission\\|null\\.$#" + message: '#^Method pocketmine\\permission\\DefaultPermissions\:\:registerPermission\(\) should return pocketmine\\permission\\Permission but returns pocketmine\\permission\\Permission\|null\.$#' + identifier: return.type count: 1 path: ../../../src/permission/DefaultPermissions.php - - message: "#^Parameter \\#1 \\$value of static method pocketmine\\\\permission\\\\PermissionParser\\:\\:defaultFromString\\(\\) expects bool\\|string, mixed given\\.$#" + message: '#^Parameter \#1 \$value of static method pocketmine\\permission\\PermissionParser\:\:defaultFromString\(\) expects bool\|string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/permission/PermissionParser.php - - message: "#^Parameter \\#2 \\$description of class pocketmine\\\\permission\\\\Permission constructor expects pocketmine\\\\lang\\\\Translatable\\|string\\|null, mixed given\\.$#" + message: '#^Parameter \#2 \$description of class pocketmine\\permission\\Permission constructor expects pocketmine\\lang\\Translatable\|string\|null, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/permission/PermissionParser.php - - message: "#^Cannot call method getSpawnLocation\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method getSpawnLocation\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/player/Player.php - - message: "#^Method pocketmine\\\\player\\\\Player\\:\\:getSpawn\\(\\) should return pocketmine\\\\world\\\\Position but returns pocketmine\\\\world\\\\Position\\|null\\.$#" + message: '#^Method pocketmine\\player\\Player\:\:getSpawn\(\) should return pocketmine\\world\\Position but returns pocketmine\\world\\Position\|null\.$#' + identifier: return.type count: 1 path: ../../../src/player/Player.php - - message: "#^Method pocketmine\\\\plugin\\\\PluginBase\\:\\:getConfig\\(\\) should return pocketmine\\\\utils\\\\Config but returns pocketmine\\\\utils\\\\Config\\|null\\.$#" + message: '#^Method pocketmine\\plugin\\PluginBase\:\:getConfig\(\) should return pocketmine\\utils\\Config but returns pocketmine\\utils\\Config\|null\.$#' + identifier: return.type count: 1 path: ../../../src/plugin/PluginBase.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#1 \\$haystack of function stripos expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$haystack of function stripos expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + message: '#^Parameter \#2 \$subject of function preg_match expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + message: '#^Parameter \#3 \$subject of function str_replace expects array\\|string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$main \\(string\\) does not accept mixed\\.$#" + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$authors \(array\\) does not accept list\\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$name \\(string\\) does not accept mixed\\.$#" + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$main \(string\) does not accept mixed\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Cannot call method addChild\\(\\) on pocketmine\\\\permission\\\\Permission\\|null\\.$#" + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$name \(string\) does not accept mixed\.$#' + identifier: assign.propertyType + count: 1 + path: ../../../src/plugin/PluginDescription.php + + - + message: '#^Cannot call method addChild\(\) on pocketmine\\permission\\Permission\|null\.$#' + identifier: method.nonObject count: 4 path: ../../../src/plugin/PluginManager.php - - message: "#^Method pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:getPackSize\\(\\) should return int but returns int\\<0, max\\>\\|false\\.$#" + message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#' + identifier: return.type count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Method pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:getSha256\\(\\) should return string but returns string\\|false\\.$#" + message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getSha256\(\) should return string but returns string\|false\.$#' + identifier: return.type count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$fileResource \\(resource\\) does not accept resource\\|false\\.$#" + message: '#^Property pocketmine\\resourcepacks\\ZippedResourcePack\:\:\$fileResource \(resource\) does not accept resource\|false\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$sha256 \\(string\\|null\\) does not accept string\\|false\\.$#" + message: '#^Property pocketmine\\resourcepacks\\ZippedResourcePack\:\:\$sha256 \(string\|null\) does not accept string\|false\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Property pocketmine\\\\scheduler\\\\BulkCurlTask\\:\\:\\$operations \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\scheduler\\BulkCurlTask\:\:\$operations \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/scheduler/BulkCurlTask.php - - message: "#^Cannot call method getNextRun\\(\\) on array\\\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\\\.$#" + message: '#^Cannot call method getNextRun\(\) on array\\>\|int\|pocketmine\\scheduler\\TaskHandler\\.$#' + identifier: method.nonObject count: 1 path: ../../../src/scheduler/TaskScheduler.php - - message: "#^Cannot access offset string on mixed\\.$#" + message: '#^Cannot access offset string on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 2 path: ../../../src/utils/Config.php - - message: "#^Method pocketmine\\\\utils\\\\Config\\:\\:fixYAMLIndexes\\(\\) should return string but returns string\\|null\\.$#" + message: '#^Method pocketmine\\utils\\Config\:\:fixYAMLIndexes\(\) should return string but returns string\|null\.$#' + identifier: return.type count: 1 path: ../../../src/utils/Config.php - - message: "#^Parameter \\#1 \\$config of static method pocketmine\\\\utils\\\\Config\\:\\:writeProperties\\(\\) expects array\\, array\\ given\\.$#" + message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\, array\ given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Config.php - - message: "#^Parameter \\#1 \\$string of function trim expects string, string\\|false given\\.$#" + message: '#^Parameter \#1 \$string of function trim expects string, string\|false given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Timezone.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Binary operation "\." between mixed and ''\-\>''\|''\:\:'' results in an error\.$#' + identifier: binaryOp.invalid count: 1 path: ../../../src/utils/Utils.php - - message: "#^Method pocketmine\\\\utils\\\\Utils\\:\\:printable\\(\\) should return string but returns string\\|null\\.$#" + message: '#^Binary operation "\." between non\-falsy\-string and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 2 + path: ../../../src/utils/Utils.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#" + message: '#^Method pocketmine\\utils\\Utils\:\:printable\(\) should return string but returns string\|null\.$#' + identifier: return.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#" + message: '#^Parameter \#1 \$path of static method pocketmine\\utils\\Filesystem\:\:cleanPath\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#3 \\$line of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects int, mixed given\\.$#" + message: '#^Parameter \#2 \$file of class pocketmine\\thread\\ThreadCrashInfoFrame constructor expects string\|null, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$line of class pocketmine\\thread\\ThreadCrashInfoFrame constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/utils/Utils.php + + - + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable count: 1 path: ../../../src/world/World.php - - message: "#^Cannot access offset 'data' on array\\{priority\\: int, data\\: pocketmine\\\\math\\\\Vector3\\}\\|int\\|pocketmine\\\\math\\\\Vector3\\.$#" + message: '#^Cannot access offset ''data'' on array\{priority\: int, data\: pocketmine\\math\\Vector3\}\|int\|pocketmine\\math\\Vector3\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 1 path: ../../../src/world/World.php - - message: "#^Cannot access offset 'priority' on array\\{priority\\: int, data\\: pocketmine\\\\math\\\\Vector3\\}\\|int\\|pocketmine\\\\math\\\\Vector3\\.$#" + message: '#^Cannot access offset ''priority'' on array\{priority\: int, data\: pocketmine\\math\\Vector3\}\|int\|pocketmine\\math\\Vector3\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 1 path: ../../../src/world/World.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$x of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$y of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#4 \\$z of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Method pocketmine\\\\world\\\\biome\\\\BiomeRegistry\\:\\:getBiome\\(\\) should return pocketmine\\\\world\\\\biome\\\\Biome but returns pocketmine\\\\world\\\\biome\\\\Biome\\|null\\.$#" + message: '#^Method pocketmine\\world\\biome\\BiomeRegistry\:\:getBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/biome/BiomeRegistry.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:getSubChunk\\(\\) should return pocketmine\\\\world\\\\format\\\\SubChunk but returns pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Method pocketmine\\world\\format\\Chunk\:\:getSubChunk\(\) should return pocketmine\\world\\format\\SubChunk but returns pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\format\\Chunk\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\format\\Chunk\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\format\\Chunk\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\HeightArray\\:\\:get\\(\\) should return int but returns int\\|null\\.$#" + message: '#^Method pocketmine\\world\\format\\HeightArray\:\:get\(\) should return int but returns int\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/HeightArray.php - - message: "#^Only numeric types are allowed in %%, int\\<0, max\\>\\|false given on the left side\\.$#" + message: '#^Only numeric types are allowed in %%, int\<0, max\>\|false given on the left side\.$#' + identifier: mod.leftNonNumeric count: 1 path: ../../../src/world/format/io/region/RegionLoader.php - - message: "#^Parameter \\#2 \\$size of function ftruncate expects int\\<0, max\\>, int given\\.$#" + message: '#^Parameter \#2 \$size of function ftruncate expects int\<0, max\>, int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/format/io/region/RegionLoader.php - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Cannot access offset 1 on mixed\\.$#" - count: 2 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Cannot access offset 2 on mixed\\.$#" - count: 2 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Cannot cast mixed to int\\.$#" - count: 4 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Method pocketmine\\\\world\\\\generator\\\\biome\\\\BiomeSelector\\:\\:pickBiome\\(\\) should return pocketmine\\\\world\\\\biome\\\\Biome but returns pocketmine\\\\world\\\\biome\\\\Biome\\|null\\.$#" + message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/generator/biome/BiomeSelector.php - - message: "#^Cannot call method getBiomeId\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/generator/hell/Nether.php - - message: "#^Offset int does not exist on SplFixedArray\\\\|null\\.$#" - count: 4 - path: ../../../src/world/generator/noise/Noise.php - - - - message: "#^Parameter \\$q0 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#" + message: '#^Parameter \$q0 of static method pocketmine\\world\\generator\\noise\\Noise\:\:linearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/generator/noise/Noise.php - - message: "#^Parameter \\$q1 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#" + message: '#^Parameter \$q1 of static method pocketmine\\world\\generator\\noise\\Noise\:\:linearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/generator/noise/Noise.php - - message: "#^Cannot call method getBiomeId\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/generator/normal/Normal.php - - message: "#^Parameter \\#1 \\$start of method pocketmine\\\\utils\\\\Random\\:\\:nextRange\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$start of method pocketmine\\utils\\Random\:\:nextRange\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Parameter \\#2 \\$end of method pocketmine\\\\utils\\\\Random\\:\\:nextRange\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$end of method pocketmine\\utils\\Random\:\:nextRange\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\ChunkManager\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\ChunkManager\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\ChunkManager\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\ChunkManager\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Cannot call method getBlockLightArray\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockLightArray\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/BlockLightUpdate.php - - message: "#^Cannot call method getBlockStateId\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockStateId\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/BlockLightUpdate.php - - message: "#^Cannot call method getSubChunks\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getSubChunks\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/BlockLightUpdate.php - - message: "#^Property pocketmine\\\\world\\\\light\\\\LightPopulationTask\\:\\:\\$resultBlockLightArrays \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\world\\light\\LightPopulationTask\:\:\$resultBlockLightArrays \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/world/light/LightPopulationTask.php - - message: "#^Property pocketmine\\\\world\\\\light\\\\LightPopulationTask\\:\\:\\$resultHeightMap \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\world\\light\\LightPopulationTask\:\:\$resultHeightMap \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/world/light/LightPopulationTask.php - - message: "#^Property pocketmine\\\\world\\\\light\\\\LightPopulationTask\\:\\:\\$resultSkyLightArrays \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\world\\light\\LightPopulationTask\:\:\$resultSkyLightArrays \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/world/light/LightPopulationTask.php - - message: "#^Cannot call method getBlockSkyLightArray\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockSkyLightArray\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getBlockStateId\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockStateId\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getHeightMap\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getHeightMap\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 6 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getHeightMapArray\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getHeightMapArray\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getSubChunk\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getSubChunk\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method setHeightMap\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method setHeightMap\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method setHeightMapArray\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method setHeightMapArray\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Parameter \\#1 \\$chunk of static method pocketmine\\\\world\\\\light\\\\SkyLightUpdate\\:\\:recalculateHeightMap\\(\\) expects pocketmine\\\\world\\\\format\\\\Chunk, pocketmine\\\\world\\\\format\\\\Chunk\\|null given\\.$#" + message: '#^Parameter \#1 \$chunk of static method pocketmine\\world\\light\\SkyLightUpdate\:\:recalculateHeightMap\(\) expects pocketmine\\world\\format\\Chunk, pocketmine\\world\\format\\Chunk\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Parameter \\#1 \\$chunk of static method pocketmine\\\\world\\\\light\\\\SkyLightUpdate\\:\\:recalculateHeightMapColumn\\(\\) expects pocketmine\\\\world\\\\format\\\\Chunk, pocketmine\\\\world\\\\format\\\\Chunk\\|null given\\.$#" + message: '#^Parameter \#1 \$chunk of static method pocketmine\\world\\light\\SkyLightUpdate\:\:recalculateHeightMapColumn\(\) expects pocketmine\\world\\format\\Chunk, pocketmine\\world\\format\\Chunk\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/light/SkyLightUpdate.php - diff --git a/tests/phpstan/configs/dependency-problems.neon b/tests/phpstan/configs/dependency-problems.neon new file mode 100644 index 000000000..dc2a491bf --- /dev/null +++ b/tests/phpstan/configs/dependency-problems.neon @@ -0,0 +1,73 @@ +parameters: + ignoreErrors: + - + message: '#^Method pocketmine\\network\\mcpe\\convert\\BlockStateDictionary\:\:loadPaletteFromString\(\) should return list\ but returns array\\.$#' + identifier: return.type + count: 1 + path: ../../../src/network/mcpe/convert/BlockStateDictionary.php + + - + message: '#^Parameter \#3 \$entityNBT of class pocketmine\\world\\format\\io\\ChunkData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/format/io/leveldb/LevelDB.php + + - + message: '#^Parameter \#4 \$tileNBT of class pocketmine\\world\\format\\io\\ChunkData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/format/io/leveldb/LevelDB.php + + - + message: '#^Parameter \#1 \$array of static method pocketmine\\world\\format\\io\\ChunkUtils\:\:convertBiomeColors\(\) expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/format/io/region/Anvil.php + + - + message: '#^Parameter \#1 \$array of static method pocketmine\\world\\format\\io\\ChunkUtils\:\:convertBiomeColors\(\) expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/format/io/region/McRegion.php + + - + message: '#^Parameter \#1 \$array of static method pocketmine\\world\\format\\io\\ChunkUtils\:\:convertBiomeColors\(\) expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/format/io/region/PMAnvil.php + + - + message: '#^Parameter \#1 \$oldNewStateList of function pocketmine\\tools\\blockstate_upgrade_schema_utils\\buildUpgradeTableFromData expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../tools/blockstate-upgrade-schema-utils.php + + - + message: '#^Parameter \#1 \$input of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../tools/generate-bedrock-data-from-packets.php + + - + message: '#^Parameter \#2 \$output of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../tools/generate-bedrock-data-from-packets.php + + - + message: '#^Parameter \#3 \$output of class pocketmine\\crafting\\json\\ShapedRecipeData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../tools/generate-bedrock-data-from-packets.php + + - + message: '#^Parameter \#5 \$unlockingIngredients of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../tools/generate-bedrock-data-from-packets.php + + - + message: '#^Parameter \#6 \$unlockingIngredients of class pocketmine\\crafting\\json\\ShapedRecipeData constructor expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../tools/generate-bedrock-data-from-packets.php diff --git a/tests/phpstan/configs/impossible-generics.neon b/tests/phpstan/configs/impossible-generics.neon index b0e67d294..e0b944e69 100644 --- a/tests/phpstan/configs/impossible-generics.neon +++ b/tests/phpstan/configs/impossible-generics.neon @@ -1,12 +1,14 @@ parameters: ignoreErrors: - - message: "#^Method pocketmine\\\\event\\\\RegisteredListener\\:\\:__construct\\(\\) has parameter \\$handler with no signature specified for Closure\\.$#" + message: '#^Method pocketmine\\event\\RegisteredListener\:\:__construct\(\) has parameter \$handler with no signature specified for Closure\.$#' + identifier: missingType.callable count: 1 path: ../../../src/event/RegisteredListener.php - - message: "#^Method pocketmine\\\\event\\\\RegisteredListener\\:\\:getHandler\\(\\) return type has no signature specified for Closure\\.$#" + message: '#^Method pocketmine\\event\\RegisteredListener\:\:getHandler\(\) return type has no signature specified for Closure\.$#' + identifier: missingType.callable count: 1 path: ../../../src/event/RegisteredListener.php diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 0ee2b68a0..7e3484bc5 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -1,112 +1,260 @@ parameters: ignoreErrors: - - message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Access to an undefined property object\:\:\$crashId\.$#' + identifier: property.notFound + count: 1 + path: ../../../src/Server.php + + - + message: '#^Access to an undefined property object\:\:\$crashUrl\.$#' + identifier: property.notFound + count: 1 + path: ../../../src/Server.php + + - + message: '#^Access to an undefined property object\:\:\$error\.$#' + identifier: property.notFound + count: 1 + path: ../../../src/Server.php + + - + message: '#^Method pocketmine\\block\\Block\:\:readStateFromWorld\(\) is marked as impure but does not have any side effects\.$#' + identifier: impureMethod.pure + count: 1 + path: ../../../src/block/Block.php + + - + message: '#^Method pocketmine\\block\\DoubleTallGrass\:\:traitGetDropsForIncompatibleTool\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: ../../../src/block/DoubleTallGrass.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:ACACIA_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:ACACIA_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:BIRCH_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:BIRCH_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:CHERRY_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CHERRY_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:CRIMSON_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CRIMSON_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:DARK_OAK_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:DARK_OAK_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:JUNGLE_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:JUNGLE_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:MANGROVE_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:MANGROVE_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:OAK_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:OAK_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:PALE_OAK_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:PALE_OAK_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:SPRUCE_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:SPRUCE_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:WARPED_SIGN\\(\\)\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:WARPED_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php - - message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#" + message: '#^Strict comparison using \=\=\= between \*NEVER\* and 5 will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: ../../../src/command/defaults/TeleportCommand.php + + - + message: '#^Method pocketmine\\crafting\\ShapedRecipe\:\:getIngredientMap\(\) should return list\\> but returns array\, non\-empty\-array\, pocketmine\\crafting\\RecipeIngredient\|null\>\>\.$#' + identifier: return.type + count: 1 + path: ../../../src/crafting/ShapedRecipe.php + + - + message: '#^Property pocketmine\\crash\\CrashDumpData\:\:\$parameters \(list\\) does not accept array\.$#' + identifier: assign.propertyType + count: 1 + path: ../../../src/crash/CrashDump.php + + - + message: '#^Call to function assert\(\) with false and ''unknown hit type'' will always evaluate to false\.$#' + identifier: function.impossibleType count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\PthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#" + message: '#^Property pocketmine\\item\\WritableBookBase\:\:\$pages \(list\\) does not accept non\-empty\-array\\.$#' + identifier: assign.propertyType + count: 1 + path: ../../../src/item/WritableBookBase.php + + - + message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 1 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: ../../../src/network/mcpe/compression/ZlibCompressor.php + + - + message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 255 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: ../../../src/network/mcpe/compression/ZlibCompressor.php + + - + message: '#^Parameter \#1 \$states of class pocketmine\\network\\mcpe\\convert\\BlockStateDictionary constructor expects list\, array\, pocketmine\\network\\mcpe\\convert\\BlockStateDictionaryEntry\> given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/network/mcpe/convert/BlockStateDictionary.php + + - + message: '#^Cannot access offset ''default'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: ../../../src/network/mcpe/convert/LegacySkinAdapter.php + + - + message: '#^Property pocketmine\\network\\mcpe\\raklib\\PthreadsChannelWriter\:\:\$buffer is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: ../../../src/network/mcpe/raklib/PthreadsChannelWriter.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\SnoozeAwarePthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#" + message: '#^Property pocketmine\\network\\mcpe\\raklib\\SnoozeAwarePthreadsChannelWriter\:\:\$buffer is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: ../../../src/network/mcpe/raklib/SnoozeAwarePthreadsChannelWriter.php - - message: "#^Dead catch \\- RuntimeException is never thrown in the try block\\.$#" + message: '#^Dead catch \- RuntimeException is never thrown in the try block\.$#' + identifier: catch.neverThrown count: 1 path: ../../../src/plugin/PluginManager.php - - message: "#^Method pocketmine\\\\timings\\\\TimingsHandler\\:\\:lazyGetSet\\(\\) should return pocketmine\\\\utils\\\\ObjectSet\\ but returns pocketmine\\\\utils\\\\ObjectSet\\\\.$#" + message: '#^Binary operation "\." between mixed and ''/''\|''\\\\'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/thread/ThreadSafeClassLoader.php + + - + message: '#^Binary operation "\." between mixed and non\-falsy\-string results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/thread/ThreadSafeClassLoader.php + + - + message: '#^Method pocketmine\\timings\\TimingsHandler\:\:lazyGetSet\(\) should return pocketmine\\utils\\ObjectSet\ but returns pocketmine\\utils\\ObjectSet\\.$#' + identifier: return.type count: 1 path: ../../../src/timings/TimingsHandler.php - - message: "#^Casting to int something that's already int\\.$#" + message: '#^Parameter &\$where @param\-out type of method pocketmine\\timings\\TimingsHandler\:\:lazyGetSet\(\) expects pocketmine\\utils\\ObjectSet\, pocketmine\\utils\\ObjectSet\ given\.$#' + identifier: paramOut.type + count: 1 + path: ../../../src/timings/TimingsHandler.php + + - + message: '#^Binary operation "\*" between mixed and 3600 results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/utils/Timezone.php + + - + message: '#^Binary operation "\*" between mixed and 60 results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/utils/Timezone.php + + - + message: '#^Binary operation "\+" between \(float\|int\) and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/utils/Timezone.php + + - + message: '#^Property pocketmine\\world\\format\\io\\region\\RegionLoader\:\:\$locationTable \(list\\) does not accept non\-empty\-array\\.$#' + identifier: assign.propertyType + count: 2 + path: ../../../src/world/format/io/region/RegionLoader.php + + - + message: '#^Property pocketmine\\world\\format\\io\\region\\RegionLoader\:\:\$locationTable \(list\\) does not accept non\-empty\-array\, pocketmine\\world\\format\\io\\region\\RegionLocationTableEntry\|null\>\.$#' + identifier: assign.propertyType + count: 3 + path: ../../../src/world/format/io/region/RegionLoader.php + + - + message: '#^Method pocketmine\\world\\format\\io\\region\\RegionWorldProvider\:\:createRegionIterator\(\) should return RegexIterator\ but returns RegexIterator\\>\.$#' + identifier: return.type + count: 1 + path: ../../../src/world/format/io/region/RegionWorldProvider.php + + - + message: '#^Casting to int something that''s already int\.$#' + identifier: cast.useless count: 1 path: ../../../src/world/generator/normal/Normal.php - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFalse\\(\\) with false will always evaluate to true\\.$#" + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertFalse\(\) with false will always evaluate to true\.$#' + identifier: staticMethod.alreadyNarrowedType count: 1 path: ../../phpunit/promise/PromiseTest.php - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false and 'All promise should…' will always evaluate to false\\.$#" + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with false and ''All promise should…'' will always evaluate to false\.$#' + identifier: staticMethod.impossibleType count: 1 path: ../../phpunit/promise/PromiseTest.php - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false will always evaluate to false\\.$#" + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with false will always evaluate to false\.$#' + identifier: staticMethod.impossibleType count: 2 path: ../../phpunit/promise/PromiseTest.php - - message: "#^Strict comparison using \\=\\=\\= between 0 and 0 will always evaluate to true\\.$#" + message: '#^Strict comparison using \=\=\= between 0 and 0 will always evaluate to true\.$#' + identifier: identical.alwaysTrue count: 1 path: ../rules/UnsafeForeachArrayOfStringRule.php diff --git a/tests/phpstan/configs/spl-fixed-array-sucks.neon b/tests/phpstan/configs/spl-fixed-array-sucks.neon index 7c2b2b91a..05524fb8c 100644 --- a/tests/phpstan/configs/spl-fixed-array-sucks.neon +++ b/tests/phpstan/configs/spl-fixed-array-sucks.neon @@ -1,22 +1,32 @@ parameters: ignoreErrors: - - message: "#^Cannot call method collectGarbage\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method collectGarbage\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:getSubChunks\\(\\) should return array\\ but returns array\\\\.$#" + message: '#^Method pocketmine\\world\\format\\Chunk\:\:getSubChunks\(\) should return array\ but returns array\\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\)\\: mixed\\)\\|null, Closure\\(pocketmine\\\\world\\\\format\\\\SubChunk\\)\\: pocketmine\\\\world\\\\format\\\\SubChunk given\\.$#" + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(pocketmine\\world\\format\\SubChunk\|null\)\: mixed\)\|null, Closure\(pocketmine\\world\\format\\SubChunk\)\: pocketmine\\world\\format\\SubChunk given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\HeightArray\\:\\:getValues\\(\\) should return non\\-empty\\-array\\ but returns array\\\\.$#" + message: '#^Method pocketmine\\world\\format\\HeightArray\:\:getValues\(\) should return non\-empty\-list\ but returns array\\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/HeightArray.php + - + message: '#^Offset int might not exist on SplFixedArray\\|null\.$#' + identifier: offsetAccess.notFound + count: 4 + path: ../../../src/world/generator/noise/Noise.php + diff --git a/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php b/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php index 4fa767022..5753bb628 100644 --- a/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php +++ b/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php @@ -28,7 +28,6 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\TypeWithClassName; use pocketmine\utils\LegacyEnumShimTrait; use function sprintf; @@ -51,18 +50,15 @@ final class DeprecatedLegacyEnumAccessRule implements Rule{ $scope->resolveTypeByName($node->class) : $scope->getType($node->class); - if(!$classType instanceof TypeWithClassName){ - return []; - } + $errors = []; + $reflections = $classType->getObjectClassReflections(); + foreach($reflections as $reflection){ + if(!$reflection->hasTraitUse(LegacyEnumShimTrait::class) || !$reflection->implementsInterface(\UnitEnum::class)){ + continue; + } - $reflection = $classType->getClassReflection(); - if($reflection === null || !$reflection->hasTraitUse(LegacyEnumShimTrait::class) || !$reflection->implementsInterface(\UnitEnum::class)){ - return []; - } - - if(!$reflection->hasNativeMethod($caseName)){ - return [ - RuleErrorBuilder::message(sprintf( + if(!$reflection->hasNativeMethod($caseName)){ + $errors[] = RuleErrorBuilder::message(sprintf( 'Use of legacy enum case accessor %s::%s().', $reflection->getName(), $caseName @@ -70,10 +66,11 @@ final class DeprecatedLegacyEnumAccessRule implements Rule{ 'Access the enum constant directly instead (remove the brackets), e.g. %s::%s', $reflection->getName(), $caseName - ))->build() - ]; + ))->identifier('pocketmine.enum.deprecatedAccessor') + ->build(); + } } - return []; + return $errors; } } diff --git a/tests/phpstan/rules/DisallowEnumComparisonRule.php b/tests/phpstan/rules/DisallowEnumComparisonRule.php index fc5377173..d73cc3972 100644 --- a/tests/phpstan/rules/DisallowEnumComparisonRule.php +++ b/tests/phpstan/rules/DisallowEnumComparisonRule.php @@ -30,7 +30,6 @@ use PhpParser\Node\Expr\BinaryOp\NotIdentical; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -61,7 +60,7 @@ class DisallowEnumComparisonRule implements Rule{ $node instanceof Identical ? '===' : '!==', $leftType->describe(VerbosityLevel::value()), $rightType->describe(VerbosityLevel::value()) - ))->build()]; + ))->identifier('pocketmine.enum.badComparison')->build()]; } return []; } @@ -69,7 +68,7 @@ class DisallowEnumComparisonRule implements Rule{ private function checkForEnumTypes(Type $comparedType) : bool{ //TODO: what we really want to do here is iterate over the contained types, but there's no universal way to //do that. This might break with other circumstances. - if($comparedType instanceof ObjectType){ + if($comparedType->isObject()->yes()){ $types = [$comparedType]; }elseif($comparedType instanceof UnionType){ $types = $comparedType->getTypes(); @@ -77,12 +76,14 @@ class DisallowEnumComparisonRule implements Rule{ return false; } foreach($types as $containedType){ - if(!($containedType instanceof ObjectType)){ + if(!($containedType->isObject()->yes())){ continue; } - $class = $containedType->getClassReflection(); - if($class !== null && $class->hasTraitUse(EnumTrait::class)){ - return true; + $classes = $containedType->getObjectClassReflections(); + foreach($classes as $class){ + if($class->hasTraitUse(EnumTrait::class)){ + return true; + } } } return false; diff --git a/tests/phpstan/rules/DisallowForeachByReferenceRule.php b/tests/phpstan/rules/DisallowForeachByReferenceRule.php index 79124d328..eb6589705 100644 --- a/tests/phpstan/rules/DisallowForeachByReferenceRule.php +++ b/tests/phpstan/rules/DisallowForeachByReferenceRule.php @@ -44,6 +44,7 @@ final class DisallowForeachByReferenceRule implements Rule{ 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.") + ->identifier('pocketmine.foreach.byRef') ->build() ]; } diff --git a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php index 745cf2109..34056131b 100644 --- a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php +++ b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php @@ -101,7 +101,7 @@ final class UnsafeForeachArrayOfStringRule implements Rule{ RuleErrorBuilder::message(sprintf( "Unsafe foreach on array with key type %s (they might be casted to int).", $iterableType->getIterableKeyType()->describe(VerbosityLevel::value()) - ))->tip($tip)->build() + ))->tip($tip)->identifier('pocketmine.foreach.stringKeys')->build() ]; } return []; diff --git a/tests/phpunit/utils/fixtures/TestTrait.php b/tests/phpunit/utils/fixtures/TestTrait.php index bc32c0cff..3e749c0b1 100644 --- a/tests/phpunit/utils/fixtures/TestTrait.php +++ b/tests/phpunit/utils/fixtures/TestTrait.php @@ -23,6 +23,6 @@ declare(strict_types=1); namespace pocketmine\utils\fixtures; -trait TestTrait{ +trait TestTrait{ // @phpstan-ignore trait.unused } From 794641c0f80eba3bd1670e70503f1cc5e0fc18ab Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:35:19 +0000 Subject: [PATCH 088/334] Utils: split some horrifying code across multiple lines --- src/utils/Utils.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utils/Utils.php b/src/utils/Utils.php index f557562c9..b1f7e2842 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -469,7 +469,15 @@ final class Utils{ } $params = implode(", ", $paramsList); } - $messages[] = "#$i " . (isset($trace[$i]["file"]) ? Filesystem::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" || $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")"; + $messages[] = "#$i " . + (isset($trace[$i]["file"]) ? Filesystem::cleanPath($trace[$i]["file"]) : "") . + "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . + (isset($trace[$i]["class"]) ? + $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" || $trace[$i]["type"] === "->") ? "->" : "::") : + "" + ) . + $trace[$i]["function"] . + "(" . Utils::printable($params) . ")"; } return $messages; } From 689a7996b96869bdab6555b58a9e77b253d1a591 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:51:38 +0000 Subject: [PATCH 089/334] Update NBT dependency --- composer.lock | 16 +++++----- .../phpstan/configs/dependency-problems.neon | 30 ------------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/composer.lock b/composer.lock index 8df6c329d..25c42d297 100644 --- a/composer.lock +++ b/composer.lock @@ -576,16 +576,16 @@ }, { "name": "pocketmine/nbt", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/pmmp/NBT.git", - "reference": "20540271cb59e04672cb163dca73366f207974f1" + "reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/NBT/zipball/20540271cb59e04672cb163dca73366f207974f1", - "reference": "20540271cb59e04672cb163dca73366f207974f1", + "url": "https://api.github.com/repos/pmmp/NBT/zipball/53db37487bc5ddbfbd84247966e1a073bdcfdb7d", + "reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d", "shasum": "" }, "require": { @@ -595,8 +595,8 @@ }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "1.10.25", - "phpstan/phpstan-strict-rules": "^1.0", + "phpstan/phpstan": "2.1.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.5" }, "type": "library", @@ -612,9 +612,9 @@ "description": "PHP library for working with Named Binary Tags", "support": { "issues": "https://github.com/pmmp/NBT/issues", - "source": "https://github.com/pmmp/NBT/tree/1.0.0" + "source": "https://github.com/pmmp/NBT/tree/1.0.1" }, - "time": "2023-07-14T13:01:49+00:00" + "time": "2025-01-07T22:47:46+00:00" }, { "name": "pocketmine/raklib", diff --git a/tests/phpstan/configs/dependency-problems.neon b/tests/phpstan/configs/dependency-problems.neon index dc2a491bf..c1c0fceea 100644 --- a/tests/phpstan/configs/dependency-problems.neon +++ b/tests/phpstan/configs/dependency-problems.neon @@ -1,11 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Method pocketmine\\network\\mcpe\\convert\\BlockStateDictionary\:\:loadPaletteFromString\(\) should return list\ but returns array\\.$#' - identifier: return.type - count: 1 - path: ../../../src/network/mcpe/convert/BlockStateDictionary.php - - message: '#^Parameter \#3 \$entityNBT of class pocketmine\\world\\format\\io\\ChunkData constructor expects list\, array\ given\.$#' identifier: argument.type @@ -18,30 +12,6 @@ parameters: count: 1 path: ../../../src/world/format/io/leveldb/LevelDB.php - - - message: '#^Parameter \#1 \$array of static method pocketmine\\world\\format\\io\\ChunkUtils\:\:convertBiomeColors\(\) expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/world/format/io/region/Anvil.php - - - - message: '#^Parameter \#1 \$array of static method pocketmine\\world\\format\\io\\ChunkUtils\:\:convertBiomeColors\(\) expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/world/format/io/region/McRegion.php - - - - message: '#^Parameter \#1 \$array of static method pocketmine\\world\\format\\io\\ChunkUtils\:\:convertBiomeColors\(\) expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/world/format/io/region/PMAnvil.php - - - - message: '#^Parameter \#1 \$oldNewStateList of function pocketmine\\tools\\blockstate_upgrade_schema_utils\\buildUpgradeTableFromData expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../tools/blockstate-upgrade-schema-utils.php - - message: '#^Parameter \#1 \$input of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' identifier: argument.type From e8c4b743b5e62554fd0e728d14d0c8817ee6099e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 22:54:10 +0000 Subject: [PATCH 090/334] LevelDB: stop overriding types from NBT NBT has better quality type info already --- src/world/format/io/leveldb/LevelDB.php | 2 -- tests/phpstan/configs/dependency-problems.neon | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index dda489d31..41c477867 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -711,7 +711,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $nbt = new LittleEndianNbtSerializer(); - /** @var CompoundTag[] $entities */ $entities = []; if(($entityData = $this->db->get($index . ChunkDataKey::ENTITIES)) !== false && $entityData !== ""){ try{ @@ -721,7 +720,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ } } - /** @var CompoundTag[] $tiles */ $tiles = []; if(($tileData = $this->db->get($index . ChunkDataKey::BLOCK_ENTITIES)) !== false && $tileData !== ""){ try{ diff --git a/tests/phpstan/configs/dependency-problems.neon b/tests/phpstan/configs/dependency-problems.neon index c1c0fceea..e95fcd79c 100644 --- a/tests/phpstan/configs/dependency-problems.neon +++ b/tests/phpstan/configs/dependency-problems.neon @@ -1,17 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Parameter \#3 \$entityNBT of class pocketmine\\world\\format\\io\\ChunkData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/world/format/io/leveldb/LevelDB.php - - - - message: '#^Parameter \#4 \$tileNBT of class pocketmine\\world\\format\\io\\ChunkData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/world/format/io/leveldb/LevelDB.php - - message: '#^Parameter \#1 \$input of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' identifier: argument.type From e34f34f9f42aa3aaf0b145291a75e8cc4b6c879f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 7 Jan 2025 23:09:28 +0000 Subject: [PATCH 091/334] Update BedrockProtocol dependency --- composer.lock | 20 ++++++------ phpstan.neon.dist | 1 - .../phpstan/configs/dependency-problems.neon | 31 ------------------- tests/phpstan/configs/phpstan-bugs.neon | 6 ++++ 4 files changed, 16 insertions(+), 42 deletions(-) delete mode 100644 tests/phpstan/configs/dependency-problems.neon diff --git a/composer.lock b/composer.lock index 25c42d297..d59c10727 100644 --- a/composer.lock +++ b/composer.lock @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "35.0.0+bedrock-1.21.50", + "version": "35.0.3+bedrock-1.21.50", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435" + "reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435", - "reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c4d62581cb62d29ec426914c6b4d7e0ff835da9c", + "reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c", "shasum": "" }, "require": { @@ -278,10 +278,10 @@ "ramsey/uuid": "^4.1" }, "require-dev": { - "phpstan/phpstan": "1.11.9", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.0.0", - "phpunit/phpunit": "^9.5 || ^10.0" + "phpstan/phpstan": "2.1.0", + "phpstan/phpstan-phpunit": "^2.0.0", + "phpstan/phpstan-strict-rules": "^2.0.0", + "phpunit/phpunit": "^9.5 || ^10.0 || ^11.0" }, "type": "library", "autoload": { @@ -296,9 +296,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/35.0.0+bedrock-1.21.50" + "source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.3+bedrock-1.21.50" }, - "time": "2024-12-04T13:02:00+00:00" + "time": "2025-01-07T23:06:29+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index dfaa964e4..63155cbc4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,6 @@ includes: - tests/phpstan/analyse-for-current-php-version.neon.php - tests/phpstan/configs/actual-problems.neon - - tests/phpstan/configs/dependency-problems.neon - tests/phpstan/configs/impossible-generics.neon - tests/phpstan/configs/php-bugs.neon - tests/phpstan/configs/phpstan-bugs.neon diff --git a/tests/phpstan/configs/dependency-problems.neon b/tests/phpstan/configs/dependency-problems.neon deleted file mode 100644 index e95fcd79c..000000000 --- a/tests/phpstan/configs/dependency-problems.neon +++ /dev/null @@ -1,31 +0,0 @@ -parameters: - ignoreErrors: - - - message: '#^Parameter \#1 \$input of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../tools/generate-bedrock-data-from-packets.php - - - - message: '#^Parameter \#2 \$output of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../tools/generate-bedrock-data-from-packets.php - - - - message: '#^Parameter \#3 \$output of class pocketmine\\crafting\\json\\ShapedRecipeData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../tools/generate-bedrock-data-from-packets.php - - - - message: '#^Parameter \#5 \$unlockingIngredients of class pocketmine\\crafting\\json\\ShapelessRecipeData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../tools/generate-bedrock-data-from-packets.php - - - - message: '#^Parameter \#6 \$unlockingIngredients of class pocketmine\\crafting\\json\\ShapedRecipeData constructor expects list\, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../tools/generate-bedrock-data-from-packets.php diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 7e3484bc5..9ab125763 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -126,6 +126,12 @@ parameters: count: 1 path: ../../../src/item/WritableBookBase.php + - + message: '#^Parameter \#3 \$input of class pocketmine\\network\\mcpe\\protocol\\types\\recipe\\ShapedRecipe constructor expects list\\>, array\, non\-empty\-array\, pocketmine\\network\\mcpe\\protocol\\types\\recipe\\RecipeIngredient\>\> given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/network/mcpe/cache/CraftingDataCache.php + - message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 1 so it can be removed from the return type\.$#' identifier: return.unusedType From 0a16daa61924581bec2ef35fee651e2b598a80a0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 8 Jan 2025 01:45:28 +0000 Subject: [PATCH 092/334] Avoid dodgy array_flip hash building the conventional way is using array_keys and array_fill_keys. Behaviour is more predictable & also avoids benevolent union fuckery from PHPStan. --- src/item/enchantment/ProtectionEnchantment.php | 10 +++++++--- src/plugin/PluginGraylist.php | 14 ++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/item/enchantment/ProtectionEnchantment.php b/src/item/enchantment/ProtectionEnchantment.php index be78a2306..817466875 100644 --- a/src/item/enchantment/ProtectionEnchantment.php +++ b/src/item/enchantment/ProtectionEnchantment.php @@ -25,18 +25,22 @@ namespace pocketmine\item\enchantment; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\lang\Translatable; -use function array_flip; +use function array_fill_keys; use function floor; class ProtectionEnchantment extends Enchantment{ protected float $typeModifier; - /** @var int[]|null */ + /** + * @var true[]|null + * @phpstan-var array + */ protected ?array $applicableDamageTypes = null; /** * ProtectionEnchantment constructor. * * @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower + * @phpstan-param list|null $applicableDamageTypes * * @param int $primaryItemFlags @deprecated * @param int $secondaryItemFlags @deprecated @@ -48,7 +52,7 @@ class ProtectionEnchantment extends Enchantment{ $this->typeModifier = $typeModifier; if($applicableDamageTypes !== null){ - $this->applicableDamageTypes = array_flip($applicableDamageTypes); + $this->applicableDamageTypes = array_fill_keys($applicableDamageTypes, true); } } diff --git a/src/plugin/PluginGraylist.php b/src/plugin/PluginGraylist.php index ff9d71832..f3c9cf2a3 100644 --- a/src/plugin/PluginGraylist.php +++ b/src/plugin/PluginGraylist.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace pocketmine\plugin; use pocketmine\utils\Utils; -use function array_flip; +use function array_fill_keys; +use function array_keys; use function is_array; use function is_float; use function is_int; @@ -32,23 +33,28 @@ use function is_string; class PluginGraylist{ - /** @var string[] */ + /** + * @var true[] + * @phpstan-var array + */ private array $plugins; private bool $isWhitelist = false; /** * @param string[] $plugins + * @phpstan-param list $plugins */ public function __construct(array $plugins = [], bool $whitelist = false){ - $this->plugins = array_flip($plugins); + $this->plugins = array_fill_keys($plugins, true); $this->isWhitelist = $whitelist; } /** * @return string[] + * @phpstan-return list */ public function getPlugins() : array{ - return array_flip($this->plugins); + return array_keys($this->plugins); } public function isWhitelist() : bool{ From 4a83920db9a1bc5b7deef6a605b7cf5f68a17866 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 8 Jan 2025 01:47:04 +0000 Subject: [PATCH 093/334] PlayerPreLoginEvent: improve array type info --- src/event/player/PlayerPreLoginEvent.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/event/player/PlayerPreLoginEvent.php b/src/event/player/PlayerPreLoginEvent.php index 5a69c0e17..4af0cdd89 100644 --- a/src/event/player/PlayerPreLoginEvent.php +++ b/src/event/player/PlayerPreLoginEvent.php @@ -52,9 +52,15 @@ class PlayerPreLoginEvent extends Event{ self::KICK_FLAG_BANNED ]; - /** @var Translatable[]|string[] reason const => associated message */ + /** + * @var Translatable[]|string[] reason const => associated message + * @phpstan-var array + */ protected array $disconnectReasons = []; - /** @var Translatable[]|string[] */ + /** + * @var Translatable[]|string[] + * @phpstan-var array + */ protected array $disconnectScreenMessages = []; public function __construct( @@ -93,6 +99,7 @@ class PlayerPreLoginEvent extends Event{ * Returns an array of kick flags currently assigned. * * @return int[] + * @phpstan-return list */ public function getKickFlags() : array{ return array_keys($this->disconnectReasons); From 5e0f03dff0e46ea5bfb54069e0f1f4ab8fcdd4cd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 8 Jan 2025 01:48:15 +0000 Subject: [PATCH 094/334] Stub PalettedBlockArray functions that work with arrays and workaround PHPStan stupidity --- phpstan.neon.dist | 1 + src/world/format/io/BaseWorldProvider.php | 4 ++-- tests/phpstan/stubs/chunkutils2.stub | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 tests/phpstan/stubs/chunkutils2.stub diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 63155cbc4..5e917dc75 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -45,6 +45,7 @@ parameters: - pocketmine\DEBUG - pocketmine\IS_DEVELOPMENT_BUILD stubFiles: + - tests/phpstan/stubs/chunkutils2.stub - tests/phpstan/stubs/JsonMapper.stub - tests/phpstan/stubs/leveldb.stub - tests/phpstan/stubs/pmmpthread.stub diff --git a/src/world/format/io/BaseWorldProvider.php b/src/world/format/io/BaseWorldProvider.php index 79f6875a4..6fcb8e10b 100644 --- a/src/world/format/io/BaseWorldProvider.php +++ b/src/world/format/io/BaseWorldProvider.php @@ -83,11 +83,11 @@ abstract class BaseWorldProvider implements WorldProvider{ } try{ - $newPalette[$k] = $this->blockStateDeserializer->deserialize($newStateData); + $newPalette[] = $this->blockStateDeserializer->deserialize($newStateData); }catch(BlockStateDeserializeException $e){ //this should never happen anyway - if the upgrader returned an invalid state, we have bigger problems $blockDecodeErrors[] = "Palette offset $k / Failed to deserialize upgraded state $id:$meta: " . $e->getMessage(); - $newPalette[$k] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); + $newPalette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); } } diff --git a/tests/phpstan/stubs/chunkutils2.stub b/tests/phpstan/stubs/chunkutils2.stub new file mode 100644 index 000000000..b23e4a7fd --- /dev/null +++ b/tests/phpstan/stubs/chunkutils2.stub @@ -0,0 +1,15 @@ + $palette + */ + public static function fromData(int $bitsPerBlock, string $wordArray, array $palette): PalettedBlockArray {} + + /** + * @return list + */ + public function getPalette(): array {} +} From d42ec06647dbac8f8fa7634159286bceec9507ed Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 8 Jan 2025 01:48:55 +0000 Subject: [PATCH 095/334] ZippedResourcePack: don't pass exception code to new exception this is a BUT (int|string) under PHPStan, and we don't need the errors. We don't care about this code anyway. --- src/resourcepacks/ZippedResourcePack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resourcepacks/ZippedResourcePack.php b/src/resourcepacks/ZippedResourcePack.php index c4daeedf7..4fcf204d9 100644 --- a/src/resourcepacks/ZippedResourcePack.php +++ b/src/resourcepacks/ZippedResourcePack.php @@ -100,7 +100,7 @@ class ZippedResourcePack implements ResourcePack{ try{ $manifest = (new CommentedJsonDecoder())->decode($manifestData); }catch(\RuntimeException $e){ - throw new ResourcePackException("Failed to parse manifest.json: " . $e->getMessage(), $e->getCode(), $e); + throw new ResourcePackException("Failed to parse manifest.json: " . $e->getMessage(), 0, $e); } if(!($manifest instanceof \stdClass)){ throw new ResourcePackException("manifest.json should contain a JSON object, not " . gettype($manifest)); From 847ae26cad24df4ad3e30f911f913574684cb11d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 8 Jan 2025 02:04:06 +0000 Subject: [PATCH 096/334] PHPStan: don't remember possibly-impure function return values I don't think we get much benefit from this, and the assumption that functions with a return value are pure is sketchy. In any case, it's better to avoid these repeated calls anyway. --- phpstan.neon.dist | 1 + src/entity/projectile/Projectile.php | 5 +++-- src/permission/BanEntry.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5e917dc75..5a816f81c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -19,6 +19,7 @@ rules: parameters: level: 9 checkMissingCallableSignature: true + rememberPossiblyImpureFunctionValues: false #risky to remember these, better for performance to avoid repeated calls anyway treatPhpDocTypesAsCertain: false bootstrapFiles: - tests/phpstan/bootstrap.php diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php index 0abc274b5..d6ab1c175 100644 --- a/src/entity/projectile/Projectile.php +++ b/src/entity/projectile/Projectile.php @@ -290,10 +290,11 @@ abstract class Projectile extends Entity{ $damage = $this->getResultDamage(); if($damage >= 0){ - if($this->getOwningEntity() === null){ + $owner = $this->getOwningEntity(); + if($owner === null){ $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); }else{ - $ev = new EntityDamageByChildEntityEvent($this->getOwningEntity(), $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); + $ev = new EntityDamageByChildEntityEvent($owner, $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); } $entityHit->attack($ev); diff --git a/src/permission/BanEntry.php b/src/permission/BanEntry.php index 0d5ed0c76..5f235f1a9 100644 --- a/src/permission/BanEntry.php +++ b/src/permission/BanEntry.php @@ -101,11 +101,12 @@ class BanEntry{ } public function getString() : string{ + $expires = $this->getExpires(); return implode("|", [ $this->getName(), $this->getCreated()->format(self::$format), $this->getSource(), - $this->getExpires() === null ? "Forever" : $this->getExpires()->format(self::$format), + $expires === null ? "Forever" : $expires->format(self::$format), $this->getReason() ]); } From b3f15435cc9dc50fb93c3cf2c8023c0b8a27d3db Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 8 Jan 2025 02:31:50 +0000 Subject: [PATCH 097/334] Projectile: clean up dodgy code --- src/entity/projectile/Projectile.php | 35 +++++++++------------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php index d6ab1c175..68b6c4763 100644 --- a/src/entity/projectile/Projectile.php +++ b/src/entity/projectile/Projectile.php @@ -44,7 +44,6 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; use pocketmine\timings\Timings; -use function assert; use function atan2; use function ceil; use function count; @@ -170,8 +169,6 @@ abstract class Projectile extends Entity{ $start = $this->location->asVector3(); $end = $start->add($dx, $dy, $dz); - $blockHit = null; - $entityHit = null; $hitResult = null; $world = $this->getWorld(); @@ -181,8 +178,7 @@ abstract class Projectile extends Entity{ $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end); if($blockHitResult !== null){ $end = $blockHitResult->hitVector; - $blockHit = $block; - $hitResult = $blockHitResult; + $hitResult = [$block, $blockHitResult]; break; } } @@ -206,8 +202,7 @@ abstract class Projectile extends Entity{ if($distance < $entityDistance){ $entityDistance = $distance; - $entityHit = $entity; - $hitResult = $entityHitResult; + $hitResult = [$entity, $entityHitResult]; $end = $entityHitResult->hitVector; } } @@ -223,26 +218,18 @@ abstract class Projectile extends Entity{ $this->recalculateBoundingBox(); if($hitResult !== null){ - /** @var ProjectileHitEvent|null $ev */ - $ev = null; - if($entityHit !== null){ - $ev = new ProjectileHitEntityEvent($this, $hitResult, $entityHit); - }elseif($blockHit !== null){ - $ev = new ProjectileHitBlockEvent($this, $hitResult, $blockHit); + [$objectHit, $rayTraceResult] = $hitResult; + if($objectHit instanceof Entity){ + $ev = new ProjectileHitEntityEvent($this, $rayTraceResult, $objectHit); + $specificHitFunc = fn() => $this->onHitEntity($objectHit, $rayTraceResult); }else{ - assert(false, "unknown hit type"); + $ev = new ProjectileHitBlockEvent($this, $rayTraceResult, $objectHit); + $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult); } - if($ev !== null){ - $ev->call(); - $this->onHit($ev); - - if($ev instanceof ProjectileHitEntityEvent){ - $this->onHitEntity($ev->getEntityHit(), $ev->getRayTraceResult()); - }elseif($ev instanceof ProjectileHitBlockEvent){ - $this->onHitBlock($ev->getBlockHit(), $ev->getRayTraceResult()); - } - } + $ev->call(); + $this->onHit($ev); + $specificHitFunc(); $this->isCollided = $this->onGround = true; $this->motion = Vector3::zero(); From f349ce75e4ff0632fee57fad0a9b976b5feed26c Mon Sep 17 00:00:00 2001 From: Sergi del Olmo Date: Thu, 9 Jan 2025 21:13:46 +0100 Subject: [PATCH 098/334] Player: add ability to get & set flight speed multiplier (#6076) Since this doesn't directly correspond to flight speed (it's multiplied by different values depending on whether sprinting or not, and possibly other states), "multiplier" was preferred instead of directly calling it flight speed. Default value is 0.05. --- src/network/mcpe/NetworkSession.php | 3 +-- src/player/Player.php | 39 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 511d0dece..fdb63c467 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1058,8 +1058,7 @@ class NetworkSession{ ]; $layers = [ - //TODO: dynamic flying speed! FINALLY!!!!!!!!!!!!!!!!! - new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1), + new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 0.1), ]; if(!$for->hasBlockCollision()){ //TODO: HACK! In 1.19.80, the client starts falling in our faux spectator mode when it clips into a diff --git a/src/player/Player.php b/src/player/Player.php index 66ba6f376..1c67b7182 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -184,6 +184,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ private const MAX_REACH_DISTANCE_SURVIVAL = 7; private const MAX_REACH_DISTANCE_ENTITY_INTERACTION = 8; + public const DEFAULT_FLIGHT_SPEED_MULTIPLIER = 0.05; + public const TAG_FIRST_PLAYED = "firstPlayed"; //TAG_Long public const TAG_LAST_PLAYED = "lastPlayed"; //TAG_Long private const TAG_GAME_MODE = "playerGameType"; //TAG_Int @@ -285,6 +287,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected bool $blockCollision = true; protected bool $flying = false; + protected float $flightSpeedMultiplier = self::DEFAULT_FLIGHT_SPEED_MULTIPLIER; + /** @phpstan-var positive-int|null */ protected ?int $lineHeight = null; protected string $locale = "en_US"; @@ -518,6 +522,41 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return $this->flying; } + /** + * Sets the player's flight speed multiplier. + * + * Normal flying speed in blocks-per-tick is (multiplier * 10) blocks per tick. + * When sprint-flying, this is doubled to 20. + * + * If set to zero, the player will not be able to move in the xz plane when flying. + * Negative values will invert the controls. + * + * Note: Movement speed attribute does not influence flight speed. + * + * @see Player::DEFAULT_FLIGHT_SPEED_MULTIPLIER + */ + public function setFlightSpeedMultiplier(float $flightSpeedMultiplier) : void{ + if($this->flightSpeedMultiplier !== $flightSpeedMultiplier){ + $this->flightSpeedMultiplier = $flightSpeedMultiplier; + $this->getNetworkSession()->syncAbilities($this); + } + } + + /** + * Returns the player's flight speed multiplier. + * + * Normal flying speed in blocks-per-tick is (multiplier * 10) blocks per tick. + * When sprint-flying, this is doubled to 20. + * + * If set to zero, the player will not be able to move in the xz plane when flying. + * Negative values will invert the controls. + * + * @see Player::DEFAULT_FLIGHT_SPEED_MULTIPLIER + */ + public function getFlightSpeedMultiplier() : float{ + return $this->flightSpeedMultiplier; + } + public function setAutoJump(bool $value) : void{ if($this->autoJump !== $value){ $this->autoJump = $value; From d1066d0199ed3f7e775e3ff98b2847a57b703385 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:54:48 +0000 Subject: [PATCH 099/334] Bump build/php from `56cec11` to `ae94694` (#6595) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 56cec1174..ae946949c 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 56cec11745bbd87719a303a0a1de41ee1d1d69c2 +Subproject commit ae946949c528acf8c3f05dfceadc1d66b42d1f2f From 0b60a47cdecd9ab84a4e6edcbd13b5abf634b14c Mon Sep 17 00:00:00 2001 From: Dries C Date: Fri, 17 Jan 2025 20:56:19 +0100 Subject: [PATCH 100/334] Noteblock instrument changes from 1.21.50 (#6596) Good thing this isn't saved on disk :| --- src/data/bedrock/NoteInstrumentIdMap.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/bedrock/NoteInstrumentIdMap.php b/src/data/bedrock/NoteInstrumentIdMap.php index c847ecd98..7f0c0f310 100644 --- a/src/data/bedrock/NoteInstrumentIdMap.php +++ b/src/data/bedrock/NoteInstrumentIdMap.php @@ -39,10 +39,10 @@ final class NoteInstrumentIdMap{ NoteInstrument::SNARE => 2, NoteInstrument::CLICKS_AND_STICKS => 3, NoteInstrument::DOUBLE_BASS => 4, - NoteInstrument::BELL => 5, - NoteInstrument::FLUTE => 6, - NoteInstrument::CHIME => 7, - NoteInstrument::GUITAR => 8, + NoteInstrument::FLUTE => 5, + NoteInstrument::BELL => 6, + NoteInstrument::GUITAR => 7, + NoteInstrument::CHIME => 8, NoteInstrument::XYLOPHONE => 9, NoteInstrument::IRON_XYLOPHONE => 10, NoteInstrument::COW_BELL => 11, From 04e63172c3b819bb2d01483cf9de52d9ced0b347 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 22 Jan 2025 00:08:49 +0000 Subject: [PATCH 101/334] 5.23.3 (#6597) --- changelogs/5.23.md | 10 ++++++++++ src/VersionInfo.php | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelogs/5.23.md b/changelogs/5.23.md index 6e72e9403..3a287608f 100644 --- a/changelogs/5.23.md +++ b/changelogs/5.23.md @@ -127,3 +127,13 @@ Released 9th December 2024. ## Internals - Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow. + +# 5.23.3 +Released 22nd January 2025. + +## Fixes +- Fixed crashes with PHP internal stack frames being flagged as plugin crashes. +- Fixed note block instrument sounds in 1.21.50. + +## Internals +- Updated GitHub issue templates to use issue forms. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 48ce7dc9e..368328f20 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.23.3"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 3453ff03fd2a6c7572beafcfc26c2a902ae1e1a5 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:10:10 +0000 Subject: [PATCH 102/334] 5.23.4 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12898306655 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 368328f20..10a316835 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.23.3"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.23.4"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 406ddf3e5319eb28b2dd996962315fc59cd7dc96 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 22 Jan 2025 17:44:23 +0000 Subject: [PATCH 103/334] Revert "Internet: make postURL() error reporting behaviour more predictable" This reverts commit 97c5902ae2fea587faaee7487bbe14fa6100d67e. --- src/utils/Internet.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/Internet.php b/src/utils/Internet.php index 1811ce51e..89af2a77e 100644 --- a/src/utils/Internet.php +++ b/src/utils/Internet.php @@ -157,21 +157,22 @@ class Internet{ * POSTs data to an URL * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. * + * @phpstan-template TErrorVar of mixed + * * @param string[]|string $args * @param string[] $extraHeaders * @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation. * @phpstan-param string|array $args * @phpstan-param list $extraHeaders - * @phpstan-param-out string|null $err + * @phpstan-param TErrorVar $err + * @phpstan-param-out TErrorVar|string $err */ public static function postURL(string $page, array|string $args, int $timeout = 10, array $extraHeaders = [], &$err = null) : ?InternetRequestResult{ try{ - $result = self::simpleCurl($page, $timeout, $extraHeaders, [ + return self::simpleCurl($page, $timeout, $extraHeaders, [ CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $args ]); - $err = null; - return $result; }catch(InternetException $ex){ $err = $ex->getMessage(); return null; From 6b606dca95b413fb675562c858d0c8786dad76b1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 22 Jan 2025 17:46:43 +0000 Subject: [PATCH 104/334] UPnP: better fix for postURL error that doesn't require behavioural breaks --- src/network/upnp/UPnP.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/upnp/UPnP.php b/src/network/upnp/UPnP.php index 2d48a2db8..bd8e8376f 100644 --- a/src/network/upnp/UPnP.php +++ b/src/network/upnp/UPnP.php @@ -215,6 +215,7 @@ class UPnP{ 'SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"' ]; + $err = ""; if(Internet::postURL($serviceURL, $contents, 3, $headers, $err) === null){ throw new UPnPException("Failed to portforward using UPnP: " . $err); } From b625fee94b7e0ed3486b0c12fe32980ff5f6f7e8 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 22 Jan 2025 18:00:41 +0000 Subject: [PATCH 105/334] Prepare 5.24.0 release --- changelogs/5.24.md | 108 ++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.24.md diff --git a/changelogs/5.24.md b/changelogs/5.24.md new file mode 100644 index 000000000..a159d0e76 --- /dev/null +++ b/changelogs/5.24.md @@ -0,0 +1,108 @@ +# 5.24.0 +Released 22nd January 2025. + +This is a minor feature release, including new gameplay features, performance improvements, and minor API additions. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## Performance +- PHP garbage collection is now managed by the server, instead of being automatically triggered by PHP. + - The mechanism for GC triggering is designed to mimic PHP's to avoid behavioural changes. Only the place it's triggered from should be significantly different. + - This change also avoids unnecessary GCs during object-heavy operations, such as encoding `CraftingDataPacket`. As such, performance during server join should see an improvement. + - Timings is now able to directly measure the impact of GC. Previously, GC would show up as random spikes under random timers, skewing timing results. +- `ChunkCache` now uses `string` for completed caches directly instead of keeping them wrapped in `CompressBatchPromise`s. This reduces memory usage, improves performance, and reduces GC workload. + +## Configuration +- The following settings have been removed from `pocketmine.yml` and will no longer have any effect: + - `memory.garbage-collection.collect-async-worker` (now always `true`) + - `memory.garbage-collection.low-memory-trigger` (now always `true`) + - `memory.max-chunks.trigger-chunk-collect` (now always `true`) + - `memory.world-caches.disable-chunk-cache` (now always `true`) + - `memory.world-caches.low-memory-trigger` (now always `true`) + +## Gameplay +- Added the following new blocks: + - All types of pale oak wood, and leaves + - Resin + - Resin Bricks, Slabs, Stairs, and Walls + - Resin Clump + - Chiseled Resin Bricks +- Some blocks have had their tool tier requirements adjusted to match latest Bedrock updates. +- Added the following new items: + - Resin Brick + - Music Disc - Creator + - Music Disc - Creator (Music Box) + - Music Disc - Precipice + - Music Disc - Relic + +## API +### General +- Many places had their PHPDoc improved to address issues highlighted by PHPStan 2.x. This may cause new, previously unreported issues to be reported in plugins using PHPStan. + +### `pocketmine` +- The following methods have been deprecated: + - `MemoryManager->canUseChunkCache()` + - `MemoryManager::dumpMemory()` - relocated to `MemoryDump` class + +### `pocketmine\item` +- The following new enum cases have been added: + - `RecordType::DISK_CREATOR` + - `RecordType::DISK_CREATOR_MUSIC_BOX` + - `RecordType::DISK_PRECIPICE` + - `RecordType::DISK_RELIC` + +### `pocketmine\player` +- The following new methods have been added: + - `public Player->getFlightSpeedMultiplier() : float` - a base multiplier for player's flight speed + - `public Player->setFlightSpeedMultiplier(float $flightSpeedMultiplier) : void` - sets the player's flight speed multiplier +- The following new constants have been added: + - `Player::DEFAULT_FLIGHT_SPEED_MULTIPLIER` + +### `pocketmine\utils` +- The following new methods have been added: + - `public static TextFormat::javaToBedrock(string $string) : string` - removes unsupported Java Edition format codes to prevent them being incorrectly displayed on Bedrock +- The following methods have behavioural changes: + - `TextFormat::toHTML()` now converts `§m` to redstone red (instead of strikethrough), and `§n` to copper orange (instead of underline). This is because the codes previously used for `STRIKETHROUGH` and `UNDERLINE` conflict with the new material codes introduced by Minecraft Bedrock. + - `Terminal::toANSI()` now converts `§m` to redstone red (instead of strikethrough), and `§n` to copper orange (instead of underline), as above. However, underline and strikethrough can still be used on the terminal using `Terminal::$FORMAT_UNDERLINE` and `Terminal::$FORMAT_STRIKETHROUGH` respectively. +- The following new constants have been added: + - `TextFormat::MATERIAL_QUARTZ` + - `TextFormat::MATERIAL_IRON` + - `TextFormat::MATERIAL_NETHERITE` + - `TextFormat::MATERIAL_REDSTONE` + - `TextFormat::MATERIAL_COPPER` + - `TextFormat::MATERIAL_GOLD` + - `TextFormat::MATERIAL_EMERALD` + - `TextFormat::MATERIAL_DIAMOND` + - `TextFormat::MATERIAL_LAPIS` + - `TextFormat::MATERIAL_AMETHYST` +- The following constants have been deprecated: + - `TextFormat::STRIKETHROUGH` + - `TextFormat::UNDERLINE` +- The following static properties have been added: + - `Terminal::$COLOR_MATERIAL_QUARTZ` + - `Terminal::$COLOR_MATERIAL_IRON` + - `Terminal::$COLOR_MATERIAL_NETHERITE` + - `Terminal::$COLOR_MATERIAL_REDSTONE` + - `Terminal::$COLOR_MATERIAL_COPPER` + - `Terminal::$COLOR_MATERIAL_GOLD` + - `Terminal::$COLOR_MATERIAL_EMERALD` + - `Terminal::$COLOR_MATERIAL_DIAMOND` + - `Terminal::$COLOR_MATERIAL_LAPIS` + - `Terminal::$COLOR_MATERIAL_AMETHYST` + +## Tools +- Fixed some UI issues in `tools/convert-world.php` + +## Internals +- Block cache in `World` is now size-limited. This prevents memory exhaustion when plugins call `getBlock()` many thousands of times with cache misses. +- `RakLibServer` now disables PHP GC. As RakLib doesn't generate any unmanaged cycles, GC is just a waste of CPU time in this context. +- `MemoryManager` now has the responsibility for triggering cycle GC. It's checked every tick, but GC won't take place unless the GC threshold is exceeded, similar to PHP. + - This mechanism could probably do with alterations to better suit PocketMine-MP, but it was chosen to mimic PHP's own GC to minimize behavioural changes for now. +- `AsyncTask` now triggers cycle GC after `onRun()` completes. As with `MemoryManager`, this is based on a threshold designed to mimic PHP's own behaviour. +- `FormatConverter` now performs world provider GC periodically. This is not needed with current active providers, but was found to be a problem while developing custom providers. +- Various internal adjustments were made to avoid returning incorrectly-keyed arrays in the code. These changes shouldn't affect anything as the arrays should have been properly ordered anyway. +- Many places that previously used `==` and `!=` have been updated to use strict variants. This kind of change needs to be done carefully to avoid breaking `int|float` comparisons. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 10a316835..fcfb1c7e9 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.23.4"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.24.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From fc86d3a44e54e2720e886de83b5e6c6ee8934045 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 20:50:42 +0000 Subject: [PATCH 106/334] 5.24.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12916833718 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index fcfb1c7e9..aaa357e0f 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.24.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.24.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From f60120a75ea8e80fadbedfad0148da10ea29c91f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 10:43:28 +0000 Subject: [PATCH 107/334] Bump the development-patch-updates group with 3 updates (#6603) --- composer.json | 2 +- composer.lock | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index a40f3733e..b9fb70b0e 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.1", + "phpstan/phpstan": "2.1.2", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index d59c10727..7f9055539 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": "994ccffe45f066768542019f6f9d237b", + "content-hash": "5321ac37e6830cae119d03082b2668a5", "packages": [ { "name": "adhocore/json-comment", @@ -1386,16 +1386,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" + "reference": "7d08f569e582ade182a375c366cbd896eccadd3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a", + "reference": "7d08f569e582ade182a375c366cbd896eccadd3a", "shasum": "" }, "require": { @@ -1440,20 +1440,20 @@ "type": "github" } ], - "time": "2025-01-05T16:43:48+00:00" + "time": "2025-01-21T14:54:06+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18" + "reference": "d09e152f403c843998d7a52b5d87040c937525dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e32ac656788a5bf3dedda89e6a2cad5643bf1a18", - "reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd", + "reference": "d09e152f403c843998d7a52b5d87040c937525dd", "shasum": "" }, "require": { @@ -1489,22 +1489,22 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.3" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4" }, - "time": "2024-12-19T09:14:43+00:00" + "time": "2025-01-22T13:07:38+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3" + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3", - "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba", "shasum": "" }, "require": { @@ -1537,9 +1537,9 @@ "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/2.0.1" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3" }, - "time": "2024-12-12T20:21:10+00:00" + "time": "2025-01-21T10:52:14+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From 70fb9bbdfd06c7eda00b4cd2c2c3840755e6b8f6 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 27 Jan 2025 21:28:26 +0000 Subject: [PATCH 108/334] ChorusPlant: fixed recalculateCollisionBoxes() depending on the world --- src/block/ChorusPlant.php | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/block/ChorusPlant.php b/src/block/ChorusPlant.php index 9013f6825..88cf1787c 100644 --- a/src/block/ChorusPlant.php +++ b/src/block/ChorusPlant.php @@ -34,11 +34,16 @@ use function mt_rand; final class ChorusPlant extends Flowable{ use StaticSupportTrait; + /** + * @var true[] + * @phpstan-var array + */ + protected array $connections = []; + protected function recalculateCollisionBoxes() : array{ $bb = AxisAlignedBB::one(); - foreach($this->getAllSides() as $facing => $block){ - $id = $block->getTypeId(); - if($id !== BlockTypeIds::END_STONE && $id !== BlockTypeIds::CHORUS_FLOWER && !$block->hasSameTypeId($this)){ + foreach(Facing::ALL as $facing){ + if(!isset($this->connections[$facing])){ $bb->trim($facing, 2 / 16); } } @@ -46,6 +51,26 @@ final class ChorusPlant extends Flowable{ return [$bb]; } + public function readStateFromWorld() : Block{ + parent::readStateFromWorld(); + + $this->collisionBoxes = null; + + foreach(Facing::ALL as $facing){ + $block = $this->getSide($facing); + if(match($block->getTypeId()){ + BlockTypeIds::END_STONE, BlockTypeIds::CHORUS_FLOWER, $this->getTypeId() => true, + default => false + }){ + $this->connections[$facing] = true; + }else{ + unset($this->connections[$facing]); + } + } + + return $this; + } + private function canBeSupportedBy(Block $block) : bool{ return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE; } From 07987890a048507d618afc69336e7144f963e607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 13:38:25 +0000 Subject: [PATCH 109/334] Bump the github-actions group with 2 updates (#6613) --- .github/workflows/build-docker-image.yml | 8 ++++---- .github/workflows/draft-release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 94856fa44..a0cb1f1d9 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.13.0 with: push: true context: ./pocketmine-mp @@ -66,7 +66,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.13.0 with: push: true context: ./pocketmine-mp @@ -79,7 +79,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.13.0 with: push: true context: ./pocketmine-mp @@ -92,7 +92,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.13.0 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 445a1f7a6..8a35e2904 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -165,7 +165,7 @@ jobs: ${{ github.workspace }}/core-permissions.rst - name: Create draft release - uses: ncipollo/release-action@v1.14.0 + uses: ncipollo/release-action@v1.15.0 id: create-draft with: artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst From 0a9a45a126ff30bf385a53d5b170b80071c6198a Mon Sep 17 00:00:00 2001 From: GameParrot <85067619+GameParrot@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:30:29 +0000 Subject: [PATCH 110/334] Improve block break progress closes #6500 This fixes break time animations for mining fatigue and haste, and improves the underwater and on-ground behaviour. on-ground is still not quite right for reasons not related to this PR (see #6547). I'm also not quite sure the underwater logic is correct (in water vs underwater?) but it's definitely better than what we have currently. --- src/player/SurvivalBlockBreakHandler.php | 31 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/player/SurvivalBlockBreakHandler.php b/src/player/SurvivalBlockBreakHandler.php index e31e77ef7..57c2842ce 100644 --- a/src/player/SurvivalBlockBreakHandler.php +++ b/src/player/SurvivalBlockBreakHandler.php @@ -25,6 +25,8 @@ namespace pocketmine\player; use pocketmine\block\Block; use pocketmine\entity\animation\ArmSwingAnimation; +use pocketmine\entity\effect\VanillaEffects; +use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\LevelEventPacket; @@ -65,11 +67,29 @@ final class SurvivalBlockBreakHandler{ if(!$this->block->getBreakInfo()->isBreakable()){ return 0.0; } - //TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211) $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20; - + if(!$this->player->isOnGround() && !$this->player->isFlying()){ + $breakTimePerTick *= 5; + } + if($this->player->isUnderwater() && !$this->player->getArmorInventory()->getHelmet()->hasEnchantment(VanillaEnchantments::AQUA_AFFINITY())){ + $breakTimePerTick *= 5; + } if($breakTimePerTick > 0){ - return 1 / $breakTimePerTick; + $progressPerTick = 1 / $breakTimePerTick; + + $haste = $this->player->getEffects()->get(VanillaEffects::HASTE()); + if($haste !== null){ + $hasteLevel = $haste->getEffectLevel(); + $progressPerTick *= (1 + 0.2 * $hasteLevel) * (1.2 ** $hasteLevel); + } + + $miningFatigue = $this->player->getEffects()->get(VanillaEffects::MINING_FATIGUE()); + if($miningFatigue !== null){ + $miningFatigueLevel = $miningFatigue->getEffectLevel(); + $progressPerTick *= 0.21 ** $miningFatigueLevel; + } + + return $progressPerTick; } return 1; } @@ -82,7 +102,10 @@ final class SurvivalBlockBreakHandler{ $newBreakSpeed = $this->calculateBreakProgressPerTick(); if(abs($newBreakSpeed - $this->breakSpeed) > 0.0001){ $this->breakSpeed = $newBreakSpeed; - //TODO: sync with client + $this->player->getWorld()->broadcastPacketToViewers( + $this->blockPos, + LevelEventPacket::create(LevelEvent::BLOCK_BREAK_SPEED, (int) (65535 * $this->breakSpeed), $this->blockPos) + ); } $this->breakProgress += $this->breakSpeed; From 21ccd9014706fa9e5f7661ade27d344390224483 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 2 Feb 2025 19:43:04 +0000 Subject: [PATCH 111/334] ChunkCache: parameterize dimension ID (cc @Muqsit) --- src/network/mcpe/cache/ChunkCache.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/network/mcpe/cache/ChunkCache.php b/src/network/mcpe/cache/ChunkCache.php index 2c5f67f08..2b4265fcf 100644 --- a/src/network/mcpe/cache/ChunkCache.php +++ b/src/network/mcpe/cache/ChunkCache.php @@ -88,9 +88,13 @@ class ChunkCache implements ChunkListener{ private int $hits = 0; private int $misses = 0; + /** + * @phpstan-param DimensionIds::* $dimensionId + */ private function __construct( private World $world, - private Compressor $compressor + private Compressor $compressor, + private int $dimensionId = DimensionIds::OVERWORLD ){} private function prepareChunkAsync(int $chunkX, int $chunkZ, int $chunkHash) : CompressBatchPromise{ @@ -109,7 +113,7 @@ class ChunkCache implements ChunkListener{ new ChunkRequestTask( $chunkX, $chunkZ, - DimensionIds::OVERWORLD, //TODO: not hardcode this + $this->dimensionId, $chunk, $promise, $this->compressor From 9d6a0cc7385976fb350f6f919ecc5580b508a783 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 2 Feb 2025 19:46:54 +0000 Subject: [PATCH 112/334] Update composer dependencies --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 7f9055539..26a3a510c 100644 --- a/composer.lock +++ b/composer.lock @@ -1013,8 +1013,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1092,8 +1092,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1210,16 +1210,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -1262,9 +1262,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -1864,16 +1864,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.38", + "version": "10.5.44", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" + "reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", - "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1381c62769be4bb88fa4c5aec1366c7c66ca4f36", + "reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36", "shasum": "" }, "require": { @@ -1883,7 +1883,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -1945,7 +1945,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.5.38" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.44" }, "funding": [ { @@ -1961,7 +1961,7 @@ "type": "tidelift" } ], - "time": "2024-10-28T13:06:21+00:00" + "time": "2025-01-31T07:00:38+00:00" }, { "name": "sebastian/cli-parser", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From 03f98ee0a9f3293b426acaa7d6763dd565d31fd6 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 2 Feb 2025 19:47:47 +0000 Subject: [PATCH 113/334] New version of NBT for performance improvements --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index b9fb70b0e..9c5919027 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "pocketmine/locale-data": "~2.22.0", "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", - "pocketmine/nbt": "~1.0.0", + "pocketmine/nbt": "~1.1.0", "pocketmine/raklib": "~1.1.0", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", diff --git a/composer.lock b/composer.lock index 26a3a510c..a6159bd93 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": "5321ac37e6830cae119d03082b2668a5", + "content-hash": "ddcce2cd4c9a4217df3c101cc1183722", "packages": [ { "name": "adhocore/json-comment", @@ -576,16 +576,16 @@ }, { "name": "pocketmine/nbt", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/pmmp/NBT.git", - "reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d" + "reference": "cfd53a86166b851786967fc560cdb372e66fde96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/NBT/zipball/53db37487bc5ddbfbd84247966e1a073bdcfdb7d", - "reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d", + "url": "https://api.github.com/repos/pmmp/NBT/zipball/cfd53a86166b851786967fc560cdb372e66fde96", + "reference": "cfd53a86166b851786967fc560cdb372e66fde96", "shasum": "" }, "require": { @@ -612,9 +612,9 @@ "description": "PHP library for working with Named Binary Tags", "support": { "issues": "https://github.com/pmmp/NBT/issues", - "source": "https://github.com/pmmp/NBT/tree/1.0.1" + "source": "https://github.com/pmmp/NBT/tree/1.1.0" }, - "time": "2025-01-07T22:47:46+00:00" + "time": "2025-02-01T21:20:26+00:00" }, { "name": "pocketmine/raklib", From 94797e3afab4ce153d6e430baaa0da2995a0b346 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:42:37 +0000 Subject: [PATCH 114/334] Bump build/php from `ae94694` to `1549433` (#6616) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index ae946949c..154943379 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit ae946949c528acf8c3f05dfceadc1d66b42d1f2f +Subproject commit 15494337976e645499e2e3e8c8b491227522be91 From 39e69276a1bbe86ebd583e834381e5bc73948564 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:43:03 +0000 Subject: [PATCH 115/334] Bump tests/plugins/DevTools from `c6dca35` to `a030d39` (#6617) --- tests/plugins/DevTools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins/DevTools b/tests/plugins/DevTools index c6dca357c..a030d39e5 160000 --- a/tests/plugins/DevTools +++ b/tests/plugins/DevTools @@ -1 +1 @@ -Subproject commit c6dca357c7e8a37ce3479a1bedfe849451e072e3 +Subproject commit a030d39e51d267b5cd7e09069844a06910072ae7 From 9b3b45258aa33143cb23bb6e759e8e5367b5e347 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 6 Feb 2025 15:42:10 +0000 Subject: [PATCH 116/334] Optimise collision box checks by caching some basic info (#6606) This PR significantly improves performance of entity movement calculation. Previous attempts to optimise this were ineffective, as they used a cache to mitigate the cost of recomputing AABBs. Entities tend to move around randomly, so the non-cached pathway really needed to be optimized. This change improves performance on multiple fronts: 1) avoiding Block allocations for blocks with 1x1x1 AABBs and with no AABBs (the most common) 2) avoiding Block allocations and overlapping intersection checks unless a stateID is specifically known to potentially exceed its cell boundaries (like fences) 3) avoiding overlapping AABB checks when overlaps can't make any difference anyway (cubes) Together, these changes improve the performance of World->getBlockCollisionBoxes() by a factor of 5. In real-world terms, this shows up as a major performance improvement in situations with lots of entities moving in random directions. Testing with item entities showed an increase from 400 to 1200 moving items with the same CPU usage. This change is built on the assumption that `Block->recalculateCollisionBoxes()` and its overrides don't interact with any world. This is technically possible due to the crappy design of the `Block` architecture, but should be avoided. As a world is not available during `RuntimeBlockStateRegistry` initialization, attempting to interact with a world during `recalculateCollisionBoxes()` will now cause a crash. This turned out to be a problem for `ChorusPlant`, which was fixed by 70fb9bbdfd06c7eda00b4cd2c2c3840755e6b8f6. The correct solution in this case was to use dynamic states similar to how we currently deal with fence connections. --- src/block/RuntimeBlockStateRegistry.php | 79 ++++++++++++++++++++ src/world/World.php | 98 +++++++++++++++++++------ 2 files changed, 155 insertions(+), 22 deletions(-) diff --git a/src/block/RuntimeBlockStateRegistry.php b/src/block/RuntimeBlockStateRegistry.php index a8ece7722..d13b942ba 100644 --- a/src/block/RuntimeBlockStateRegistry.php +++ b/src/block/RuntimeBlockStateRegistry.php @@ -28,6 +28,7 @@ use pocketmine\block\BlockIdentifier as BID; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use pocketmine\world\light\LightUpdate; +use function count; use function min; /** @@ -40,6 +41,11 @@ use function min; class RuntimeBlockStateRegistry{ use SingletonTrait; + public const COLLISION_CUSTOM = 0; + public const COLLISION_CUBE = 1; + public const COLLISION_NONE = 2; + public const COLLISION_MAY_OVERFLOW = 3; + /** * @var Block[] * @phpstan-var array @@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{ */ public array $blastResistance = []; + /** + * Map of state ID -> useful AABB info to avoid unnecessary block allocations + * @var int[] + * @phpstan-var array + */ + public array $collisionInfo = []; + public function __construct(){ foreach(VanillaBlocks::getAll() as $block){ $this->register($block); @@ -100,6 +113,70 @@ class RuntimeBlockStateRegistry{ } } + /** + * Checks if the given class method overrides a method in Block. + * Used to determine if a block might need to disable fast path optimizations. + * + * @phpstan-param anyClosure $closure + */ + private static function overridesBlockMethod(\Closure $closure) : bool{ + $declarer = (new \ReflectionFunction($closure))->getClosureScopeClass(); + return $declarer !== null && $declarer->getName() !== Block::class; + } + + /** + * A big ugly hack to set up fast paths for handling collisions on blocks with common shapes. + * The information returned here is stored in RuntimeBlockStateRegistry->collisionInfo, and is used during entity + * collision box calculations to avoid complex logic and unnecessary block object allocations. + * This hack allows significant performance improvements. + * + * TODO: We'll want to redesign block collision box handling and block shapes in the future, but that's a job for a + * major version. For now, this hack nets major performance wins. + */ + private static function calculateCollisionInfo(Block $block) : int{ + if( + self::overridesBlockMethod($block->getModelPositionOffset(...)) || + self::overridesBlockMethod($block->readStateFromWorld(...)) + ){ + //getModelPositionOffset() might cause AABBs to shift outside the cell + //readStateFromWorld() might cause overflow in ways we can't predict just by looking at known states + //TODO: excluding overriders of readStateFromWorld() also excludes blocks with tiles that don't do anything + //weird with their AABBs, but for now this is the best we can do. + return self::COLLISION_MAY_OVERFLOW; + } + + //TODO: this could blow up if any recalculateCollisionBoxes() uses the world + //it shouldn't, but that doesn't mean that custom blocks won't... + $boxes = $block->getCollisionBoxes(); + if(count($boxes) === 0){ + return self::COLLISION_NONE; + } + + if( + count($boxes) === 1 && + $boxes[0]->minX === 0.0 && + $boxes[0]->minY === 0.0 && + $boxes[0]->minZ === 0.0 && + $boxes[0]->maxX === 1.0 && + $boxes[0]->maxY === 1.0 && + $boxes[0]->maxZ === 1.0 + ){ + return self::COLLISION_CUBE; + } + + foreach($boxes as $box){ + if( + $box->minX < 0 || $box->maxX > 1 || + $box->minY < 0 || $box->maxY > 1 || + $box->minZ < 0 || $box->maxZ > 1 + ){ + return self::COLLISION_MAY_OVERFLOW; + } + } + + return self::COLLISION_CUSTOM; + } + private function fillStaticArrays(int $index, Block $block) : void{ $fullId = $block->getStateId(); if($index !== $fullId){ @@ -112,6 +189,8 @@ class RuntimeBlockStateRegistry{ if($block->blocksDirectSkyLight()){ $this->blocksDirectSkyLight[$index] = true; } + + $this->collisionInfo[$index] = self::calculateCollisionInfo($block); } } diff --git a/src/world/World.php b/src/world/World.php index 7395afa78..86ca44848 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -375,6 +375,8 @@ class World implements ChunkManager{ private \Logger $logger; + private RuntimeBlockStateRegistry $blockStateRegistry; + /** * @phpstan-return ChunkPosHash */ @@ -488,6 +490,7 @@ class World implements ChunkManager{ $this->displayName = $this->provider->getWorldData()->getName(); $this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName"); + $this->blockStateRegistry = RuntimeBlockStateRegistry::getInstance(); $this->minY = $this->provider->getWorldMinY(); $this->maxY = $this->provider->getWorldMaxY(); @@ -559,7 +562,7 @@ class World implements ChunkManager{ }catch(BlockStateDeserializeException){ continue; } - $block = RuntimeBlockStateRegistry::getInstance()->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData)); + $block = $this->blockStateRegistry->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData)); }else{ //TODO: we probably ought to log an error here continue; @@ -570,7 +573,7 @@ class World implements ChunkManager{ } } - foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state){ + foreach($this->blockStateRegistry->getAllKnownStates() as $state){ $dontTickName = $dontTickBlocks[$state->getTypeId()] ?? null; if($dontTickName === null && $state->ticksRandomly()){ $this->randomTickBlocks[$state->getStateId()] = true; @@ -1394,7 +1397,7 @@ class World implements ChunkManager{ $entity->onRandomUpdate(); } - $blockFactory = RuntimeBlockStateRegistry::getInstance(); + $blockFactory = $this->blockStateRegistry; foreach($chunk->getSubChunks() as $Y => $subChunk){ if(!$subChunk->isEmptyFast()){ $k = 0; @@ -1528,13 +1531,18 @@ class World implements ChunkManager{ $collides = []; + $collisionInfo = $this->blockStateRegistry->collisionInfo; if($targetFirst){ for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ for($y = $minY; $y <= $maxY; ++$y){ - $block = $this->getBlockAt($x, $y, $z); - if($block->collidesWithBB($bb)){ - return [$block]; + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); + if(match($stateCollisionInfo){ + RuntimeBlockStateRegistry::COLLISION_CUBE => true, + RuntimeBlockStateRegistry::COLLISION_NONE => false, + default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) + }){ + return [$this->getBlockAt($x, $y, $z)]; } } } @@ -1543,9 +1551,13 @@ class World implements ChunkManager{ for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ for($y = $minY; $y <= $maxY; ++$y){ - $block = $this->getBlockAt($x, $y, $z); - if($block->collidesWithBB($bb)){ - $collides[] = $block; + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); + if(match($stateCollisionInfo){ + RuntimeBlockStateRegistry::COLLISION_CUBE => true, + RuntimeBlockStateRegistry::COLLISION_NONE => false, + default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) + }){ + $collides[] = $this->getBlockAt($x, $y, $z); } } } @@ -1555,24 +1567,64 @@ class World implements ChunkManager{ return $collides; } + /** + * @param int[] $collisionInfo + * @phpstan-param array $collisionInfo + */ + private function getBlockCollisionInfo(int $x, int $y, int $z, array $collisionInfo) : int{ + if(!$this->isInWorld($x, $y, $z)){ + return RuntimeBlockStateRegistry::COLLISION_NONE; + } + $chunk = $this->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE); + if($chunk === null){ + return RuntimeBlockStateRegistry::COLLISION_NONE; + } + $stateId = $chunk + ->getSubChunk($y >> SubChunk::COORD_BIT_SIZE) + ->getBlockStateId( + $x & SubChunk::COORD_MASK, + $y & SubChunk::COORD_MASK, + $z & SubChunk::COORD_MASK + ); + return $collisionInfo[$stateId]; + } + /** * 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. * + * @param int[] $collisionInfo + * @phpstan-param array $collisionInfo + * * @return AxisAlignedBB[] * @phpstan-return list */ - private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{ - $block = $this->getBlockAt($x, $y, $z); - $boxes = $block->getCollisionBoxes(); + private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{ + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); + $boxes = match($stateCollisionInfo){ + RuntimeBlockStateRegistry::COLLISION_NONE => [], + RuntimeBlockStateRegistry::COLLISION_CUBE => [AxisAlignedBB::one()->offset($x, $y, $z)], + default => $this->getBlockAt($x, $y, $z)->getCollisionBoxes() + }; - $cellBB = AxisAlignedBB::one()->offset($x, $y, $z); - foreach(Facing::OFFSET as [$dx, $dy, $dz]){ - $extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes(); - foreach($extraBoxes as $extraBox){ - if($extraBox->intersectsWith($cellBB)){ - $boxes[] = $extraBox; + //overlapping AABBs can't make any difference if this is a cube, so we can save some CPU cycles in this common case + if($stateCollisionInfo !== RuntimeBlockStateRegistry::COLLISION_CUBE){ + $cellBB = null; + foreach(Facing::OFFSET as [$dx, $dy, $dz]){ + $offsetX = $x + $dx; + $offsetY = $y + $dy; + $offsetZ = $z + $dz; + $stateCollisionInfo = $this->getBlockCollisionInfo($offsetX, $offsetY, $offsetZ, $collisionInfo); + if($stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW){ + //avoid allocating this unless it's needed + $cellBB ??= AxisAlignedBB::one()->offset($x, $y, $z); + $extraBoxes = $this->getBlockAt($offsetX, $offsetY, $offsetZ)->getCollisionBoxes(); + foreach($extraBoxes as $extraBox){ + if($extraBox->intersectsWith($cellBB)){ + $boxes[] = $extraBox; + } + } } } } @@ -1594,13 +1646,15 @@ class World implements ChunkManager{ $collides = []; + $collisionInfo = $this->blockStateRegistry->collisionInfo; + 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){ $relativeBlockHash = World::chunkBlockHash($x, $y, $z); - $boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z); + $boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z, $collisionInfo); foreach($boxes as $blockBB){ if($blockBB->intersectsWith($bb)){ @@ -1795,7 +1849,7 @@ class World implements ChunkManager{ return; } - $blockFactory = RuntimeBlockStateRegistry::getInstance(); + $blockFactory = $this->blockStateRegistry; $this->timings->doBlockSkyLightUpdates->startTiming(); if($this->skyLightUpdate === null){ $this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight); @@ -1914,7 +1968,7 @@ class World implements ChunkManager{ $chunk = $this->chunks[$chunkHash] ?? null; if($chunk !== null){ - $block = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK)); + $block = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK)); }else{ $addToCache = false; $block = VanillaBlocks::AIR(); @@ -2573,7 +2627,7 @@ class World implements ChunkManager{ $localY = $tilePosition->getFloorY(); $localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK; - $newBlock = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ)); + $newBlock = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ)); $expectedTileClass = $newBlock->getIdInfo()->getTileClass(); if( $expectedTileClass === null || //new block doesn't expect a tile From 5ef89200c683f3b06d54b2d1509400a0f95152f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:14:30 +0000 Subject: [PATCH 117/334] Bump phpstan/phpstan in the development-patch-updates group (#6620) --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index b9fb70b0e..bbcbe7314 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.2", + "phpstan/phpstan": "2.1.4", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index 26a3a510c..52490968f 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": "5321ac37e6830cae119d03082b2668a5", + "content-hash": "a6aa0a34a230d325528d2e11f1439057", "packages": [ { "name": "adhocore/json-comment", @@ -1386,16 +1386,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.2", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7d08f569e582ade182a375c366cbd896eccadd3a" + "reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a", - "reference": "7d08f569e582ade182a375c366cbd896eccadd3a", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f99e18eb775dbaf6460c95fa0b65312da9c746a", + "reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a", "shasum": "" }, "require": { @@ -1440,7 +1440,7 @@ "type": "github" } ], - "time": "2025-01-21T14:54:06+00:00" + "time": "2025-02-10T08:25:21+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From e29aa2f337f2cc8779e8b98653ca90729984c313 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:15:42 +0300 Subject: [PATCH 118/334] Added resin material color (#6622) --- src/utils/Terminal.php | 6 +++++- src/utils/TextFormat.php | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/utils/Terminal.php b/src/utils/Terminal.php index 2abdbc357..bbd232969 100644 --- a/src/utils/Terminal.php +++ b/src/utils/Terminal.php @@ -69,6 +69,7 @@ abstract class Terminal{ public static string $COLOR_MATERIAL_DIAMOND = ""; public static string $COLOR_MATERIAL_LAPIS = ""; public static string $COLOR_MATERIAL_AMETHYST = ""; + public static string $COLOR_MATERIAL_RESIN = ""; private static ?bool $formattingCodes = null; @@ -131,6 +132,7 @@ abstract class Terminal{ self::$COLOR_MATERIAL_DIAMOND = $color(37); self::$COLOR_MATERIAL_LAPIS = $color(24); self::$COLOR_MATERIAL_AMETHYST = $color(98); + self::$COLOR_MATERIAL_RESIN = $color(208); } protected static function getEscapeCodes() : void{ @@ -174,11 +176,12 @@ abstract class Terminal{ self::$COLOR_MATERIAL_DIAMOND = $colors >= 256 ? $setaf(37) : $setaf(14); self::$COLOR_MATERIAL_LAPIS = $colors >= 256 ? $setaf(24) : $setaf(12); self::$COLOR_MATERIAL_AMETHYST = $colors >= 256 ? $setaf(98) : $setaf(13); + self::$COLOR_MATERIAL_RESIN = $colors >= 256 ? $setaf(208) : $setaf(11); }else{ self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = self::$COLOR_MATERIAL_NETHERITE = $setaf(0); self::$COLOR_RED = self::$COLOR_DARK_RED = self::$COLOR_MATERIAL_REDSTONE = self::$COLOR_MATERIAL_COPPER = $setaf(1); self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = self::$COLOR_MATERIAL_EMERALD = $setaf(2); - self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = $setaf(3); + self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = self::$COLOR_MATERIAL_RESIN = $setaf(3); self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = self::$COLOR_MATERIAL_LAPIS = $setaf(4); self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = self::$COLOR_MATERIAL_AMETHYST = $setaf(5); self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = self::$COLOR_MATERIAL_DIAMOND = $setaf(6); @@ -253,6 +256,7 @@ abstract class Terminal{ TextFormat::MATERIAL_DIAMOND => Terminal::$COLOR_MATERIAL_DIAMOND, TextFormat::MATERIAL_LAPIS => Terminal::$COLOR_MATERIAL_LAPIS, TextFormat::MATERIAL_AMETHYST => Terminal::$COLOR_MATERIAL_AMETHYST, + TextFormat::MATERIAL_RESIN => Terminal::$COLOR_MATERIAL_RESIN, default => $token, }; } diff --git a/src/utils/TextFormat.php b/src/utils/TextFormat.php index fadf6ea4e..0c948592a 100644 --- a/src/utils/TextFormat.php +++ b/src/utils/TextFormat.php @@ -73,6 +73,7 @@ abstract class TextFormat{ public const MATERIAL_DIAMOND = TextFormat::ESCAPE . "s"; public const MATERIAL_LAPIS = TextFormat::ESCAPE . "t"; public const MATERIAL_AMETHYST = TextFormat::ESCAPE . "u"; + public const MATERIAL_RESIN = TextFormat::ESCAPE . "v"; public const COLORS = [ self::BLACK => self::BLACK, @@ -102,6 +103,7 @@ abstract class TextFormat{ self::MATERIAL_DIAMOND => self::MATERIAL_DIAMOND, self::MATERIAL_LAPIS => self::MATERIAL_LAPIS, self::MATERIAL_AMETHYST => self::MATERIAL_AMETHYST, + self::MATERIAL_RESIN => self::MATERIAL_RESIN, ]; public const OBFUSCATED = TextFormat::ESCAPE . "k"; @@ -150,7 +152,7 @@ abstract class TextFormat{ * @return string[] */ public static function tokenize(string $string) : array{ - $result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-u])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-v])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); if($result === false) throw self::makePcreError(); return $result; } @@ -164,7 +166,7 @@ abstract class TextFormat{ $string = mb_scrub($string, 'UTF-8'); $string = self::preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console) if($removeFormat){ - $string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-u]/u", "", $string)); + $string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-v]/u", "", $string)); } return str_replace("\x1b", "", self::preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string)); } @@ -175,7 +177,7 @@ abstract class TextFormat{ * @param string $placeholder default "&" */ public static function colorize(string $string, string $placeholder = "&") : string{ - return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-u])/u', TextFormat::ESCAPE . '$1', $string); + return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-v])/u', TextFormat::ESCAPE . '$1', $string); } /** @@ -252,6 +254,7 @@ abstract class TextFormat{ TextFormat::MATERIAL_DIAMOND => "color:#2cb9a8", TextFormat::MATERIAL_LAPIS => "color:#20487a", TextFormat::MATERIAL_AMETHYST => "color:#9a5cc5", + TextFormat::MATERIAL_RESIN => "color:#fc7812", TextFormat::BOLD => "font-weight:bold", TextFormat::ITALIC => "font-style:italic", default => null From 9402a20ee3de0232761433565fc984953939c754 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 16 Feb 2025 16:12:29 +0000 Subject: [PATCH 119/334] Update Utils::getOS() doc comment closes #6628 --- src/utils/Utils.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/utils/Utils.php b/src/utils/Utils.php index b1f7e2842..46cd49ec4 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -264,14 +264,7 @@ final class Utils{ } /** - * Returns the current Operating System - * Windows => win - * MacOS => mac - * iOS => ios - * Android => android - * Linux => Linux - * BSD => bsd - * Other => other + * @return string one of the Utils::OS_* constants */ public static function getOS(bool $recalculate = false) : string{ if(self::$os === null || $recalculate){ From 91ac64783f0e5d2cffbff8b58c4bc98d5283832a Mon Sep 17 00:00:00 2001 From: Dries C Date: Sun, 16 Feb 2025 21:51:53 +0100 Subject: [PATCH 120/334] Bedrock 1.21.60 (#6627) Co-authored-by: Dylan K. Taylor --- composer.json | 8 +- composer.lock | 52 +-- src/data/bedrock/BedrockDataFiles.php | 3 +- src/data/bedrock/block/BlockStateData.php | 4 +- src/data/bedrock/block/BlockStateNames.php | 1 + .../bedrock/block/BlockStateStringValues.php | 4 + .../convert/BlockStateDeserializerHelper.php | 4 +- .../convert/BlockStateSerializerHelper.php | 4 +- src/entity/Human.php | 1 + src/inventory/CreativeCategory.php | 34 ++ src/inventory/CreativeGroup.php | 51 +++ src/inventory/CreativeInventory.php | 69 ++-- src/inventory/CreativeInventoryEntry.php | 48 +++ src/inventory/json/CreativeGroupData.php | 38 ++ src/lang/KnownTranslationFactory.php | 324 ++++++++++++++++++ src/lang/KnownTranslationKeys.php | 81 +++++ src/network/mcpe/InventoryManager.php | 2 +- src/network/mcpe/NetworkSession.php | 4 +- .../mcpe/cache/CreativeInventoryCache.php | 106 +++++- .../cache/CreativeInventoryCacheEntry.php | 48 +++ .../ItemTypeDictionaryFromDataHelper.php | 12 +- .../mcpe/handler/PreSpawnPacketHandler.php | 5 +- src/world/format/io/data/BedrockWorldData.php | 6 +- tools/generate-bedrock-data-from-packets.php | 89 ++++- 24 files changed, 902 insertions(+), 96 deletions(-) create mode 100644 src/inventory/CreativeCategory.php create mode 100644 src/inventory/CreativeGroup.php create mode 100644 src/inventory/CreativeInventoryEntry.php create mode 100644 src/inventory/json/CreativeGroupData.php create mode 100644 src/network/mcpe/cache/CreativeInventoryCacheEntry.php diff --git a/composer.json b/composer.json index bbcbe7314..f8d060bcd 100644 --- a/composer.json +++ b/composer.json @@ -33,15 +33,15 @@ "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", - "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40", - "pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50", + "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", + "pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50", + "pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", "pocketmine/errorhandler": "^0.7.0", - "pocketmine/locale-data": "~2.22.0", + "pocketmine/locale-data": "~2.24.0", "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", "pocketmine/nbt": "~1.0.0", diff --git a/composer.lock b/composer.lock index 52490968f..cc7f1f716 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": "a6aa0a34a230d325528d2e11f1439057", + "content-hash": "af7547291a131bfac6d7087957601325", "packages": [ { "name": "adhocore/json-comment", @@ -178,16 +178,16 @@ }, { "name": "pocketmine/bedrock-block-upgrade-schema", - "version": "5.0.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git", - "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52" + "reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52", - "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52", + "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", + "reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", "shasum": "" }, "type": "library", @@ -198,22 +198,22 @@ "description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves", "support": { "issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0" + "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0" }, - "time": "2024-11-03T14:13:50+00:00" + "time": "2025-02-11T17:41:44+00:00" }, { "name": "pocketmine/bedrock-data", - "version": "2.15.0+bedrock-1.21.50", + "version": "4.0.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad" + "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad", - "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e", + "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60" }, - "time": "2024-12-04T12:59:12+00:00" + "time": "2025-02-16T15:56:56+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "35.0.3+bedrock-1.21.50", + "version": "36.0.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c" + "reference": "2057de319c5c551001c2a544e08d1bc7727d9963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c4d62581cb62d29ec426914c6b4d7e0ff835da9c", - "reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963", + "reference": "2057de319c5c551001c2a544e08d1bc7727d9963", "shasum": "" }, "require": { @@ -296,9 +296,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/35.0.3+bedrock-1.21.50" + "source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60" }, - "time": "2025-01-07T23:06:29+00:00" + "time": "2025-02-16T15:59:08+00:00" }, { "name": "pocketmine/binaryutils", @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.22.1", + "version": "2.24.0", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49" + "reference": "6ec5e92c77a2102b2692763733e4763012facae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/fa4e377c437391cfcfdedd08eea3a848eabd1b49", - "reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49", + "url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9", + "reference": "6ec5e92c77a2102b2692763733e4763012facae9", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.22.1" + "source": "https://github.com/pmmp/Language/tree/2.24.0" }, - "time": "2024-12-06T14:44:17+00:00" + "time": "2025-02-16T20:46:34+00:00" }, { "name": "pocketmine/log", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 3341c0503..6176eee34 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -39,12 +39,11 @@ final class BedrockDataFiles{ public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json'; public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt'; public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json'; - public const CREATIVEITEMS_JSON = BEDROCK_DATA_PATH . '/creativeitems.json'; public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json'; public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt'; + public const ENUMS_PY = BEDROCK_DATA_PATH . '/enums.py'; public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; - public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json'; public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; diff --git a/src/data/bedrock/block/BlockStateData.php b/src/data/bedrock/block/BlockStateData.php index 6624c4ae0..e90410ac7 100644 --- a/src/data/bedrock/block/BlockStateData.php +++ b/src/data/bedrock/block/BlockStateData.php @@ -45,8 +45,8 @@ final class BlockStateData{ public const CURRENT_VERSION = (1 << 24) | //major (21 << 16) | //minor - (40 << 8) | //patch - (1); //revision + (60 << 8) | //patch + (33); //revision public const TAG_NAME = "name"; public const TAG_STATES = "states"; diff --git a/src/data/bedrock/block/BlockStateNames.php b/src/data/bedrock/block/BlockStateNames.php index 68a3e1b1d..9fed77e4a 100644 --- a/src/data/bedrock/block/BlockStateNames.php +++ b/src/data/bedrock/block/BlockStateNames.php @@ -59,6 +59,7 @@ final class BlockStateNames{ public const COVERED_BIT = "covered_bit"; public const CRACKED_STATE = "cracked_state"; public const CRAFTING = "crafting"; + public const CREAKING_HEART_STATE = "creaking_heart_state"; public const DEAD_BIT = "dead_bit"; public const DEPRECATED = "deprecated"; public const DIRECTION = "direction"; diff --git a/src/data/bedrock/block/BlockStateStringValues.php b/src/data/bedrock/block/BlockStateStringValues.php index e8171e4da..e6ae30e64 100644 --- a/src/data/bedrock/block/BlockStateStringValues.php +++ b/src/data/bedrock/block/BlockStateStringValues.php @@ -56,6 +56,10 @@ final class BlockStateStringValues{ public const CRACKED_STATE_MAX_CRACKED = "max_cracked"; public const CRACKED_STATE_NO_CRACKS = "no_cracks"; + public const CREAKING_HEART_STATE_AWAKE = "awake"; + public const CREAKING_HEART_STATE_DORMANT = "dormant"; + public const CREAKING_HEART_STATE_UPROOTED = "uprooted"; + public const DRIPSTONE_THICKNESS_BASE = "base"; public const DRIPSTONE_THICKNESS_FRUSTUM = "frustum"; public const DRIPSTONE_THICKNESS_MERGE = "merge"; diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index fda0455aa..342f31490 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -131,7 +131,7 @@ final class BlockStateDeserializerHelper{ //TODO: check if these need any special treatment to get the appropriate data to both halves of the door return $block ->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT)) - ->setFacing(Facing::rotateY($in->readLegacyHorizontalFacing(), false)) + ->setFacing($in->readCardinalHorizontalFacing()) ->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } @@ -145,7 +145,7 @@ final class BlockStateDeserializerHelper{ /** @throws BlockStateDeserializeException */ public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{ return $block - ->setFacing($in->readLegacyHorizontalFacing()) + ->setFacing($in->readCardinalHorizontalFacing()) ->setInWall($in->readBool(BlockStateNames::IN_WALL_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php index 3e2215746..fa0591669 100644 --- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php @@ -100,7 +100,7 @@ final class BlockStateSerializerHelper{ public static function encodeDoor(Door $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop()) - ->writeLegacyHorizontalFacing(Facing::rotateY($block->getFacing(), true)) + ->writeCardinalHorizontalFacing($block->getFacing()) ->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } @@ -112,7 +112,7 @@ final class BlockStateSerializerHelper{ public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{ return $out - ->writeLegacyHorizontalFacing($block->getFacing()) + ->writeCardinalHorizontalFacing($block->getFacing()) ->writeBool(BlockStateNames::IN_WALL_BIT, $block->isInWall()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } diff --git a/src/entity/Human.php b/src/entity/Human.php index 833196651..c94b76097 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -513,6 +513,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ AbilitiesLayer::LAYER_BASE, array_fill(0, AbilitiesLayer::NUMBER_OF_ABILITIES, false), 0.0, + 0.0, 0.0 ) ])), diff --git a/src/inventory/CreativeCategory.php b/src/inventory/CreativeCategory.php new file mode 100644 index 000000000..bede48b28 --- /dev/null +++ b/src/inventory/CreativeCategory.php @@ -0,0 +1,34 @@ +getText()) : strlen($name); + if($nameLength === 0){ + throw new \InvalidArgumentException("Creative group name cannot be empty"); + } + } + + public function getName() : Translatable|string{ return $this->name; } + + public function getIcon() : Item{ return clone $this->icon; } +} diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 57e5cbb4e..77eda8138 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -24,21 +24,23 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\crafting\CraftingManagerFromDataHelper; -use pocketmine\crafting\json\ItemStackData; -use pocketmine\data\bedrock\BedrockDataFiles; +use pocketmine\inventory\json\CreativeGroupData; use pocketmine\item\Item; +use pocketmine\lang\Translatable; use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\ObjectSet; use pocketmine\utils\SingletonTrait; -use pocketmine\utils\Utils; +use Symfony\Component\Filesystem\Path; +use function array_filter; +use function array_map; final class CreativeInventory{ use SingletonTrait; use DestructorCallbackTrait; /** - * @var Item[] - * @phpstan-var array + * @var CreativeInventoryEntry[] + * @phpstan-var array */ private array $creative = []; @@ -47,17 +49,32 @@ final class CreativeInventory{ private function __construct(){ $this->contentChangedCallbacks = new ObjectSet(); - $creativeItems = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( - BedrockDataFiles::CREATIVEITEMS_JSON, - ItemStackData::class - ); - foreach($creativeItems as $data){ - $item = CraftingManagerFromDataHelper::deserializeItemStack($data); - if($item === null){ - //unknown item - continue; + + foreach([ + "construction" => CreativeCategory::CONSTRUCTION, + "nature" => CreativeCategory::NATURE, + "equipment" => CreativeCategory::EQUIPMENT, + "items" => CreativeCategory::ITEMS, + ] as $categoryId => $categoryEnum){ + $groups = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( + Path::join(\pocketmine\BEDROCK_DATA_PATH, "creative", $categoryId . ".json"), + CreativeGroupData::class + ); + + foreach($groups as $groupData){ + $icon = $groupData->group_icon === null ? null : CraftingManagerFromDataHelper::deserializeItemStack($groupData->group_icon); + + $group = $icon === null ? null : new CreativeGroup( + new Translatable($groupData->group_name), + $icon + ); + + $items = array_filter(array_map(static fn($itemStack) => CraftingManagerFromDataHelper::deserializeItemStack($itemStack), $groupData->items)); + + foreach($items as $item){ + $this->add($item, $categoryEnum, $group); + } } - $this->add($item); } } @@ -75,16 +92,28 @@ final class CreativeInventory{ * @phpstan-return array */ public function getAll() : array{ - return Utils::cloneObjectArray($this->creative); + return array_map(fn(CreativeInventoryEntry $entry) => $entry->getItem(), $this->creative); + } + + /** + * @return CreativeInventoryEntry[] + * @phpstan-return array + */ + public function getAllEntries() : array{ + return $this->creative; } public function getItem(int $index) : ?Item{ - return isset($this->creative[$index]) ? clone $this->creative[$index] : null; + return $this->getEntry($index)?->getItem(); + } + + public function getEntry(int $index) : ?CreativeInventoryEntry{ + return $this->creative[$index] ?? null; } public function getItemIndex(Item $item) : int{ foreach($this->creative as $i => $d){ - if($item->equals($d, true, false)){ + if($d->matchesItem($item)){ return $i; } } @@ -96,8 +125,8 @@ final class CreativeInventory{ * Adds an item to the creative menu. * Note: Players who are already online when this is called will not see this change. */ - public function add(Item $item) : void{ - $this->creative[] = clone $item; + public function add(Item $item, CreativeCategory $category = CreativeCategory::ITEMS, ?CreativeGroup $group = null) : void{ + $this->creative[] = new CreativeInventoryEntry($item, $category, $group); $this->onContentChange(); } diff --git a/src/inventory/CreativeInventoryEntry.php b/src/inventory/CreativeInventoryEntry.php new file mode 100644 index 000000000..a5d568ee8 --- /dev/null +++ b/src/inventory/CreativeInventoryEntry.php @@ -0,0 +1,48 @@ +item = clone $item; + } + + public function getItem() : Item{ return clone $this->item; } + + public function getCategory() : CreativeCategory{ return $this->category; } + + public function getGroup() : ?CreativeGroup{ return $this->group; } + + public function matchesItem(Item $item) : bool{ + return $item->equals($this->item, checkDamage: true, checkCompound: false); + } +} diff --git a/src/inventory/json/CreativeGroupData.php b/src/inventory/json/CreativeGroupData.php new file mode 100644 index 000000000..70b73d3de --- /dev/null +++ b/src/inventory/json/CreativeGroupData.php @@ -0,0 +1,38 @@ +session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory())); + $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->buildPacket($this->player->getCreativeInventory(), $this->session)); } /** diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index fdb63c467..8c457ed40 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1058,7 +1058,7 @@ class NetworkSession{ ]; $layers = [ - new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 0.1), + new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 1, 0.1), ]; if(!$for->hasBlockCollision()){ //TODO: HACK! In 1.19.80, the client starts falling in our faux spectator mode when it clips into a @@ -1068,7 +1068,7 @@ class NetworkSession{ $layers[] = new AbilitiesLayer(AbilitiesLayer::LAYER_SPECTATOR, [ AbilitiesLayer::ABILITY_FLYING => true, - ], null, null); + ], null, null, null); } $this->sendDataPacket(UpdateAbilitiesPacket::create(new AbilitiesData( diff --git a/src/network/mcpe/cache/CreativeInventoryCache.php b/src/network/mcpe/cache/CreativeInventoryCache.php index 04fc52604..e543f343e 100644 --- a/src/network/mcpe/cache/CreativeInventoryCache.php +++ b/src/network/mcpe/cache/CreativeInventoryCache.php @@ -23,23 +23,30 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\cache; +use pocketmine\inventory\CreativeCategory; use pocketmine\inventory\CreativeInventory; +use pocketmine\lang\Translatable; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\CreativeContentPacket; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeItemEntry; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\utils\SingletonTrait; +use function is_string; use function spl_object_id; +use const PHP_INT_MIN; final class CreativeInventoryCache{ use SingletonTrait; /** - * @var CreativeContentPacket[] - * @phpstan-var array + * @var CreativeInventoryCacheEntry[] + * @phpstan-var array */ private array $caches = []; - public function getCache(CreativeInventory $inventory) : CreativeContentPacket{ + private function getCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{ $id = spl_object_id($inventory); if(!isset($this->caches[$id])){ $inventory->getDestructorCallbacks()->add(function() use ($id) : void{ @@ -48,7 +55,7 @@ final class CreativeInventoryCache{ $inventory->getContentChangedCallbacks()->add(function() use ($id) : void{ unset($this->caches[$id]); }); - $this->caches[$id] = $this->buildCreativeInventoryCache($inventory); + $this->caches[$id] = $this->buildCacheEntry($inventory); } return $this->caches[$id]; } @@ -56,14 +63,91 @@ final class CreativeInventoryCache{ /** * Rebuild the cache for the given inventory. */ - private function buildCreativeInventoryCache(CreativeInventory $inventory) : CreativeContentPacket{ - $entries = []; + private function buildCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{ + $categories = []; + $groups = []; + $typeConverter = TypeConverter::getInstance(); - //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent - foreach($inventory->getAll() as $k => $item){ - $entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item)); + + $nextIndex = 0; + $groupIndexes = []; + $itemGroupIndexes = []; + + foreach($inventory->getAllEntries() as $k => $entry){ + $group = $entry->getGroup(); + $category = $entry->getCategory(); + if($group === null){ + $groupId = PHP_INT_MIN; + }else{ + $groupId = spl_object_id($group); + unset($groupIndexes[$category->name][PHP_INT_MIN]); //start a new anonymous group for this category + } + + //group object may be reused by multiple categories + if(!isset($groupIndexes[$category->name][$groupId])){ + $groupIndexes[$category->name][$groupId] = $nextIndex++; + $categories[] = $category; + $groups[] = $group; + } + $itemGroupIndexes[$k] = $groupIndexes[$category->name][$groupId]; } - return CreativeContentPacket::create($entries); + //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent + $items = []; + foreach($inventory->getAllEntries() as $k => $entry){ + $items[] = new CreativeItemEntry( + $k, + $typeConverter->coreItemStackToNet($entry->getItem()), + $itemGroupIndexes[$k] + ); + } + + return new CreativeInventoryCacheEntry($categories, $groups, $items); + } + + public function buildPacket(CreativeInventory $inventory, NetworkSession $session) : CreativeContentPacket{ + $player = $session->getPlayer() ?? throw new \LogicException("Cannot prepare creative data for a session without a player"); + $language = $player->getLanguage(); + $forceLanguage = $player->getServer()->isLanguageForced(); + $typeConverter = $session->getTypeConverter(); + $cachedEntry = $this->getCacheEntry($inventory); + $translate = function(Translatable|string $translatable) use ($session, $language, $forceLanguage) : string{ + if(is_string($translatable)){ + $message = $translatable; + }elseif(!$forceLanguage){ + [$message,] = $session->prepareClientTranslatableMessage($translatable); + }else{ + $message = $language->translate($translatable); + } + return $message; + }; + + $groupEntries = []; + foreach($cachedEntry->categories as $index => $category){ + $group = $cachedEntry->groups[$index]; + $categoryId = match ($category) { + CreativeCategory::CONSTRUCTION => CreativeContentPacket::CATEGORY_CONSTRUCTION, + CreativeCategory::NATURE => CreativeContentPacket::CATEGORY_NATURE, + CreativeCategory::EQUIPMENT => CreativeContentPacket::CATEGORY_EQUIPMENT, + CreativeCategory::ITEMS => CreativeContentPacket::CATEGORY_ITEMS + }; + if($group === null){ + $groupEntries[] = new CreativeGroupEntry($categoryId, "", ItemStack::null()); + }else{ + $groupIcon = $group->getIcon(); + //TODO: HACK! In 1.21.60, Workaround glitchy behaviour when an item is used as an icon for a group it + //doesn't belong to. Without this hack, both instances of the item will show a +, but neither of them + //will actually expand the group work correctly. + $groupIcon->getNamedTag()->setInt("___GroupBugWorkaround___", $index); + $groupName = $group->getName(); + $groupEntries[] = new CreativeGroupEntry( + $categoryId, + $translate($groupName), + $typeConverter->coreItemStackToNet($groupIcon) + ); + } + } + + return CreativeContentPacket::create($groupEntries, $cachedEntry->items); } } diff --git a/src/network/mcpe/cache/CreativeInventoryCacheEntry.php b/src/network/mcpe/cache/CreativeInventoryCacheEntry.php new file mode 100644 index 000000000..1fc0767df --- /dev/null +++ b/src/network/mcpe/cache/CreativeInventoryCacheEntry.php @@ -0,0 +1,48 @@ + $categories + * @phpstan-param list $groups + * @phpstan-param list $items + */ + public function __construct( + public readonly array $categories, + public readonly array $groups, + public readonly array $items, + ){ + //NOOP + } +} diff --git a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php index d962063d3..ed8ae2cc7 100644 --- a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php +++ b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php @@ -23,10 +23,15 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pocketmine\errorhandler\ErrorToExceptionHandler; +use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; +use pocketmine\network\mcpe\protocol\types\CacheableNbt; use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; +use function base64_decode; use function is_array; use function is_bool; use function is_int; @@ -41,12 +46,15 @@ final class ItemTypeDictionaryFromDataHelper{ throw new AssumptionFailedError("Invalid item list format"); } + $emptyNBT = new CacheableNbt(new CompoundTag()); + $nbtSerializer = new LittleEndianNbtSerializer(); + $params = []; foreach(Utils::promoteKeys($table) as $name => $entry){ - if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){ + if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"], $entry["version"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"]) || !is_int($entry["version"]) || !(is_string($componentNbt = $entry["component_nbt"] ?? null) || $componentNbt === null)){ throw new AssumptionFailedError("Invalid item list format"); } - $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]); + $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"], $entry["version"], $componentNbt === null ? $emptyNBT : new CacheableNbt($nbtSerializer->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($componentNbt, true)))->mustGetCompoundTag())); } return new ItemTypeDictionary($params); } diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index b80874938..9aa302c0c 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -28,6 +28,7 @@ use pocketmine\network\mcpe\cache\CraftingDataCache; use pocketmine\network\mcpe\cache\StaticPacketCache; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; @@ -110,9 +111,11 @@ class PreSpawnPacketHandler extends PacketHandler{ new NetworkPermissions(disableClientSounds: true), [], 0, - $typeConverter->getItemTypeDictionary()->getEntries(), )); + $this->session->getLogger()->debug("Sending items"); + $this->session->sendDataPacket(ItemRegistryPacket::create($typeConverter->getItemTypeDictionary()->getEntries())); + $this->session->getLogger()->debug("Sending actor identifiers"); $this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers()); diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index b2e079124..a9ca43bc3 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -51,12 +51,12 @@ use function time; class BedrockWorldData extends BaseNbtWorldData{ public const CURRENT_STORAGE_VERSION = 10; - public const CURRENT_STORAGE_NETWORK_VERSION = 748; + public const CURRENT_STORAGE_NETWORK_VERSION = 776; public const CURRENT_CLIENT_VERSION_TARGET = [ 1, //major 21, //minor - 40, //patch - 1, //revision + 60, //patch + 33, //revision 0 //is beta ]; diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 9aac5185e..c48a4f017 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -35,6 +35,7 @@ use pocketmine\crafting\json\SmithingTrimRecipeData; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeNames; +use pocketmine\inventory\json\CreativeGroupData; use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -48,15 +49,17 @@ use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket; use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket; use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\CreativeContentPacket; +use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\StartGamePacket; use pocketmine\network\mcpe\protocol\types\CacheableNbt; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield; +use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\network\mcpe\protocol\types\recipe\ComplexAliasItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; @@ -134,6 +137,19 @@ class ParserPacketHandler extends PacketHandler{ return base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($statePropertiesTag))); } + /** + * @param ItemStackData[] $items + */ + private function creativeGroupEntryToJson(CreativeGroupEntry $entry, array $items) : CreativeGroupData{ + $data = new CreativeGroupData(); + + $data->group_name = $entry->getCategoryName(); + $data->group_icon = $entry->getIcon()->getId() === 0 ? null : $this->itemStackToJson($entry->getIcon()); + $data->items = $items; + + return $data; + } + private function itemStackToJson(ItemStack $itemStack) : ItemStackData{ if($itemStack->getId() === 0){ throw new InvalidArgumentException("Cannot serialize a null itemstack"); @@ -234,31 +250,68 @@ class ParserPacketHandler extends PacketHandler{ } public function handleStartGame(StartGamePacket $packet) : bool{ - $this->itemTypeDictionary = new ItemTypeDictionary($packet->itemTable); - - echo "updating legacy item ID mapping table\n"; - $table = []; - foreach($packet->itemTable as $entry){ - $table[$entry->getStringId()] = [ - "runtime_id" => $entry->getNumericId(), - "component_based" => $entry->isComponentBased() - ]; - } - ksort($table, SORT_STRING); - file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); - foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){ echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n"; } return true; } + public function handleItemRegistry(ItemRegistryPacket $packet) : bool{ + $this->itemTypeDictionary = new ItemTypeDictionary($packet->getEntries()); + + echo "updating legacy item ID mapping table\n"; + $emptyNBT = new CompoundTag(); + $table = []; + foreach($packet->getEntries() as $entry){ + $table[$entry->getStringId()] = [ + "runtime_id" => $entry->getNumericId(), + "component_based" => $entry->isComponentBased(), + "version" => $entry->getVersion(), + ]; + + $componentNBT = $entry->getComponentNbt()->getRoot(); + if(!$componentNBT->equals($emptyNBT)){ + $table[$entry->getStringId()]["component_nbt"] = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($componentNBT))); + } + } + ksort($table, SORT_STRING); + file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); + + echo "updating item registry\n"; + $items = array_map(function(ItemTypeEntry $entry) : array{ + return self::objectToOrderedArray($entry); + }, $packet->getEntries()); + file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); + return true; + } + public function handleCreativeContent(CreativeContentPacket $packet) : bool{ echo "updating creative inventory data\n"; - $items = array_map(function(CreativeContentEntry $entry) : array{ - return self::objectToOrderedArray($this->itemStackToJson($entry->getItem())); - }, $packet->getEntries()); - file_put_contents($this->bedrockDataPath . '/creativeitems.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); + + $groupItems = []; + foreach($packet->getItems() as $itemEntry){ + $groupItems[$itemEntry->getGroupId()][] = $this->itemStackToJson($itemEntry->getItem()); + } + + static $typeMap = [ + CreativeContentPacket::CATEGORY_CONSTRUCTION => "construction", + CreativeContentPacket::CATEGORY_NATURE => "nature", + CreativeContentPacket::CATEGORY_EQUIPMENT => "equipment", + CreativeContentPacket::CATEGORY_ITEMS => "items", + ]; + + $groupCategories = []; + foreach(Utils::promoteKeys($packet->getGroups()) as $groupId => $group){ + $category = $typeMap[$group->getCategoryId()] ?? throw new PacketHandlingException("Unknown creative category ID " . $group->getCategoryId()); + //FIXME: objectToOrderedArray might mess with the order of groupItems + //this isn't a problem right now because it's a list, but could cause problems in the future + $groupCategories[$category][] = self::objectToOrderedArray($this->creativeGroupEntryToJson($group, $groupItems[$groupId])); + } + + foreach(Utils::promoteKeys($groupCategories) as $category => $categoryGroups){ + file_put_contents($this->bedrockDataPath . '/creative/' . $category . '.json', json_encode($categoryGroups, JSON_PRETTY_PRINT) . "\n"); + } + return true; } From 03e4b53ac46e020ed93723e3ee2bf2b673c93d9a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 20:57:16 +0000 Subject: [PATCH 121/334] BedrockDataFiles: added constants for folders as well as files we probably should have it recurse too, but this is an easy win. --- build/generate-bedrockdata-path-consts.php | 4 +++- src/Server.php | 3 ++- src/data/bedrock/BedrockDataFiles.php | 3 +++ src/inventory/CreativeInventory.php | 3 ++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/build/generate-bedrockdata-path-consts.php b/build/generate-bedrockdata-path-consts.php index 6ad6d83fd..f74137dd2 100644 --- a/build/generate-bedrockdata-path-consts.php +++ b/build/generate-bedrockdata-path-consts.php @@ -28,6 +28,7 @@ use function dirname; use function fclose; use function fopen; use function fwrite; +use function is_dir; use function is_file; use function scandir; use function str_replace; @@ -59,7 +60,7 @@ foreach($files as $file){ continue; } $path = Path::join(BEDROCK_DATA_PATH, $file); - if(!is_file($path)){ + if(!is_file($path) && !is_dir($path)){ continue; } @@ -67,6 +68,7 @@ foreach($files as $file){ 'README.md', 'LICENSE', 'composer.json', + '.github' ] as $ignored){ if($file === $ignored){ continue 2; diff --git a/src/Server.php b/src/Server.php index 5a72ad048..252964a91 100644 --- a/src/Server.php +++ b/src/Server.php @@ -36,6 +36,7 @@ use pocketmine\crafting\CraftingManager; use pocketmine\crafting\CraftingManagerFromDataHelper; use pocketmine\crash\CrashDump; use pocketmine\crash\CrashDumpRenderer; +use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\entity\EntityDataHelper; use pocketmine\entity\Location; use pocketmine\event\HandlerListManager; @@ -1005,7 +1006,7 @@ class Server{ $this->commandMap = new SimpleCommandMap($this); - $this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes")); + $this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES); $this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger); diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 6176eee34..1ecb707cc 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -39,13 +39,16 @@ final class BedrockDataFiles{ public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json'; public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt'; public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json'; + public const CREATIVE = BEDROCK_DATA_PATH . '/creative'; public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json'; public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt'; + public const ENUMS = BEDROCK_DATA_PATH . '/enums'; public const ENUMS_PY = BEDROCK_DATA_PATH . '/enums.py'; public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; + public const RECIPES = BEDROCK_DATA_PATH . '/recipes'; public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json'; } diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 77eda8138..ee27068c7 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\crafting\CraftingManagerFromDataHelper; +use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\inventory\json\CreativeGroupData; use pocketmine\item\Item; use pocketmine\lang\Translatable; @@ -57,7 +58,7 @@ final class CreativeInventory{ "items" => CreativeCategory::ITEMS, ] as $categoryId => $categoryEnum){ $groups = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( - Path::join(\pocketmine\BEDROCK_DATA_PATH, "creative", $categoryId . ".json"), + Path::join(BedrockDataFiles::CREATIVE, $categoryId . ".json"), CreativeGroupData::class ); From 246c1776dfb96bf49194f4fad487b84ed2e40bf4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 21:47:35 +0000 Subject: [PATCH 122/334] InventoryAction: avoid throwaway Item clones --- src/inventory/transaction/InventoryTransaction.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 8f7b57610..6e010c7b8 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -144,8 +144,9 @@ class InventoryTransaction{ $needItems = []; $haveItems = []; foreach($this->actions as $key => $action){ - if(!$action->getTargetItem()->isNull()){ - $needItems[] = $action->getTargetItem(); + $targetItem = $action->getTargetItem(); + if(!$targetItem->isNull()){ + $needItems[] = $targetItem; } try{ @@ -154,8 +155,9 @@ class InventoryTransaction{ throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e); } - if(!$action->getSourceItem()->isNull()){ - $haveItems[] = $action->getSourceItem(); + $sourceItem = $action->getSourceItem(); + if(!$sourceItem->isNull()){ + $haveItems[] = $sourceItem; } } From d96ef21c4d30ea60e76de7b9188fa0829e14137d Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 16 Feb 2025 22:16:47 +0000 Subject: [PATCH 123/334] Prepare 5.25.0 release (#6631) --- changelogs/5.25.md | 38 ++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.25.md diff --git a/changelogs/5.25.md b/changelogs/5.25.md new file mode 100644 index 000000000..cd3fb9365 --- /dev/null +++ b/changelogs/5.25.md @@ -0,0 +1,38 @@ +# 5.25.0 +Released 16th February 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.60. It also includes some minor API additions supporting new features in this version. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.60. +- Removed support for earlier versions. + +## Documentation +- Fixed the documentation of `Utils::getOS()`. It now refers to the `Utils::OS_*` constants instead of a list of hardcoded strings. + +## API +### `pocketmine\inventory` +This release allows plugins to decide which creative tab they want to add their new items to. +It also allows creating new collapsible groups of items, and modifying or removing existing ones. + +- The following new methods have been added: + - `public CreativeInventory->getAllEntries() : list` - returns an array of objects, each containing a creative item and information about its category and collapsible group (if applicable). + - `public CreativeInventory->getEntry(int $index) : ?CreativeInventoryEntry` - returns the creative item with the specified identifier, or `null` if not found +- The following methods have signature changes: + - `CreativeInventory->add()` now accepts two additional optional parameters: `CreativeCategory $category, ?CreativeGroup $group`. If not specified, the item will be added to the Items tab without a group. +- The following new classes have been added: + - `CreativeCategory` - enum of possible creative inventory categories (each has its own tab on the GUI) + - `CreativeGroup` - contains information about a collapsible group of creative items, including tooltip text and icon + - `CreativeInventoryEntry` - contains information about a creative inventory item, including its category and collapsible group (if applicable) + +## Internals +- `CreativeContentPacket` is no longer fully cached due to the requirement for translation context during construction. The individual items are still cached (which is the most expensive part), but the packet itself is now constructed on demand, and group entries are constructed on the fly. This may affect performance, but this has not been investigated. +- `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files. +- The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category. +- New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index aaa357e0f..f5aa59c4f 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.24.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.25.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 34f801ee3c6d8c99dfd928b19fc58597b5b997ea Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sun, 16 Feb 2025 22:18:20 +0000 Subject: [PATCH 124/334] 5.25.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13359320328 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index f5aa59c4f..f8b5d7c74 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.25.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.25.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 788ee9a008e7561a112764efdea63dc9cf711fea Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 22:49:40 +0000 Subject: [PATCH 125/334] Allow overriding deserializers for block and item IDs there's no technical reason not to support this, since it doesn't violate any assumptions and the type returned is a base anyway. this enables implementing stuff like snow cauldrons in a plugin, which previously would require reflection due to the minecraft:cauldron deserializer being registered already. it also enables overriding IDs to map to custom blocks, which might be useful for overriding some functionality (although this is inadvisable - and won't alter the usage of stuff like VanillaBlocks::WHATEVER()). we do *not* allow overriding serializers, since type IDs are expected to be paired to block implementations, and allowing them to be reassigned could lead to crashes if the new class was incorrect. So the correct approach for overriding nether portals would be to create a custom type ID as if you were adding a fully custom item. This will also allow other plugins to distinguish between your implementation and the built-in one. --- .../convert/BlockStateToObjectDeserializer.php | 14 +++++++++++--- src/data/bedrock/item/ItemDeserializer.php | 13 ++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index cb9a6e7ae..42d8ee0cd 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -103,10 +103,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ /** @phpstan-param \Closure(Reader) : Block $c */ public function map(string $id, \Closure $c) : void{ - if(array_key_exists($id, $this->deserializeFuncs)){ - throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\""); - } $this->deserializeFuncs[$id] = $c; + $this->simpleCache = []; + } + + /** + * Returns the existing data deserializer for the given ID, or null if none exists. + * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. + * + * @phpstan-return ?\Closure(Reader) : Block + */ + public function getDeserializerForId(string $id) : ?\Closure{ + return $this->deserializeFuncs[$id] ?? null; } /** @phpstan-param \Closure() : Block $getBlock */ diff --git a/src/data/bedrock/item/ItemDeserializer.php b/src/data/bedrock/item/ItemDeserializer.php index f7854313f..da96f4ffe 100644 --- a/src/data/bedrock/item/ItemDeserializer.php +++ b/src/data/bedrock/item/ItemDeserializer.php @@ -51,12 +51,19 @@ final class ItemDeserializer{ * @phpstan-param \Closure(Data) : Item $deserializer */ public function map(string $id, \Closure $deserializer) : void{ - if(isset($this->deserializers[$id])){ - throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\""); - } $this->deserializers[$id] = $deserializer; } + /** + * Returns the existing data deserializer for the given ID, or null if none exists. + * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. + * + * @phpstan-return \Closure(Data) : Item + */ + public function getDeserializerForId(string $id) : ?\Closure{ + return $this->deserializers[$id] ?? null; + } + /** * @phpstan-param \Closure(Data) : Block $deserializer */ From d2d6a59c727967738a8b3a372d7dcc23bba69910 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 22:52:11 +0000 Subject: [PATCH 126/334] ItemDeserializer: fix doc comment typo --- src/data/bedrock/item/ItemDeserializer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/bedrock/item/ItemDeserializer.php b/src/data/bedrock/item/ItemDeserializer.php index da96f4ffe..ef3bee2a0 100644 --- a/src/data/bedrock/item/ItemDeserializer.php +++ b/src/data/bedrock/item/ItemDeserializer.php @@ -58,7 +58,7 @@ final class ItemDeserializer{ * Returns the existing data deserializer for the given ID, or null if none exists. * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. * - * @phpstan-return \Closure(Data) : Item + * @phpstan-return ?\Closure(Data) : Item */ public function getDeserializerForId(string $id) : ?\Closure{ return $this->deserializers[$id] ?? null; From 51cf6817b13708303e585a398b33dbf631065cb4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 23:24:39 +0000 Subject: [PATCH 127/334] World: fixed overflow checks for getCollisionBlocks(), closes #6630 --- src/world/World.php | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/world/World.php b/src/world/World.php index 86ca44848..3a7d0c538 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -1534,29 +1534,44 @@ class World implements ChunkManager{ $collisionInfo = $this->blockStateRegistry->collisionInfo; if($targetFirst){ for($z = $minZ; $z <= $maxZ; ++$z){ + $zOverflow = $z === $minZ || $z === $maxZ; for($x = $minX; $x <= $maxX; ++$x){ + $zxOverflow = $zOverflow || $x === $minX || $x === $maxX; for($y = $minY; $y <= $maxY; ++$y){ + $overflow = $zxOverflow || $y === $minY || $y === $maxY; + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); - if(match($stateCollisionInfo){ - RuntimeBlockStateRegistry::COLLISION_CUBE => true, - RuntimeBlockStateRegistry::COLLISION_NONE => false, - default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) - }){ + if($overflow ? + $stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) : + match ($stateCollisionInfo) { + RuntimeBlockStateRegistry::COLLISION_CUBE => true, + RuntimeBlockStateRegistry::COLLISION_NONE => false, + default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) + } + ){ return [$this->getBlockAt($x, $y, $z)]; } } } } }else{ + //TODO: duplicated code :( this way is better for performance though for($z = $minZ; $z <= $maxZ; ++$z){ + $zOverflow = $z === $minZ || $z === $maxZ; for($x = $minX; $x <= $maxX; ++$x){ + $zxOverflow = $zOverflow || $x === $minX || $x === $maxX; for($y = $minY; $y <= $maxY; ++$y){ + $overflow = $zxOverflow || $y === $minY || $y === $maxY; + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); - if(match($stateCollisionInfo){ - RuntimeBlockStateRegistry::COLLISION_CUBE => true, - RuntimeBlockStateRegistry::COLLISION_NONE => false, - default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) - }){ + if($overflow ? + $stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) : + match ($stateCollisionInfo) { + RuntimeBlockStateRegistry::COLLISION_CUBE => true, + RuntimeBlockStateRegistry::COLLISION_NONE => false, + default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) + } + ){ $collides[] = $this->getBlockAt($x, $y, $z); } } From 9744bd70733aa2fd7181242d9baadde0de0ec686 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 17 Feb 2025 15:35:18 +0000 Subject: [PATCH 128/334] CraftingManager: make a less dumb hash function fixes #4415 --- src/crafting/CraftingManager.php | 48 +++++++++++++------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 895eeaccc..93d6e1838 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -29,8 +29,12 @@ use pocketmine\nbt\TreeRoot; use pocketmine\utils\BinaryStream; use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\ObjectSet; +use function array_shift; +use function count; +use function implode; +use function ksort; use function spl_object_id; -use function usort; +use const SORT_STRING; class CraftingManager{ use DestructorCallbackTrait; @@ -100,6 +104,7 @@ class CraftingManager{ /** * Function used to arrange Shapeless Recipe ingredient lists into a consistent order. + * @deprecated */ public static function sort(Item $i1, Item $i2) : int{ //Use spaceship operator to compare each property, then try the next one if they are equivalent. @@ -108,45 +113,30 @@ class CraftingManager{ return $retval; } - /** - * @param Item[] $items - * - * @return Item[] - * @phpstan-return list - */ - private static function pack(array $items) : array{ - $result = []; + private static function hashOutput(Item $output) : string{ + $write = new BinaryStream(); + $write->putVarInt($output->getStateId()); + $write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag()))); - foreach($items as $item){ - foreach($result as $otherItem){ - if($item->canStackWith($otherItem)){ - $otherItem->setCount($otherItem->getCount() + $item->getCount()); - continue 2; - } - } - - //No matching item found - $result[] = clone $item; - } - - return $result; + return $write->getBuffer(); } /** * @param Item[] $outputs */ private static function hashOutputs(array $outputs) : string{ - $outputs = self::pack($outputs); - usort($outputs, [self::class, "sort"]); - $result = new BinaryStream(); + if(count($outputs) === 1){ + return self::hashOutput(array_shift($outputs)); + } + $unique = []; foreach($outputs as $o){ //count is not written because the outputs might be from multiple repetitions of a single recipe //this reduces the accuracy of the hash, but it won't matter in most cases. - $result->putVarInt($o->getStateId()); - $result->put((new LittleEndianNbtSerializer())->write(new TreeRoot($o->getNamedTag()))); + $hash = self::hashOutput($o); + $unique[$hash] = $hash; } - - return $result->getBuffer(); + ksort($unique, SORT_STRING); + return implode("", $unique); } /** From 77be5f8e25cb0a9f0fb7fcd79183ff63e65d4e05 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 17 Feb 2025 17:51:39 +0000 Subject: [PATCH 129/334] Update PHPStan --- composer.json | 2 +- composer.lock | 36 +++++++++++----------- src/Server.php | 2 +- src/utils/Utils.php | 2 +- tests/phpstan/configs/actual-problems.neon | 32 +------------------ tests/phpstan/configs/phpstan-bugs.neon | 6 ---- 6 files changed, 22 insertions(+), 58 deletions(-) diff --git a/composer.json b/composer.json index f8d060bcd..ac35fef77 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.4", + "phpstan/phpstan": "2.1.5", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index cc7f1f716..e5c60aa7d 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": "af7547291a131bfac6d7087957601325", + "content-hash": "dcf1176890f95cb24670e69c8c8f1216", "packages": [ { "name": "adhocore/json-comment", @@ -1150,16 +1150,16 @@ "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -1198,7 +1198,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -1206,7 +1206,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", @@ -1386,16 +1386,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.4", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a" + "reference": "451b17f9665481ee502adc39be987cb71067ece2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f99e18eb775dbaf6460c95fa0b65312da9c746a", - "reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/451b17f9665481ee502adc39be987cb71067ece2", + "reference": "451b17f9665481ee502adc39be987cb71067ece2", "shasum": "" }, "require": { @@ -1440,7 +1440,7 @@ "type": "github" } ], - "time": "2025-02-10T08:25:21+00:00" + "time": "2025-02-13T12:49:56+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1864,16 +1864,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.44", + "version": "10.5.45", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36" + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1381c62769be4bb88fa4c5aec1366c7c66ca4f36", - "reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", "shasum": "" }, "require": { @@ -1945,7 +1945,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.5.44" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" }, "funding": [ { @@ -1961,7 +1961,7 @@ "type": "tidelift" } ], - "time": "2025-01-31T07:00:38+00:00" + "time": "2025-02-06T16:08:12+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/Server.php b/src/Server.php index 252964a91..679a0ef0b 100644 --- a/src/Server.php +++ b/src/Server.php @@ -699,7 +699,7 @@ class Server{ public function removeOp(string $name) : void{ $lowercaseName = strtolower($name); - foreach($this->operators->getAll() as $operatorName => $_){ + foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){ $operatorName = (string) $operatorName; if($lowercaseName === strtolower($operatorName)){ $this->operators->remove($operatorName); diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 46cd49ec4..2a9dd65da 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -587,7 +587,7 @@ final class Utils{ * @phpstan-param \Closure(TMemberType) : void $validator */ public static function validateArrayValueType(array $array, \Closure $validator) : void{ - foreach($array as $k => $v){ + foreach(Utils::promoteKeys($array) as $k => $v){ try{ $validator($v); }catch(\TypeError $e){ diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index ef9828b37..73b7a65c1 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -48,12 +48,6 @@ parameters: count: 1 path: ../../../src/VersionInfo.php - - - message: '#^Method pocketmine\\VersionInfo\:\:GIT_HASH\(\) should return string but returns mixed\.$#' - identifier: return.type - count: 1 - path: ../../../src/VersionInfo.php - - message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#' identifier: assign.propertyType @@ -918,24 +912,6 @@ parameters: count: 1 path: ../../../src/plugin/PluginDescription.php - - - message: '#^Parameter \#1 \$haystack of function stripos expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: '#^Parameter \#2 \$subject of function preg_match expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: '#^Parameter \#3 \$subject of function str_replace expects array\\|string, mixed given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/plugin/PluginDescription.php - - message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$authors \(array\\) does not accept list\\.$#' identifier: assign.propertyType @@ -966,12 +942,6 @@ parameters: count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - - message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getSha256\(\) should return string but returns string\|false\.$#' - identifier: return.type - count: 1 - path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: '#^Property pocketmine\\resourcepacks\\ZippedResourcePack\:\:\$fileResource \(resource\) does not accept resource\|false\.$#' identifier: assign.propertyType @@ -1009,7 +979,7 @@ parameters: path: ../../../src/utils/Config.php - - message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\, array\ given\.$#' + message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\, array\ given\.$#' identifier: argument.type count: 1 path: ../../../src/utils/Config.php diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 9ab125763..aeb3fae29 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -114,12 +114,6 @@ parameters: count: 1 path: ../../../src/crash/CrashDump.php - - - message: '#^Call to function assert\(\) with false and ''unknown hit type'' will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: ../../../src/entity/projectile/Projectile.php - - message: '#^Property pocketmine\\item\\WritableBookBase\:\:\$pages \(list\\) does not accept non\-empty\-array\\.$#' identifier: assign.propertyType From c876253f76104c56947f82aecd342951a8848a76 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 18 Feb 2025 15:30:39 +0000 Subject: [PATCH 130/334] tools/blockstate-upgrade-schema-utils: added dump-table command this command dumps a human-readable version of pmmp/mapping palette mapping files to a .txt file. may be useful for debugging issues with the schema generator or the upgrade process. --- tools/blockstate-upgrade-schema-utils.php | 42 ++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index 4f5c8740c..6bfe44a03 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -51,8 +51,10 @@ use function array_unique; use function array_values; use function count; use function dirname; +use function fclose; use function file_exists; use function file_put_contents; +use function fopen; use function fwrite; use function get_class; use function get_debug_type; @@ -885,6 +887,43 @@ function cmdUpdateAll(array $argv) : int{ return 0; } +/** + * @param string[] $argv + */ +function cmdDumpTable(array $argv) : int{ + $tableFile = $argv[2]; + $outputFile = $argv[3]; + + $output = fopen($outputFile, 'wb'); + if($output === false){ + fwrite(STDERR, "Failed to open output file: $outputFile\n"); + return 1; + } + + $table = loadUpgradeTableFromFile($tableFile, reverse: false); + + foreach(Utils::stringifyKeys($table) as $oldName => $mappings){ + fwrite($output, "---------- MAPPING LIST: $oldName ----------\n"); + foreach($mappings as $mapping){ + $oldNbt = $mapping->old->toVanillaNbt(); + $oldNbt->setInt("version", $mapping->new->getVersion()); + + //intentionally not reused result of toVanillaNbt otherwise output wouldn't include version + fwrite($output, "OLD: " . $mapping->old->toVanillaNbt() . "\n"); + if(!$oldNbt->equals($mapping->new->toVanillaNbt())){ + fwrite($output, "NEW: " . $mapping->new->toVanillaNbt() . "\n"); + }else{ + fwrite($output, "NEW: version bump only (" . $mapping->new->getVersion() . ")\n"); + } + fwrite($output, "-----\n"); + } + } + + fclose($output); + \GlobalLogger::get()->info("Table dump file $outputFile generated successfully."); + return 0; +} + /** * @param string[] $argv */ @@ -893,7 +932,8 @@ function main(array $argv) : int{ "generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)], "test" => [["palette upgrade table file", "schema output file"], cmdTest(...)], "update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)], - "update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)] + "update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)], + "dump-table" => [["palette upgrade table file", "txt output file"], cmdDumpTable(...)] ]; $selected = $argv[1] ?? null; From a08b06d3224215e001b54ff30f3017bfba0b1b81 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 18 Feb 2025 15:34:20 +0000 Subject: [PATCH 131/334] also sort table by ID --- tools/blockstate-upgrade-schema-utils.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index 6bfe44a03..7c34b7728 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -901,6 +901,7 @@ function cmdDumpTable(array $argv) : int{ } $table = loadUpgradeTableFromFile($tableFile, reverse: false); + ksort($table, SORT_STRING); foreach(Utils::stringifyKeys($table) as $oldName => $mappings){ fwrite($output, "---------- MAPPING LIST: $oldName ----------\n"); From 3050af0bc0c75e7d13ca0ed3c502c7c846ba2cd7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 23 Feb 2025 19:45:38 +0000 Subject: [PATCH 132/334] ResourcePackManager: validate pack UUIDs fixes CrashArchive ##12248760 --- src/resourcepacks/ResourcePackManager.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index c4668eb2a..ad4417769 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -26,6 +26,7 @@ namespace pocketmine\resourcepacks; use pocketmine\utils\Config; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; +use Ramsey\Uuid\Uuid; use Symfony\Component\Filesystem\Path; use function array_keys; use function copy; @@ -103,9 +104,14 @@ class ResourcePackManager{ try{ $newPack = $this->loadPackFromPath(Path::join($this->path, $pack)); - $this->resourcePacks[] = $newPack; $index = strtolower($newPack->getPackId()); + if(!Uuid::isValid($index)){ + //TODO: we should use Uuid in ResourcePack interface directly but that would break BC + //for now we need to validate this here to make sure it doesn't cause crashes later on + throw new ResourcePackException("Invalid UUID ($index)"); + } $this->uuidList[$index] = $newPack; + $this->resourcePacks[] = $newPack; $keyPath = Path::join($this->path, $pack . ".key"); if(file_exists($keyPath)){ @@ -190,6 +196,11 @@ class ResourcePackManager{ $resourcePacks = []; foreach($resourceStack as $pack){ $uuid = strtolower($pack->getPackId()); + if(!Uuid::isValid($uuid)){ + //TODO: we should use Uuid in ResourcePack interface directly but that would break BC + //for now we need to validate this here to make sure it doesn't cause crashes later on + throw new \InvalidArgumentException("Invalid resource pack UUID ($uuid)"); + } if(isset($uuidList[$uuid])){ throw new \InvalidArgumentException("Cannot load two resource pack with the same UUID ($uuid)"); } From 1fed9f6cb560fe3069c157736ff1c25145aef403 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 23 Feb 2025 20:02:27 +0000 Subject: [PATCH 133/334] BlockBreakInfo: fixed confusing error message --- src/block/BlockBreakInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/BlockBreakInfo.php b/src/block/BlockBreakInfo.php index 8cfa425dc..e77e06cfd 100644 --- a/src/block/BlockBreakInfo.php +++ b/src/block/BlockBreakInfo.php @@ -154,7 +154,7 @@ class BlockBreakInfo{ $efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0); if($efficiency <= 0){ - throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency"); + throw new \InvalidArgumentException(get_class($item) . " must have a positive mining efficiency, but got $efficiency"); } $base /= $efficiency; From 3df2bdb87979aa699d17e5b82ec2368e123a1411 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Feb 2025 01:04:05 +0000 Subject: [PATCH 134/334] Fixed door facing this was broken in 1.21.60 update. should've known better to expect a blockstate upgrade to mean a blockstate fix... --- .../bedrock/block/convert/BlockStateDeserializerHelper.php | 3 ++- src/data/bedrock/block/convert/BlockStateSerializerHelper.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index 342f31490..1d7b4bb76 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -131,7 +131,8 @@ final class BlockStateDeserializerHelper{ //TODO: check if these need any special treatment to get the appropriate data to both halves of the door return $block ->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT)) - ->setFacing($in->readCardinalHorizontalFacing()) + //a door facing "east" is actually facing north - thanks mojang + ->setFacing(Facing::rotateY($in->readCardinalHorizontalFacing(), clockwise: false)) ->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php index fa0591669..a25044153 100644 --- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php @@ -100,7 +100,8 @@ final class BlockStateSerializerHelper{ public static function encodeDoor(Door $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop()) - ->writeCardinalHorizontalFacing($block->getFacing()) + //a door facing north is encoded as "east" + ->writeCardinalHorizontalFacing(Facing::rotateY($block->getFacing(), clockwise: true)) ->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } From 7c654271a85f9fad3ea549357f66d13c176d6f6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:40:11 +0000 Subject: [PATCH 135/334] Bump phpstan/phpstan in the development-patch-updates group (#6640) --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index ac35fef77..10454c560 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.5", + "phpstan/phpstan": "2.1.6", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index e5c60aa7d..dc49252d6 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": "dcf1176890f95cb24670e69c8c8f1216", + "content-hash": "bef9decc40d9f5bd82e1de2d151bd99f", "packages": [ { "name": "adhocore/json-comment", @@ -1386,16 +1386,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.5", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "451b17f9665481ee502adc39be987cb71067ece2" + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/451b17f9665481ee502adc39be987cb71067ece2", - "reference": "451b17f9665481ee502adc39be987cb71067ece2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", "shasum": "" }, "require": { @@ -1440,7 +1440,7 @@ "type": "github" } ], - "time": "2025-02-13T12:49:56+00:00" + "time": "2025-02-19T15:46:42+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From 706f391068539340bbc01222e5e115a52786fd10 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 26 Feb 2025 17:13:44 +0000 Subject: [PATCH 136/334] Release 5.25.1 (#6641) --- changelogs/5.25.md | 8 ++++++++ src/VersionInfo.php | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/changelogs/5.25.md b/changelogs/5.25.md index cd3fb9365..5fdc013a6 100644 --- a/changelogs/5.25.md +++ b/changelogs/5.25.md @@ -36,3 +36,11 @@ It also allows creating new collapsible groups of items, and modifying or removi - `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files. - The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category. - New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases. + +# 5.25.1 +Released 26th February 2025. + +## Fixes +- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero. +- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.) +- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index f8b5d7c74..e206b09f6 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.25.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 092ea07d5134dbcfd282f5a534eb9e4f4392d605 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 17:14:49 +0000 Subject: [PATCH 137/334] 5.25.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13549549222 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index e206b09f6..712ad6e3b 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.25.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.25.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 32b98dcbdea3142ab48db3f3b1868517a8262064 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 26 Feb 2025 17:23:27 +0000 Subject: [PATCH 138/334] draft-release: add a warning about bug reporting too many people just spam on discord and expect that to somehow do something ... --- .github/workflows/draft-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 8a35e2904..db7aa3b23 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -182,6 +182,8 @@ jobs: :information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}). + :warning: Found a bug? Report it on our [issue tracker](${{ github.server_url }}/${{ github.repository }}/issues). **We can't fix bugs if you don't report them.** + - name: Post draft release URL on PR if: github.event_name == 'pull_request_target' uses: thollander/actions-comment-pull-request@v3 From 3a2d0d77d184760dc5dd2b57c874cd3757de0d5e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 26 Feb 2025 17:30:20 +0000 Subject: [PATCH 139/334] Update composer dependencies --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index dc49252d6..e224887d6 100644 --- a/composer.lock +++ b/composer.lock @@ -67,16 +67,16 @@ }, { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.2", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40", + "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40", "shasum": "" }, "require": { @@ -85,7 +85,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -115,7 +115,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.2" }, "funding": [ { @@ -123,7 +123,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-26T10:21:45+00:00" }, { "name": "netresearch/jsonmapper", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From e2f5e3e73c8abf9d06f62a0c1733ef0ff1d15a1b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 26 Feb 2025 17:31:26 +0000 Subject: [PATCH 140/334] Update composer dependencies --- composer.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/composer.lock b/composer.lock index 91ce96fa8..6d2981a4c 100644 --- a/composer.lock +++ b/composer.lock @@ -67,16 +67,16 @@ }, { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.2", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40", + "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40", "shasum": "" }, "require": { @@ -85,7 +85,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -115,7 +115,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.2" }, "funding": [ { @@ -123,7 +123,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-26T10:21:45+00:00" }, { "name": "netresearch/jsonmapper", @@ -1150,16 +1150,16 @@ "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -1198,7 +1198,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -1206,7 +1206,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", @@ -1864,16 +1864,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.44", + "version": "10.5.45", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36" + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1381c62769be4bb88fa4c5aec1366c7c66ca4f36", - "reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", "shasum": "" }, "require": { @@ -1945,7 +1945,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.5.44" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" }, "funding": [ { @@ -1961,7 +1961,7 @@ "type": "tidelift" } ], - "time": "2025-01-31T07:00:38+00:00" + "time": "2025-02-06T16:08:12+00:00" }, { "name": "sebastian/cli-parser", From e3e0c14275ad715ee71dc36d8acbe1c5b00ba511 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:04:01 +0000 Subject: [PATCH 141/334] Bump the github-actions group with 2 updates (#6644) --- .github/workflows/build-docker-image.yml | 8 ++++---- .github/workflows/draft-release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index a0cb1f1d9..6199ad7a9 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v6.13.0 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp @@ -66,7 +66,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.13.0 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp @@ -79,7 +79,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.13.0 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp @@ -92,7 +92,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.13.0 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index db7aa3b23..d2e9eb0d0 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -165,7 +165,7 @@ jobs: ${{ github.workspace }}/core-permissions.rst - name: Create draft release - uses: ncipollo/release-action@v1.15.0 + uses: ncipollo/release-action@v1.16.0 id: create-draft with: artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst From d0d84d4c5195fb0a68ea7725424fda63b85cd831 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 4 Mar 2025 20:44:01 +0000 Subject: [PATCH 142/334] New rule: explode() limit parameter must be set --- build/dump-version-info.php | 2 +- phpstan.neon.dist | 1 + src/PocketMine.php | 2 +- src/block/tile/Sign.php | 3 +- src/block/utils/SignText.php | 2 +- src/command/Command.php | 3 +- src/command/defaults/HelpCommand.php | 3 +- src/command/defaults/ParticleCommand.php | 6 +- src/console/ConsoleCommandSender.php | 2 +- src/item/LegacyStringToItemParser.php | 3 +- src/lang/Language.php | 2 +- src/network/mcpe/JwtUtils.php | 6 +- src/permission/BanEntry.php | 4 +- src/utils/Config.php | 9 +- src/utils/Internet.php | 6 +- src/utils/Utils.php | 2 +- src/world/generator/FlatGeneratorOptions.php | 11 ++- tests/phpstan/rules/ExplodeLimitRule.php | 92 ++++++++++++++++++++ tools/generate-bedrock-data-from-packets.php | 2 +- 19 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 tests/phpstan/rules/ExplodeLimitRule.php diff --git a/build/dump-version-info.php b/build/dump-version-info.php index 166264d98..e13696f3d 100644 --- a/build/dump-version-info.php +++ b/build/dump-version-info.php @@ -36,7 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; */ $options = [ "base_version" => VersionInfo::BASE_VERSION, - "major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0], + "major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0], "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK, "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD, "changelog_file_name" => function() : string{ diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5a816f81c..48bfd4a78 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -13,6 +13,7 @@ rules: - pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule - pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowForeachByReferenceRule + - pocketmine\phpstan\rules\ExplodeLimitRule - pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule diff --git a/src/PocketMine.php b/src/PocketMine.php index ffcfd91db..a71c9768d 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -264,7 +264,7 @@ JIT_WARNING $composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp'); if($composerGitHash !== null){ //we can't verify dependency versions if we were installed without using git - $currentGitHash = explode("-", VersionInfo::GIT_HASH())[0]; + $currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0]; if($currentGitHash !== $composerGitHash){ critical_error("Composer dependencies and/or autoloader are out of sync."); critical_error("- Current revision is $currentGitHash"); diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index 2ced414ff..0bb21a6d3 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -62,9 +62,10 @@ class Sign extends Spawnable{ /** * @return string[] + * @deprecated */ public static function fixTextBlob(string $blob) : array{ - return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4); + return array_slice(array_pad(explode("\n", $blob, limit: 5), 4, ""), 0, 4); } protected SignText $text; diff --git a/src/block/utils/SignText.php b/src/block/utils/SignText.php index a7e8759b8..219899761 100644 --- a/src/block/utils/SignText.php +++ b/src/block/utils/SignText.php @@ -79,7 +79,7 @@ class SignText{ * @throws \InvalidArgumentException if the text is not valid UTF-8 */ public static function fromBlob(string $blob, ?Color $baseColor = null, bool $glowing = false) : SignText{ - return new self(array_slice(array_pad(explode("\n", $blob), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing); + return new self(array_slice(array_pad(explode("\n", $blob, limit: self::LINE_COUNT + 1), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing); } /** diff --git a/src/command/Command.php b/src/command/Command.php index aea57e6a2..54822d80e 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -37,6 +37,7 @@ use function array_values; use function explode; use function implode; use function str_replace; +use const PHP_INT_MAX; abstract class Command{ @@ -113,7 +114,7 @@ abstract class Command{ } public function setPermission(?string $permission) : void{ - $this->setPermissions($permission === null ? [] : explode(";", $permission)); + $this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX)); } public function testPermission(CommandSender $target, ?string $permission = null) : bool{ diff --git a/src/command/defaults/HelpCommand.php b/src/command/defaults/HelpCommand.php index 487c915f2..054585455 100644 --- a/src/command/defaults/HelpCommand.php +++ b/src/command/defaults/HelpCommand.php @@ -39,6 +39,7 @@ use function ksort; use function min; use function sort; use function strtolower; +use const PHP_INT_MAX; use const SORT_FLAG_CASE; use const SORT_NATURAL; @@ -108,7 +109,7 @@ class HelpCommand extends VanillaCommand{ $usage = $cmd->getUsage(); $usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage; - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString))) + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX))) ->prefix(TextFormat::GOLD)); $aliases = $cmd->getAliases(); diff --git a/src/command/defaults/ParticleCommand.php b/src/command/defaults/ParticleCommand.php index f20d47ccc..4867e3eb5 100644 --- a/src/command/defaults/ParticleCommand.php +++ b/src/command/defaults/ParticleCommand.php @@ -219,7 +219,11 @@ class ParticleCommand extends VanillaCommand{ break; case "blockdust": if($data !== null){ - $d = explode("_", $data); + //to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts + //this allows the 4th argument to be processed normally if given without forcing it to also consume + //any unexpected parts + //we probably ought to error in this case, but this will do for now + $d = explode("_", $data, limit: 5); if(count($d) >= 3){ return new DustParticle(new Color( ((int) $d[0]) & 0xff, diff --git a/src/console/ConsoleCommandSender.php b/src/console/ConsoleCommandSender.php index aa7ea6e69..a0c1c5200 100644 --- a/src/console/ConsoleCommandSender.php +++ b/src/console/ConsoleCommandSender.php @@ -62,7 +62,7 @@ class ConsoleCommandSender implements CommandSender{ $message = $this->getLanguage()->translate($message); } - foreach(explode("\n", trim($message)) as $line){ + foreach(explode("\n", trim($message), limit: PHP_INT_MAX) as $line){ Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line)); } } diff --git a/src/item/LegacyStringToItemParser.php b/src/item/LegacyStringToItemParser.php index 6969190d5..19a6d1f6c 100644 --- a/src/item/LegacyStringToItemParser.php +++ b/src/item/LegacyStringToItemParser.php @@ -111,7 +111,8 @@ final class LegacyStringToItemParser{ */ public function parse(string $input) : Item{ $key = $this->reprocess($input); - $b = explode(":", $key); + //TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given a string like 351:4:1 + $b = explode(":", $key, limit: 3); if(!isset($b[1])){ $meta = 0; diff --git a/src/lang/Language.php b/src/lang/Language.php index 29f28917d..59a309524 100644 --- a/src/lang/Language.php +++ b/src/lang/Language.php @@ -71,7 +71,7 @@ class Language{ foreach($files as $file){ try{ - $code = explode(".", $file)[0]; + $code = explode(".", $file, limit: 2)[0]; $strings = self::loadLang($path, $code); if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){ $result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME]; diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index 259a602d4..987ed6e61 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -72,9 +72,11 @@ final class JwtUtils{ * @throws JwtException */ public static function split(string $jwt) : array{ - $v = explode(".", $jwt); + //limit of 4 allows us to detect too many parts without having to split the string up into a potentially large + //number of parts + $v = explode(".", $jwt, limit: 4); if(count($v) !== 3){ - throw new JwtException("Expected exactly 3 JWT parts, got " . count($v)); + throw new JwtException("Expected exactly 3 JWT parts delimited by a period"); } return [$v[0], $v[1], $v[2]]; //workaround phpstan bug } diff --git a/src/permission/BanEntry.php b/src/permission/BanEntry.php index 5f235f1a9..a766a6fcc 100644 --- a/src/permission/BanEntry.php +++ b/src/permission/BanEntry.php @@ -148,7 +148,9 @@ class BanEntry{ return null; } - $parts = explode("|", trim($str)); + //we expect at most 5 parts, but accept 6 in case of an extra unexpected delimiter + //we don't want to include unexpected data into the ban reason + $parts = explode("|", trim($str), limit: 6); $entry = new BanEntry(trim(array_shift($parts))); if(count($parts) > 0){ $entry->setCreated(self::parseDate(array_shift($parts))); diff --git a/src/utils/Config.php b/src/utils/Config.php index 7b6da6389..7d0501935 100644 --- a/src/utils/Config.php +++ b/src/utils/Config.php @@ -54,6 +54,7 @@ use const CASE_LOWER; use const JSON_BIGINT_AS_STRING; use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; +use const PHP_INT_MAX; use const YAML_UTF8_ENCODING; /** @@ -339,7 +340,7 @@ class Config{ } public function setNested(string $key, mixed $value) : void{ - $vars = explode(".", $key); + $vars = explode(".", $key, limit: PHP_INT_MAX); $base = array_shift($vars); if(!isset($this->config[$base])){ @@ -366,7 +367,7 @@ class Config{ return $this->nestedCache[$key]; } - $vars = explode(".", $key); + $vars = explode(".", $key, limit: PHP_INT_MAX); $base = array_shift($vars); if(isset($this->config[$base])){ $base = $this->config[$base]; @@ -390,7 +391,7 @@ class Config{ $this->nestedCache = []; $this->changed = true; - $vars = explode(".", $key); + $vars = explode(".", $key, limit: PHP_INT_MAX); $currentNode = &$this->config; while(count($vars) > 0){ @@ -495,7 +496,7 @@ class Config{ */ public static function parseList(string $content) : array{ $result = []; - foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){ + foreach(explode("\n", trim(str_replace("\r\n", "\n", $content)), limit: PHP_INT_MAX) as $v){ $v = trim($v); if($v === ""){ continue; diff --git a/src/utils/Internet.php b/src/utils/Internet.php index 89af2a77e..4b0e00f4a 100644 --- a/src/utils/Internet.php +++ b/src/utils/Internet.php @@ -60,6 +60,7 @@ use const CURLOPT_RETURNTRANSFER; use const CURLOPT_SSL_VERIFYHOST; use const CURLOPT_SSL_VERIFYPEER; use const CURLOPT_TIMEOUT_MS; +use const PHP_INT_MAX; use const SOCK_DGRAM; use const SOL_UDP; @@ -227,9 +228,10 @@ class Internet{ $rawHeaders = substr($raw, 0, $headerSize); $body = substr($raw, $headerSize); $headers = []; - foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){ + //TODO: explore if we can set these limits lower + foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){ $headerGroup = []; - foreach(explode("\r\n", $rawHeaderGroup) as $line){ + foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){ $nameValue = explode(":", $line, 2); if(isset($nameValue[1])){ $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 2a9dd65da..046296cf4 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -369,7 +369,7 @@ final class Utils{ debug_zval_dump($value); $contents = ob_get_contents(); if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here"); - $ret = explode("\n", $contents); + $ret = explode("\n", $contents, limit: 2); ob_end_clean(); if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){ diff --git a/src/world/generator/FlatGeneratorOptions.php b/src/world/generator/FlatGeneratorOptions.php index 563297b00..8271ebcbf 100644 --- a/src/world/generator/FlatGeneratorOptions.php +++ b/src/world/generator/FlatGeneratorOptions.php @@ -26,10 +26,12 @@ namespace pocketmine\world\generator; use pocketmine\data\bedrock\BiomeIds; use pocketmine\item\LegacyStringToItemParser; use pocketmine\item\LegacyStringToItemParserException; +use pocketmine\world\World; use function array_map; use function explode; use function preg_match; use function preg_match_all; +use const PHP_INT_MAX; /** * @internal @@ -70,7 +72,7 @@ final class FlatGeneratorOptions{ */ public static function parseLayers(string $layers) : array{ $result = []; - $split = array_map('\trim', explode(',', $layers)); + $split = array_map('\trim', explode(',', $layers, limit: World::Y_MAX - World::Y_MIN)); $y = 0; $itemParser = LegacyStringToItemParser::getInstance(); foreach($split as $line){ @@ -96,7 +98,7 @@ final class FlatGeneratorOptions{ * @throws InvalidGeneratorOptionsException */ public static function parsePreset(string $presetString) : self{ - $preset = explode(";", $presetString); + $preset = explode(";", $presetString, limit: 4); $blocks = $preset[1] ?? ""; $biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS); $optionsString = $preset[3] ?? ""; @@ -109,9 +111,10 @@ final class FlatGeneratorOptions{ $params = true; if($matches[3][$i] !== ""){ $params = []; - $p = explode(" ", $matches[3][$i]); + $p = explode(" ", $matches[3][$i], limit: PHP_INT_MAX); foreach($p as $k){ - $k = explode("=", $k); + //TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given e.g. treecount=20=1 + $k = explode("=", $k, limit: 3); if(isset($k[1])){ $params[$k[0]] = $k[1]; } diff --git a/tests/phpstan/rules/ExplodeLimitRule.php b/tests/phpstan/rules/ExplodeLimitRule.php new file mode 100644 index 000000000..4e8a341ad --- /dev/null +++ b/tests/phpstan/rules/ExplodeLimitRule.php @@ -0,0 +1,92 @@ + + */ +final class ExplodeLimitRule implements Rule{ + private ReflectionProvider $reflectionProvider; + + public function __construct( + ReflectionProvider $reflectionProvider + ){ + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType() : string{ + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope) : array{ + if(!$node->name instanceof Name){ + return []; + } + + if(!$this->reflectionProvider->hasFunction($node->name, $scope)){ + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + + if($functionReflection->getName() !== 'explode'){ + return []; + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->getArgs(), + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + + $normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + + if($normalizedFuncCall === null){ + return []; + } + + $count = count($normalizedFuncCall->getArgs()); + if($count !== 3){ + return [ + RuleErrorBuilder::message('The $limit parameter of explode() must be set to prevent malicious client data wasting resources.') + ->identifier("pocketmine.explode.limit") + ->build() + ]; + } + + return []; + } +} diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index c48a4f017..50639f51d 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -624,7 +624,7 @@ function main(array $argv) : int{ } foreach($packets as $lineNum => $line){ - $parts = explode(':', $line); + $parts = explode(':', $line, limit: 3); if(count($parts) !== 2){ fwrite(STDERR, 'Wrong packet format at line ' . ($lineNum + 1) . ', expected read:base64 or write:base64'); return 1; From 9e9f8a487084b695985302eedb1931d6353434d1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 4 Mar 2025 20:57:47 +0000 Subject: [PATCH 143/334] Prepare 5.25.2 release --- changelogs/5.25.md | 6 ++++++ src/VersionInfo.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelogs/5.25.md b/changelogs/5.25.md index 5fdc013a6..39862cbb7 100644 --- a/changelogs/5.25.md +++ b/changelogs/5.25.md @@ -44,3 +44,9 @@ Released 26th February 2025. - Fixed confusing exception message when a block-breaking tool has an efficiency value of zero. - Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.) - Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join. + +# 5.25.2 +Released 4th March 2025. + +## Fixes +- Added limits to various `explode()` calls. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 712ad6e3b..c966c03cb 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.25.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 50a1e59aa4a34580f20b1c9eec3f9b6e8ad654a9 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 21:05:10 +0000 Subject: [PATCH 144/334] 5.25.3 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13662905668 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index c966c03cb..569e457da 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.25.2"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.25.3"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From aad2bce9e43c44d4e6f32ded0a5a40080caf0016 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 9 Mar 2025 00:23:59 +0000 Subject: [PATCH 145/334] readme: call out Easy tasks issues --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b9e2e1888..6f2b715ab 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ PocketMine-MP accepts community contributions! The following resources will be u * [Building and running PocketMine-MP from source](BUILDING.md) * [Contributing Guidelines](CONTRIBUTING.md) +New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase. + ## Donate PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love. From 22915466105e9bffd8924365e184e44688f4664a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 9 Mar 2025 00:51:12 +0000 Subject: [PATCH 146/334] phpstan: added rule to ban new $class see #6635 for rationale on why we want to get rid of this for now, this rule will prevent this anti-feature from being used in new code --- phpstan.neon.dist | 1 + tests/phpstan/configs/actual-problems.neon | 30 ++++++++++ .../phpstan/rules/DisallowDynamicNewRule.php | 55 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/phpstan/rules/DisallowDynamicNewRule.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 48bfd4a78..13f35c121 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,6 +11,7 @@ includes: rules: - pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule + - pocketmine\phpstan\rules\DisallowDynamicNewRule - pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowForeachByReferenceRule - pocketmine\phpstan\rules\ExplodeLimitRule diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 73b7a65c1..e54bb166e 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -18,6 +18,12 @@ parameters: count: 1 path: ../../../src/Server.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.noDynamic + count: 1 + path: ../../../src/Server.php + - message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\\> but returns array\\>\.$#' identifier: return.type @@ -54,6 +60,12 @@ parameters: count: 1 path: ../../../src/VersionInfo.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.noDynamic + count: 1 + path: ../../../src/block/Block.php + - message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' identifier: argument.type @@ -510,6 +522,12 @@ parameters: count: 3 path: ../../../src/block/tile/Spawnable.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.noDynamic + count: 1 + path: ../../../src/block/tile/TileFactory.php + - message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' identifier: argument.type @@ -936,6 +954,12 @@ parameters: count: 4 path: ../../../src/plugin/PluginManager.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.noDynamic + count: 1 + path: ../../../src/plugin/PluginManager.php + - message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#' identifier: return.type @@ -1248,6 +1272,12 @@ parameters: count: 1 path: ../../../src/world/format/io/region/RegionLoader.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.noDynamic + count: 1 + path: ../../../src/world/generator/GeneratorRegisterTask.php + - message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' identifier: return.type diff --git a/tests/phpstan/rules/DisallowDynamicNewRule.php b/tests/phpstan/rules/DisallowDynamicNewRule.php new file mode 100644 index 000000000..c6c15a5aa --- /dev/null +++ b/tests/phpstan/rules/DisallowDynamicNewRule.php @@ -0,0 +1,55 @@ + + */ +final class DisallowDynamicNewRule implements Rule{ + + public function getNodeType() : string{ + return New_::class; + } + + public function processNode(Node $node, Scope $scope) : array{ + /** @var New_ $node */ + if($node->class instanceof Expr){ + return [ + RuleErrorBuilder::message("Dynamic new is not allowed.") + ->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.") + ->identifier("pocketmine.new.noDynamic") + ->build() + ]; + } + + return []; + } +} From 95284bc9decef6869e7d42c1d5b213a0d683562e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 9 Mar 2025 00:54:39 +0000 Subject: [PATCH 147/334] change error identifier --- tests/phpstan/configs/actual-problems.neon | 10 +++++----- tests/phpstan/rules/DisallowDynamicNewRule.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index e54bb166e..d3adde422 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -20,7 +20,7 @@ parameters: - message: '#^Dynamic new is not allowed\.$#' - identifier: pocketmine.new.noDynamic + identifier: pocketmine.new.dynamic count: 1 path: ../../../src/Server.php @@ -62,7 +62,7 @@ parameters: - message: '#^Dynamic new is not allowed\.$#' - identifier: pocketmine.new.noDynamic + identifier: pocketmine.new.dynamic count: 1 path: ../../../src/block/Block.php @@ -524,7 +524,7 @@ parameters: - message: '#^Dynamic new is not allowed\.$#' - identifier: pocketmine.new.noDynamic + identifier: pocketmine.new.dynamic count: 1 path: ../../../src/block/tile/TileFactory.php @@ -956,7 +956,7 @@ parameters: - message: '#^Dynamic new is not allowed\.$#' - identifier: pocketmine.new.noDynamic + identifier: pocketmine.new.dynamic count: 1 path: ../../../src/plugin/PluginManager.php @@ -1274,7 +1274,7 @@ parameters: - message: '#^Dynamic new is not allowed\.$#' - identifier: pocketmine.new.noDynamic + identifier: pocketmine.new.dynamic count: 1 path: ../../../src/world/generator/GeneratorRegisterTask.php diff --git a/tests/phpstan/rules/DisallowDynamicNewRule.php b/tests/phpstan/rules/DisallowDynamicNewRule.php index c6c15a5aa..c25e6a18b 100644 --- a/tests/phpstan/rules/DisallowDynamicNewRule.php +++ b/tests/phpstan/rules/DisallowDynamicNewRule.php @@ -45,7 +45,7 @@ final class DisallowDynamicNewRule implements Rule{ return [ RuleErrorBuilder::message("Dynamic new is not allowed.") ->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.") - ->identifier("pocketmine.new.noDynamic") + ->identifier("pocketmine.new.dynamic") ->build() ]; } From 7af5eb3da2bb50cdb11c06622e003283fdffd0ca Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 9 Mar 2025 01:10:03 +0000 Subject: [PATCH 148/334] crafting: validate array inputs this makes sure wrong parameters don't show up as core errors, as seen in crash report 12373907 closes #6642 --- src/crafting/ShapedRecipe.php | 2 ++ src/crafting/ShapelessRecipe.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/crafting/ShapedRecipe.php b/src/crafting/ShapedRecipe.php index 4c40eb0f5..2af3f5134 100644 --- a/src/crafting/ShapedRecipe.php +++ b/src/crafting/ShapedRecipe.php @@ -97,6 +97,7 @@ class ShapedRecipe implements CraftingRecipe{ $this->shape = $shape; + Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{}); foreach(Utils::stringifyKeys($ingredients) as $char => $i){ if(!str_contains(implode($this->shape), $char)){ throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape"); @@ -105,6 +106,7 @@ class ShapedRecipe implements CraftingRecipe{ $this->ingredientList[$char] = clone $i; } + Utils::validateArrayValueType($results, function(Item $_) : void{}); $this->results = Utils::cloneObjectArray($results); } diff --git a/src/crafting/ShapelessRecipe.php b/src/crafting/ShapelessRecipe.php index 7a4a22fda..b139439ef 100644 --- a/src/crafting/ShapelessRecipe.php +++ b/src/crafting/ShapelessRecipe.php @@ -53,7 +53,9 @@ class ShapelessRecipe implements CraftingRecipe{ if(count($ingredients) > 9){ throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients"); } + Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{}); $this->ingredients = $ingredients; + Utils::validateArrayValueType($results, function(Item $_) : void{}); $this->results = Utils::cloneObjectArray($results); } From afc4a3c7f18d42b41cbfde84ab6a2e4dd7c03045 Mon Sep 17 00:00:00 2001 From: Lee Siu San <15855635+leolee3914@users.noreply.github.com> Date: Sun, 9 Mar 2025 10:09:53 +0800 Subject: [PATCH 149/334] Fixed entity position offset not being included in AddActorPacket (#6643) this was causing TNT and falling blocks to briefly appear half a block lower than their true position, because their positions are measured from the center and not the base. --- src/entity/Entity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 7f0f6028b..e24c6067c 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -1495,7 +1495,7 @@ abstract class Entity{ $this->getId(), //TODO: actor unique ID $this->getId(), static::getNetworkTypeId(), - $this->location->asVector3(), + $this->getOffsetPosition($this->location->asVector3()), $this->getMotion(), $this->location->pitch, $this->location->yaw, From 00df5087279c08f079f06a74b60e8c439a3e53d4 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 12 Mar 2025 13:06:57 +0000 Subject: [PATCH 150/334] Update bug-report.yml --- .github/ISSUE_TEMPLATE/bug-report.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 3a4e49100..209ba8880 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -57,6 +57,9 @@ body: attributes: value: | ## Version, OS and game info + > [!WARNING] + > "Latest" is not a valid version. + > Failure to fill these fields with valid information may result in your issue being closed. - type: input attributes: From 73a4b076d6b62a9e325ce200f282c09cefcb27e7 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 12 Mar 2025 16:19:11 +0000 Subject: [PATCH 151/334] actions: tidy support response message --- .github/workflows/support.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/support.yml b/.github/workflows/support.yml index 68da365cb..3a65f78a4 100644 --- a/.github/workflows/support.yml +++ b/.github/workflows/support.yml @@ -20,10 +20,7 @@ jobs: - Check our [Documentation](https://doc.pmmp.io) to see if you can find answers there - - Ask the community on our [Discord server](https://discord.gg/bmSAZBG) or our [Forums](https://forums.pmmp.io) - - - [Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io) + - Ask the community on our [Discord server](https://discord.gg/bmSAZBG) close-issue: true lock-issue: false From 341c7a03a9413b5b6d48e7b9fab4ae9e3161ca4b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 14 Mar 2025 15:40:05 +0000 Subject: [PATCH 152/334] CopperMaterial: fixed missing @return $this docs --- src/block/utils/CopperMaterial.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/block/utils/CopperMaterial.php b/src/block/utils/CopperMaterial.php index 6df22620b..ef6560620 100644 --- a/src/block/utils/CopperMaterial.php +++ b/src/block/utils/CopperMaterial.php @@ -30,9 +30,15 @@ interface CopperMaterial{ public function getOxidation() : CopperOxidation; + /** + * @return $this + */ public function setOxidation(CopperOxidation $oxidation) : CopperMaterial; public function isWaxed() : bool; + /** + * @return $this + */ public function setWaxed(bool $waxed) : CopperMaterial; } From d9e0e51e14cac88f78c7fc12bb5c371b54ad126d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 14 Mar 2025 16:08:06 +0000 Subject: [PATCH 153/334] Reduce code duplication in copper block serialization handling --- .../convert/BlockObjectToStateSerializer.php | 342 +++++++++--------- .../convert/BlockStateDeserializerHelper.php | 40 +- .../BlockStateToObjectDeserializer.php | 251 +++++++------ 3 files changed, 356 insertions(+), 277 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index e41e82054..367d38449 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -214,6 +214,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->registerLeavesSerializers(); $this->registerSaplingSerializers(); $this->registerMobHeadSerializers(); + $this->registerCopperSerializers(); $this->registerSimpleSerializers(); $this->registerSerializers(); } @@ -791,6 +792,178 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ })->writeFacingWithoutDown($block->getFacing())); } + private function registerCopperSerializers() : void{ + $this->map(Blocks::COPPER(), function(Copper $block) : Writer{ + $oxidation = $block->getOxidation(); + return new Writer($block->isWaxed() ? + Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) : + Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER) + ); + }); + $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{ + $oxidation = $block->getOxidation(); + return new Writer($block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_CHISELED_COPPER, + Ids::WAXED_EXPOSED_CHISELED_COPPER, + Ids::WAXED_WEATHERED_CHISELED_COPPER, + Ids::WAXED_OXIDIZED_CHISELED_COPPER + ) : + Helper::selectCopperId($oxidation, + Ids::CHISELED_COPPER, + Ids::EXPOSED_CHISELED_COPPER, + Ids::WEATHERED_CHISELED_COPPER, + Ids::OXIDIZED_CHISELED_COPPER + ) + ); + }); + $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{ + $oxidation = $block->getOxidation(); + return new Writer($block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_COPPER_GRATE, + Ids::WAXED_EXPOSED_COPPER_GRATE, + Ids::WAXED_WEATHERED_COPPER_GRATE, + Ids::WAXED_OXIDIZED_COPPER_GRATE + ) : + Helper::selectCopperId($oxidation, + Ids::COPPER_GRATE, + Ids::EXPOSED_COPPER_GRATE, + Ids::WEATHERED_COPPER_GRATE, + Ids::OXIDIZED_COPPER_GRATE + ) + ); + }); + $this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{ + $oxidation = $block->getOxidation(); + return new Writer($block->isWaxed() ? + Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) : + Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER) + ); + }); + $this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{ + $oxidation = $block->getOxidation(); + return Helper::encodeSlab( + $block, + ($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_CUT_COPPER_SLAB, + Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, + Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, + Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB + ) : + Helper::selectCopperId( + $oxidation, + Ids::CUT_COPPER_SLAB, + Ids::EXPOSED_CUT_COPPER_SLAB, + Ids::WEATHERED_CUT_COPPER_SLAB, + Ids::OXIDIZED_CUT_COPPER_SLAB + ) + ), + ($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB + ) : + Helper::selectCopperId( + $oxidation, + Ids::DOUBLE_CUT_COPPER_SLAB, + Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, + Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, + Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB + ) + ) + ); + }); + $this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{ + $oxidation = $block->getOxidation(); + return Helper::encodeStairs( + $block, + new Writer($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_CUT_COPPER_STAIRS, + Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, + Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, + Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS + ) : + Helper::selectCopperId( + $oxidation, + Ids::CUT_COPPER_STAIRS, + Ids::EXPOSED_CUT_COPPER_STAIRS, + Ids::WEATHERED_CUT_COPPER_STAIRS, + Ids::OXIDIZED_CUT_COPPER_STAIRS + ) + ) + ); + }); + $this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{ + $oxidation = $block->getOxidation(); + return Writer::create($block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_COPPER_BULB, + Ids::WAXED_EXPOSED_COPPER_BULB, + Ids::WAXED_WEATHERED_COPPER_BULB, + Ids::WAXED_OXIDIZED_COPPER_BULB) : + Helper::selectCopperId($oxidation, + Ids::COPPER_BULB, + Ids::EXPOSED_COPPER_BULB, + Ids::WEATHERED_COPPER_BULB, + Ids::OXIDIZED_COPPER_BULB + )) + ->writeBool(StateNames::LIT, $block->isLit()) + ->writeBool(StateNames::POWERED_BIT, $block->isPowered()); + }); + $this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{ + $oxidation = $block->getOxidation(); + return Helper::encodeDoor( + $block, + new Writer($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_COPPER_DOOR, + Ids::WAXED_EXPOSED_COPPER_DOOR, + Ids::WAXED_WEATHERED_COPPER_DOOR, + Ids::WAXED_OXIDIZED_COPPER_DOOR + ) : + Helper::selectCopperId( + $oxidation, + Ids::COPPER_DOOR, + Ids::EXPOSED_COPPER_DOOR, + Ids::WEATHERED_COPPER_DOOR, + Ids::OXIDIZED_COPPER_DOOR + ) + ) + ); + }); + $this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{ + $oxidation = $block->getOxidation(); + return Helper::encodeTrapdoor( + $block, + new Writer($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_COPPER_TRAPDOOR, + Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, + Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, + Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR + ) : + Helper::selectCopperId( + $oxidation, + Ids::COPPER_TRAPDOOR, + Ids::EXPOSED_COPPER_TRAPDOOR, + Ids::WEATHERED_COPPER_TRAPDOOR, + Ids::OXIDIZED_COPPER_TRAPDOOR + ) + ) + ); + }); + } + private function registerSimpleSerializers() : void{ $this->mapSimple(Blocks::AIR(), Ids::AIR); $this->mapSimple(Blocks::AMETHYST(), Ids::AMETHYST_BLOCK); @@ -1265,175 +1438,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSlab(Blocks::COBBLESTONE_SLAB(), Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB); $this->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS); $this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::COBBLESTONE_WALL))); - $this->map(Blocks::COPPER(), function(Copper $block) : Writer{ - $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) : - Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER) - ); - }); - $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{ - $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_CHISELED_COPPER, - Ids::WAXED_EXPOSED_CHISELED_COPPER, - Ids::WAXED_WEATHERED_CHISELED_COPPER, - Ids::WAXED_OXIDIZED_CHISELED_COPPER - ) : - Helper::selectCopperId($oxidation, - Ids::CHISELED_COPPER, - Ids::EXPOSED_CHISELED_COPPER, - Ids::WEATHERED_CHISELED_COPPER, - Ids::OXIDIZED_CHISELED_COPPER - ) - ); - }); - $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{ - $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_COPPER_GRATE, - Ids::WAXED_EXPOSED_COPPER_GRATE, - Ids::WAXED_WEATHERED_COPPER_GRATE, - Ids::WAXED_OXIDIZED_COPPER_GRATE - ) : - Helper::selectCopperId($oxidation, - Ids::COPPER_GRATE, - Ids::EXPOSED_COPPER_GRATE, - Ids::WEATHERED_COPPER_GRATE, - Ids::OXIDIZED_COPPER_GRATE - ) - ); - }); - $this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{ - $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) : - Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER) - ); - }); - $this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeSlab( - $block, - ($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_CUT_COPPER_SLAB, - Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, - Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, - Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB - ) : - Helper::selectCopperId( - $oxidation, - Ids::CUT_COPPER_SLAB, - Ids::EXPOSED_CUT_COPPER_SLAB, - Ids::WEATHERED_CUT_COPPER_SLAB, - Ids::OXIDIZED_CUT_COPPER_SLAB - ) - ), - ($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB - ) : - Helper::selectCopperId( - $oxidation, - Ids::DOUBLE_CUT_COPPER_SLAB, - Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, - Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, - Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB - ) - ) - ); - }); - $this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeStairs( - $block, - new Writer($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_CUT_COPPER_STAIRS, - Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, - Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, - Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS - ) : - Helper::selectCopperId( - $oxidation, - Ids::CUT_COPPER_STAIRS, - Ids::EXPOSED_CUT_COPPER_STAIRS, - Ids::WEATHERED_CUT_COPPER_STAIRS, - Ids::OXIDIZED_CUT_COPPER_STAIRS - ) - ) - ); - }); - $this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{ - $oxidation = $block->getOxidation(); - return Writer::create($block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_COPPER_BULB, - Ids::WAXED_EXPOSED_COPPER_BULB, - Ids::WAXED_WEATHERED_COPPER_BULB, - Ids::WAXED_OXIDIZED_COPPER_BULB) : - Helper::selectCopperId($oxidation, - Ids::COPPER_BULB, - Ids::EXPOSED_COPPER_BULB, - Ids::WEATHERED_COPPER_BULB, - Ids::OXIDIZED_COPPER_BULB - )) - ->writeBool(StateNames::LIT, $block->isLit()) - ->writeBool(StateNames::POWERED_BIT, $block->isPowered()); - }); - $this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeDoor( - $block, - new Writer($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_COPPER_DOOR, - Ids::WAXED_EXPOSED_COPPER_DOOR, - Ids::WAXED_WEATHERED_COPPER_DOOR, - Ids::WAXED_OXIDIZED_COPPER_DOOR - ) : - Helper::selectCopperId( - $oxidation, - Ids::COPPER_DOOR, - Ids::EXPOSED_COPPER_DOOR, - Ids::WEATHERED_COPPER_DOOR, - Ids::OXIDIZED_COPPER_DOOR - ) - ) - ); - }); - $this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeTrapdoor( - $block, - new Writer($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_COPPER_TRAPDOOR, - Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, - Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, - Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR - ) : - Helper::selectCopperId( - $oxidation, - Ids::COPPER_TRAPDOOR, - Ids::EXPOSED_COPPER_TRAPDOOR, - Ids::WEATHERED_COPPER_TRAPDOOR, - Ids::OXIDIZED_COPPER_TRAPDOOR - ) - ) - ); - }); $this->map(Blocks::COCOA_POD(), function(CocoaBlock $block) : Writer{ return Writer::create(Ids::COCOA) ->writeInt(StateNames::AGE, $block->getAge()) diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index 1d7b4bb76..5cf3f7f76 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -126,7 +126,13 @@ final class BlockStateDeserializerHelper{ ->setOutputSignalStrength($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15)); } - /** @throws BlockStateDeserializeException */ + /** + * @phpstan-template TDoor of Door + * @phpstan-param TDoor $block + * @phpstan-return TDoor + * + * @throws BlockStateDeserializeException + */ public static function decodeDoor(Door $block, BlockStateReader $in) : Door{ //TODO: check if these need any special treatment to get the appropriate data to both halves of the door return $block @@ -237,18 +243,36 @@ final class BlockStateDeserializerHelper{ return $block->setPressed($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15) !== 0); } - /** @throws BlockStateDeserializeException */ + /** + * @phpstan-template TSlab of Slab + * @phpstan-param TSlab $block + * @phpstan-return TSlab + * + * @throws BlockStateDeserializeException + */ public static function decodeSingleSlab(Slab $block, BlockStateReader $in) : Slab{ return $block->setSlabType($in->readSlabPosition()); } - /** @throws BlockStateDeserializeException */ + /** + * @phpstan-template TSlab of Slab + * @phpstan-param TSlab $block + * @phpstan-return TSlab + * + * @throws BlockStateDeserializeException + */ public static function decodeDoubleSlab(Slab $block, BlockStateReader $in) : Slab{ $in->ignored(StateNames::MC_VERTICAL_HALF); return $block->setSlabType(SlabType::DOUBLE); } - /** @throws BlockStateDeserializeException */ + /** + * @phpstan-template TStair of Stair + * @phpstan-param TStair $block + * @phpstan-return TStair + * + * @throws BlockStateDeserializeException + */ public static function decodeStairs(Stair $block, BlockStateReader $in) : Stair{ return $block ->setUpsideDown($in->readBool(BlockStateNames::UPSIDE_DOWN_BIT)) @@ -265,7 +289,13 @@ final class BlockStateDeserializerHelper{ ->setFacing($facing === Facing::DOWN ? Facing::UP : $facing); } - /** @throws BlockStateDeserializeException */ + /** + * @phpstan-template TTrapdoor of Trapdoor + * @phpstan-param TTrapdoor $block + * @phpstan-return TTrapdoor + * + * @throws BlockStateDeserializeException + */ public static function decodeTrapdoor(Trapdoor $block, BlockStateReader $in) : Trapdoor{ return $block ->setFacing($in->read5MinusHorizontalFacing()) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 42d8ee0cd..35f7f0df9 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -38,6 +38,7 @@ use pocketmine\block\Stair; use pocketmine\block\SweetBerryBush; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\ChiseledBookshelfSlot; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\DirtType; @@ -59,6 +60,7 @@ use pocketmine\data\bedrock\block\convert\BlockStateDeserializerHelper as Helper use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader; use pocketmine\math\Axis; use pocketmine\math\Facing; +use pocketmine\utils\Utils; use function array_key_exists; use function count; use function min; @@ -87,6 +89,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->registerSaplingDeserializers(); $this->registerLightDeserializers(); $this->registerMobHeadDeserializers(); + $this->registerCopperDeserializers(); $this->registerSimpleDeserializers(); $this->registerDeserializers(); } @@ -723,6 +726,150 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ } } + /** + * @phpstan-param \Closure(Reader) : (CopperMaterial&Block) $deserializer + */ + private function mapCopper( + string $normalId, + string $waxedNormalId, + string $exposedId, + string $waxedExposedId, + string $weatheredId, + string $waxedWeatheredId, + string $oxidizedId, + string $waxedOxidizedId, + \Closure $deserializer + ) : void{ + foreach(Utils::stringifyKeys([ + $normalId => [CopperOxidation::NONE, false], + $waxedNormalId => [CopperOxidation::NONE, true], + $exposedId => [CopperOxidation::EXPOSED, false], + $waxedExposedId => [CopperOxidation::EXPOSED, true], + $weatheredId => [CopperOxidation::WEATHERED, false], + $waxedWeatheredId => [CopperOxidation::WEATHERED, true], + $oxidizedId => [CopperOxidation::OXIDIZED, false], + $waxedOxidizedId => [CopperOxidation::OXIDIZED, true], + ]) as $id => [$oxidation, $waxed]){ + $this->map($id, fn(Reader $in) => $deserializer($in)->setOxidation($oxidation)->setWaxed($waxed)); + } + } + + private function registerCopperDeserializers() : void{ + $this->mapCopper( + Ids::CUT_COPPER_SLAB, + Ids::WAXED_CUT_COPPER_SLAB, + Ids::EXPOSED_CUT_COPPER_SLAB, + Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, + Ids::WEATHERED_CUT_COPPER_SLAB, + Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, + Ids::OXIDIZED_CUT_COPPER_SLAB, + Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, + fn(Reader $in) => Helper::decodeSingleSlab(Blocks::CUT_COPPER_SLAB(), $in) + ); + $this->mapCopper( + Ids::DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, + Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, + Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, + Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, + Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, + fn(Reader $in) => Helper::decodeDoubleSlab(Blocks::CUT_COPPER_SLAB(), $in) + ); + + $this->mapCopper( + Ids::COPPER_BULB, + Ids::WAXED_COPPER_BULB, + Ids::EXPOSED_COPPER_BULB, + Ids::WAXED_EXPOSED_COPPER_BULB, + Ids::WEATHERED_COPPER_BULB, + Ids::WAXED_WEATHERED_COPPER_BULB, + Ids::OXIDIZED_COPPER_BULB, + Ids::WAXED_OXIDIZED_COPPER_BULB, + fn(Reader $in) => Blocks::COPPER_BULB() + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)) + ); + $this->mapCopper( + Ids::COPPER_DOOR, + Ids::WAXED_COPPER_DOOR, + Ids::EXPOSED_COPPER_DOOR, + Ids::WAXED_EXPOSED_COPPER_DOOR, + Ids::WEATHERED_COPPER_DOOR, + Ids::WAXED_WEATHERED_COPPER_DOOR, + Ids::OXIDIZED_COPPER_DOOR, + Ids::WAXED_OXIDIZED_COPPER_DOOR, + fn(Reader $in) => Helper::decodeDoor(Blocks::COPPER_DOOR(), $in) + ); + $this->mapCopper( + Ids::COPPER_TRAPDOOR, + Ids::WAXED_COPPER_TRAPDOOR, + Ids::EXPOSED_COPPER_TRAPDOOR, + Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, + Ids::WEATHERED_COPPER_TRAPDOOR, + Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, + Ids::OXIDIZED_COPPER_TRAPDOOR, + Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, + fn(Reader $in) => Helper::decodeTrapdoor(Blocks::COPPER_TRAPDOOR(), $in) + ); + $this->mapCopper( + Ids::COPPER_BLOCK, + Ids::WAXED_COPPER, + Ids::EXPOSED_COPPER, + Ids::WAXED_EXPOSED_COPPER, + Ids::WEATHERED_COPPER, + Ids::WAXED_WEATHERED_COPPER, + Ids::OXIDIZED_COPPER, + Ids::WAXED_OXIDIZED_COPPER, + fn(Reader $in) => Blocks::COPPER() + ); + $this->mapCopper( + Ids::CHISELED_COPPER, + Ids::WAXED_CHISELED_COPPER, + Ids::EXPOSED_CHISELED_COPPER, + Ids::WAXED_EXPOSED_CHISELED_COPPER, + Ids::WEATHERED_CHISELED_COPPER, + Ids::WAXED_WEATHERED_CHISELED_COPPER, + Ids::OXIDIZED_CHISELED_COPPER, + Ids::WAXED_OXIDIZED_CHISELED_COPPER, + Blocks::CHISELED_COPPER(...) + ); + $this->mapCopper( + Ids::COPPER_GRATE, + Ids::WAXED_COPPER_GRATE, + Ids::EXPOSED_COPPER_GRATE, + Ids::WAXED_EXPOSED_COPPER_GRATE, + Ids::WEATHERED_COPPER_GRATE, + Ids::WAXED_WEATHERED_COPPER_GRATE, + Ids::OXIDIZED_COPPER_GRATE, + Ids::WAXED_OXIDIZED_COPPER_GRATE, + Blocks::COPPER_GRATE(...) + ); + $this->mapCopper( + Ids::CUT_COPPER, + Ids::WAXED_CUT_COPPER, + Ids::EXPOSED_CUT_COPPER, + Ids::WAXED_EXPOSED_CUT_COPPER, + Ids::WEATHERED_CUT_COPPER, + Ids::WAXED_WEATHERED_CUT_COPPER, + Ids::OXIDIZED_CUT_COPPER, + Ids::WAXED_OXIDIZED_CUT_COPPER, + Blocks::CUT_COPPER(...) + ); + $this->mapCopper( + Ids::CUT_COPPER_STAIRS, + Ids::WAXED_CUT_COPPER_STAIRS, + Ids::EXPOSED_CUT_COPPER_STAIRS, + Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, + Ids::WEATHERED_CUT_COPPER_STAIRS, + Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, + Ids::OXIDIZED_CUT_COPPER_STAIRS, + Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, + fn(Reader $in) => Helper::decodeStairs(Blocks::CUT_COPPER_STAIRS(), $in) + ); + } + private function registerSimpleDeserializers() : void{ $this->mapSimple(Ids::AIR, fn() => Blocks::AIR()); $this->mapSimple(Ids::AMETHYST_BLOCK, fn() => Blocks::AMETHYST()); @@ -1228,18 +1375,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map(Ids::COMPOUND_CREATOR, fn(Reader $in) => Blocks::COMPOUND_CREATOR() ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) ); - $this->map(Ids::COPPER_BLOCK, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::NONE)); - $this->map(Ids::COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in)); - $this->map(Ids::COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE)); - $this->map(Ids::COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in)); - $this->map(Ids::CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE)); - $this->mapSlab(Ids::CUT_COPPER_SLAB, Ids::DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE)); - $this->mapStairs(Ids::CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE)); $this->mapSlab(Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_RED_SANDSTONE_SLAB()); $this->mapSlab(Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_SANDSTONE_SLAB()); $this->mapSlab(Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB, fn() => Blocks::DARK_PRISMARINE_SLAB()); @@ -1294,19 +1429,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return Blocks::ENDER_CHEST() ->setFacing($in->readCardinalHorizontalFacing()); }); - $this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED)); - $this->map(Ids::EXPOSED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED)); - $this->map(Ids::EXPOSED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED)); - $this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED)); - $this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED)); - $this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED)); - $this->map(Ids::EXPOSED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in)); - $this->map(Ids::EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in)); $this->map(Ids::FARMLAND, function(Reader $in) : Block{ return Blocks::FARMLAND() ->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7)); @@ -1459,19 +1581,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSlab(Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB, fn() => Blocks::STONE_SLAB()); $this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS()); $this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis())); - $this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED)); - $this->map(Ids::OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED)); - $this->map(Ids::OXIDIZED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED)); - $this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED)); - $this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED)); - $this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED)); - $this->map(Ids::OXIDIZED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in)); - $this->map(Ids::OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in)); $this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis())); $this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB()); $this->map(Ids::PINK_PETALS, function(Reader $in) : Block{ @@ -1744,71 +1853,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readHorizontalFacing()); }); $this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in)); - $this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE)); - $this->map(Ids::WAXED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE)); - $this->map(Ids::WAXED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE)); - $this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE)); - $this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE)); - $this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE)); - $this->map(Ids::WAXED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::WAXED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in)); - $this->map(Ids::WAXED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in)); - $this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED)); - $this->map(Ids::WAXED_EXPOSED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED)); - $this->map(Ids::WAXED_EXPOSED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED)); - $this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED)); - $this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED)); - $this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED)); - $this->map(Ids::WAXED_EXPOSED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::WAXED_EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in)); - $this->map(Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in)); - $this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED)); - $this->map(Ids::WAXED_OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED)); - $this->map(Ids::WAXED_OXIDIZED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED)); - $this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED)); - $this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED)); - $this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED)); - $this->map(Ids::WAXED_OXIDIZED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::WAXED_OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in)); - $this->map(Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in)); - $this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED)); - $this->map(Ids::WAXED_WEATHERED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED)); - $this->map(Ids::WAXED_WEATHERED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED)); - $this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED)); - $this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED)); - $this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED)); - $this->map(Ids::WAXED_WEATHERED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::WAXED_WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in)); - $this->map(Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in)); - $this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED)); - $this->map(Ids::WEATHERED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED)); - $this->map(Ids::WEATHERED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED)); - $this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED)); - $this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED)); - $this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED)); - $this->map(Ids::WEATHERED_COPPER_BULB, function(Reader $in) : Block{ - return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED) - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in)); - $this->map(Ids::WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in)); + $this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{ return Blocks::WEEPING_VINES() ->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25)); From 7cfaf04b8794cc72f186360de7778b64f757d696 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 14 Mar 2025 16:10:56 +0000 Subject: [PATCH 154/334] stfu --- .../block/convert/BlockStateToObjectDeserializer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 35f7f0df9..2ea2ecf8c 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -833,7 +833,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ Ids::WAXED_WEATHERED_CHISELED_COPPER, Ids::OXIDIZED_CHISELED_COPPER, Ids::WAXED_OXIDIZED_CHISELED_COPPER, - Blocks::CHISELED_COPPER(...) + fn(Reader $in) => Blocks::CHISELED_COPPER() ); $this->mapCopper( Ids::COPPER_GRATE, @@ -844,7 +844,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ Ids::WAXED_WEATHERED_COPPER_GRATE, Ids::OXIDIZED_COPPER_GRATE, Ids::WAXED_OXIDIZED_COPPER_GRATE, - Blocks::COPPER_GRATE(...) + fn(Reader $in) => Blocks::COPPER_GRATE() ); $this->mapCopper( Ids::CUT_COPPER, @@ -855,7 +855,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ Ids::WAXED_WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER, - Blocks::CUT_COPPER(...) + fn(Reader $in) => Blocks::CUT_COPPER() ); $this->mapCopper( Ids::CUT_COPPER_STAIRS, From 09acbfab4c954edf445177389c4347725d14fb04 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 15 Mar 2025 00:03:09 +0000 Subject: [PATCH 155/334] dependabot: ignore phpstan/phpstan updates these are noisy and cause conflicts. Since they also usually cause new errors to be reported, we often can't directly update it anyway. Better to test & update this locally. --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 13721f0ba..ded96ca5d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,6 +12,10 @@ updates: update-types: - "version-update:semver-major" - "version-update:semver-minor" + + #since we lock this to exact versions, it causes conflicts with minor-next & major-next in composer.lock + #better to just test updates to this locally anyway since almost every version breaks something + - dependency-name: phpstan/phpstan groups: production-patch-updates: dependency-type: production From e03c586c86801664fa484ee034f65eefee3ff894 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 15 Mar 2025 01:29:36 +0000 Subject: [PATCH 156/334] GarbageCollectorManager: promote debug message to info this has such a big impact on performance that I think this is warranted. Should also make it more obvious what the GC is doing without needing to enable ALL debug info. --- src/GarbageCollectorManager.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/GarbageCollectorManager.php b/src/GarbageCollectorManager.php index a8912a90d..5cea2e879 100644 --- a/src/GarbageCollectorManager.php +++ b/src/GarbageCollectorManager.php @@ -48,6 +48,7 @@ final class GarbageCollectorManager{ private int $threshold = self::GC_THRESHOLD_DEFAULT; private int $collectionTimeTotalNs = 0; + private int $runs = 0; private \Logger $logger; private TimingsHandler $timings; @@ -96,7 +97,16 @@ final class GarbageCollectorManager{ $time = $end - $start; $this->collectionTimeTotalNs += $time; - $this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->collectionTimeTotalNs) . " ns"); + $this->runs++; + $this->logger->info(sprintf( + "Run #%d took %s ms (%s -> %s roots, %s cycles collected) - cumulative GC time: %s ms", + $this->runs, + number_format($time / 1_000_000, 2), + $rootsBefore, + $rootsAfter, + $cycles, + number_format($this->collectionTimeTotalNs / 1_000_000, 2) + )); return $cycles; } From 8b57e9007a18569e71abe8f78a3f1d6a620b2d39 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 15 Mar 2025 01:33:29 +0000 Subject: [PATCH 157/334] :japanese_goblin: --- src/GarbageCollectorManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GarbageCollectorManager.php b/src/GarbageCollectorManager.php index 5cea2e879..294403a05 100644 --- a/src/GarbageCollectorManager.php +++ b/src/GarbageCollectorManager.php @@ -31,6 +31,7 @@ use function hrtime; use function max; use function min; use function number_format; +use function sprintf; /** * Allows threads to manually trigger the cyclic garbage collector using a threshold like PHP's own garbage collector, From 463be36b72d4f519674ec472ca492773c197dbce Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 15 Mar 2025 20:33:47 +0000 Subject: [PATCH 158/334] Update composer dependencies --- composer.json | 2 +- composer.lock | 77 +++++++++++++++++++++------------------------------ 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index 10454c560..757ae5f57 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.6", + "phpstan/phpstan": "2.1.8", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index e224887d6..01b9ea5df 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": "bef9decc40d9f5bd82e1de2d151bd99f", + "content-hash": "d3a1dc398ea7b59dcb674143ac10f289", "packages": [ { "name": "adhocore/json-comment", @@ -67,16 +67,16 @@ }, { "name": "brick/math", - "version": "0.12.2", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40", - "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -115,7 +115,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.2" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -123,7 +123,7 @@ "type": "github" } ], - "time": "2025-02-26T10:21:45+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "netresearch/jsonmapper", @@ -742,16 +742,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", "shasum": "" }, "require": { @@ -759,25 +759,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "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.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" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -815,19 +812,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-02T04:48:29+00:00" }, { "name": "ramsey/uuid", @@ -1386,16 +1373,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.6", + "version": "2.1.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", "shasum": "" }, "require": { @@ -1440,7 +1427,7 @@ "type": "github" } ], - "time": "2025-02-19T15:46:42+00:00" + "time": "2025-03-09T09:30:48+00:00" }, { "name": "phpstan/phpstan-phpunit", From 4407e585e4fdc3c2825fbbba9b0ecfd6833b17cb Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 15 Mar 2025 20:36:39 +0000 Subject: [PATCH 159/334] Update composer dependencies (minor-next) --- composer.json | 4 +- composer.lock | 101 ++++++++++++++++++++++---------------------------- 2 files changed, 46 insertions(+), 59 deletions(-) diff --git a/composer.json b/composer.json index 11c9ae515..74441af76 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", "pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60", + "pocketmine/bedrock-protocol": "~36.2.0+bedrock-1.21.60", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.6", + "phpstan/phpstan": "2.1.8", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index 521b5b372..deb0b43b2 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": "2a56fc6dee1dac2ade34d965aa49dc82", + "content-hash": "6314ae9a7919042f10bd17f0a3ef6e9d", "packages": [ { "name": "adhocore/json-comment", @@ -67,16 +67,16 @@ }, { "name": "brick/math", - "version": "0.12.2", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40", - "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -115,7 +115,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.2" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -123,7 +123,7 @@ "type": "github" } ], - "time": "2025-02-26T10:21:45+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "netresearch/jsonmapper", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "36.0.0+bedrock-1.21.60", + "version": "36.2.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "2057de319c5c551001c2a544e08d1bc7727d9963" + "reference": "6830e8a9ee9ecb6984b7b02f412473136ae92d50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963", - "reference": "2057de319c5c551001c2a544e08d1bc7727d9963", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6830e8a9ee9ecb6984b7b02f412473136ae92d50", + "reference": "6830e8a9ee9ecb6984b7b02f412473136ae92d50", "shasum": "" }, "require": { @@ -296,9 +296,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/36.0.0+bedrock-1.21.60" + "source": "https://github.com/pmmp/BedrockProtocol/tree/36.2.0+bedrock-1.21.60" }, - "time": "2025-02-16T15:59:08+00:00" + "time": "2025-03-14T17:17:21+00:00" }, { "name": "pocketmine/binaryutils", @@ -576,16 +576,16 @@ }, { "name": "pocketmine/nbt", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/pmmp/NBT.git", - "reference": "cfd53a86166b851786967fc560cdb372e66fde96" + "reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/NBT/zipball/cfd53a86166b851786967fc560cdb372e66fde96", - "reference": "cfd53a86166b851786967fc560cdb372e66fde96", + "url": "https://api.github.com/repos/pmmp/NBT/zipball/c3c7b0a7295daeaf7873d90fed5c5d10381d12e1", + "reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1", "shasum": "" }, "require": { @@ -612,9 +612,9 @@ "description": "PHP library for working with Named Binary Tags", "support": { "issues": "https://github.com/pmmp/NBT/issues", - "source": "https://github.com/pmmp/NBT/tree/1.1.0" + "source": "https://github.com/pmmp/NBT/tree/1.1.1" }, - "time": "2025-02-01T21:20:26+00:00" + "time": "2025-03-09T01:46:03+00:00" }, { "name": "pocketmine/raklib", @@ -742,16 +742,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", "shasum": "" }, "require": { @@ -759,25 +759,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "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.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" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -815,19 +812,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-02T04:48:29+00:00" }, { "name": "ramsey/uuid", @@ -1386,16 +1373,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.6", + "version": "2.1.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", "shasum": "" }, "require": { @@ -1440,7 +1427,7 @@ "type": "github" } ], - "time": "2025-02-19T15:46:42+00:00" + "time": "2025-03-09T09:30:48+00:00" }, { "name": "phpstan/phpstan-phpunit", From c2f8e9365bdf359b55f91056fd158d9d2c2a9f6b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 15 Mar 2025 20:53:49 +0000 Subject: [PATCH 160/334] BlockStateToObjectDeserializer: check that the returned state is actually registered if not, this will cause random crashes in core code, which assumes that state IDs found on runtime chunk memory are valid and registered. this problem exists in other places too, and probably requires a rethink of how we're dealing with this, but for now, this will do as a band-aid. --- src/block/RuntimeBlockStateRegistry.php | 4 ++++ .../convert/BlockStateToObjectDeserializer.php | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/block/RuntimeBlockStateRegistry.php b/src/block/RuntimeBlockStateRegistry.php index d13b942ba..a458b3368 100644 --- a/src/block/RuntimeBlockStateRegistry.php +++ b/src/block/RuntimeBlockStateRegistry.php @@ -209,6 +209,10 @@ class RuntimeBlockStateRegistry{ return $block; } + public function hasStateId(int $stateId) : bool{ + return isset($this->fullList[$stateId]); + } + /** * @return Block[] * @phpstan-return array diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 2ea2ecf8c..ed45a47d3 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -33,6 +33,7 @@ use pocketmine\block\DoublePitcherCrop; use pocketmine\block\Opaque; use pocketmine\block\PinkPetals; use pocketmine\block\PitcherCrop; +use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\block\Slab; use pocketmine\block\Stair; use pocketmine\block\SweetBerryBush; @@ -97,11 +98,21 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ public function deserialize(BlockStateData $stateData) : int{ if(count($stateData->getStates()) === 0){ //if a block has zero properties, we can keep a map of string ID -> internal blockstate ID - return $this->simpleCache[$stateData->getName()] ??= $this->deserializeBlock($stateData)->getStateId(); + return $this->simpleCache[$stateData->getName()] ??= $this->deserializeToStateId($stateData); } //we can't cache blocks that have properties - go ahead and deserialize the slow way - return $this->deserializeBlock($stateData)->getStateId(); + return $this->deserializeToStateId($stateData); + } + + private function deserializeToStateId(BlockStateData $stateData) : int{ + $stateId = $this->deserializeBlock($stateData)->getStateId(); + //plugin devs seem to keep missing this and causing core crashes, so we need to verify this at the earliest + //available opportunity + if(!RuntimeBlockStateRegistry::getInstance()->hasStateId($stateId)){ + throw new \LogicException("State ID $stateId returned by deserializer for " . $stateData->getName() . " is not registered in RuntimeBlockStateRegistry"); + } + return $stateId; } /** @phpstan-param \Closure(Reader) : Block $c */ From 1c6a4bde86a0083bce4c85dffd256e534c621a1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 13:20:59 +0000 Subject: [PATCH 161/334] Bump pocketmine/locale-data in the production-patch-updates group (#6656) --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 01b9ea5df..cd4490296 100644 --- a/composer.lock +++ b/composer.lock @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.24.0", + "version": "2.24.1", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "6ec5e92c77a2102b2692763733e4763012facae9" + "reference": "8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9", - "reference": "6ec5e92c77a2102b2692763733e4763012facae9", + "url": "https://api.github.com/repos/pmmp/Language/zipball/8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5", + "reference": "8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.24.0" + "source": "https://github.com/pmmp/Language/tree/2.24.1" }, - "time": "2025-02-16T20:46:34+00:00" + "time": "2025-03-16T19:04:15+00:00" }, { "name": "pocketmine/log", @@ -2954,5 +2954,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From de26ebd12490491a0c364dd2ae1017ef6150030a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 22 Mar 2025 17:59:20 +0000 Subject: [PATCH 162/334] Prepare 5.26.0 release --- changelogs/5.26.md | 71 +++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +-- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.26.md diff --git a/changelogs/5.26.md b/changelogs/5.26.md new file mode 100644 index 000000000..8fa5cb35d --- /dev/null +++ b/changelogs/5.26.md @@ -0,0 +1,71 @@ +# 5.26.0 +Released 22nd March 2025. + +This is a minor feature release focused on performance improvements. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## Performance +- Significantly improved performance of entity movement. Load testing with item entities showed a 3x increase in the number of entities supported without lag. +- Significantly improved performance of on-ground checks for player movement. This still needs further work, but optimisations implemented in this version should improve performance substantially. +- Updated `pocketmine/nbt` dependency with performance improvements to `TAG_Compound` and `TAG_List` comparison. This should improve performance of inventory-related actions. +- `InventoryTransaction` now avoids useless item clones when processing transactions, which should improve performance of inventory-related actions. + +## Dependencies +- `pocketmine/bedrock-protocol` has been updated to `36.2.0`, which adds new functions to access some packet fields. +- `pocketmine/nbt` has been updated to `1.1.0`, which improves performance when comparing NBT object trees. + +## Gameplay +- Block break progress now takes into account the following: jumping, being in water, haste, mining fatigue + +## Tools +- `blockstate-upgrade-schema-utils.php` now has a new `dump-table` command, which turns a `.bin` palette table file into human-readable text for debugging. + +## API +### `pocketmine\block` +- The following methods have been added: + - `public RuntimeBlockStateRegistry->hasStateId(int $stateId) : bool` - checks whether the given state ID is registered + +### `pocketmine\crafting` +- The following methods have been deprecated: + - `CraftingManager::sort()` - this was implicitly internal anyway + +### `pocketmine\utils` +- The following constants have been added: + - `TextFormat::MATERIAL_RESIN` +- The following static properties have been added: + - `Terminal::$COLOR_MATERIAL_RESIN` + +### `pocketmine\data\bedrock\block` +- `BlockStateToObjectDeserializer` now permits overriding **deserializers** for Bedrock IDs. This may be useful to implement custom state handling, or to implement missing block variants (such as snow cauldron). + - This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**. + - Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts. + - If you want to make a custom version of a vanilla block, create a custom type ID for it, exactly as you would for a regular custom block. +- The following methods have been added: + - `public BlockStateToObjectDeserializer->getDeserializerForId(string $id) : ?(\Closure(BlockStateReader) : Block)` + +### `pocketmine\data\bedrock\item` +- `ItemDeserializer` now permits overriding **deserializers** for Bedrock IDs. As above, this may be useful to implement custom data handling, or to implement missing variants of existing items. + - This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**. + - Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts. + - As above, if you want to make a custom version of a vanilla item, create a custom type ID for it, exactly as you would for a regular custom item. +- The following methods have been added: + - `public ItemDeserializer->getDeserializerForId(string $id) : ?(\Closure(SavedItemData) : Item)` + +## Internals +- `new $class` is now banned on new internals code by a PHPStan rule. Closures or factory objects should be used instead for greater flexibility and better static analysis. +- `CraftingManager` now uses a more stable hash function for recipe output filtering. +- `ChunkCache` now accepts `int $dimensionId` in the constructor. This may be useful for plugins which implement the nether. +- `RuntimeBlockStateRegistry` now precomputes basic collision info about known states for fast paths. + - This permits specialization for common shapes like cubes and collisionless blocks, which allows skipping complex logic in entity movement calculation. This vastly improves performance. + - Any block whose class overrides `readStateFromWorld()` or `getModelPositionOffset()` will *not* be optimised. + - `Block->recalculateCollisionBoxes()` now has a hard requirement not to depend on anything other than available properties. It must not use `World` or its position. + - This change was problematic for `ChorusPlant`, which used nearby blocks to calculate its collision boxes. + - Blocks which need nearby blocks should override `readStateFromWorld()` and set dynamic state properties, similar to fences. + - This design flaw will be corrected with a major change to `Block` internals currently in planning for a future major version. +- `Block->getCollisionBoxes()` may not be called at all during gameplay for blocks with shapes determined to be simple, like cubes and collisionless blocks. +- `BlockStateToObjectDeserializer` now checks if the returned blockstate is registered in `RuntimeBlockStateRegistry` to promote earlier error detection (instead of crashing in random code paths). diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 569e457da..c50af5023 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.25.3"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.26.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From f123df5e0d7b79fd3a239419eca8f6a9b36d1866 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 22 Mar 2025 18:16:33 +0000 Subject: [PATCH 163/334] changelog: use more accurate terminology --- changelogs/5.26.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/5.26.md b/changelogs/5.26.md index 8fa5cb35d..f8adc969f 100644 --- a/changelogs/5.26.md +++ b/changelogs/5.26.md @@ -20,7 +20,7 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - `pocketmine/nbt` has been updated to `1.1.0`, which improves performance when comparing NBT object trees. ## Gameplay -- Block break progress now takes into account the following: jumping, being in water, haste, mining fatigue +- Block breaking animation speed now takes into account the following: jumping, being in water, haste, mining fatigue ## Tools - `blockstate-upgrade-schema-utils.php` now has a new `dump-table` command, which turns a `.bin` palette table file into human-readable text for debugging. From c80a4d5b550b6e2ecbcfecb041477309fb8a0e24 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 18:33:19 +0000 Subject: [PATCH 164/334] 5.26.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14011298411 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index c50af5023..8b9b68faf 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.26.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.26.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From c9e85603b09b5f727440fa74b9af69064068e99c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:43:29 +0000 Subject: [PATCH 165/334] Bump phpstan/phpstan-strict-rules in the development-patch-updates group (#6664) --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index cb1a3e39f..b8c45c248 100644 --- a/composer.lock +++ b/composer.lock @@ -1482,16 +1482,16 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba" + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba", - "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", "shasum": "" }, "require": { @@ -1524,9 +1524,9 @@ "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/2.0.3" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4" }, - "time": "2025-01-21T10:52:14+00:00" + "time": "2025-03-18T11:42:40+00:00" }, { "name": "phpunit/php-code-coverage", From 687112f4cd5cf5ff6fe591495040c86b476bfeda Mon Sep 17 00:00:00 2001 From: Dries C Date: Thu, 27 Mar 2025 16:48:19 +0100 Subject: [PATCH 166/334] 5.27.0, Bedrock 1.21.70 support (#6665) Co-authored-by: Dylan K. Taylor --- changelogs/5.27.md | 18 ++++++++++++ composer.json | 4 +-- composer.lock | 28 +++++++++---------- src/VersionInfo.php | 4 +-- src/data/bedrock/block/BlockStateData.php | 4 +-- src/data/bedrock/block/BlockTypeNames.php | 7 +++++ src/data/bedrock/item/ItemTypeNames.php | 2 ++ .../mcpe/handler/InGamePacketHandler.php | 5 ---- src/world/format/io/data/BedrockWorldData.php | 6 ++-- src/world/sound/EntityAttackNoDamageSound.php | 3 +- src/world/sound/EntityAttackSound.php | 3 +- src/world/sound/EntityLandSound.php | 3 +- src/world/sound/EntityLongFallSound.php | 3 +- src/world/sound/EntityShortFallSound.php | 3 +- src/world/sound/ThrowSound.php | 2 +- src/world/sound/WaterSplashSound.php | 3 +- 16 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 changelogs/5.27.md diff --git a/changelogs/5.27.md b/changelogs/5.27.md new file mode 100644 index 000000000..f3923d466 --- /dev/null +++ b/changelogs/5.27.md @@ -0,0 +1,18 @@ +# 5.27.0 +Released 27th March 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.70. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## Interim releases +If you're upgrading from 5.25.x directly to 5.27.0, please also read the following changelogs, as the interim releases contain important changes: +- [5.26.0](https://github.com/pmmp/PocketMine-MP/blob/5.26.0/changelogs/5.26.md#5260) - Performance improvements and other internal improvements + +## General +- Aded support for Minecraft: Bedrock Edition 1.21.70. +- Removed support for earlier versions. diff --git a/composer.json b/composer.json index 74441af76..8da46090b 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60", + "pocketmine/bedrock-data": "~4.1.0+bedrock-1.21.70", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~36.2.0+bedrock-1.21.60", + "pocketmine/bedrock-protocol": "~37.0.0+bedrock-1.21.70", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index b8c45c248..405a92414 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": "6314ae9a7919042f10bd17f0a3ef6e9d", + "content-hash": "28b4de9a23a293646dbad2707cdfd9e0", "packages": [ { "name": "adhocore/json-comment", @@ -204,16 +204,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "4.0.0+bedrock-1.21.60", + "version": "4.1.0+bedrock-1.21.70", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e" + "reference": "d53fe98cb3b596ac016e275df5bd5e89b04a4817" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e", - "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/d53fe98cb3b596ac016e275df5bd5e89b04a4817", + "reference": "d53fe98cb3b596ac016e275df5bd5e89b04a4817", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.70" }, - "time": "2025-02-16T15:56:56+00:00" + "time": "2025-03-25T19:43:31+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "36.2.0+bedrock-1.21.60", + "version": "37.0.0+bedrock-1.21.70", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "6830e8a9ee9ecb6984b7b02f412473136ae92d50" + "reference": "7091dad2c12ed4a4106432df21fc698960c6be9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6830e8a9ee9ecb6984b7b02f412473136ae92d50", - "reference": "6830e8a9ee9ecb6984b7b02f412473136ae92d50", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/7091dad2c12ed4a4106432df21fc698960c6be9e", + "reference": "7091dad2c12ed4a4106432df21fc698960c6be9e", "shasum": "" }, "require": { @@ -296,9 +296,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/36.2.0+bedrock-1.21.60" + "source": "https://github.com/pmmp/BedrockProtocol/tree/37.0.0+bedrock-1.21.70" }, - "time": "2025-03-14T17:17:21+00:00" + "time": "2025-03-27T15:19:36+00:00" }, { "name": "pocketmine/binaryutils", @@ -2954,5 +2954,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 8b9b68faf..a51a077ac 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.26.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.27.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** diff --git a/src/data/bedrock/block/BlockStateData.php b/src/data/bedrock/block/BlockStateData.php index e90410ac7..600eba938 100644 --- a/src/data/bedrock/block/BlockStateData.php +++ b/src/data/bedrock/block/BlockStateData.php @@ -45,8 +45,8 @@ final class BlockStateData{ public const CURRENT_VERSION = (1 << 24) | //major (21 << 16) | //minor - (60 << 8) | //patch - (33); //revision + (70 << 8) | //patch + (1); //revision public const TAG_NAME = "name"; public const TAG_STATES = "states"; diff --git a/src/data/bedrock/block/BlockTypeNames.php b/src/data/bedrock/block/BlockTypeNames.php index eec1ab8d1..bc30800fc 100644 --- a/src/data/bedrock/block/BlockTypeNames.php +++ b/src/data/bedrock/block/BlockTypeNames.php @@ -175,7 +175,9 @@ final class BlockTypeNames{ public const BUBBLE_CORAL_FAN = "minecraft:bubble_coral_fan"; public const BUBBLE_CORAL_WALL_FAN = "minecraft:bubble_coral_wall_fan"; public const BUDDING_AMETHYST = "minecraft:budding_amethyst"; + public const BUSH = "minecraft:bush"; public const CACTUS = "minecraft:cactus"; + public const CACTUS_FLOWER = "minecraft:cactus_flower"; public const CAKE = "minecraft:cake"; public const CALCITE = "minecraft:calcite"; public const CALIBRATED_SCULK_SENSOR = "minecraft:calibrated_sculk_sensor"; @@ -545,6 +547,7 @@ final class BlockTypeNames{ public const FIRE_CORAL_BLOCK = "minecraft:fire_coral_block"; public const FIRE_CORAL_FAN = "minecraft:fire_coral_fan"; public const FIRE_CORAL_WALL_FAN = "minecraft:fire_coral_wall_fan"; + public const FIREFLY_BUSH = "minecraft:firefly_bush"; public const FLETCHING_TABLE = "minecraft:fletching_table"; public const FLOWER_POT = "minecraft:flower_pot"; public const FLOWERING_AZALEA = "minecraft:flowering_azalea"; @@ -685,6 +688,7 @@ final class BlockTypeNames{ public const LARGE_AMETHYST_BUD = "minecraft:large_amethyst_bud"; public const LARGE_FERN = "minecraft:large_fern"; public const LAVA = "minecraft:lava"; + public const LEAF_LITTER = "minecraft:leaf_litter"; public const LECTERN = "minecraft:lectern"; public const LEVER = "minecraft:lever"; public const LIGHT_BLOCK_0 = "minecraft:light_block_0"; @@ -1043,6 +1047,7 @@ final class BlockTypeNames{ public const SEA_LANTERN = "minecraft:sea_lantern"; public const SEA_PICKLE = "minecraft:sea_pickle"; public const SEAGRASS = "minecraft:seagrass"; + public const SHORT_DRY_GRASS = "minecraft:short_dry_grass"; public const SHORT_GRASS = "minecraft:short_grass"; public const SHROOMLIGHT = "minecraft:shroomlight"; public const SILVER_GLAZED_TERRACOTTA = "minecraft:silver_glazed_terracotta"; @@ -1140,6 +1145,7 @@ final class BlockTypeNames{ public const SUSPICIOUS_GRAVEL = "minecraft:suspicious_gravel"; public const SUSPICIOUS_SAND = "minecraft:suspicious_sand"; public const SWEET_BERRY_BUSH = "minecraft:sweet_berry_bush"; + public const TALL_DRY_GRASS = "minecraft:tall_dry_grass"; public const TALL_GRASS = "minecraft:tall_grass"; public const TARGET = "minecraft:target"; public const TINTED_GLASS = "minecraft:tinted_glass"; @@ -1267,6 +1273,7 @@ final class BlockTypeNames{ public const WHITE_TERRACOTTA = "minecraft:white_terracotta"; public const WHITE_TULIP = "minecraft:white_tulip"; public const WHITE_WOOL = "minecraft:white_wool"; + public const WILDFLOWERS = "minecraft:wildflowers"; public const WITHER_ROSE = "minecraft:wither_rose"; public const WITHER_SKELETON_SKULL = "minecraft:wither_skeleton_skull"; public const WOODEN_BUTTON = "minecraft:wooden_button"; diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php index b0d49fc31..ea95d57f0 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -75,6 +75,7 @@ final class ItemTypeNames{ public const BLEACH = "minecraft:bleach"; public const BLUE_BUNDLE = "minecraft:blue_bundle"; public const BLUE_DYE = "minecraft:blue_dye"; + public const BLUE_EGG = "minecraft:blue_egg"; public const BOARD = "minecraft:board"; public const BOAT = "minecraft:boat"; public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg"; @@ -93,6 +94,7 @@ final class ItemTypeNames{ public const BRICK = "minecraft:brick"; public const BROWN_BUNDLE = "minecraft:brown_bundle"; public const BROWN_DYE = "minecraft:brown_dye"; + public const BROWN_EGG = "minecraft:brown_egg"; public const BRUSH = "minecraft:brush"; public const BUCKET = "minecraft:bucket"; public const BUNDLE = "minecraft:bundle"; diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index dba16e1e6..93a01fdcc 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -64,7 +64,6 @@ use pocketmine\network\mcpe\protocol\ItemStackResponsePacket; use pocketmine\network\mcpe\protocol\LabTablePacket; use pocketmine\network\mcpe\protocol\LecternUpdatePacket; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; -use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1; use pocketmine\network\mcpe\protocol\MapInfoRequestPacket; use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket; use pocketmine\network\mcpe\protocol\MobEquipmentPacket; @@ -296,10 +295,6 @@ class InGamePacketHandler extends PacketHandler{ return $packetHandled; } - public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{ - return true; //useless leftover from 1.8 - } - public function handleActorEvent(ActorEventPacket $packet) : bool{ if($packet->actorRuntimeId !== $this->player->getId()){ //TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier) diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index a9ca43bc3..5b1945739 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -51,12 +51,12 @@ use function time; class BedrockWorldData extends BaseNbtWorldData{ public const CURRENT_STORAGE_VERSION = 10; - public const CURRENT_STORAGE_NETWORK_VERSION = 776; + public const CURRENT_STORAGE_NETWORK_VERSION = 786; public const CURRENT_CLIENT_VERSION_TARGET = [ 1, //major 21, //minor - 60, //patch - 33, //revision + 70, //patch + 3, //revision 0 //is beta ]; diff --git a/src/world/sound/EntityAttackNoDamageSound.php b/src/world/sound/EntityAttackNoDamageSound.php index 6804c668e..d46340a28 100644 --- a/src/world/sound/EntityAttackNoDamageSound.php +++ b/src/world/sound/EntityAttackNoDamageSound.php @@ -39,7 +39,8 @@ class EntityAttackNoDamageSound implements Sound{ -1, "minecraft:player", false, - false + false, + -1 )]; } } diff --git a/src/world/sound/EntityAttackSound.php b/src/world/sound/EntityAttackSound.php index bf1877444..28be1e62f 100644 --- a/src/world/sound/EntityAttackSound.php +++ b/src/world/sound/EntityAttackSound.php @@ -39,7 +39,8 @@ class EntityAttackSound implements Sound{ -1, "minecraft:player", false, - false + false, + -1 )]; } } diff --git a/src/world/sound/EntityLandSound.php b/src/world/sound/EntityLandSound.php index 998c5ed02..ef25395cf 100644 --- a/src/world/sound/EntityLandSound.php +++ b/src/world/sound/EntityLandSound.php @@ -46,7 +46,8 @@ class EntityLandSound implements Sound{ TypeConverter::getInstance()->getBlockTranslator()->internalIdToNetworkId($this->blockLandedOn->getStateId()), $this->entity::getNetworkTypeId(), false, //TODO: does isBaby have any relevance here? - false + false, + $this->entity->getId() )]; } } diff --git a/src/world/sound/EntityLongFallSound.php b/src/world/sound/EntityLongFallSound.php index e0dabe3a5..5e0186f8e 100644 --- a/src/world/sound/EntityLongFallSound.php +++ b/src/world/sound/EntityLongFallSound.php @@ -42,7 +42,8 @@ class EntityLongFallSound implements Sound{ -1, $this->entity::getNetworkTypeId(), false, //TODO: is isBaby relevant here? - false + false, + $this->entity->getId() )]; } } diff --git a/src/world/sound/EntityShortFallSound.php b/src/world/sound/EntityShortFallSound.php index 8955c3552..d230294d7 100644 --- a/src/world/sound/EntityShortFallSound.php +++ b/src/world/sound/EntityShortFallSound.php @@ -41,7 +41,8 @@ class EntityShortFallSound implements Sound{ -1, $this->entity::getNetworkTypeId(), false, //TODO: does isBaby have any relevance here? - false + false, + -1 )]; } } diff --git a/src/world/sound/ThrowSound.php b/src/world/sound/ThrowSound.php index ebf64725e..d93ac951b 100644 --- a/src/world/sound/ThrowSound.php +++ b/src/world/sound/ThrowSound.php @@ -30,6 +30,6 @@ use pocketmine\network\mcpe\protocol\types\LevelSoundEvent; class ThrowSound implements Sound{ public function encode(Vector3 $pos) : array{ - return [LevelSoundEventPacket::create(LevelSoundEvent::THROW, $pos, -1, "minecraft:player", false, false)]; + return [LevelSoundEventPacket::create(LevelSoundEvent::THROW, $pos, -1, "minecraft:player", false, false, -1)]; } } diff --git a/src/world/sound/WaterSplashSound.php b/src/world/sound/WaterSplashSound.php index f7b662ec8..b24c10e80 100644 --- a/src/world/sound/WaterSplashSound.php +++ b/src/world/sound/WaterSplashSound.php @@ -42,7 +42,8 @@ final class WaterSplashSound implements Sound{ (int) ($this->volume * 16777215), ":", false, - false + false, + -1 )]; } } From e88b81a4cbd9274aa3b6faa0b3f847c13ca0ecd5 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:49:33 +0000 Subject: [PATCH 167/334] 5.27.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14110940403 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index a51a077ac..0f776dc1d 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.27.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.27.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 673b39e2a10dae212c6a764683aa0a04bd1a618c Mon Sep 17 00:00:00 2001 From: Muqsit Date: Thu, 3 Apr 2025 05:24:50 +0800 Subject: [PATCH 168/334] Internet: remove curl_close() call (#6667) curl_close() has no effect as of php8: https://www.php.net/manual/en/function.curl-close.php --- src/utils/Internet.php | 53 +++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/utils/Internet.php b/src/utils/Internet.php index 4b0e00f4a..febc30715 100644 --- a/src/utils/Internet.php +++ b/src/utils/Internet.php @@ -25,7 +25,6 @@ namespace pocketmine\utils; use pocketmine\VersionInfo; use function array_merge; -use function curl_close; use function curl_error; use function curl_exec; use function curl_getinfo; @@ -217,34 +216,30 @@ class Internet{ CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . VersionInfo::NAME . "/" . VersionInfo::VERSION()->getFullVersion(true)], $extraHeaders), CURLOPT_HEADER => true ]); - try{ - $raw = curl_exec($ch); - if($raw === false){ - throw new InternetException(curl_error($ch)); - } - if(!is_string($raw)) throw new AssumptionFailedError("curl_exec() should return string|false when CURLOPT_RETURNTRANSFER is set"); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); - $rawHeaders = substr($raw, 0, $headerSize); - $body = substr($raw, $headerSize); - $headers = []; - //TODO: explore if we can set these limits lower - foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){ - $headerGroup = []; - foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){ - $nameValue = explode(":", $line, 2); - if(isset($nameValue[1])){ - $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); - } - } - $headers[] = $headerGroup; - } - if($onSuccess !== null){ - $onSuccess($ch); - } - return new InternetRequestResult($headers, $body, $httpCode); - }finally{ - curl_close($ch); + $raw = curl_exec($ch); + if($raw === false){ + throw new InternetException(curl_error($ch)); } + if(!is_string($raw)) throw new AssumptionFailedError("curl_exec() should return string|false when CURLOPT_RETURNTRANSFER is set"); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $rawHeaders = substr($raw, 0, $headerSize); + $body = substr($raw, $headerSize); + $headers = []; + //TODO: explore if we can set these limits lower + foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){ + $headerGroup = []; + foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){ + $nameValue = explode(":", $line, 2); + if(isset($nameValue[1])){ + $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); + } + } + $headers[] = $headerGroup; + } + if($onSuccess !== null){ + $onSuccess($ch); + } + return new InternetRequestResult($headers, $body, $httpCode); } } From 071c15d7de86f3b0c8a7eb5e78417ce77793d9a9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 5 Apr 2025 17:40:48 +0100 Subject: [PATCH 169/334] NetworkSession: immediate-send now causes a buffer flush when the packet is ready instead of skipping queues and forcing sync compression as previously seen. this maintains proper packet order and allows immediate-flush to be used to reduce latency in-game. Small servers won't notice any difference, but for larger ones it may make a difference, since the buffer time effectively depends on the amount of load RakLib is under. closes #3325 --- src/network/mcpe/NetworkSession.php | 47 ++++++++++++----------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 8c457ed40..bea3f8131 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -174,7 +174,7 @@ class NetworkSession{ */ private array $sendBufferAckPromises = []; - /** @phpstan-var \SplQueue>}> */ + /** @phpstan-var \SplQueue>, bool}> */ private \SplQueue $compressedQueue; private bool $forceAsyncCompression = true; private bool $enableCompression = false; //disabled until handshake completed @@ -235,7 +235,7 @@ class NetworkSession{ private function onSessionStartSuccess() : void{ $this->logger->debug("Session start handshake completed, awaiting login packet"); - $this->flushSendBuffer(true); + $this->flushGamePacketQueue(); $this->enableCompression = true; $this->setHandler(new LoginPacketHandler( $this->server, @@ -529,7 +529,7 @@ class NetworkSession{ $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket)); } if($immediate){ - $this->flushSendBuffer(true); + $this->flushGamePacketQueue(); } return true; @@ -577,14 +577,12 @@ class NetworkSession{ $this->sendBuffer[] = $buffer; } - private function flushSendBuffer(bool $immediate = false) : void{ + private function flushGamePacketQueue() : void{ if(count($this->sendBuffer) > 0){ Timings::$playerNetworkSend->startTiming(); try{ $syncMode = null; //automatic - if($immediate){ - $syncMode = true; - }elseif($this->forceAsyncCompression){ + if($this->forceAsyncCompression){ $syncMode = false; } @@ -599,7 +597,9 @@ class NetworkSession{ $this->sendBuffer = []; $ackPromises = $this->sendBufferAckPromises; $this->sendBufferAckPromises = []; - $this->queueCompressedNoBufferFlush($batch, $immediate, $ackPromises); + //these packets were already potentially buffered for up to 50ms - make sure the transport layer doesn't + //delay them any longer + $this->queueCompressedNoGamePacketFlush($batch, networkFlush: true, ackPromises: $ackPromises); }finally{ Timings::$playerNetworkSend->stopTiming(); } @@ -619,8 +619,10 @@ class NetworkSession{ public function queueCompressed(CompressBatchPromise|string $payload, bool $immediate = false) : void{ Timings::$playerNetworkSend->startTiming(); try{ - $this->flushSendBuffer($immediate); //Maintain ordering if possible - $this->queueCompressedNoBufferFlush($payload, $immediate); + //if the next packet causes a flush, avoid unnecessarily flushing twice + //however, if the next packet does *not* cause a flush, game packets should be flushed to avoid delays + $this->flushGamePacketQueue(); + $this->queueCompressedNoGamePacketFlush($payload, $immediate); }finally{ Timings::$playerNetworkSend->stopTiming(); } @@ -631,22 +633,13 @@ class NetworkSession{ * * @phpstan-param list> $ackPromises */ - private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false, array $ackPromises = []) : void{ + private function queueCompressedNoGamePacketFlush(CompressBatchPromise|string $batch, bool $networkFlush = false, array $ackPromises = []) : void{ Timings::$playerNetworkSend->startTiming(); try{ + $this->compressedQueue->enqueue([$batch, $ackPromises, $networkFlush]); if(is_string($batch)){ - if($immediate){ - //Skips all queues - $this->sendEncoded($batch, true, $ackPromises); - }else{ - $this->compressedQueue->enqueue([$batch, $ackPromises]); - $this->flushCompressedQueue(); - } - }elseif($immediate){ - //Skips all queues - $this->sendEncoded($batch->getResult(), true, $ackPromises); + $this->flushCompressedQueue(); }else{ - $this->compressedQueue->enqueue([$batch, $ackPromises]); $batch->onResolve(function() : void{ if($this->connected){ $this->flushCompressedQueue(); @@ -663,14 +656,14 @@ class NetworkSession{ try{ while(!$this->compressedQueue->isEmpty()){ /** @var CompressBatchPromise|string $current */ - [$current, $ackPromises] = $this->compressedQueue->bottom(); + [$current, $ackPromises, $networkFlush] = $this->compressedQueue->bottom(); if(is_string($current)){ $this->compressedQueue->dequeue(); - $this->sendEncoded($current, false, $ackPromises); + $this->sendEncoded($current, $networkFlush, $ackPromises); }elseif($current->hasResult()){ $this->compressedQueue->dequeue(); - $this->sendEncoded($current->getResult(), false, $ackPromises); + $this->sendEncoded($current->getResult(), $networkFlush, $ackPromises); }else{ //can't send any more queued until this one is ready @@ -710,7 +703,7 @@ class NetworkSession{ $this->disconnectGuard = true; $func(); $this->disconnectGuard = false; - $this->flushSendBuffer(true); + $this->flushGamePacketQueue(); $this->sender->close(""); foreach($this->disposeHooks as $callback){ $callback(); @@ -1345,6 +1338,6 @@ class NetworkSession{ Timings::$playerNetworkSendInventorySync->stopTiming(); } - $this->flushSendBuffer(); + $this->flushGamePacketQueue(); } } From 6f3851be80b148d83f696f38f0fe16088877fda6 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 6 Apr 2025 04:52:15 +0100 Subject: [PATCH 170/334] 5.27.1 (#6670) --- changelogs/5.27.md | 6 ++++++ composer.json | 2 +- composer.lock | 18 +++++++++--------- src/VersionInfo.php | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/changelogs/5.27.md b/changelogs/5.27.md index f3923d466..07bc8c26e 100644 --- a/changelogs/5.27.md +++ b/changelogs/5.27.md @@ -16,3 +16,9 @@ If you're upgrading from 5.25.x directly to 5.27.0, please also read the followi ## General - Aded support for Minecraft: Bedrock Edition 1.21.70. - Removed support for earlier versions. + +# 5.27.1 +Released 6th April 2025. + +## Fixes +- Updated RakLib to get ping timestamp handling fixes. diff --git a/composer.json b/composer.json index 8da46090b..77e32e3da 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", "pocketmine/nbt": "~1.1.0", - "pocketmine/raklib": "~1.1.0", + "pocketmine/raklib": "~1.1.2", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", "ramsey/uuid": "~4.7.0", diff --git a/composer.lock b/composer.lock index 405a92414..16dcb5e3d 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": "28b4de9a23a293646dbad2707cdfd9e0", + "content-hash": "8d3061c5cc77e5b1dfa1fcf77f5146c6", "packages": [ { "name": "adhocore/json-comment", @@ -618,16 +618,16 @@ }, { "name": "pocketmine/raklib", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/pmmp/RakLib.git", - "reference": "be2783be516bf6e2872ff5c81fb9048596617b97" + "reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97", - "reference": "be2783be516bf6e2872ff5c81fb9048596617b97", + "url": "https://api.github.com/repos/pmmp/RakLib/zipball/4145a31cd812fe8931c3c9c691fcd2ded2f47e7f", + "reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f", "shasum": "" }, "require": { @@ -639,8 +639,8 @@ "pocketmine/log": "^0.3.0 || ^0.4.0" }, "require-dev": { - "phpstan/phpstan": "1.10.1", - "phpstan/phpstan-strict-rules": "^1.0" + "phpstan/phpstan": "2.1.0", + "phpstan/phpstan-strict-rules": "^2.0" }, "type": "library", "autoload": { @@ -655,9 +655,9 @@ "description": "A RakNet server implementation written in PHP", "support": { "issues": "https://github.com/pmmp/RakLib/issues", - "source": "https://github.com/pmmp/RakLib/tree/1.1.1" + "source": "https://github.com/pmmp/RakLib/tree/1.1.2" }, - "time": "2024-03-04T14:02:14+00:00" + "time": "2025-04-06T03:38:21+00:00" }, { "name": "pocketmine/raklib-ipc", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 0f776dc1d..249a27176 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.27.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From d3f6c22996de69c215b87d51d21514761462e979 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sun, 6 Apr 2025 03:53:08 +0000 Subject: [PATCH 171/334] 5.27.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14288755593 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 249a27176..44238dba3 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.27.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.27.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 835c383d4e126df6f38000e3217ad6a325b7a1f7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 6 Apr 2025 19:59:02 +0100 Subject: [PATCH 172/334] Update Composer dependencies --- composer.json | 2 +- composer.lock | 50 ++++++++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index 77e32e3da..87086f456 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.8", + "phpstan/phpstan": "2.1.11", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index 16dcb5e3d..23f312317 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": "8d3061c5cc77e5b1dfa1fcf77f5146c6", + "content-hash": "818c679a25da8e6b466bc454ad48dec3", "packages": [ { "name": "adhocore/json-comment", @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.24.1", + "version": "2.24.2", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5" + "reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5", - "reference": "8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5", + "url": "https://api.github.com/repos/pmmp/Language/zipball/2a00c44c52bce98e7a43aa31517df78cbb2ba23b", + "reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.24.1" + "source": "https://github.com/pmmp/Language/tree/2.24.2" }, - "time": "2025-03-16T19:04:15+00:00" + "time": "2025-04-03T01:23:27+00:00" }, { "name": "pocketmine/log", @@ -742,16 +742,16 @@ }, { "name": "ramsey/collection", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -812,9 +812,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2025-03-02T04:48:29+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -1373,16 +1373,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.8", + "version": "2.1.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" + "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", - "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", "shasum": "" }, "require": { @@ -1427,20 +1427,20 @@ "type": "github" } ], - "time": "2025-03-09T09:30:48+00:00" + "time": "2025-03-24T13:45:00+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.4", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "d09e152f403c843998d7a52b5d87040c937525dd" + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd", - "reference": "d09e152f403c843998d7a52b5d87040c937525dd", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", "shasum": "" }, "require": { @@ -1451,7 +1451,9 @@ "phpunit/phpunit": "<7.0" }, "require-dev": { + "nikic/php-parser": "^5", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6" }, @@ -1476,9 +1478,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6" }, - "time": "2025-01-22T13:07:38+00:00" + "time": "2025-03-26T12:47:06+00:00" }, { "name": "phpstan/phpstan-strict-rules", From f661443ec79d44bbe850e18b58bc0d670dfe093a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 15 Apr 2025 16:48:13 +0100 Subject: [PATCH 173/334] Update Ubuntu base image for GitHub Actions --- .github/workflows/build-docker-image.yml | 2 +- .github/workflows/draft-release-pr-check.yml | 4 ++-- .github/workflows/draft-release.yml | 6 +++--- .github/workflows/main-php-matrix.yml | 2 +- .github/workflows/main.yml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 6199ad7a9..83d568878 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Update Docker Hub images - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Set up Docker Buildx diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index 131c0dde2..303f61ccf 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -24,7 +24,7 @@ permissions: jobs: check-intent: name: Check release trigger - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }} @@ -43,7 +43,7 @@ jobs: #don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses if: needs.check-intent.outputs.valid == 'true' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index d2e9eb0d0..02cdeec6f 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -23,7 +23,7 @@ env: jobs: skip: name: Check whether to ignore this tag - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: skip: ${{ steps.exists.outputs.exists == 'true' }} @@ -54,7 +54,7 @@ jobs: needs: [check] if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Generate access token @@ -79,7 +79,7 @@ jobs: needs: [check] if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml index e26f7c318..015a33188 100644 --- a/.github/workflows/main-php-matrix.yml +++ b/.github/workflows/main-php-matrix.yml @@ -15,7 +15,7 @@ on: type: number image: description: 'Runner image to use' - default: 'ubuntu-20.04' + default: 'ubuntu-22.04' type: string jobs: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 571868747..051a3a790 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: codestyle: name: Code Style checks - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -40,7 +40,7 @@ jobs: shellcheck: name: ShellCheck - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false From 028815490ee69885bd5c9d2b1ef620ca8f0df4b7 Mon Sep 17 00:00:00 2001 From: zSALLAZAR <59490940+zSALLAZAR@users.noreply.github.com> Date: Fri, 18 Apr 2025 11:19:46 +0200 Subject: [PATCH 174/334] Add EntityExtinguishEvent (#6671) --- src/block/Water.php | 3 +- src/block/WaterCauldron.php | 3 +- src/entity/Entity.php | 10 ++-- src/event/entity/EntityExtinguishEvent.php | 53 ++++++++++++++++++++++ src/player/Player.php | 3 +- 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/event/entity/EntityExtinguishEvent.php diff --git a/src/block/Water.php b/src/block/Water.php index b711ab5a1..44759783a 100644 --- a/src/block/Water.php +++ b/src/block/Water.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\entity\Entity; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\world\sound\BucketEmptyWaterSound; use pocketmine\world\sound\BucketFillWaterSound; use pocketmine\world\sound\Sound; @@ -53,7 +54,7 @@ class Water extends Liquid{ public function onEntityInside(Entity $entity) : bool{ $entity->resetFallDistance(); if($entity->isOnFire()){ - $entity->extinguish(); + $entity->extinguish(EntityExtinguishEvent::CAUSE_WATER); } return true; } diff --git a/src/block/WaterCauldron.php b/src/block/WaterCauldron.php index e470aa6cb..8129f2960 100644 --- a/src/block/WaterCauldron.php +++ b/src/block/WaterCauldron.php @@ -27,6 +27,7 @@ use pocketmine\block\tile\Cauldron as TileCauldron; use pocketmine\block\utils\DyeColor; use pocketmine\color\Color; use pocketmine\entity\Entity; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\item\Armor; use pocketmine\item\Banner; use pocketmine\item\Dye; @@ -183,7 +184,7 @@ final class WaterCauldron extends FillableCauldron{ public function onEntityInside(Entity $entity) : bool{ if($entity->isOnFire()){ - $entity->extinguish(); + $entity->extinguish(EntityExtinguishEvent::CAUSE_WATER_CAULDRON); //TODO: particles $this->position->getWorld()->setBlock($this->position, $this->withFillLevel($this->getFillLevel() - self::ENTITY_EXTINGUISH_USE_AMOUNT)); diff --git a/src/entity/Entity.php b/src/entity/Entity.php index e24c6067c..1637e0755 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -31,6 +31,7 @@ use pocketmine\block\Water; use pocketmine\entity\animation\Animation; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDespawnEvent; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\event\entity\EntityMotionEvent; use pocketmine\event\entity\EntityRegainHealthEvent; use pocketmine\event\entity\EntitySpawnEvent; @@ -709,7 +710,10 @@ abstract class Entity{ } } - public function extinguish() : void{ + public function extinguish(int $cause = EntityExtinguishEvent::CAUSE_CUSTOM) : void{ + $ev = new EntityExtinguishEvent($this, $cause); + $ev->call(); + $this->fireTicks = 0; $this->networkPropertiesDirty = true; } @@ -720,7 +724,7 @@ abstract class Entity{ protected function doOnFireTick(int $tickDiff = 1) : bool{ if($this->isFireProof() && $this->isOnFire()){ - $this->extinguish(); + $this->extinguish(EntityExtinguishEvent::CAUSE_FIRE_PROOF); return false; } @@ -731,7 +735,7 @@ abstract class Entity{ } if(!$this->isOnFire()){ - $this->extinguish(); + $this->extinguish(EntityExtinguishEvent::CAUSE_TICKING); }else{ return true; } diff --git a/src/event/entity/EntityExtinguishEvent.php b/src/event/entity/EntityExtinguishEvent.php new file mode 100644 index 000000000..b39d12231 --- /dev/null +++ b/src/event/entity/EntityExtinguishEvent.php @@ -0,0 +1,53 @@ + + */ +class EntityExtinguishEvent extends EntityEvent{ + public const CAUSE_CUSTOM = 0; + public const CAUSE_WATER = 1; + public const CAUSE_WATER_CAULDRON = 2; + public const CAUSE_RESPAWN = 3; + public const CAUSE_FIRE_PROOF = 4; + public const CAUSE_TICKING = 5; + public const CAUSE_RAIN = 6; + public const CAUSE_POWDER_SNOW = 7; + + public function __construct( + Entity $entity, + private int $cause + ){ + $this->entity = $entity; + } + + public function getCause() : int{ + return $this->cause; + } +} diff --git a/src/player/Player.php b/src/player/Player.php index 1c67b7182..3c494b980 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -45,6 +45,7 @@ use pocketmine\entity\projectile\Arrow; use pocketmine\entity\Skin; use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\event\inventory\InventoryCloseEvent; use pocketmine\event\inventory\InventoryOpenEvent; use pocketmine\event\player\PlayerBedEnterEvent; @@ -2546,7 +2547,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->setSneaking(false); $this->setFlying(false); - $this->extinguish(); + $this->extinguish(EntityExtinguishEvent::CAUSE_RESPAWN); $this->setAirSupplyTicks($this->getMaxAirSupplyTicks()); $this->deadTicks = 0; $this->noDamageTicks = 60; From 2548422973a4b6f4417f9fe9b0e02164991ad583 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 20 Apr 2025 16:44:23 +0100 Subject: [PATCH 175/334] AvailableEnchantmentRegistry: reject non-string tags fixes https://crash.pmmp.io/view/12627328 --- src/item/enchantment/AvailableEnchantmentRegistry.php | 3 +++ src/utils/Utils.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php index eed7bff52..2d8dafa4b 100644 --- a/src/item/enchantment/AvailableEnchantmentRegistry.php +++ b/src/item/enchantment/AvailableEnchantmentRegistry.php @@ -28,6 +28,7 @@ use pocketmine\item\enchantment\ItemEnchantmentTags as Tags; use pocketmine\item\enchantment\VanillaEnchantments as Enchantments; use pocketmine\item\Item; use pocketmine\utils\SingletonTrait; +use pocketmine\utils\Utils; use function array_filter; use function array_values; use function count; @@ -129,6 +130,7 @@ final class AvailableEnchantmentRegistry{ if(!$this->isRegistered($enchantment)){ throw new \LogicException("Cannot set primary item tags for non-registered enchantment"); } + Utils::validateArrayValueType($tags, fn(string $v) => 1); $this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags); } @@ -152,6 +154,7 @@ final class AvailableEnchantmentRegistry{ if(!$this->isRegistered($enchantment)){ throw new \LogicException("Cannot set secondary item tags for non-registered enchantment"); } + Utils::validateArrayValueType($tags, fn(string $v) => 1); $this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags); } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 046296cf4..800bd0183 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -584,7 +584,7 @@ final class Utils{ /** * @phpstan-template TMemberType * @phpstan-param array $array - * @phpstan-param \Closure(TMemberType) : void $validator + * @phpstan-param \Closure(TMemberType) : mixed $validator */ public static function validateArrayValueType(array $array, \Closure $validator) : void{ foreach(Utils::promoteKeys($array) as $k => $v){ From 4a5c1e75407f9cf8b37795210fc0957f9a37059a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 20 Apr 2025 16:57:44 +0100 Subject: [PATCH 176/334] Entity: truncate fire ticks instead of throwing exceptions as written in the comments, it's not reasonable to propagate this limitation, since it ultimately comes from a shortfall in the Mojang save format, not a limitation of PM's capability. It's also not obvious how this would be propagated to the likes of setOnFire(), as this would translate into a max time of 1638 seconds, a value no one is going to remember. There's a case to be made for truncating this on save rather than on initial set, but this is at least better than having Fire Aspect level 1000 cause crashes and whatever other gameplay logic that would have to work around this stupid limitation. --- src/entity/Entity.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/entity/Entity.php b/src/entity/Entity.php index e24c6067c..97cdc19de 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -60,6 +60,7 @@ use pocketmine\player\Player; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; +use pocketmine\utils\Limits; use pocketmine\utils\Utils; use pocketmine\VersionInfo; use pocketmine\world\format\Chunk; @@ -700,9 +701,16 @@ abstract class Entity{ * @throws \InvalidArgumentException */ public function setFireTicks(int $fireTicks) : void{ - if($fireTicks < 0 || $fireTicks > 0x7fff){ - throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks"); + if($fireTicks < 0){ + throw new \InvalidArgumentException("Fire ticks cannot be negative"); } + + //Since the max value is not externally obvious or intuitive, many plugins use this without being aware that + //reasonably large values are not accepted. We even have such usages within PM itself. It doesn't make sense + //to force all those calls to be aware of this limitation, as it's not a functional limit but a limitation of + //the Mojang save format. Truncating this to the max acceptable value is the next best thing we can do. + $fireTicks = min($fireTicks, Limits::INT16_MAX); + if(!$this->isFireProof()){ $this->fireTicks = $fireTicks; $this->networkPropertiesDirty = true; From 1ea5c060fdd23023857cdc7f874a6562d117a72d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 20 Apr 2025 18:16:54 +0100 Subject: [PATCH 177/334] bruh --- src/entity/Entity.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 97cdc19de..6681558ad 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -77,6 +77,7 @@ use function floatval; use function floor; use function fmod; use function get_class; +use function min; use function sin; use function spl_object_id; use const M_PI_2; From ad6f7dfedb31a299d6d9c30961a76acf1f888f65 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 20 Apr 2025 19:48:28 +0100 Subject: [PATCH 178/334] World: verify saveability of blocks, entities and tiles at entry points I want to do the same for items, but items are going to be a pain in the ass. For items there are multiple possible entry points and all of them will need to be checked: - dropped items - inventory contents - lecterns - item frames I don't see a good way to deal with all these. We can't check for registration in the constructor because we need to fully construct the item in order to register it. Blocks are also a potential issue in other areas, but setBlock() is definitely the biggest offender. --- src/block/tile/TileFactory.php | 7 +++++++ .../convert/BlockObjectToStateSerializer.php | 4 ++++ src/entity/EntityFactory.php | 7 +++++++ src/world/World.php | 17 +++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/src/block/tile/TileFactory.php b/src/block/tile/TileFactory.php index 515dd8c63..26e0af6a5 100644 --- a/src/block/tile/TileFactory.php +++ b/src/block/tile/TileFactory.php @@ -114,6 +114,13 @@ final class TileFactory{ $this->saveNames[$className] = reset($saveNames); } + /** + * @phpstan-param class-string $class + */ + public function isRegistered(string $class) : bool{ + return isset($this->saveNames[$class]); + } + /** * @internal * @throws SavedDataLoadingException diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 367d38449..45784d409 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -225,6 +225,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return $this->cache[$stateId] ??= $this->serializeBlock(RuntimeBlockStateRegistry::getInstance()->fromStateId($stateId)); } + public function isRegistered(Block $block) : bool{ + return isset($this->serializers[$block->getTypeId()]); + } + /** * @phpstan-template TBlockType of Block * @phpstan-param TBlockType $block diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 03d9c03e6..970fd986f 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -219,6 +219,13 @@ final class EntityFactory{ $this->saveNames[$className] = reset($saveNames); } + /** + * @phpstan-param class-string $class + */ + public function isRegistered(string $class) : bool{ + return isset($this->saveNames[$class]); + } + /** * Creates an entity from data stored on a chunk. * diff --git a/src/world/World.php b/src/world/World.php index 3a7d0c538..afd01c628 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2047,6 +2047,15 @@ class World implements ChunkManager{ throw new WorldException("Cannot set a block in un-generated terrain"); } + //TODO: this computes state ID twice (we do it again in writeStateToWorld()). Not great for performance :( + $stateId = $block->getStateId(); + if(!$this->blockStateRegistry->hasStateId($stateId)){ + throw new \LogicException("Block state ID not known to RuntimeBlockStateRegistry (probably not registered)"); + } + if(!GlobalBlockStateHandlers::getSerializer()->isRegistered($block)){ + throw new \LogicException("Block not registered with GlobalBlockStateHandlers serializer"); + } + $this->timings->setBlock->startTiming(); $this->unlockChunk($chunkX, $chunkZ, null); @@ -2769,6 +2778,11 @@ class World implements ChunkManager{ throw new AssumptionFailedError("Found two different entities sharing entity ID " . $entity->getId()); } } + if(!EntityFactory::getInstance()->isRegistered($entity::class)){ + //canSaveWithChunk is mutable, so that means it could be toggled after adding the entity and cause a crash + //later on. Better we just force all entities to have a save ID, even if it might not be needed. + throw new \LogicException("Entity " . $entity::class . " is not registered for a save ID in EntityFactory"); + } $pos = $entity->getPosition()->asVector3(); $this->entitiesByChunk[World::chunkHash($pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE)][$entity->getId()] = $entity; $this->entityLastKnownPositions[$entity->getId()] = $pos; @@ -2870,6 +2884,9 @@ class World implements ChunkManager{ if(!$this->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ())){ throw new \InvalidArgumentException("Tile position is outside the world bounds"); } + if(!TileFactory::getInstance()->isRegistered($tile::class)){ + throw new \LogicException("Tile " . $tile::class . " is not registered for a save ID in TileFactory"); + } $chunkX = $pos->getFloorX() >> Chunk::COORD_BIT_SIZE; $chunkZ = $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE; From fe70b3188138e3262adad6e96d3f4cadd6b6c144 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 26 Apr 2025 22:11:03 +0100 Subject: [PATCH 179/334] Fix crash when a player is added to the world --- src/world/World.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/world/World.php b/src/world/World.php index afd01c628..7ffe8e00b 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2778,7 +2778,7 @@ class World implements ChunkManager{ throw new AssumptionFailedError("Found two different entities sharing entity ID " . $entity->getId()); } } - if(!EntityFactory::getInstance()->isRegistered($entity::class)){ + if(!EntityFactory::getInstance()->isRegistered($entity::class) && !$entity instanceof Player){ //canSaveWithChunk is mutable, so that means it could be toggled after adding the entity and cause a crash //later on. Better we just force all entities to have a save ID, even if it might not be needed. throw new \LogicException("Entity " . $entity::class . " is not registered for a save ID in EntityFactory"); From efaf9311b332e49dc643de5237b73b1613e627cc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 30 Apr 2025 17:38:16 +0100 Subject: [PATCH 180/334] Extract population business logic from PopulationTask --- src/world/generator/PopulationTask.php | 40 +++---------- src/world/generator/PopulationUtils.php | 74 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 31 deletions(-) create mode 100644 src/world/generator/PopulationUtils.php diff --git a/src/world/generator/PopulationTask.php b/src/world/generator/PopulationTask.php index bad134324..a8366a306 100644 --- a/src/world/generator/PopulationTask.php +++ b/src/world/generator/PopulationTask.php @@ -27,8 +27,6 @@ use pocketmine\scheduler\AsyncTask; use pocketmine\utils\AssumptionFailedError; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\FastChunkSerializer; -use pocketmine\world\SimpleChunkManager; -use pocketmine\world\World; use function array_map; use function igbinary_serialize; use function igbinary_unserialize; @@ -71,8 +69,6 @@ class PopulationTask extends AsyncTask{ if($context === null){ throw new AssumptionFailedError("Generator context should have been initialized before any PopulationTask execution"); } - $generator = $context->getGenerator(); - $manager = new SimpleChunkManager($context->getWorldMinY(), $context->getWorldMaxY()); $chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null; @@ -93,21 +89,15 @@ class PopulationTask extends AsyncTask{ $serialChunks ); - self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk); - - $resultChunks = []; //this is just to keep phpstan's type inference happy - foreach($chunks as $relativeChunkHash => $c){ - World::getXZ($relativeChunkHash, $relativeX, $relativeZ); - $resultChunks[$relativeChunkHash] = self::setOrGenerateChunk($manager, $generator, $this->chunkX + $relativeX, $this->chunkZ + $relativeZ, $c); - } - $chunks = $resultChunks; - - $generator->populateChunk($manager, $this->chunkX, $this->chunkZ); - $chunk = $manager->getChunk($this->chunkX, $this->chunkZ); - if($chunk === null){ - throw new AssumptionFailedError("We just generated this chunk, so it must exist"); - } - $chunk->setPopulated(); + [$chunk, $chunks] = PopulationUtils::populateChunkWithAdjacents( + $context->getWorldMinY(), + $context->getWorldMaxY(), + $context->getGenerator(), + $this->chunkX, + $this->chunkZ, + $chunk, + $chunks + ); $this->chunk = FastChunkSerializer::serializeTerrain($chunk); @@ -118,18 +108,6 @@ class PopulationTask extends AsyncTask{ $this->adjacentChunks = igbinary_serialize($serialChunks) ?? throw new AssumptionFailedError("igbinary_serialize() returned null"); } - private static function setOrGenerateChunk(SimpleChunkManager $manager, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $chunk) : Chunk{ - $manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], false)); - if($chunk === null){ - $generator->generateChunk($manager, $chunkX, $chunkZ); - $chunk = $manager->getChunk($chunkX, $chunkZ); - if($chunk === null){ - throw new AssumptionFailedError("We just set this chunk, so it must exist"); - } - } - return $chunk; - } - public function onCompletion() : void{ /** * @var \Closure $onCompletion diff --git a/src/world/generator/PopulationUtils.php b/src/world/generator/PopulationUtils.php new file mode 100644 index 000000000..84840ee3e --- /dev/null +++ b/src/world/generator/PopulationUtils.php @@ -0,0 +1,74 @@ +setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], false)); + if($chunk === null){ + $generator->generateChunk($manager, $chunkX, $chunkZ); + $chunk = $manager->getChunk($chunkX, $chunkZ); + if($chunk === null){ + throw new AssumptionFailedError("We just set this chunk, so it must exist"); + } + } + return $chunk; + } + + /** + * @param Chunk[]|null[] $adjacentChunks + * @phpstan-param array $adjacentChunks + * + * @return Chunk[]|Chunk[][] + * @phpstan-return array{Chunk, array} + */ + public static function populateChunkWithAdjacents(int $minY, int $maxY, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks) : array{ + $manager = new SimpleChunkManager($minY, $maxY); + self::setOrGenerateChunk($manager, $generator, $chunkX, $chunkZ, $centerChunk); + + $resultChunks = []; //this is just to keep phpstan's type inference happy + foreach($adjacentChunks as $relativeChunkHash => $c){ + World::getXZ($relativeChunkHash, $relativeX, $relativeZ); + $resultChunks[$relativeChunkHash] = self::setOrGenerateChunk($manager, $generator, $chunkX + $relativeX, $chunkZ + $relativeZ, $c); + } + $adjacentChunks = $resultChunks; + + $generator->populateChunk($manager, $chunkX, $chunkZ); + $centerChunk = $manager->getChunk($chunkX, $chunkZ); + if($centerChunk === null){ + throw new AssumptionFailedError("We just generated this chunk, so it must exist"); + } + $centerChunk->setPopulated(); + return [$centerChunk, $adjacentChunks]; + } +} From 6f3506360e8777dbe7b0e0eb05f717678e755658 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 08:30:26 +0000 Subject: [PATCH 181/334] Bump the github-actions group with 3 updates (#6683) --- .github/workflows/build-docker-image.yml | 8 ++++---- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release-pr-check.yml | 2 +- .github/workflows/draft-release.yml | 4 ++-- .github/workflows/main.yml | 2 +- .github/workflows/team-pr-auto-approve.yml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 83d568878..dc282ab71 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: push: true context: ./pocketmine-mp @@ -66,7 +66,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: push: true context: ./pocketmine-mp @@ -79,7 +79,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: push: true context: ./pocketmine-mp @@ -92,7 +92,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.16.0 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index fde5e3099..93b2978aa 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.32.0 + uses: shivammathur/setup-php@2.33.0 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index 303f61ccf..20b2200e6 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@2.32.0 + uses: shivammathur/setup-php@2.33.0 with: php-version: 8.2 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 02cdeec6f..fa20d1912 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -59,7 +59,7 @@ jobs: steps: - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} @@ -87,7 +87,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.32.0 + uses: shivammathur/setup-php@2.33.0 with: php-version: ${{ env.PHP_VERSION }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 051a3a790..cfe97aa7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.32.0 + uses: shivammathur/setup-php@2.33.0 with: php-version: 8.2 tools: php-cs-fixer:3.49 diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml index 0c2fdd81c..cc5c47139 100644 --- a/.github/workflows/team-pr-auto-approve.yml +++ b/.github/workflows/team-pr-auto-approve.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} From 6bf9a305de7d722d15736f379cac944a5310ebee Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 3 May 2025 19:23:50 +0100 Subject: [PATCH 182/334] Rename confusing PHPStan rule name it never occurred to me that this was misleading until I read some Devin documentation, noticed that Devin misunderstood was the class was for, and then realized actually Devin understood correctly, and it was the name of the class that was wrong. Funny how that happens... --- phpstan.neon.dist | 2 +- tests/phpstan/configs/phpstan-bugs.neon | 2 +- ...fStringRule.php => UnsafeForeachArrayWithStringKeysRule.php} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename tests/phpstan/rules/{UnsafeForeachArrayOfStringRule.php => UnsafeForeachArrayWithStringKeysRule.php} (98%) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 13f35c121..391f0f54c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,7 +15,7 @@ rules: - pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowForeachByReferenceRule - pocketmine\phpstan\rules\ExplodeLimitRule - - pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule + - pocketmine\phpstan\rules\UnsafeForeachArrayWithStringKeysRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule parameters: diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index aeb3fae29..cb92bf968 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -256,5 +256,5 @@ parameters: message: '#^Strict comparison using \=\=\= between 0 and 0 will always evaluate to true\.$#' identifier: identical.alwaysTrue count: 1 - path: ../rules/UnsafeForeachArrayOfStringRule.php + path: ../rules/UnsafeForeachArrayWithStringKeysRule.php diff --git a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php b/tests/phpstan/rules/UnsafeForeachArrayWithStringKeysRule.php similarity index 98% rename from tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php rename to tests/phpstan/rules/UnsafeForeachArrayWithStringKeysRule.php index 34056131b..83f47f092 100644 --- a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php +++ b/tests/phpstan/rules/UnsafeForeachArrayWithStringKeysRule.php @@ -41,7 +41,7 @@ use function sprintf; /** * @implements Rule */ -final class UnsafeForeachArrayOfStringRule implements Rule{ +final class UnsafeForeachArrayWithStringKeysRule implements Rule{ public function getNodeType() : string{ return Foreach_::class; From f2e74736296eca93f3732cc64041535e824dab00 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 4 May 2025 17:18:58 +0100 Subject: [PATCH 183/334] Update PHP-CS-Fixer --- .github/workflows/main.yml | 4 ++-- .php-cs-fixer.php | 6 ++++++ build/dump-version-info.php | 4 ++-- src/block/tile/Spawnable.php | 4 ++-- src/world/World.php | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cfe97aa7e..cabda54be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,8 +30,8 @@ jobs: - name: Setup PHP and tools uses: shivammathur/setup-php@2.33.0 with: - php-version: 8.2 - tools: php-cs-fixer:3.49 + php-version: 8.3 + tools: php-cs-fixer:3.75 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 32af1ef48..5a14b1d35 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -6,6 +6,12 @@ $finder = PhpCsFixer\Finder::create() ->in(__DIR__ . '/tests') ->in(__DIR__ . '/tools') ->notPath('plugins/DevTools') + //JsonMapper will break if the FQNs in the doc comments for these are shortened :( + ->notPath('crafting/json') + ->notPath('inventory/json') + ->notPath('data/bedrock/block/upgrade/model') + ->notPath('data/bedrock/item/upgrade/model') + ->notName('PocketMine.php'); return (new PhpCsFixer\Config) diff --git a/build/dump-version-info.php b/build/dump-version-info.php index e13696f3d..3181acba6 100644 --- a/build/dump-version-info.php +++ b/build/dump-version-info.php @@ -31,8 +31,8 @@ require dirname(__DIR__) . '/vendor/autoload.php'; */ /** - * @var string[]|\Closure[] $options - * @phpstan-var array $options + * @var string[]|Closure[] $options + * @phpstan-var array $options */ $options = [ "base_version" => VersionInfo::BASE_VERSION, diff --git a/src/block/tile/Spawnable.php b/src/block/tile/Spawnable.php index 67bc72fd9..0c41713f2 100644 --- a/src/block/tile/Spawnable.php +++ b/src/block/tile/Spawnable.php @@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\types\CacheableNbt; use function get_class; abstract class Spawnable extends Tile{ - /** @phpstan-var CacheableNbt<\pocketmine\nbt\tag\CompoundTag>|null */ + /** @phpstan-var CacheableNbt|null */ private ?CacheableNbt $spawnCompoundCache = null; /** @@ -73,7 +73,7 @@ abstract class Spawnable extends Tile{ * Returns encoded NBT (varint, little-endian) used to spawn this tile to clients. Uses cache where possible, * populates cache if it is null. * - * @phpstan-return CacheableNbt<\pocketmine\nbt\tag\CompoundTag> + * @phpstan-return CacheableNbt */ final public function getSerializedSpawnCompound() : CacheableNbt{ if($this->spawnCompoundCache === null){ diff --git a/src/world/World.php b/src/world/World.php index 3a7d0c538..63a617126 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -360,7 +360,7 @@ class World implements ChunkManager{ private bool $doingTick = false; - /** @phpstan-var class-string<\pocketmine\world\generator\Generator> */ + /** @phpstan-var class-string */ private string $generator; private bool $unloaded = false; From d789c75c0084cacac09baa33d8ec5462d1f9c089 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 8 May 2025 02:21:39 +0100 Subject: [PATCH 184/334] Improve PHPStan error reporting for unsafe foreaches these are actually two separate concerns: one for dodgy PHPStan type suppression on implicit keys, and the other for arrays being casted to strings by PHP. --- phpstan.neon.dist | 2 +- ...OfStringRule.php => UnsafeForeachRule.php} | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) rename tests/phpstan/rules/{UnsafeForeachArrayOfStringRule.php => UnsafeForeachRule.php} (69%) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 13f35c121..12c739f2f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,7 +15,7 @@ rules: - pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowForeachByReferenceRule - pocketmine\phpstan\rules\ExplodeLimitRule - - pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule + - pocketmine\phpstan\rules\UnsafeForeachRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule parameters: diff --git a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php b/tests/phpstan/rules/UnsafeForeachRule.php similarity index 69% rename from tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php rename to tests/phpstan/rules/UnsafeForeachRule.php index 34056131b..cb463c61d 100644 --- a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php +++ b/tests/phpstan/rules/UnsafeForeachRule.php @@ -41,7 +41,7 @@ use function sprintf; /** * @implements Rule */ -final class UnsafeForeachArrayOfStringRule implements Rule{ +final class UnsafeForeachRule implements Rule{ public function getNodeType() : string{ return Foreach_::class; @@ -73,7 +73,7 @@ final class UnsafeForeachArrayOfStringRule implements Rule{ $benevolentUnionDepth--; return $result; } - if($type instanceof IntegerType && $benevolentUnionDepth === 0){ + if($type instanceof IntegerType){ $expectsIntKeyTypes = true; return $type; } @@ -87,24 +87,31 @@ final class UnsafeForeachArrayOfStringRule implements Rule{ $hasCastableKeyTypes = true; return $type; }); - if($hasCastableKeyTypes && !$expectsIntKeyTypes){ - $tip = $implicitType ? - sprintf( - "Declare a key type using @phpstan-var or @phpstan-param, or use %s() to promote the key type to get proper error reporting", + $errors = []; + if($implicitType){ + $errors[] = RuleErrorBuilder::message("Possible unreported errors in foreach on array with unspecified key type.") + ->tip(sprintf( + <<getIterableKeyType()->describe(VerbosityLevel::value()) - ))->tip($tip)->identifier('pocketmine.foreach.stringKeys')->build() - ]; + ))->identifier('pocketmine.foreach.implicitKeys')->build(); } - return []; + if($hasCastableKeyTypes && !$expectsIntKeyTypes){ + $errors[] = RuleErrorBuilder::message(sprintf( + "Unsafe foreach on array with key type %s.", + $iterableType->getIterableKeyType()->describe(VerbosityLevel::value()) + )) + ->tip(sprintf( + <<identifier('pocketmine.foreach.stringKeys')->build(); + } + return $errors; } } From 5e830c732075d067887eaac9fb605d6a347e8115 Mon Sep 17 00:00:00 2001 From: Dries C Date: Fri, 9 May 2025 16:29:05 +0200 Subject: [PATCH 185/334] Protocol changes for 1.21.80 (#6687) * Bedrock 1.21.80 support * Update bedrock-data * Add required tags to models * Fixed biome data loading * Support newest world format apparently I messed up the blockstate data version last time around... it hasn't changed since 1.21.60 * always CS has to complain... * Sync with release versions * Ready 5.28.0 release * this might help... --------- Co-authored-by: Dylan T. --- changelogs/5.28.md | 21 ++++++ composer.json | 4 +- composer.lock | 30 ++++---- src/VersionInfo.php | 4 +- src/data/bedrock/BedrockDataFiles.php | 3 +- src/data/bedrock/block/BlockStateData.php | 4 +- src/data/bedrock/block/BlockStateNames.php | 1 + src/data/bedrock/block/BlockTypeNames.php | 1 + src/data/bedrock/item/ItemTypeNames.php | 17 +++++ src/network/mcpe/cache/StaticPacketCache.php | 63 ++++++++++++++++- .../mcpe/handler/InGamePacketHandler.php | 5 -- .../biome/model/BiomeDefinitionEntryData.php | 69 +++++++++++++++++++ src/world/biome/model/ColorData.php | 41 +++++++++++ src/world/format/io/data/BedrockWorldData.php | 4 +- tools/generate-bedrock-data-from-packets.php | 54 ++++++++------- 15 files changed, 264 insertions(+), 57 deletions(-) create mode 100644 changelogs/5.28.md create mode 100644 src/world/biome/model/BiomeDefinitionEntryData.php create mode 100644 src/world/biome/model/ColorData.php diff --git a/changelogs/5.28.md b/changelogs/5.28.md new file mode 100644 index 000000000..f368e819e --- /dev/null +++ b/changelogs/5.28.md @@ -0,0 +1,21 @@ +# 5.28.0 +Released 9th May 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.80. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Aded support for Minecraft: Bedrock Edition 1.21.70. +- Removed support for earlier versions. + +## Fixes +- `AvailableEnchantmentRegistry` now requires provided tags to always be `string`. Previously, this wasn't enforced, leading to random crashes in core code related to enchanting. +- `Entity->setFireTicks()` and `Entity->setOnFire()` now truncate the fire time to the max value instead of throwing exceptions. + +## Internals +- Improved PHPStan error reporting for unsafe foreaches. Foreach on an array with implicit keys now generates different errors than foreach on an array with string keys. diff --git a/composer.json b/composer.json index 87086f456..7545806b4 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "~4.1.0+bedrock-1.21.70", + "pocketmine/bedrock-data": "5.0.0+bedrock-1.21.80", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~37.0.0+bedrock-1.21.70", + "pocketmine/bedrock-protocol": "38.0.0+bedrock-1.21.80", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index 23f312317..d45311018 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": "818c679a25da8e6b466bc454ad48dec3", + "content-hash": "4dfc7b8c912d8d5fa194ddc0e97903fb", "packages": [ { "name": "adhocore/json-comment", @@ -204,16 +204,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "4.1.0+bedrock-1.21.70", + "version": "5.0.0+bedrock-1.21.80", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "d53fe98cb3b596ac016e275df5bd5e89b04a4817" + "reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/d53fe98cb3b596ac016e275df5bd5e89b04a4817", - "reference": "d53fe98cb3b596ac016e275df5bd5e89b04a4817", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/e38d5ea19f794ec5216e5f96742237e8c4e7f080", + "reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.70" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.80" }, - "time": "2025-03-25T19:43:31+00:00" + "time": "2025-05-09T14:15:18+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "37.0.0+bedrock-1.21.70", + "version": "38.0.0+bedrock-1.21.80", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "7091dad2c12ed4a4106432df21fc698960c6be9e" + "reference": "a626561eaefeb6333c0d2726e2789ceb0aac0724" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/7091dad2c12ed4a4106432df21fc698960c6be9e", - "reference": "7091dad2c12ed4a4106432df21fc698960c6be9e", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a626561eaefeb6333c0d2726e2789ceb0aac0724", + "reference": "a626561eaefeb6333c0d2726e2789ceb0aac0724", "shasum": "" }, "require": { @@ -296,9 +296,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/37.0.0+bedrock-1.21.70" + "source": "https://github.com/pmmp/BedrockProtocol/tree/38.0.0+bedrock-1.21.80" }, - "time": "2025-03-27T15:19:36+00:00" + "time": "2025-05-09T14:17:07+00:00" }, { "name": "pocketmine/binaryutils", @@ -2921,7 +2921,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -2952,7 +2952,7 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1.0" }, diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 44238dba3..c5b38f072 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.27.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.28.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 1ecb707cc..53bd9b11e 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -31,8 +31,7 @@ final class BedrockDataFiles{ } public const BANNER_PATTERNS_JSON = BEDROCK_DATA_PATH . '/banner_patterns.json'; - public const BIOME_DEFINITIONS_NBT = BEDROCK_DATA_PATH . '/biome_definitions.nbt'; - public const BIOME_DEFINITIONS_FULL_NBT = BEDROCK_DATA_PATH . '/biome_definitions_full.nbt'; + public const BIOME_DEFINITIONS_JSON = BEDROCK_DATA_PATH . '/biome_definitions.json'; public const BIOME_ID_MAP_JSON = BEDROCK_DATA_PATH . '/biome_id_map.json'; public const BLOCK_ID_TO_ITEM_ID_MAP_JSON = BEDROCK_DATA_PATH . '/block_id_to_item_id_map.json'; public const BLOCK_PROPERTIES_TABLE_JSON = BEDROCK_DATA_PATH . '/block_properties_table.json'; diff --git a/src/data/bedrock/block/BlockStateData.php b/src/data/bedrock/block/BlockStateData.php index 600eba938..e90410ac7 100644 --- a/src/data/bedrock/block/BlockStateData.php +++ b/src/data/bedrock/block/BlockStateData.php @@ -45,8 +45,8 @@ final class BlockStateData{ public const CURRENT_VERSION = (1 << 24) | //major (21 << 16) | //minor - (70 << 8) | //patch - (1); //revision + (60 << 8) | //patch + (33); //revision public const TAG_NAME = "name"; public const TAG_STATES = "states"; diff --git a/src/data/bedrock/block/BlockStateNames.php b/src/data/bedrock/block/BlockStateNames.php index 9fed77e4a..704798d1d 100644 --- a/src/data/bedrock/block/BlockStateNames.php +++ b/src/data/bedrock/block/BlockStateNames.php @@ -113,6 +113,7 @@ final class BlockStateNames{ public const RAIL_DATA_BIT = "rail_data_bit"; public const RAIL_DIRECTION = "rail_direction"; public const REDSTONE_SIGNAL = "redstone_signal"; + public const REHYDRATION_LEVEL = "rehydration_level"; public const REPEATER_DELAY = "repeater_delay"; public const RESPAWN_ANCHOR_CHARGE = "respawn_anchor_charge"; public const ROTATION = "rotation"; diff --git a/src/data/bedrock/block/BlockTypeNames.php b/src/data/bedrock/block/BlockTypeNames.php index bc30800fc..527a01345 100644 --- a/src/data/bedrock/block/BlockTypeNames.php +++ b/src/data/bedrock/block/BlockTypeNames.php @@ -392,6 +392,7 @@ final class BlockTypeNames{ public const DOUBLE_CUT_COPPER_SLAB = "minecraft:double_cut_copper_slab"; public const DRAGON_EGG = "minecraft:dragon_egg"; public const DRAGON_HEAD = "minecraft:dragon_head"; + public const DRIED_GHAST = "minecraft:dried_ghast"; public const DRIED_KELP_BLOCK = "minecraft:dried_kelp_block"; public const DRIPSTONE_BLOCK = "minecraft:dripstone_block"; public const DROPPER = "minecraft:dropper"; diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php index ea95d57f0..5f86cde96 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -68,6 +68,7 @@ final class ItemTypeNames{ public const BIRCH_SIGN = "minecraft:birch_sign"; public const BLACK_BUNDLE = "minecraft:black_bundle"; public const BLACK_DYE = "minecraft:black_dye"; + public const BLACK_HARNESS = "minecraft:black_harness"; public const BLADE_POTTERY_SHERD = "minecraft:blade_pottery_sherd"; public const BLAZE_POWDER = "minecraft:blaze_powder"; public const BLAZE_ROD = "minecraft:blaze_rod"; @@ -76,6 +77,7 @@ final class ItemTypeNames{ public const BLUE_BUNDLE = "minecraft:blue_bundle"; public const BLUE_DYE = "minecraft:blue_dye"; public const BLUE_EGG = "minecraft:blue_egg"; + public const BLUE_HARNESS = "minecraft:blue_harness"; public const BOARD = "minecraft:board"; public const BOAT = "minecraft:boat"; public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg"; @@ -95,6 +97,7 @@ final class ItemTypeNames{ public const BROWN_BUNDLE = "minecraft:brown_bundle"; public const BROWN_DYE = "minecraft:brown_dye"; public const BROWN_EGG = "minecraft:brown_egg"; + public const BROWN_HARNESS = "minecraft:brown_harness"; public const BRUSH = "minecraft:brush"; public const BUCKET = "minecraft:bucket"; public const BUNDLE = "minecraft:bundle"; @@ -166,6 +169,7 @@ final class ItemTypeNames{ public const CROSSBOW = "minecraft:crossbow"; public const CYAN_BUNDLE = "minecraft:cyan_bundle"; public const CYAN_DYE = "minecraft:cyan_dye"; + public const CYAN_HARNESS = "minecraft:cyan_harness"; public const DANGER_POTTERY_SHERD = "minecraft:danger_pottery_sherd"; public const DARK_OAK_BOAT = "minecraft:dark_oak_boat"; public const DARK_OAK_CHEST_BOAT = "minecraft:dark_oak_chest_boat"; @@ -265,12 +269,15 @@ final class ItemTypeNames{ public const GOLDEN_SWORD = "minecraft:golden_sword"; public const GRAY_BUNDLE = "minecraft:gray_bundle"; public const GRAY_DYE = "minecraft:gray_dye"; + public const GRAY_HARNESS = "minecraft:gray_harness"; public const GREEN_BUNDLE = "minecraft:green_bundle"; public const GREEN_DYE = "minecraft:green_dye"; + public const GREEN_HARNESS = "minecraft:green_harness"; public const GUARDIAN_SPAWN_EGG = "minecraft:guardian_spawn_egg"; public const GUNPOWDER = "minecraft:gunpowder"; public const GUSTER_BANNER_PATTERN = "minecraft:guster_banner_pattern"; public const GUSTER_POTTERY_SHERD = "minecraft:guster_pottery_sherd"; + public const HAPPY_GHAST_SPAWN_EGG = "minecraft:happy_ghast_spawn_egg"; public const HARD_STAINED_GLASS = "minecraft:hard_stained_glass"; public const HARD_STAINED_GLASS_PANE = "minecraft:hard_stained_glass_pane"; public const HEART_OF_THE_SEA = "minecraft:heart_of_the_sea"; @@ -321,10 +328,13 @@ final class ItemTypeNames{ public const LIGHT_BLOCK = "minecraft:light_block"; public const LIGHT_BLUE_BUNDLE = "minecraft:light_blue_bundle"; public const LIGHT_BLUE_DYE = "minecraft:light_blue_dye"; + public const LIGHT_BLUE_HARNESS = "minecraft:light_blue_harness"; public const LIGHT_GRAY_BUNDLE = "minecraft:light_gray_bundle"; public const LIGHT_GRAY_DYE = "minecraft:light_gray_dye"; + public const LIGHT_GRAY_HARNESS = "minecraft:light_gray_harness"; public const LIME_BUNDLE = "minecraft:lime_bundle"; public const LIME_DYE = "minecraft:lime_dye"; + public const LIME_HARNESS = "minecraft:lime_harness"; public const LINGERING_POTION = "minecraft:lingering_potion"; public const LLAMA_SPAWN_EGG = "minecraft:llama_spawn_egg"; public const LODESTONE_COMPASS = "minecraft:lodestone_compass"; @@ -333,6 +343,7 @@ final class ItemTypeNames{ public const MACE = "minecraft:mace"; public const MAGENTA_BUNDLE = "minecraft:magenta_bundle"; public const MAGENTA_DYE = "minecraft:magenta_dye"; + public const MAGENTA_HARNESS = "minecraft:magenta_harness"; public const MAGMA_CREAM = "minecraft:magma_cream"; public const MAGMA_CUBE_SPAWN_EGG = "minecraft:magma_cube_spawn_egg"; public const MANGROVE_BOAT = "minecraft:mangrove_boat"; @@ -400,6 +411,7 @@ final class ItemTypeNames{ public const OMINOUS_TRIAL_KEY = "minecraft:ominous_trial_key"; public const ORANGE_BUNDLE = "minecraft:orange_bundle"; public const ORANGE_DYE = "minecraft:orange_dye"; + public const ORANGE_HARNESS = "minecraft:orange_harness"; public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door"; public const PAINTING = "minecraft:painting"; public const PALE_OAK_BOAT = "minecraft:pale_oak_boat"; @@ -419,6 +431,7 @@ final class ItemTypeNames{ public const PILLAGER_SPAWN_EGG = "minecraft:pillager_spawn_egg"; public const PINK_BUNDLE = "minecraft:pink_bundle"; public const PINK_DYE = "minecraft:pink_dye"; + public const PINK_HARNESS = "minecraft:pink_harness"; public const PITCHER_POD = "minecraft:pitcher_pod"; public const PLANKS = "minecraft:planks"; public const PLENTY_POTTERY_SHERD = "minecraft:plenty_pottery_sherd"; @@ -439,6 +452,7 @@ final class ItemTypeNames{ public const PUMPKIN_SEEDS = "minecraft:pumpkin_seeds"; public const PURPLE_BUNDLE = "minecraft:purple_bundle"; public const PURPLE_DYE = "minecraft:purple_dye"; + public const PURPLE_HARNESS = "minecraft:purple_harness"; public const QUARTZ = "minecraft:quartz"; public const RABBIT = "minecraft:rabbit"; public const RABBIT_FOOT = "minecraft:rabbit_foot"; @@ -455,6 +469,7 @@ final class ItemTypeNames{ public const RED_BUNDLE = "minecraft:red_bundle"; public const RED_DYE = "minecraft:red_dye"; public const RED_FLOWER = "minecraft:red_flower"; + public const RED_HARNESS = "minecraft:red_harness"; public const REDSTONE = "minecraft:redstone"; public const REPEATER = "minecraft:repeater"; public const RESIN_BRICK = "minecraft:resin_brick"; @@ -563,6 +578,7 @@ final class ItemTypeNames{ public const WHEAT_SEEDS = "minecraft:wheat_seeds"; public const WHITE_BUNDLE = "minecraft:white_bundle"; public const WHITE_DYE = "minecraft:white_dye"; + public const WHITE_HARNESS = "minecraft:white_harness"; public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wild_armor_trim_smithing_template"; public const WIND_CHARGE = "minecraft:wind_charge"; public const WITCH_SPAWN_EGG = "minecraft:witch_spawn_egg"; @@ -583,6 +599,7 @@ final class ItemTypeNames{ public const WRITTEN_BOOK = "minecraft:written_book"; public const YELLOW_BUNDLE = "minecraft:yellow_bundle"; public const YELLOW_DYE = "minecraft:yellow_dye"; + public const YELLOW_HARNESS = "minecraft:yellow_harness"; public const ZOGLIN_SPAWN_EGG = "minecraft:zoglin_spawn_egg"; public const ZOMBIE_HORSE_SPAWN_EGG = "minecraft:zombie_horse_spawn_egg"; public const ZOMBIE_PIGMAN_SPAWN_EGG = "minecraft:zombie_pigman_spawn_egg"; diff --git a/src/network/mcpe/cache/StaticPacketCache.php b/src/network/mcpe/cache/StaticPacketCache.php index 88a522600..861881437 100644 --- a/src/network/mcpe/cache/StaticPacketCache.php +++ b/src/network/mcpe/cache/StaticPacketCache.php @@ -23,13 +23,22 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\cache; +use pocketmine\color\Color; use pocketmine\data\bedrock\BedrockDataFiles; +use pocketmine\data\SavedDataLoadingException; use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket; use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; +use pocketmine\network\mcpe\protocol\types\biome\BiomeDefinitionEntry; use pocketmine\network\mcpe\protocol\types\CacheableNbt; use pocketmine\utils\Filesystem; use pocketmine\utils\SingletonTrait; +use pocketmine\utils\Utils; +use pocketmine\world\biome\model\BiomeDefinitionEntryData; +use function count; +use function get_debug_type; +use function is_array; +use function json_decode; class StaticPacketCache{ use SingletonTrait; @@ -41,9 +50,61 @@ class StaticPacketCache{ return new CacheableNbt((new NetworkNbtSerializer())->read(Filesystem::fileGetContents($filePath))->mustGetCompoundTag()); } + /** + * @return list + */ + private static function loadBiomeDefinitionModel(string $filePath) : array{ + $biomeEntries = json_decode(Filesystem::fileGetContents($filePath), associative: true); + if(!is_array($biomeEntries)){ + throw new SavedDataLoadingException("$filePath root should be an array, got " . get_debug_type($biomeEntries)); + } + + $jsonMapper = new \JsonMapper(); + $jsonMapper->bExceptionOnMissingData = true; + $jsonMapper->bStrictObjectTypeChecking = true; + $jsonMapper->bEnforceMapType = false; + + $entries = []; + foreach(Utils::promoteKeys($biomeEntries) as $biomeName => $entry){ + if(!is_array($entry)){ + throw new SavedDataLoadingException("$filePath should be an array of objects, got " . get_debug_type($entry)); + } + + try{ + $biomeDefinition = $jsonMapper->map($entry, new BiomeDefinitionEntryData()); + + $mapWaterColour = $biomeDefinition->mapWaterColour; + $entries[] = new BiomeDefinitionEntry( + (string) $biomeName, + $biomeDefinition->id, + $biomeDefinition->temperature, + $biomeDefinition->downfall, + $biomeDefinition->redSporeDensity, + $biomeDefinition->blueSporeDensity, + $biomeDefinition->ashDensity, + $biomeDefinition->whiteAshDensity, + $biomeDefinition->depth, + $biomeDefinition->scale, + new Color( + $mapWaterColour->r, + $mapWaterColour->g, + $mapWaterColour->b, + $mapWaterColour->a + ), + $biomeDefinition->rain, + count($biomeDefinition->tags) > 0 ? $biomeDefinition->tags : null, + ); + }catch(\JsonMapper_Exception $e){ + throw new \RuntimeException($e->getMessage(), 0, $e); + } + } + + return $entries; + } + private static function make() : self{ return new self( - BiomeDefinitionListPacket::create(self::loadCompoundFromFile(BedrockDataFiles::BIOME_DEFINITIONS_NBT)), + BiomeDefinitionListPacket::fromDefinitions(self::loadBiomeDefinitionModel(BedrockDataFiles::BIOME_DEFINITIONS_JSON)), AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(BedrockDataFiles::ENTITY_IDENTIFIERS_NBT)) ); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 93a01fdcc..eec200e4b 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -73,7 +73,6 @@ use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket; use pocketmine\network\mcpe\protocol\PlayerActionPacket; use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\PlayerHotbarPacket; -use pocketmine\network\mcpe\protocol\PlayerInputPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\serializer\BitSet; @@ -781,10 +780,6 @@ class InGamePacketHandler extends PacketHandler{ return false; } - public function handlePlayerInput(PlayerInputPacket $packet) : bool{ - return false; //TODO - } - public function handleSetPlayerGameType(SetPlayerGameTypePacket $packet) : bool{ $gameMode = $this->session->getTypeConverter()->protocolGameModeToCore($packet->gamemode); if($gameMode !== $this->player->getGamemode()){ diff --git a/src/world/biome/model/BiomeDefinitionEntryData.php b/src/world/biome/model/BiomeDefinitionEntryData.php new file mode 100644 index 000000000..8a5c3d354 --- /dev/null +++ b/src/world/biome/model/BiomeDefinitionEntryData.php @@ -0,0 +1,69 @@ + + */ + public array $tags; +} diff --git a/src/world/biome/model/ColorData.php b/src/world/biome/model/ColorData.php new file mode 100644 index 000000000..f70a77d15 --- /dev/null +++ b/src/world/biome/model/ColorData.php @@ -0,0 +1,41 @@ +bedrockDataPath . '/biome_definitions_full.nbt', $packet->definitions->getEncodedNbt()); + $definitions = []; + foreach($packet->buildDefinitionsFromData() as $entry){ + $mapWaterColor = new ColorData(); + $mapWaterColor->r = $entry->getMapWaterColor()->getR(); + $mapWaterColor->g = $entry->getMapWaterColor()->getG(); + $mapWaterColor->b = $entry->getMapWaterColor()->getB(); + $mapWaterColor->a = $entry->getMapWaterColor()->getA(); - $nbt = $packet->definitions->getRoot(); - if(!$nbt instanceof CompoundTag){ - throw new AssumptionFailedError(); - } - $strippedNbt = clone $nbt; - foreach($strippedNbt as $compound){ - if($compound instanceof CompoundTag){ - foreach([ - "minecraft:capped_surface", - "minecraft:consolidated_features", - "minecraft:frozen_ocean_surface", - "minecraft:legacy_world_generation_rules", - "minecraft:mesa_surface", - "minecraft:mountain_parameters", - "minecraft:multinoise_generation_rules", - "minecraft:overworld_generation_rules", - "minecraft:surface_material_adjustments", - "minecraft:surface_parameters", - "minecraft:swamp_surface", - ] as $remove){ - $compound->removeTag($remove); - } - } + $data = new BiomeDefinitionEntryData(); + $data->id = $entry->getId(); + $data->temperature = round($entry->getTemperature(), 3); + $data->downfall = round($entry->getDownfall(), 3); + $data->redSporeDensity = round($entry->getRedSporeDensity(), 3); + $data->blueSporeDensity = round($entry->getBlueSporeDensity(), 3); + $data->ashDensity = round($entry->getAshDensity(), 3); + $data->whiteAshDensity = round($entry->getWhiteAshDensity(), 3); + $data->depth = round($entry->getDepth(), 3); + $data->scale = round($entry->getScale(), 3); + $data->mapWaterColour = $mapWaterColor; + $data->rain = $entry->hasRain(); + $data->tags = $entry->getTags() ?? []; + + $definitions[$entry->getBiomeName()] = self::objectToOrderedArray($data); } - file_put_contents($this->bedrockDataPath . '/biome_definitions.nbt', (new CacheableNbt($strippedNbt))->getEncodedNbt()); + ksort($definitions, SORT_STRING); + + file_put_contents($this->bedrockDataPath . '/biome_definitions.json', json_encode($definitions, JSON_PRETTY_PRINT) . "\n"); return true; } From 134c7309c5985cd6df0a323a8465f44e9099510a Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 14:30:04 +0000 Subject: [PATCH 186/334] 5.28.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14931216524 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index c5b38f072..acc7db91c 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.28.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.28.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From d90fc3415c611a976cdde80bbcd79574fadb38bf Mon Sep 17 00:00:00 2001 From: ItzxDwi <107537435+ItzxDwi@users.noreply.github.com> Date: Fri, 9 May 2025 23:33:55 +0800 Subject: [PATCH 187/334] fixed wrong version info (#6689) --- changelogs/5.28.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/5.28.md b/changelogs/5.28.md index f368e819e..8a69b97e0 100644 --- a/changelogs/5.28.md +++ b/changelogs/5.28.md @@ -10,7 +10,7 @@ Do not update plugin minimum API versions unless you need new features added in Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. ## General -- Aded support for Minecraft: Bedrock Edition 1.21.70. +- Aded support for Minecraft: Bedrock Edition 1.21.80. - Removed support for earlier versions. ## Fixes From 04de72e85ec8c8da36e1d527db3cbe4ee855a124 Mon Sep 17 00:00:00 2001 From: Sergi del Olmo Date: Sat, 10 May 2025 15:34:37 +0200 Subject: [PATCH 188/334] Fix changelog typo (#6690) --- changelogs/5.28.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/5.28.md b/changelogs/5.28.md index 8a69b97e0..a2ede942f 100644 --- a/changelogs/5.28.md +++ b/changelogs/5.28.md @@ -10,7 +10,7 @@ Do not update plugin minimum API versions unless you need new features added in Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. ## General -- Aded support for Minecraft: Bedrock Edition 1.21.80. +- Added support for Minecraft: Bedrock Edition 1.21.80. - Removed support for earlier versions. ## Fixes From 84bb9d2ab41fbb1ecfcd9d2e11530121e23db12c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 13:33:42 +0100 Subject: [PATCH 189/334] Consolidate Bedrock data version info this ensures we don't have to go into a bunch of randomly scattered files to update version numbers. --- src/data/bedrock/WorldDataVersions.php | 67 +++++++++++++++++++ src/data/bedrock/block/BlockStateData.php | 13 +--- src/world/format/io/data/BedrockWorldData.php | 13 ++-- src/world/format/io/leveldb/LevelDB.php | 5 +- 4 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 src/data/bedrock/WorldDataVersions.php diff --git a/src/data/bedrock/WorldDataVersions.php b/src/data/bedrock/WorldDataVersions.php new file mode 100644 index 000000000..498dac9da --- /dev/null +++ b/src/data/bedrock/WorldDataVersions.php @@ -0,0 +1,67 @@ + Date: Sat, 17 May 2025 13:35:52 +0100 Subject: [PATCH 190/334] always the CS... --- src/data/bedrock/WorldDataVersions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/bedrock/WorldDataVersions.php b/src/data/bedrock/WorldDataVersions.php index 498dac9da..e79478b11 100644 --- a/src/data/bedrock/WorldDataVersions.php +++ b/src/data/bedrock/WorldDataVersions.php @@ -43,7 +43,6 @@ final class WorldDataVersions{ (60 << 8) | //patch (33); //revision - public const CHUNK = ChunkVersion::v1_21_40; public const SUBCHUNK = SubChunkVersion::PALETTED_MULTI; From 67f3bb9c5242dfab49ccb25d65edb4e00ac3f7e2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 13:46:33 +0100 Subject: [PATCH 191/334] Update composer dependencies and fix an error found by new PHPStan update --- composer.json | 2 +- composer.lock | 63 +++++++++++++++++++--------------- src/thread/ThreadCrashInfo.php | 2 +- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 7545806b4..8900eea51 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.11", + "phpstan/phpstan": "2.1.16", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index d45311018..94eebda8c 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": "4dfc7b8c912d8d5fa194ddc0e97903fb", + "content-hash": "e3fffa76c2ce9dd0f5c2cd66a5aa097c", "packages": [ { "name": "adhocore/json-comment", @@ -976,7 +976,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1035,7 +1035,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -1055,19 +1055,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -1115,7 +1116,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -1131,22 +1132,22 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" } ], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -1185,7 +1186,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -1193,7 +1194,7 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nikic/php-parser", @@ -1373,16 +1374,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.11", + "version": "2.1.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" + "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", + "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", "shasum": "" }, "require": { @@ -1427,7 +1428,7 @@ "type": "github" } ], - "time": "2025-03-24T13:45:00+00:00" + "time": "2025-05-16T09:40:10+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1853,16 +1854,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.45", + "version": "10.5.46", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", "shasum": "" }, "require": { @@ -1872,7 +1873,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -1934,7 +1935,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.5.45" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46" }, "funding": [ { @@ -1945,12 +1946,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-02-06T16:08:12+00:00" + "time": "2025-05-02T06:46:24+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/thread/ThreadCrashInfo.php b/src/thread/ThreadCrashInfo.php index 66aae927a..6fffdc83b 100644 --- a/src/thread/ThreadCrashInfo.php +++ b/src/thread/ThreadCrashInfo.php @@ -84,6 +84,6 @@ final class ThreadCrashInfo extends ThreadSafe{ public function getThreadName() : string{ return $this->threadName; } public function makePrettyMessage() : string{ - return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line); + return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type, $this->message, Filesystem::cleanPath($this->file), $this->line); } } From dca37d5842a6405004aff5eeb3fca264f2b260df Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 14:11:57 +0100 Subject: [PATCH 192/334] Hack: forcibly remove symfony/polyfill-mbstring we don't need this dependency anyway because mbstring is already provided. --- composer.json | 3 ++ composer.lock | 83 +-------------------------------------------------- 2 files changed, 4 insertions(+), 82 deletions(-) diff --git a/composer.json b/composer.json index 8900eea51..f24ddc7e5 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,9 @@ "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" }, + "provide": { + "symfony/polyfill-mbstring": "*" + }, "autoload": { "psr-4": { "pocketmine\\": "src/" diff --git a/composer.lock b/composer.lock index 94eebda8c..2350246ed 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": "e3fffa76c2ce9dd0f5c2cd66a5aa097c", + "content-hash": "c2f2a1e28028894c1b12484f115732f0", "packages": [ { "name": "adhocore/json-comment", @@ -1052,87 +1052,6 @@ } ], "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" } ], "packages-dev": [ From e0864e7ee82d4295e2b1d80d0d057016b5e94956 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 14:54:26 +0100 Subject: [PATCH 193/334] composer: also axe unnecessary ctype polyfill --- composer.json | 1 + composer.lock | 81 +-------------------------------------------------- 2 files changed, 2 insertions(+), 80 deletions(-) diff --git a/composer.json b/composer.json index f24ddc7e5..1482aa4cb 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,7 @@ "phpunit/phpunit": "^10.5.24" }, "provide": { + "symfony/polyfill-ctype": "*", "symfony/polyfill-mbstring": "*" }, "autoload": { diff --git a/composer.lock b/composer.lock index 2350246ed..1326fbc9a 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": "c2f2a1e28028894c1b12484f115732f0", + "content-hash": "b25d87be51beaaad7285a6b2e771ab4e", "packages": [ { "name": "adhocore/json-comment", @@ -973,85 +973,6 @@ } ], "time": "2024-10-25T15:07:50+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" } ], "packages-dev": [ From abb004fbc5d03ff58a5f68f069b70d7e682fc70a Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 17 May 2025 15:00:53 +0100 Subject: [PATCH 194/334] Ready 5.28.1 (#6696) --- changelogs/5.28.md | 6 ++++++ src/VersionInfo.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelogs/5.28.md b/changelogs/5.28.md index a2ede942f..74906ecc7 100644 --- a/changelogs/5.28.md +++ b/changelogs/5.28.md @@ -19,3 +19,9 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if ## Internals - Improved PHPStan error reporting for unsafe foreaches. Foreach on an array with implicit keys now generates different errors than foreach on an array with string keys. + +# 5.28.1 +Released 17th May 2025. + +## Fixes +- Fixed errors when PlayStation players attempt to join due to null `TitleID`. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index acc7db91c..7344085cf 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.28.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 280911ec59103128b60e40a94c388fbd5907ea59 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 14:01:49 +0000 Subject: [PATCH 195/334] 5.28.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15085916916 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 7344085cf..885099701 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.28.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.28.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From a37353c0605d6b6424ffa66dd150c2b691c6a651 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 16:37:05 +0100 Subject: [PATCH 196/334] composer: fixed borked version constraints bruhhhhhhhhhhhh --- composer.json | 4 ++-- composer.lock | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 1482aa4cb..c744c320e 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "5.0.0+bedrock-1.21.80", + "pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "38.0.0+bedrock-1.21.80", + "pocketmine/bedrock-protocol": "~38.0.0+bedrock-1.21.80", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index 1326fbc9a..b82a014da 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": "b25d87be51beaaad7285a6b2e771ab4e", + "content-hash": "d8fa42f33a3bcb26014e6f862366dbd6", "packages": [ { "name": "adhocore/json-comment", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "38.0.0+bedrock-1.21.80", + "version": "38.0.1+bedrock-1.21.80", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "a626561eaefeb6333c0d2726e2789ceb0aac0724" + "reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a626561eaefeb6333c0d2726e2789ceb0aac0724", - "reference": "a626561eaefeb6333c0d2726e2789ceb0aac0724", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f", + "reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f", "shasum": "" }, "require": { @@ -296,9 +296,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/38.0.0+bedrock-1.21.80" + "source": "https://github.com/pmmp/BedrockProtocol/tree/38.0.1+bedrock-1.21.80" }, - "time": "2025-05-09T14:17:07+00:00" + "time": "2025-05-17T11:56:33+00:00" }, { "name": "pocketmine/binaryutils", From 81d3017ad5e15e8f6ca846733826b47a5e90eba2 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 17 May 2025 16:44:19 +0100 Subject: [PATCH 197/334] Murphy's Law (#6698) --- changelogs/5.28.md | 7 +++++++ src/VersionInfo.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelogs/5.28.md b/changelogs/5.28.md index 74906ecc7..f378031f7 100644 --- a/changelogs/5.28.md +++ b/changelogs/5.28.md @@ -25,3 +25,10 @@ Released 17th May 2025. ## Fixes - Fixed errors when PlayStation players attempt to join due to null `TitleID`. + +# 5.28.2 +Released 17th May 2025. + +## Fixes +- Fixed version constraints which were incorrectly updated during the 1.21.80 update. This led to an unnoticed failure to update BedrockProtocol in the previous patch release. +- Actually fixed PlayStation issues this time diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 885099701..aa42e2e03 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.28.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 647c2587a8c40e641cebca331d06105e92edee8c Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 15:45:22 +0000 Subject: [PATCH 198/334] 5.28.3 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15086729525 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index aa42e2e03..615024656 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.28.2"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.28.3"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 657e6c8130154629c21e4ed9c148386ca6d3af89 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 17:24:53 +0100 Subject: [PATCH 199/334] Added trigger cron workflow for RestrictedActions branch sync we're having problems with the restricted action getting disabled due to repo inactivity, so it's best we trigger it from here, since this repo's activity is what it's interested in anyway. --- .../workflows/branch-sync-cron-trigger.yml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/branch-sync-cron-trigger.yml diff --git a/.github/workflows/branch-sync-cron-trigger.yml b/.github/workflows/branch-sync-cron-trigger.yml new file mode 100644 index 000000000..145fcd222 --- /dev/null +++ b/.github/workflows/branch-sync-cron-trigger.yml @@ -0,0 +1,32 @@ +#Since GitHub automatically disables cron actions after 60 days of repo inactivity, we need the active repo (PM) +#to trigger the branch merge workflow explicitly. This avoids the need for TOS-violating actions which we previously +#used to keep the restricted action active, as the workflow depends on the activity of this repo anyway. + +name: Trigger branch sync + +on: + schedule: + - cron: "0 0 * * *" #once per day so we don't spam merge commits on busy days + workflow_dispatch: #for testing + +jobs: + trigger: + name: Trigger branch sync RestrictedActions workflow + runs-on: ubuntu-22.04 + + steps: + - name: Generate access token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} + private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} + owner: ${{ github.repository_owner }} + repositories: RestrictedActions + + - name: Dispatch branch sync restricted action + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.repository_owner }}/RestrictedActions + event-type: pocketmine_mp_branch_sync From b5f236c019f360f0ac57aa4355810354f9ae80ac Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 18:09:14 +0100 Subject: [PATCH 200/334] Apparently we're supposed to use replace for this, not provide --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c744c320e..979973893 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" }, - "provide": { + "replace": { "symfony/polyfill-ctype": "*", "symfony/polyfill-mbstring": "*" }, diff --git a/composer.lock b/composer.lock index b82a014da..9cb0721fc 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": "d8fa42f33a3bcb26014e6f862366dbd6", + "content-hash": "ceb98091ac3f61f1a4b87708c48dc75a", "packages": [ { "name": "adhocore/json-comment", From 94fb5d95b92604840dabb719f04327efa559cf94 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 17 May 2025 19:09:54 +0100 Subject: [PATCH 201/334] CommonThreadPartsTrait: fixed thread crashes sometimes missing cause info closes #6669 this happens because isTerminated returns true before the thread's shutdown handler runs, so we join with the thread to make sure that shutdown handlers are done before returning. ... hopefully we don't get servers randomly deadlocking in shutdown handlers ??? --- src/thread/CommonThreadPartsTrait.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index e1c9d7c6b..de606a7b2 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -94,7 +94,17 @@ trait CommonThreadPartsTrait{ } } - public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; } + public function getCrashInfo() : ?ThreadCrashInfo{ + //TODO: Joining a crashed worker might be a bit sus, but we need to make sure the thread's shutdown + //handler has run before we try to collect the crash info. As of 6.1.1, pmmpthread sets isTerminated=true + //*before* the shutdown handler is invoked, so we might land here before the crash info has been set. + //In the future this should probably be fixed by running the shutdown handlers before setting isTerminated, + //but this workaround should be good enough for now. + if($this->isTerminated() && !$this->isJoined()){ + $this->join(); + } + return $this->crashInfo; + } public function start(int $options = NativeThread::INHERIT_NONE) : bool{ ThreadManager::getInstance()->add($this); From 9606c0e0bbe054061714e48503d993a9aa8ca7b5 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Fri, 23 May 2025 22:16:57 +0100 Subject: [PATCH 202/334] Remove stale labels as well as Waiting on Author labels actions/stale is far too slow to do this itself since it processes lots of irrelevant crap on every run --- .github/workflows/pr-remove-waiting-label.yml | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr-remove-waiting-label.yml b/.github/workflows/pr-remove-waiting-label.yml index eb46043bd..da14e36ba 100644 --- a/.github/workflows/pr-remove-waiting-label.yml +++ b/.github/workflows/pr-remove-waiting-label.yml @@ -15,19 +15,23 @@ jobs: with: github-token: ${{ github.token }} script: | - const [owner, repo] = context.payload.repository.full_name.split('/'); - try { - await github.rest.issues.removeLabel({ - owner: owner, - repo: repo, - issue_number: context.payload.number, - name: "Status: Waiting on Author", - }); - } catch (error) { - if (error.status === 404) { - //probably label wasn't set on the issue - console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message); - } else { - throw error; + function removeLabel(owner, repo, issue_number, name) { + try { + await github.rest.issues.removeLabel({ + owner: owner, + repo: repo, + issue_number: issue_number, + name: name, + }); + } catch (error) { + if (error.status === 404) { + //probably label wasn't set on the issue + console.log('Failed to remove label ' + name + ' (probably label isn\'t on the PR): ' + error.message); + } else { + throw error; + } } } + const [owner, repo] = context.payload.repository.full_name.split('/'); + removeLabel(owner, repo, context.payload.number, "Status: Waiting on Author"); + removeLabel(owner, repo, context.payload.number, "Stale"); From 3636173d75d7b97414c86d7c6f32bade005185e9 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Fri, 23 May 2025 23:28:15 +0100 Subject: [PATCH 203/334] ... --- .github/workflows/pr-remove-waiting-label.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-remove-waiting-label.yml b/.github/workflows/pr-remove-waiting-label.yml index da14e36ba..b7cd85acd 100644 --- a/.github/workflows/pr-remove-waiting-label.yml +++ b/.github/workflows/pr-remove-waiting-label.yml @@ -15,7 +15,7 @@ jobs: with: github-token: ${{ github.token }} script: | - function removeLabel(owner, repo, issue_number, name) { + async function removeLabel(owner, repo, issue_number, name) { try { await github.rest.issues.removeLabel({ owner: owner, From e1af2a4af11abf148eb38c4b5d5e05c257fb124b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 24 May 2025 16:19:48 +0100 Subject: [PATCH 204/334] Update language dependency --- composer.json | 2 +- composer.lock | 14 +++++++------- src/lang/KnownTranslationFactory.php | 8 ++++++++ src/lang/KnownTranslationKeys.php | 2 ++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 979973893..a3b2fb6bd 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", "pocketmine/errorhandler": "^0.7.0", - "pocketmine/locale-data": "~2.24.0", + "pocketmine/locale-data": "~2.25.0", "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", "pocketmine/nbt": "~1.1.0", diff --git a/composer.lock b/composer.lock index 9cb0721fc..ef41f04cd 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": "ceb98091ac3f61f1a4b87708c48dc75a", + "content-hash": "b106b34fbd6c8abdfd45931bcb18bb69", "packages": [ { "name": "adhocore/json-comment", @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.24.2", + "version": "2.25.1", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b" + "reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/2a00c44c52bce98e7a43aa31517df78cbb2ba23b", - "reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b", + "url": "https://api.github.com/repos/pmmp/Language/zipball/8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd", + "reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.24.2" + "source": "https://github.com/pmmp/Language/tree/2.25.1" }, - "time": "2025-04-03T01:23:27+00:00" + "time": "2025-04-16T11:15:32+00:00" }, { "name": "pocketmine/log", diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php index 4e42419ea..eadd74f32 100644 --- a/src/lang/KnownTranslationFactory.php +++ b/src/lang/KnownTranslationFactory.php @@ -3035,6 +3035,14 @@ final class KnownTranslationFactory{ return new Translatable(KnownTranslationKeys::TILE_BED_TOOFAR, []); } + public static function tile_respawn_anchor_notValid() : Translatable{ + return new Translatable(KnownTranslationKeys::TILE_RESPAWN_ANCHOR_NOTVALID, []); + } + + public static function tile_respawn_anchor_respawnSet() : Translatable{ + return new Translatable(KnownTranslationKeys::TILE_RESPAWN_ANCHOR_RESPAWNSET, []); + } + public static function view_distance() : Translatable{ return new Translatable(KnownTranslationKeys::VIEW_DISTANCE, []); } diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php index 6fbb32ecb..44a64c489 100644 --- a/src/lang/KnownTranslationKeys.php +++ b/src/lang/KnownTranslationKeys.php @@ -658,6 +658,8 @@ final class KnownTranslationKeys{ public const TILE_BED_NOSLEEP = "tile.bed.noSleep"; public const TILE_BED_OCCUPIED = "tile.bed.occupied"; public const TILE_BED_TOOFAR = "tile.bed.tooFar"; + public const TILE_RESPAWN_ANCHOR_NOTVALID = "tile.respawn_anchor.notValid"; + public const TILE_RESPAWN_ANCHOR_RESPAWNSET = "tile.respawn_anchor.respawnSet"; public const VIEW_DISTANCE = "view_distance"; public const WELCOME_TO_POCKETMINE = "welcome_to_pocketmine"; public const WHITELIST_ENABLE = "whitelist_enable"; From 4d5c27a7343fe78bfff901f2b911fc877581862e Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Sat, 24 May 2025 23:01:36 +0300 Subject: [PATCH 205/334] Unit test block hardness & blast resistance values (#6629) --- src/block/BlockBreakInfo.php | 2 +- src/block/VanillaBlocks.php | 171 +++++++++++++++--------------- tests/phpunit/block/BlockTest.php | 55 ++++++++++ 3 files changed, 144 insertions(+), 84 deletions(-) diff --git a/src/block/BlockBreakInfo.php b/src/block/BlockBreakInfo.php index e77e06cfd..3d45caf3c 100644 --- a/src/block/BlockBreakInfo.php +++ b/src/block/BlockBreakInfo.php @@ -73,7 +73,7 @@ class BlockBreakInfo{ return new self(0.0, $toolType, $toolHarvestLevel, 0.0); } - public static function indestructible(float $blastResistance = 18000000.0) : self{ + public static function indestructible(float $blastResistance = 18000003.75) : self{ return new self(-1.0, BlockToolType::NONE, 0, $blastResistance); } diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 231004dfa..0a6d4b31c 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -859,7 +859,7 @@ final class VanillaBlocks{ $railBreakInfo = new Info(new BreakInfo(0.7)); self::register("activator_rail", fn(BID $id) => new ActivatorRail($id, "Activator Rail", $railBreakInfo)); self::register("anvil", fn(BID $id) => new Anvil($id, "Anvil", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0)))); - self::register("bamboo", fn(BID $id) => new Bamboo($id, "Bamboo", new Info(new class(2.0 /* 1.0 in PC */, ToolType::AXE) extends BreakInfo{ + self::register("bamboo", fn(BID $id) => new Bamboo($id, "Bamboo", new Info(new class(1.0, ToolType::AXE) extends BreakInfo{ public function getBreakTime(Item $item) : float{ if($item->getBlockToolType() === ToolType::SWORD){ return 0.0; @@ -867,7 +867,7 @@ final class VanillaBlocks{ return parent::getBreakTime($item); } }, [Tags::POTTABLE_PLANTS]))); - self::register("bamboo_sapling", fn(BID $id) => new BambooSapling($id, "Bamboo Sapling", new Info(BreakInfo::instant()))); + self::register("bamboo_sapling", fn(BID $id) => new BambooSapling($id, "Bamboo Sapling", new Info(new BreakInfo(1.0)))); $bannerBreakInfo = new Info(BreakInfo::axe(1.0)); self::register("banner", fn(BID $id) => new FloorBanner($id, "Banner", $bannerBreakInfo), TileBanner::class); @@ -876,7 +876,7 @@ final class VanillaBlocks{ self::register("barrier", fn(BID $id) => new Transparent($id, "Barrier", new Info(BreakInfo::indestructible()))); self::register("beacon", fn(BID $id) => new Beacon($id, "Beacon", new Info(new BreakInfo(3.0))), TileBeacon::class); self::register("bed", fn(BID $id) => new Bed($id, "Bed Block", new Info(new BreakInfo(0.2))), TileBed::class); - self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible()))); + self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible(18000000.0)))); self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant()))); self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0))), TileBell::class); @@ -913,7 +913,7 @@ final class VanillaBlocks{ self::register("cobweb", fn(BID $id) => new Cobweb($id, "Cobweb", new Info(new BreakInfo(4.0, ToolType::SWORD | ToolType::SHEARS, 1)))); self::register("cocoa_pod", fn(BID $id) => new CocoaBlock($id, "Cocoa Block", new Info(BreakInfo::axe(0.2, null, 15.0)))); - self::register("coral_block", fn(BID $id) => new CoralBlock($id, "Coral Block", new Info(BreakInfo::pickaxe(7.0, ToolTier::WOOD)))); + self::register("coral_block", fn(BID $id) => new CoralBlock($id, "Coral Block", new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)))); self::register("daylight_sensor", fn(BID $id) => new DaylightSensor($id, "Daylight Sensor", new Info(BreakInfo::axe(0.2))), TileDaylightSensor::class); self::register("dead_bush", fn(BID $id) => new DeadBush($id, "Dead Bush", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS]))); self::register("detector_rail", fn(BID $id) => new DetectorRail($id, "Detector Rail", $railBreakInfo)); @@ -930,15 +930,15 @@ final class VanillaBlocks{ self::register("pitcher_plant", fn(BID $id) => new DoublePlant($id, "Pitcher Plant", new Info(BreakInfo::instant()))); self::register("pitcher_crop", fn(BID $id) => new PitcherCrop($id, "Pitcher Crop", new Info(BreakInfo::instant()))); self::register("double_pitcher_crop", fn(BID $id) => new DoublePitcherCrop($id, "Double Pitcher Crop", new Info(BreakInfo::instant()))); - self::register("dragon_egg", fn(BID $id) => new DragonEgg($id, "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD)))); + self::register("dragon_egg", fn(BID $id) => new DragonEgg($id, "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, blastResistance: 45.0)))); self::register("dried_kelp", fn(BID $id) => new DriedKelp($id, "Dried Kelp Block", new Info(new BreakInfo(0.5, ToolType::NONE, 0, 12.5)))); self::register("emerald", fn(BID $id) => new Opaque($id, "Emerald Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0)))); self::register("enchanting_table", fn(BID $id) => new EnchantingTable($id, "Enchanting Table", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))), TileEnchantingTable::class); - self::register("end_portal_frame", fn(BID $id) => new EndPortalFrame($id, "End Portal Frame", new Info(BreakInfo::indestructible()))); + self::register("end_portal_frame", fn(BID $id) => new EndPortalFrame($id, "End Portal Frame", new Info(BreakInfo::indestructible(18000000.0)))); self::register("end_rod", fn(BID $id) => new EndRod($id, "End Rod", new Info(BreakInfo::instant()))); self::register("end_stone", fn(BID $id) => new Opaque($id, "End Stone", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0)))); - $endBrickBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD, 4.0)); + $endBrickBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0)); self::register("end_stone_bricks", fn(BID $id) => new Opaque($id, "End Stone Bricks", $endBrickBreakInfo)); self::register("end_stone_brick_stairs", fn(BID $id) => new Stair($id, "End Stone Brick Stairs", $endBrickBreakInfo)); @@ -962,7 +962,7 @@ final class VanillaBlocks{ self::register("torchflower", fn(BID $id) => new Flower($id, "Torchflower", $flowerTypeInfo)); self::register("torchflower_crop", fn(BID $id) => new TorchflowerCrop($id, "Torchflower Crop", new Info(BreakInfo::instant()))); self::register("flower_pot", fn(BID $id) => new FlowerPot($id, "Flower Pot", new Info(BreakInfo::instant())), TileFlowerPot::class); - self::register("frosted_ice", fn(BID $id) => new FrostedIce($id, "Frosted Ice", new Info(BreakInfo::pickaxe(2.5)))); + self::register("frosted_ice", fn(BID $id) => new FrostedIce($id, "Frosted Ice", new Info(BreakInfo::pickaxe(0.5)))); self::register("furnace", fn(BID $id) => new Furnace($id, "Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::FURNACE), TileNormalFurnace::class); self::register("blast_furnace", fn(BID $id) => new Furnace($id, "Blast Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::BLAST_FURNACE), TileBlastFurnace::class); self::register("smoker", fn(BID $id) => new Furnace($id, "Smoker", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::SMOKER), TileSmoker::class); @@ -970,30 +970,28 @@ final class VanillaBlocks{ $glassBreakInfo = new Info(new BreakInfo(0.3)); self::register("glass", fn(BID $id) => new Glass($id, "Glass", $glassBreakInfo)); self::register("glass_pane", fn(BID $id) => new GlassPane($id, "Glass Pane", $glassBreakInfo)); - self::register("glowing_obsidian", fn(BID $id) => new GlowingObsidian($id, "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND, 50.0)))); + self::register("glowing_obsidian", fn(BID $id) => new GlowingObsidian($id, "Glowing Obsidian", new Info(BreakInfo::pickaxe(35.0, ToolTier::DIAMOND, 6000.0)))); self::register("glowstone", fn(BID $id) => new Glowstone($id, "Glowstone", new Info(BreakInfo::pickaxe(0.3)))); - self::register("glow_lichen", fn(BID $id) => new GlowLichen($id, "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2)))); + self::register("glow_lichen", fn(BID $id) => new GlowLichen($id, "Glow Lichen", new Info(BreakInfo::axe(0.2)))); self::register("gold", fn(BID $id) => new Opaque($id, "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON, 30.0)))); - $grassBreakInfo = BreakInfo::shovel(0.6); - self::register("grass", fn(BID $id) => new Grass($id, "Grass", new Info($grassBreakInfo, [Tags::DIRT]))); - self::register("grass_path", fn(BID $id) => new GrassPath($id, "Grass Path", new Info($grassBreakInfo))); + self::register("grass", fn(BID $id) => new Grass($id, "Grass", new Info(BreakInfo::shovel(0.6), [Tags::DIRT]))); + self::register("grass_path", fn(BID $id) => new GrassPath($id, "Grass Path", new Info(BreakInfo::shovel(0.65)))); self::register("gravel", fn(BID $id) => new Gravel($id, "Gravel", new Info(BreakInfo::shovel(0.6)))); - $hardenedClayBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0)); - self::register("hardened_clay", fn(BID $id) => new HardenedClay($id, "Hardened Clay", $hardenedClayBreakInfo)); + self::register("hardened_clay", fn(BID $id) => new HardenedClay($id, "Hardened Clay", new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0)))); $hardenedGlassBreakInfo = new Info(new BreakInfo(10.0)); self::register("hardened_glass", fn(BID $id) => new HardenedGlass($id, "Hardened Glass", $hardenedGlassBreakInfo)); self::register("hardened_glass_pane", fn(BID $id) => new HardenedGlassPane($id, "Hardened Glass Pane", $hardenedGlassBreakInfo)); self::register("hay_bale", fn(BID $id) => new HayBale($id, "Hay Bale", new Info(new BreakInfo(0.5)))); - self::register("hopper", fn(BID $id) => new Hopper($id, "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 15.0))), TileHopper::class); + self::register("hopper", fn(BID $id) => new Hopper($id, "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 24.0))), TileHopper::class); self::register("ice", fn(BID $id) => new Ice($id, "Ice", new Info(BreakInfo::pickaxe(0.5)))); $updateBlockBreakInfo = new Info(new BreakInfo(1.0)); self::register("info_update", fn(BID $id) => new Opaque($id, "update!", $updateBlockBreakInfo)); self::register("info_update2", fn(BID $id) => new Opaque($id, "ate!upd", $updateBlockBreakInfo)); - self::register("invisible_bedrock", fn(BID $id) => new Transparent($id, "Invisible Bedrock", new Info(BreakInfo::indestructible()))); + self::register("invisible_bedrock", fn(BID $id) => new Transparent($id, "Invisible Bedrock", new Info(BreakInfo::indestructible(18000000.0)))); $ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE, 30.0)); self::register("iron", fn(BID $id) => new Opaque($id, "Iron Block", $ironBreakInfo)); @@ -1006,16 +1004,16 @@ final class VanillaBlocks{ self::register("item_frame", fn(BID $id) => new ItemFrame($id, "Item Frame", $itemFrameInfo), TileItemFrame::class); self::register("glowing_item_frame", fn(BID $id) => new ItemFrame($id, "Glow Item Frame", $itemFrameInfo), TileGlowingItemFrame::class); - self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(0.8))), TileJukebox::class); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not + self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(2.0, blastResistance: 30.0))), TileJukebox::class); self::register("ladder", fn(BID $id) => new Ladder($id, "Ladder", new Info(BreakInfo::axe(0.4)))); - $lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0)); + $lanternBreakInfo = new Info(BreakInfo::pickaxe(3.5)); self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15)); self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10)); self::register("lapis_lazuli", fn(BID $id) => new Opaque($id, "Lapis Lazuli Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE)))); self::register("lava", fn(BID $id) => new Lava($id, "Lava", new Info(BreakInfo::indestructible(500.0)))); - self::register("lectern", fn(BID $id) => new Lectern($id, "Lectern", new Info(BreakInfo::axe(2.0))), TileLectern::class); + self::register("lectern", fn(BID $id) => new Lectern($id, "Lectern", new Info(BreakInfo::axe(2.5))), TileLectern::class); self::register("lever", fn(BID $id) => new Lever($id, "Lever", new Info(new BreakInfo(0.5)))); self::register("magma", fn(BID $id) => new Magma($id, "Magma Block", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)))); self::register("melon", fn(BID $id) => new Melon($id, "Melon Block", new Info(BreakInfo::axe(1.0)))); @@ -1065,14 +1063,15 @@ final class VanillaBlocks{ self::register("purpur_stairs", fn(BID $id) => new Stair($id, "Purpur Stairs", $purpurBreakInfo)); $quartzBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD)); + $smoothQuartzBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); self::register("quartz", fn(BID $id) => new Opaque($id, "Quartz Block", $quartzBreakInfo)); self::register("chiseled_quartz", fn(BID $id) => new SimplePillar($id, "Chiseled Quartz Block", $quartzBreakInfo)); self::register("quartz_pillar", fn(BID $id) => new SimplePillar($id, "Quartz Pillar", $quartzBreakInfo)); - self::register("smooth_quartz", fn(BID $id) => new Opaque($id, "Smooth Quartz Block", $quartzBreakInfo)); + self::register("smooth_quartz", fn(BID $id) => new Opaque($id, "Smooth Quartz Block", $smoothQuartzBreakInfo)); self::register("quartz_bricks", fn(BID $id) => new Opaque($id, "Quartz Bricks", $quartzBreakInfo)); self::register("quartz_stairs", fn(BID $id) => new Stair($id, "Quartz Stairs", $quartzBreakInfo)); - self::register("smooth_quartz_stairs", fn(BID $id) => new Stair($id, "Smooth Quartz Stairs", $quartzBreakInfo)); + self::register("smooth_quartz_stairs", fn(BID $id) => new Stair($id, "Smooth Quartz Stairs", $smoothQuartzBreakInfo)); self::register("rail", fn(BID $id) => new Rail($id, "Rail", $railBreakInfo)); self::register("red_mushroom", fn(BID $id) => new RedMushroom($id, "Red Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]))); @@ -1127,13 +1126,13 @@ final class VanillaBlocks{ $infestedStoneBreakInfo = new Info(BreakInfo::pickaxe(0.75)); self::register("infested_stone", fn(BID $id) => new InfestedStone($id, "Infested Stone", $infestedStoneBreakInfo, $stone)); self::register("infested_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Stone Brick", $infestedStoneBreakInfo, $stoneBrick)); - self::register("infested_cobblestone", fn(BID $id) => new InfestedStone($id, "Infested Cobblestone", $infestedStoneBreakInfo, $cobblestone)); + self::register("infested_cobblestone", fn(BID $id) => new InfestedStone($id, "Infested Cobblestone", new Info(BreakInfo::pickaxe(1.0, blastResistance: 3.75)), $cobblestone)); self::register("infested_mossy_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Mossy Stone Brick", $infestedStoneBreakInfo, $mossyStoneBrick)); self::register("infested_cracked_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Cracked Stone Brick", $infestedStoneBreakInfo, $crackedStoneBrick)); self::register("infested_chiseled_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Chiseled Stone Brick", $infestedStoneBreakInfo, $chiseledStoneBrick)); self::register("stone_stairs", fn(BID $id) => new Stair($id, "Stone Stairs", $stoneBreakInfo)); - self::register("smooth_stone", fn(BID $id) => new Opaque($id, "Smooth Stone", $stoneBreakInfo)); + self::register("smooth_stone", fn(BID $id) => new Opaque($id, "Smooth Stone", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)))); self::register("andesite_stairs", fn(BID $id) => new Stair($id, "Andesite Stairs", $stoneBreakInfo)); self::register("diorite_stairs", fn(BID $id) => new Stair($id, "Diorite Stairs", $stoneBreakInfo)); self::register("granite_stairs", fn(BID $id) => new Stair($id, "Granite Stairs", $stoneBreakInfo)); @@ -1146,7 +1145,6 @@ final class VanillaBlocks{ self::register("stonecutter", fn(BID $id) => new Stonecutter($id, "Stonecutter", new Info(BreakInfo::pickaxe(3.5)))); self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5)))); - //TODO: in the future this won't be the same for all the types $stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); self::register("brick_slab", fn(BID $id) => new Slab($id, "Brick", $stoneSlabBreakInfo)); @@ -1157,28 +1155,31 @@ final class VanillaBlocks{ self::register("sandstone_slab", fn(BID $id) => new Slab($id, "Sandstone", $stoneSlabBreakInfo)); self::register("smooth_stone_slab", fn(BID $id) => new Slab($id, "Smooth Stone", $stoneSlabBreakInfo)); self::register("stone_brick_slab", fn(BID $id) => new Slab($id, "Stone Brick", $stoneSlabBreakInfo)); - self::register("dark_prismarine_slab", fn(BID $id) => new Slab($id, "Dark Prismarine", $stoneSlabBreakInfo)); - self::register("mossy_cobblestone_slab", fn(BID $id) => new Slab($id, "Mossy Cobblestone", $stoneSlabBreakInfo)); - self::register("prismarine_slab", fn(BID $id) => new Slab($id, "Prismarine", $stoneSlabBreakInfo)); - self::register("prismarine_bricks_slab", fn(BID $id) => new Slab($id, "Prismarine Bricks", $stoneSlabBreakInfo)); - self::register("purpur_slab", fn(BID $id) => new Slab($id, "Purpur", $stoneSlabBreakInfo)); self::register("red_nether_brick_slab", fn(BID $id) => new Slab($id, "Red Nether Brick", $stoneSlabBreakInfo)); self::register("red_sandstone_slab", fn(BID $id) => new Slab($id, "Red Sandstone", $stoneSlabBreakInfo)); self::register("smooth_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Sandstone", $stoneSlabBreakInfo)); - self::register("andesite_slab", fn(BID $id) => new Slab($id, "Andesite", $stoneSlabBreakInfo)); - self::register("diorite_slab", fn(BID $id) => new Slab($id, "Diorite", $stoneSlabBreakInfo)); - self::register("end_stone_brick_slab", fn(BID $id) => new Slab($id, "End Stone Brick", $stoneSlabBreakInfo)); - self::register("granite_slab", fn(BID $id) => new Slab($id, "Granite", $stoneSlabBreakInfo)); - self::register("polished_andesite_slab", fn(BID $id) => new Slab($id, "Polished Andesite", $stoneSlabBreakInfo)); - self::register("polished_diorite_slab", fn(BID $id) => new Slab($id, "Polished Diorite", $stoneSlabBreakInfo)); - self::register("polished_granite_slab", fn(BID $id) => new Slab($id, "Polished Granite", $stoneSlabBreakInfo)); - self::register("smooth_red_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Red Sandstone", $stoneSlabBreakInfo)); self::register("cut_red_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Red Sandstone", $stoneSlabBreakInfo)); self::register("cut_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Sandstone", $stoneSlabBreakInfo)); - self::register("mossy_stone_brick_slab", fn(BID $id) => new Slab($id, "Mossy Stone Brick", $stoneSlabBreakInfo)); + self::register("mossy_cobblestone_slab", fn(BID $id) => new Slab($id, "Mossy Cobblestone", $stoneSlabBreakInfo)); + self::register("purpur_slab", fn(BID $id) => new Slab($id, "Purpur", $stoneSlabBreakInfo)); + self::register("smooth_red_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Red Sandstone", $stoneSlabBreakInfo)); self::register("smooth_quartz_slab", fn(BID $id) => new Slab($id, "Smooth Quartz", $stoneSlabBreakInfo)); self::register("stone_slab", fn(BID $id) => new Slab($id, "Stone", $stoneSlabBreakInfo)); + self::register("end_stone_brick_slab", fn(BID $id) => new Slab($id, "End Stone Brick", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 30.0)))); + + $lightStoneSlabBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)); + self::register("dark_prismarine_slab", fn(BID $id) => new Slab($id, "Dark Prismarine", $lightStoneSlabBreakInfo)); + self::register("prismarine_slab", fn(BID $id) => new Slab($id, "Prismarine", $lightStoneSlabBreakInfo)); + self::register("prismarine_bricks_slab", fn(BID $id) => new Slab($id, "Prismarine Bricks", $lightStoneSlabBreakInfo)); + self::register("andesite_slab", fn(BID $id) => new Slab($id, "Andesite", $lightStoneSlabBreakInfo)); + self::register("diorite_slab", fn(BID $id) => new Slab($id, "Diorite", $lightStoneSlabBreakInfo)); + self::register("granite_slab", fn(BID $id) => new Slab($id, "Granite", $lightStoneSlabBreakInfo)); + self::register("polished_andesite_slab", fn(BID $id) => new Slab($id, "Polished Andesite", $lightStoneSlabBreakInfo)); + self::register("polished_diorite_slab", fn(BID $id) => new Slab($id, "Polished Diorite", $lightStoneSlabBreakInfo)); + self::register("polished_granite_slab", fn(BID $id) => new Slab($id, "Polished Granite", $lightStoneSlabBreakInfo)); + self::register("mossy_stone_brick_slab", fn(BID $id) => new Slab($id, "Mossy Stone Brick", $lightStoneSlabBreakInfo)); + self::register("legacy_stonecutter", fn(BID $id) => new Opaque($id, "Legacy Stonecutter", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)))); self::register("sugarcane", fn(BID $id) => new Sugarcane($id, "Sugarcane", new Info(BreakInfo::instant()))); self::register("sweet_berry_bush", fn(BID $id) => new SweetBerryBush($id, "Sweet Berry Bush", new Info(BreakInfo::instant()))); @@ -1237,25 +1238,26 @@ final class VanillaBlocks{ } $sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD)); + $smoothSandstoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo)); - self::register("smooth_red_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Red Sandstone Stairs", $sandstoneBreakInfo)); + self::register("smooth_red_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Red Sandstone Stairs", $smoothSandstoneBreakInfo)); self::register("red_sandstone", fn(BID $id) => new Opaque($id, "Red Sandstone", $sandstoneBreakInfo)); self::register("chiseled_red_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Red Sandstone", $sandstoneBreakInfo)); self::register("cut_red_sandstone", fn(BID $id) => new Opaque($id, "Cut Red Sandstone", $sandstoneBreakInfo)); - self::register("smooth_red_sandstone", fn(BID $id) => new Opaque($id, "Smooth Red Sandstone", $sandstoneBreakInfo)); + self::register("smooth_red_sandstone", fn(BID $id) => new Opaque($id, "Smooth Red Sandstone", $smoothSandstoneBreakInfo)); self::register("sandstone_stairs", fn(BID $id) => new Stair($id, "Sandstone Stairs", $sandstoneBreakInfo)); - self::register("smooth_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Sandstone Stairs", $sandstoneBreakInfo)); + self::register("smooth_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Sandstone Stairs", $smoothSandstoneBreakInfo)); self::register("sandstone", fn(BID $id) => new Opaque($id, "Sandstone", $sandstoneBreakInfo)); self::register("chiseled_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Sandstone", $sandstoneBreakInfo)); self::register("cut_sandstone", fn(BID $id) => new Opaque($id, "Cut Sandstone", $sandstoneBreakInfo)); - self::register("smooth_sandstone", fn(BID $id) => new Opaque($id, "Smooth Sandstone", $sandstoneBreakInfo)); + self::register("smooth_sandstone", fn(BID $id) => new Opaque($id, "Smooth Sandstone", $smoothSandstoneBreakInfo)); self::register("glazed_terracotta", fn(BID $id) => new GlazedTerracotta($id, "Glazed Terracotta", new Info(BreakInfo::pickaxe(1.4, ToolTier::WOOD)))); self::register("dyed_shulker_box", fn(BID $id) => new DyedShulkerBox($id, "Dyed Shulker Box", $shulkerBoxBreakInfo), TileShulkerBox::class); self::register("stained_glass", fn(BID $id) => new StainedGlass($id, "Stained Glass", $glassBreakInfo)); self::register("stained_glass_pane", fn(BID $id) => new StainedGlassPane($id, "Stained Glass Pane", $glassBreakInfo)); - self::register("stained_clay", fn(BID $id) => new StainedHardenedClay($id, "Stained Clay", $hardenedClayBreakInfo)); + self::register("stained_clay", fn(BID $id) => new StainedHardenedClay($id, "Stained Clay", new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 6.25)))); self::register("stained_hardened_glass", fn(BID $id) => new StainedHardenedGlass($id, "Stained Hardened Glass", $hardenedGlassBreakInfo)); self::register("stained_hardened_glass_pane", fn(BID $id) => new StainedHardenedGlassPane($id, "Stained Hardened Glass Pane", $hardenedGlassBreakInfo)); self::register("carpet", fn(BID $id) => new Carpet($id, "Carpet", new Info(new BreakInfo(0.1)))); @@ -1272,22 +1274,26 @@ final class VanillaBlocks{ } }))); - //TODO: in the future these won't all have the same hardness; they only do now because of the old metadata crap - $wallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); - self::register("cobblestone_wall", fn(BID $id) => new Wall($id, "Cobblestone Wall", $wallBreakInfo)); - self::register("andesite_wall", fn(BID $id) => new Wall($id, "Andesite Wall", $wallBreakInfo)); - self::register("brick_wall", fn(BID $id) => new Wall($id, "Brick Wall", $wallBreakInfo)); - self::register("diorite_wall", fn(BID $id) => new Wall($id, "Diorite Wall", $wallBreakInfo)); - self::register("end_stone_brick_wall", fn(BID $id) => new Wall($id, "End Stone Brick Wall", $wallBreakInfo)); - self::register("granite_wall", fn(BID $id) => new Wall($id, "Granite Wall", $wallBreakInfo)); - self::register("mossy_stone_brick_wall", fn(BID $id) => new Wall($id, "Mossy Stone Brick Wall", $wallBreakInfo)); - self::register("mossy_cobblestone_wall", fn(BID $id) => new Wall($id, "Mossy Cobblestone Wall", $wallBreakInfo)); - self::register("nether_brick_wall", fn(BID $id) => new Wall($id, "Nether Brick Wall", $wallBreakInfo)); - self::register("prismarine_wall", fn(BID $id) => new Wall($id, "Prismarine Wall", $wallBreakInfo)); - self::register("red_nether_brick_wall", fn(BID $id) => new Wall($id, "Red Nether Brick Wall", $wallBreakInfo)); - self::register("red_sandstone_wall", fn(BID $id) => new Wall($id, "Red Sandstone Wall", $wallBreakInfo)); - self::register("sandstone_wall", fn(BID $id) => new Wall($id, "Sandstone Wall", $wallBreakInfo)); - self::register("stone_brick_wall", fn(BID $id) => new Wall($id, "Stone Brick Wall", $wallBreakInfo)); + self::register("end_stone_brick_wall", fn(BID $id) => new Wall($id, "End Stone Brick Wall", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0)))); + + $brickWallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); + self::register("cobblestone_wall", fn(BID $id) => new Wall($id, "Cobblestone Wall", $brickWallBreakInfo)); + self::register("brick_wall", fn(BID $id) => new Wall($id, "Brick Wall", $brickWallBreakInfo)); + self::register("mossy_cobblestone_wall", fn(BID $id) => new Wall($id, "Mossy Cobblestone Wall", $brickWallBreakInfo)); + self::register("nether_brick_wall", fn(BID $id) => new Wall($id, "Nether Brick Wall", $brickWallBreakInfo)); + self::register("red_nether_brick_wall", fn(BID $id) => new Wall($id, "Red Nether Brick Wall", $brickWallBreakInfo)); + + $stoneWallBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)); + self::register("stone_brick_wall", fn(BID $id) => new Wall($id, "Stone Brick Wall", $stoneWallBreakInfo)); + self::register("mossy_stone_brick_wall", fn(BID $id) => new Wall($id, "Mossy Stone Brick Wall", $stoneWallBreakInfo)); + self::register("granite_wall", fn(BID $id) => new Wall($id, "Granite Wall", $stoneWallBreakInfo)); + self::register("diorite_wall", fn(BID $id) => new Wall($id, "Diorite Wall", $stoneWallBreakInfo)); + self::register("andesite_wall", fn(BID $id) => new Wall($id, "Andesite Wall", $stoneWallBreakInfo)); + self::register("prismarine_wall", fn(BID $id) => new Wall($id, "Prismarine Wall", $stoneWallBreakInfo)); + + $sandstoneWallBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD, 4.0)); + self::register("red_sandstone_wall", fn(BID $id) => new Wall($id, "Red Sandstone Wall", $sandstoneWallBreakInfo)); + self::register("sandstone_wall", fn(BID $id) => new Wall($id, "Sandstone Wall", $sandstoneWallBreakInfo)); self::registerElements(); @@ -1320,8 +1326,8 @@ final class VanillaBlocks{ self::register("mangrove_roots", fn(BID $id) => new MangroveRoots($id, "Mangrove Roots", new Info(BreakInfo::axe(0.7)))); self::register("muddy_mangrove_roots", fn(BID $id) => new SimplePillar($id, "Muddy Mangrove Roots", new Info(BreakInfo::shovel(0.7), [Tags::MUD]))); self::register("froglight", fn(BID $id) => new Froglight($id, "Froglight", new Info(new BreakInfo(0.3)))); - self::register("sculk", fn(BID $id) => new Sculk($id, "Sculk", new Info(new BreakInfo(0.6, ToolType::HOE)))); - self::register("reinforced_deepslate", fn(BID $id) => new class($id, "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 3600.0))) extends Opaque{ + self::register("sculk", fn(BID $id) => new Sculk($id, "Sculk", new Info(new BreakInfo(0.2, ToolType::HOE)))); + self::register("reinforced_deepslate", fn(BID $id) => new class($id, "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 6000.0))) extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return []; } @@ -1537,7 +1543,7 @@ final class VanillaBlocks{ self::register("lapis_lazuli_ore", fn(BID $id) => new LapisOre($id, "Lapis Lazuli Ore", $stoneOreBreakInfo(ToolTier::STONE))); self::register("redstone_ore", fn(BID $id) => new RedstoneOre($id, "Redstone Ore", $stoneOreBreakInfo(ToolTier::IRON))); - $deepslateOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(4.5, $toolTier)); + $deepslateOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(4.5, $toolTier, 15.0)); self::register("deepslate_coal_ore", fn(BID $id) => new CoalOre($id, "Deepslate Coal Ore", $deepslateOreBreakInfo(ToolTier::WOOD))); self::register("deepslate_copper_ore", fn(BID $id) => new CopperOre($id, "Deepslate Copper Ore", $deepslateOreBreakInfo(ToolTier::STONE))); self::register("deepslate_diamond_ore", fn(BID $id) => new DiamondOre($id, "Deepslate Diamond Ore", $deepslateOreBreakInfo(ToolTier::IRON))); @@ -1581,10 +1587,10 @@ final class VanillaBlocks{ //for some reason, slabs have weird hardness like the legacy ones $slabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); - self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{ + self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 6000.0))) extends Opaque{ public function isFireProofAsItem() : bool{ return true; } }); - $netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 3600.0)); + $netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 6000.0)); self::register("netherite", fn(BID $id) => new class($id, "Netherite Block", $netheriteBreakInfo) extends Opaque{ public function isFireProofAsItem() : bool{ return true; } }); @@ -1602,14 +1608,14 @@ final class VanillaBlocks{ self::register("gilded_blackstone", fn(BID $id) => new GildedBlackstone($id, "Gilded Blackstone", $blackstoneBreakInfo)); - //TODO: polished blackstone ought to have 2.0 hardness (as per java) but it's 1.5 in Bedrock (probably parity bug) + $polishedBlackstoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); $prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : ""); - self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $blackstoneBreakInfo)); + self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $polishedBlackstoneBreakInfo)); self::register("polished_blackstone_button", fn(BID $id) => new StoneButton($id, $prefix("Button"), new Info(BreakInfo::pickaxe(0.5)))); self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5)), 20)); self::register("polished_blackstone_slab", fn(BID $id) => new Slab($id, $prefix(""), $slabBreakInfo)); - self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo)); - self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo)); + self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $polishedBlackstoneBreakInfo)); + self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $polishedBlackstoneBreakInfo)); self::register("chiseled_polished_blackstone", fn(BID $id) => new Opaque($id, "Chiseled Polished Blackstone", $blackstoneBreakInfo)); $prefix = fn(string $thing) => "Polished Blackstone Brick" . ($thing !== "" ? " $thing" : ""); @@ -1622,8 +1628,7 @@ final class VanillaBlocks{ self::register("soul_torch", fn(BID $id) => new Torch($id, "Soul Torch", new Info(BreakInfo::instant()))); self::register("soul_fire", fn(BID $id) => new SoulFire($id, "Soul Fire", new Info(BreakInfo::instant(), [Tags::FIRE]))); - //TODO: soul soul ought to have 0.5 hardness (as per java) but it's 1.0 in Bedrock (probably parity bug) - self::register("soul_soil", fn(BID $id) => new Opaque($id, "Soul Soil", new Info(BreakInfo::shovel(1.0)))); + self::register("soul_soil", fn(BID $id) => new Opaque($id, "Soul Soil", new Info(BreakInfo::shovel(0.5)))); self::register("shroomlight", fn(BID $id) => new class($id, "Shroomlight", new Info(new BreakInfo(1.0, ToolType::HOE))) extends Opaque{ public function getLightLevel() : int{ return 15; } @@ -1641,7 +1646,7 @@ final class VanillaBlocks{ self::register("crimson_roots", fn(BID $id) => new NetherRoots($id, "Crimson Roots", $netherRootsInfo)); self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo)); - self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD)))); + self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0)))); } private static function registerBlocksR17() : void{ @@ -1659,7 +1664,7 @@ final class VanillaBlocks{ self::register("raw_gold", fn(BID $id) => new Opaque($id, "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON, 30.0)))); self::register("raw_iron", fn(BID $id) => new Opaque($id, "Raw Iron Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0)))); - $deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD, 18.0)); + $deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD, 30.0)); self::register("deepslate", fn(BID $id) => new class($id, "Deepslate", $deepslateBreakInfo) extends SimplePillar{ public function getDropsForCompatibleTool(Item $item) : array{ return [VanillaBlocks::COBBLED_DEEPSLATE()->asItem()]; @@ -1671,29 +1676,29 @@ final class VanillaBlocks{ }); //TODO: parity issue here - in Java this has a hardness of 3.0, but in bedrock it's 3.5 - self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0)))); + self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0)))); - $deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0)); + $deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0)); self::register("deepslate_bricks", fn(BID $id) => new Opaque($id, "Deepslate Bricks", $deepslateBrickBreakInfo)); self::register("deepslate_brick_slab", fn(BID $id) => new Slab($id, "Deepslate Brick", $deepslateBrickBreakInfo)); self::register("deepslate_brick_stairs", fn(BID $id) => new Stair($id, "Deepslate Brick Stairs", $deepslateBrickBreakInfo)); self::register("deepslate_brick_wall", fn(BID $id) => new Wall($id, "Deepslate Brick Wall", $deepslateBrickBreakInfo)); self::register("cracked_deepslate_bricks", fn(BID $id) => new Opaque($id, "Cracked Deepslate Bricks", $deepslateBrickBreakInfo)); - $deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0)); + $deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0)); self::register("deepslate_tiles", fn(BID $id) => new Opaque($id, "Deepslate Tiles", $deepslateTilesBreakInfo)); self::register("deepslate_tile_slab", fn(BID $id) => new Slab($id, "Deepslate Tile", $deepslateTilesBreakInfo)); self::register("deepslate_tile_stairs", fn(BID $id) => new Stair($id, "Deepslate Tile Stairs", $deepslateTilesBreakInfo)); self::register("deepslate_tile_wall", fn(BID $id) => new Wall($id, "Deepslate Tile Wall", $deepslateTilesBreakInfo)); self::register("cracked_deepslate_tiles", fn(BID $id) => new Opaque($id, "Cracked Deepslate Tiles", $deepslateTilesBreakInfo)); - $cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0)); + $cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0)); self::register("cobbled_deepslate", fn(BID $id) => new Opaque($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo)); self::register("cobbled_deepslate_slab", fn(BID $id) => new Slab($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo)); self::register("cobbled_deepslate_stairs", fn(BID $id) => new Stair($id, "Cobbled Deepslate Stairs", $cobbledDeepslateBreakInfo)); self::register("cobbled_deepslate_wall", fn(BID $id) => new Wall($id, "Cobbled Deepslate Wall", $cobbledDeepslateBreakInfo)); - $polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0)); + $polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0)); self::register("polished_deepslate", fn(BID $id) => new Opaque($id, "Polished Deepslate", $polishedDeepslateBreakInfo)); self::register("polished_deepslate_slab", fn(BID $id) => new Slab($id, "Polished Deepslate", $polishedDeepslateBreakInfo)); self::register("polished_deepslate_stairs", fn(BID $id) => new Stair($id, "Polished Deepslate Stairs", $polishedDeepslateBreakInfo)); @@ -1702,7 +1707,7 @@ final class VanillaBlocks{ self::register("tinted_glass", fn(BID $id) => new TintedGlass($id, "Tinted Glass", new Info(new BreakInfo(0.3)))); //blast resistance should be 30 if we were matched with java :( - $copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 18.0)); + $copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0)); self::register("lightning_rod", fn(BID $id) => new LightningRod($id, "Lightning Rod", $copperBreakInfo)); self::register("copper", fn(BID $id) => new Copper($id, "Copper Block", $copperBreakInfo)); @@ -1730,8 +1735,8 @@ final class VanillaBlocks{ self::register("cave_vines", fn(BID $id) => new CaveVines($id, "Cave Vines", new Info(BreakInfo::instant()))); self::register("small_dripleaf", fn(BID $id) => new SmallDripleaf($id, "Small Dripleaf", new Info(BreakInfo::instant(ToolType::SHEARS, toolHarvestLevel: 1)))); - self::register("big_dripleaf_head", fn(BID $id) => new BigDripleafHead($id, "Big Dripleaf", new Info(BreakInfo::instant()))); - self::register("big_dripleaf_stem", fn(BID $id) => new BigDripleafStem($id, "Big Dripleaf Stem", new Info(BreakInfo::instant()))); + self::register("big_dripleaf_head", fn(BID $id) => new BigDripleafHead($id, "Big Dripleaf", new Info(new BreakInfo(0.1)))); + self::register("big_dripleaf_stem", fn(BID $id) => new BigDripleafStem($id, "Big Dripleaf Stem", new Info(new BreakInfo(0.1)))); } private static function registerBlocksR18() : void{ @@ -1742,7 +1747,7 @@ final class VanillaBlocks{ self::register("mud", fn(BID $id) => new Opaque($id, "Mud", new Info(BreakInfo::shovel(0.5), [Tags::MUD]))); self::register("packed_mud", fn(BID $id) => new Opaque($id, "Packed Mud", new Info(BreakInfo::pickaxe(1.0, null, 15.0)))); - $mudBricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); + $mudBricksBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 15.0)); self::register("mud_bricks", fn(BID $id) => new Opaque($id, "Mud Bricks", $mudBricksBreakInfo)); self::register("mud_brick_slab", fn(BID $id) => new Slab($id, "Mud Brick", $mudBricksBreakInfo)); @@ -1754,7 +1759,7 @@ final class VanillaBlocks{ self::register("resin", fn(BID $id) => new Opaque($id, "Block of Resin", new Info(BreakInfo::instant()))); self::register("resin_clump", fn(BID $id) => new ResinClump($id, "Resin Clump", new Info(BreakInfo::instant()))); - $resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD)); + $resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)); self::register("resin_brick_slab", fn(BID $id) => new Slab($id, "Resin Brick", $resinBricksInfo)); self::register("resin_brick_stairs", fn(BID $id) => new Stair($id, "Resin Brick Stairs", $resinBricksInfo)); self::register("resin_brick_wall", fn(BID $id) => new Wall($id, "Resin Brick Wall", $resinBricksInfo)); diff --git a/tests/phpunit/block/BlockTest.php b/tests/phpunit/block/BlockTest.php index 971564720..138a3e4e8 100644 --- a/tests/phpunit/block/BlockTest.php +++ b/tests/phpunit/block/BlockTest.php @@ -24,16 +24,22 @@ declare(strict_types=1); namespace pocketmine\block; use PHPUnit\Framework\TestCase; +use pocketmine\data\bedrock\BedrockDataFiles; +use pocketmine\data\bedrock\block\BlockTypeNames; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; +use pocketmine\world\format\io\GlobalBlockStateHandlers; +use function array_fill_keys; use function get_debug_type; use function implode; use function is_array; +use function is_float; use function is_int; use function is_string; use function json_decode; use function log; +use function round; use const JSON_THROW_ON_ERROR; class BlockTest extends TestCase{ @@ -95,6 +101,55 @@ class BlockTest extends TestCase{ } } + public function testBlockBreakInfo() : void{ + $propertiesTable = json_decode(Filesystem::fileGetContents(BedrockDataFiles::BLOCK_PROPERTIES_TABLE_JSON), true, 3, JSON_THROW_ON_ERROR); + if(!is_array($propertiesTable)){ + throw new AssumptionFailedError("Block properties table must be an array"); + } + $exceptions = array_fill_keys([ + BlockTypeNames::AIR, + BlockTypeNames::WATER, + BlockTypeNames::FLOWING_WATER, + BlockTypeNames::LAVA, + BlockTypeNames::FLOWING_LAVA, + BlockTypeNames::MANGROVE_LOG, //For some reason ONLY this wood block has blast resistance 2 instead of 10... + ], true); + + $serializer = GlobalBlockStateHandlers::getSerializer(); + $testedBlocks = []; + $hardnessErrors = []; + $blastResistanceErrors = []; + foreach($this->blockFactory->getAllKnownStates() as $block){ + $vanillaId = $serializer->serializeBlock($block)->getName(); + if(isset($exceptions[$vanillaId]) || isset($testedBlocks[$vanillaId])){ + continue; + } + if(!isset($propertiesTable[$vanillaId]) || !is_array($propertiesTable[$vanillaId])){ + throw new AssumptionFailedError("$vanillaId does not exist in the vanilla block properties table or is not an array"); + } + if(!isset($propertiesTable[$vanillaId]["hardness"]) || !is_float($propertiesTable[$vanillaId]["hardness"])){ + throw new AssumptionFailedError("Hardness property is missing for $vanillaId or is not a float value"); + } + if(!isset($propertiesTable[$vanillaId]["blastResistance"]) || !is_float($propertiesTable[$vanillaId]["blastResistance"])){ + throw new AssumptionFailedError("Blast resistance property is missing for $vanillaId or is not a float value"); + } + $testedBlocks[$vanillaId] = true; + + $vanillaHardness = round($propertiesTable[$vanillaId]["hardness"], 5); + $vanillaBlastResistance = round($propertiesTable[$vanillaId]["blastResistance"], 5) * 5; + + $breakInfo = $block->getBreakInfo(); + if($breakInfo->getHardness() !== $vanillaHardness){ + $hardnessErrors[] = "Hardness mismatch for $vanillaId (expected: $vanillaHardness, got " . $breakInfo->getHardness() . ")"; + } + if($breakInfo->getBlastResistance() !== $vanillaBlastResistance){ + $blastResistanceErrors[] = "Blast resistance mismatch for $vanillaId (expected: $vanillaBlastResistance, got " . $breakInfo->getBlastResistance() . ")"; + } + } + self::assertEmpty($hardnessErrors, "Block hardness test failed:\n" . implode("\n", $hardnessErrors)); + self::assertEmpty($blastResistanceErrors, "Block blast resistance test failed:\n" . implode("\n", $blastResistanceErrors)); + } + /** * @return int[][]|string[][] * @phpstan-return array{array, array} From eee2e62d81b6aeec02e668b2f36680bffdf39af7 Mon Sep 17 00:00:00 2001 From: zSALLAZAR <59490940+zSALLAZAR@users.noreply.github.com> Date: Sun, 25 May 2025 10:01:46 +0200 Subject: [PATCH 206/334] Add EntityFrostWalkerEvent (#6673) --- src/entity/Living.php | 20 ++++- src/event/entity/EntityFrostWalkerEvent.php | 84 +++++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/event/entity/EntityFrostWalkerEvent.php diff --git a/src/entity/Living.php b/src/entity/Living.php index 852344784..6d62c85d2 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -38,6 +38,7 @@ use pocketmine\event\entity\EntityDamageByChildEntityEvent; use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDeathEvent; +use pocketmine\event\entity\EntityFrostWalkerEvent; use pocketmine\inventory\ArmorInventory; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; @@ -721,19 +722,30 @@ abstract class Living extends Entity{ $y = $this->location->getFloorY() - 1; $baseZ = $this->location->getFloorZ(); - $frostedIce = VanillaBlocks::FROSTED_ICE(); + $liquid = VanillaBlocks::WATER(); + $targetBlock = VanillaBlocks::FROSTED_ICE(); + if(EntityFrostWalkerEvent::hasHandlers()){ + $ev = new EntityFrostWalkerEvent($this, $radius, $liquid, $targetBlock); + $ev->call(); + if($ev->isCancelled()){ + return; + } + $radius = $ev->getRadius(); + $liquid = $ev->getLiquid(); + $targetBlock = $ev->getTargetBlock(); + } + for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){ for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){ $block = $world->getBlockAt($x, $y, $z); if( - !$block instanceof Water || - !$block->isSource() || + !$block->isSameState($liquid) || $world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR || count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0 ){ continue; } - $world->setBlockAt($x, $y, $z, $frostedIce); + $world->setBlockAt($x, $y, $z, $targetBlock); } } } diff --git a/src/event/entity/EntityFrostWalkerEvent.php b/src/event/entity/EntityFrostWalkerEvent.php new file mode 100644 index 000000000..15ba28268 --- /dev/null +++ b/src/event/entity/EntityFrostWalkerEvent.php @@ -0,0 +1,84 @@ + + */ +class EntityFrostWalkerEvent extends EntityEvent implements Cancellable{ + use CancellableTrait; + + public function __construct( + Living $entity, + private int $radius, + private Liquid $liquid, + private Block $targetBlock + ){ + $this->entity = $entity; + } + + public function getRadius() : int{ + return $this->radius; + } + + public function setRadius(int $radius) : void{ + $this->radius = $radius; + } + + /** + * Returns the liquid that gets frozen + */ + public function getLiquid() : Liquid{ + return $this->liquid; + } + + /** + * Sets the liquid that gets frozen + */ + public function setLiquid(Liquid $liquid) : void{ + $this->liquid = $liquid; + } + + /** + * Returns the block that replaces the liquid + */ + public function getTargetBlock() : Block{ + return $this->targetBlock; + } + + /** + * Sets the block that replaces the liquid + */ + public function setTargetBlock(Block $targetBlock) : void{ + $this->targetBlock = $targetBlock; + } +} From 5527a0c6bf4343b39cd6ed4526f75539ac6ddf19 Mon Sep 17 00:00:00 2001 From: ItzxDwi <107537435+ItzxDwi@users.noreply.github.com> Date: Sun, 25 May 2025 16:07:41 +0800 Subject: [PATCH 207/334] Entity: make stepHeight accessable (#6702) --- src/entity/Entity.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 6681558ad..eb7098f1e 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -1187,12 +1187,14 @@ abstract class Entity{ $moveBB->offset(0, 0, $dz); - if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ + $stepHeight = $this->getStepHeight(); + + if($stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; $dx = $wantedX; - $dy = $this->stepHeight; + $dy = $stepHeight; $dz = $wantedZ; $stepBB = clone $this->boundingBox; @@ -1262,6 +1264,14 @@ abstract class Entity{ Timings::$entityMove->stopTiming(); } + public function setStepHeight(float $stepHeight) : void{ + $this->stepHeight = $stepHeight; + } + + public function getStepHeight() : float{ + return $this->stepHeight; + } + protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{ $this->isCollidedVertically = $wantedY !== $dy; $this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz); From a554d2acf5db00f577493517e36bb3ad07a6bdd0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 25 May 2025 11:32:20 +0100 Subject: [PATCH 208/334] Revert change that can't go on stable API additions need to wait for the next minor release Revert "Entity: make stepHeight accessable (#6702)" This reverts commit 5527a0c6bf4343b39cd6ed4526f75539ac6ddf19. --- src/entity/Entity.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/entity/Entity.php b/src/entity/Entity.php index eb7098f1e..6681558ad 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -1187,14 +1187,12 @@ abstract class Entity{ $moveBB->offset(0, 0, $dz); - $stepHeight = $this->getStepHeight(); - - if($stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ + if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; $dx = $wantedX; - $dy = $stepHeight; + $dy = $this->stepHeight; $dz = $wantedZ; $stepBB = clone $this->boundingBox; @@ -1264,14 +1262,6 @@ abstract class Entity{ Timings::$entityMove->stopTiming(); } - public function setStepHeight(float $stepHeight) : void{ - $this->stepHeight = $stepHeight; - } - - public function getStepHeight() : float{ - return $this->stepHeight; - } - protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{ $this->isCollidedVertically = $wantedY !== $dy; $this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz); From dd17adeaaf96745ff78dd39107adc9f9f9a43c6d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 25 May 2025 11:33:47 +0100 Subject: [PATCH 209/334] Reintroduce step height additions for minor-next Revert "Revert change that can't go on stable" This reverts commit a554d2acf5db00f577493517e36bb3ad07a6bdd0. --- src/entity/Entity.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/entity/Entity.php b/src/entity/Entity.php index d73356df8..73a0b3a9c 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -1191,12 +1191,14 @@ abstract class Entity{ $moveBB->offset(0, 0, $dz); - if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ + $stepHeight = $this->getStepHeight(); + + if($stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; $dx = $wantedX; - $dy = $this->stepHeight; + $dy = $stepHeight; $dz = $wantedZ; $stepBB = clone $this->boundingBox; @@ -1266,6 +1268,14 @@ abstract class Entity{ Timings::$entityMove->stopTiming(); } + public function setStepHeight(float $stepHeight) : void{ + $this->stepHeight = $stepHeight; + } + + public function getStepHeight() : float{ + return $this->stepHeight; + } + protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{ $this->isCollidedVertically = $wantedY !== $dy; $this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz); From 059f4ee7bfb2b892dadce97fa3c54a0f680f60f9 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 27 May 2025 21:51:10 +0100 Subject: [PATCH 210/334] Extract GeneratorExecutor system from World, v2 (#6682) - `AsyncGeneratorExecutor` class added that encapsulates the logic of generating chunks using async tasks as previously - `GeneratorExecutor` interface added that can be implemented to provide chunks in other ways - `SyncGeneratorExecutor` which invokes the generator directly on the main thread, useful for simple generators like `Flat` where async tasks are not needed - Some redundant APIs were removed from `World` (these will probably come back as deprecated stubs for the remainder of 5.x, but I was having too much fun deleting code) - Removed internal `World->registerGeneratorToWorker()` (no longer useful) - `World` now invokes generator executor instead of posting AsyncTasks directly - Some internal classes moved to `pocketmine\world\generator\executor` (PopulationTask excluded because plugins use it in lieu of being able to regenerate chunks - Generators can opt into main-thread execution by setting the `$fast` parameter to `true` in `GeneratorManager::register()` --- src/world/World.php | 74 +++++------- src/world/generator/GeneratorManager.php | 7 +- src/world/generator/GeneratorManagerEntry.php | 5 +- src/world/generator/PopulationTask.php | 7 ++ .../executor/AsyncGeneratorExecutor.php | 106 ++++++++++++++++++ .../AsyncGeneratorRegisterTask.php} | 33 ++---- .../AsyncGeneratorUnregisterTask.php} | 15 +-- .../generator/executor/GeneratorExecutor.php | 38 +++++++ .../GeneratorExecutorSetupParameters.php | 50 +++++++++ .../executor/SyncGeneratorExecutor.php | 61 ++++++++++ .../ThreadLocalGeneratorContext.php | 4 +- tests/phpstan/configs/actual-problems.neon | 12 +- 12 files changed, 318 insertions(+), 94 deletions(-) create mode 100644 src/world/generator/executor/AsyncGeneratorExecutor.php rename src/world/generator/{GeneratorRegisterTask.php => executor/AsyncGeneratorRegisterTask.php} (54%) rename src/world/generator/{GeneratorUnregisterTask.php => executor/AsyncGeneratorUnregisterTask.php} (74%) create mode 100644 src/world/generator/executor/GeneratorExecutor.php create mode 100644 src/world/generator/executor/GeneratorExecutorSetupParameters.php create mode 100644 src/world/generator/executor/SyncGeneratorExecutor.php rename src/world/generator/{ => executor}/ThreadLocalGeneratorContext.php (94%) diff --git a/src/world/World.php b/src/world/World.php index c4d8f8671..792681a89 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -93,9 +93,11 @@ use pocketmine\world\format\io\GlobalBlockStateHandlers; use pocketmine\world\format\io\WritableWorldProvider; use pocketmine\world\format\LightArray; use pocketmine\world\format\SubChunk; +use pocketmine\world\generator\executor\AsyncGeneratorExecutor; +use pocketmine\world\generator\executor\GeneratorExecutor; +use pocketmine\world\generator\executor\GeneratorExecutorSetupParameters; +use pocketmine\world\generator\executor\SyncGeneratorExecutor; use pocketmine\world\generator\GeneratorManager; -use pocketmine\world\generator\GeneratorRegisterTask; -use pocketmine\world\generator\GeneratorUnregisterTask; use pocketmine\world\generator\PopulationTask; use pocketmine\world\light\BlockLightUpdate; use pocketmine\world\light\LightPopulationTask; @@ -336,11 +338,7 @@ class World implements ChunkManager{ */ private array $chunkPopulationRequestQueueIndex = []; - /** - * @var true[] - * @phpstan-var array - */ - private array $generatorRegisteredWorkers = []; + private readonly GeneratorExecutor $generatorExecutor; private bool $autoSave = true; @@ -360,9 +358,6 @@ class World implements ChunkManager{ private bool $doingTick = false; - /** @phpstan-var class-string */ - private string $generator; - private bool $unloaded = false; /** * @var \Closure[] @@ -498,7 +493,23 @@ class World implements ChunkManager{ $generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator()) ?? throw new AssumptionFailedError("WorldManager should already have checked that the generator exists"); $generator->validateGeneratorOptions($this->provider->getWorldData()->getGeneratorOptions()); - $this->generator = $generator->getGeneratorClass(); + + $executorSetupParameters = new GeneratorExecutorSetupParameters( + worldMinY: $this->minY, + worldMaxY: $this->maxY, + generatorSeed: $this->getSeed(), + generatorClass: $generator->getGeneratorClass(), + generatorSettings: $this->provider->getWorldData()->getGeneratorOptions() + ); + $this->generatorExecutor = $generator->isFast() ? + new SyncGeneratorExecutor($executorSetupParameters) : + new AsyncGeneratorExecutor( + $this->logger, + $this->workerPool, + $executorSetupParameters, + $this->worldId + ); + $this->chunkPopulationRequestQueue = new \SplQueue(); $this->addOnUnloadCallback(function() : void{ $this->logger->debug("Cancelling unfulfilled generation requests"); @@ -534,17 +545,6 @@ class World implements ChunkManager{ $this->initRandomTickBlocksFromConfig($cfg); $this->timings = new WorldTimings($this); - - $this->workerPool->addWorkerStartHook($workerStartHook = function(int $workerId) : void{ - if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){ - $this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered"); - unset($this->generatorRegisteredWorkers[$workerId]); - } - }); - $workerPool = $this->workerPool; - $this->addOnUnloadCallback(static function() use ($workerPool, $workerStartHook) : void{ - $workerPool->removeWorkerStartHook($workerStartHook); - }); } private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{ @@ -585,21 +585,6 @@ class World implements ChunkManager{ return $this->tickRateTime; } - public function registerGeneratorToWorker(int $worker) : void{ - $this->logger->debug("Registering generator on worker $worker"); - $this->workerPool->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getWorldData()->getGeneratorOptions()), $worker); - $this->generatorRegisteredWorkers[$worker] = true; - } - - public function unregisterGenerator() : void{ - foreach($this->workerPool->getRunningWorkers() as $i){ - if(isset($this->generatorRegisteredWorkers[$i])){ - $this->workerPool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i); - } - } - $this->generatorRegisteredWorkers = []; - } - public function getServer() : Server{ return $this->server; } @@ -657,7 +642,7 @@ class World implements ChunkManager{ $this->save(); - $this->unregisterGenerator(); + $this->generatorExecutor->shutdown(); $this->provider->close(); $this->blockCache = []; @@ -3486,8 +3471,8 @@ class World implements ChunkManager{ $centerChunk = $this->loadChunk($chunkX, $chunkZ); $adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ); - $task = new PopulationTask( - $this->worldId, + + $this->generatorExecutor->populate( $chunkX, $chunkZ, $centerChunk, @@ -3500,15 +3485,6 @@ class World implements ChunkManager{ $this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader); } ); - $workerId = $this->workerPool->selectWorker(); - if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){ - $this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline"); - unset($this->generatorRegisteredWorkers[$workerId]); - } - if(!isset($this->generatorRegisteredWorkers[$workerId])){ - $this->registerGeneratorToWorker($workerId); - } - $this->workerPool->submitTaskToWorker($task, $workerId); return $resolver->getPromise(); }finally{ diff --git a/src/world/generator/GeneratorManager.php b/src/world/generator/GeneratorManager.php index 291ea91de..a1b00480e 100644 --- a/src/world/generator/GeneratorManager.php +++ b/src/world/generator/GeneratorManager.php @@ -50,7 +50,7 @@ final class GeneratorManager{ }catch(InvalidGeneratorOptionsException $e){ return $e; } - }); + }, fast: true); $this->addGenerator(Normal::class, "normal", fn() => null); $this->addAlias("normal", "default"); $this->addGenerator(Nether::class, "nether", fn() => null); @@ -62,6 +62,7 @@ final class GeneratorManager{ * @param string $name Alias for this generator type that can be written in configs * @param \Closure $presetValidator Callback to validate generator options for new worlds * @param bool $overwrite Whether to force overwriting any existing registered generator with the same name + * @param bool $fast Whether this generator is fast enough to run without async tasks * * @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator * @@ -69,7 +70,7 @@ final class GeneratorManager{ * * @throws \InvalidArgumentException */ - public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false) : void{ + public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false, bool $fast = false) : void{ Utils::testValidInstance($class, Generator::class); $name = strtolower($name); @@ -77,7 +78,7 @@ final class GeneratorManager{ throw new \InvalidArgumentException("Alias \"$name\" is already assigned"); } - $this->list[$name] = new GeneratorManagerEntry($class, $presetValidator); + $this->list[$name] = new GeneratorManagerEntry($class, $presetValidator, $fast); } /** diff --git a/src/world/generator/GeneratorManagerEntry.php b/src/world/generator/GeneratorManagerEntry.php index 256ed27d5..942f6ee79 100644 --- a/src/world/generator/GeneratorManagerEntry.php +++ b/src/world/generator/GeneratorManagerEntry.php @@ -31,12 +31,15 @@ final class GeneratorManagerEntry{ */ public function __construct( private string $generatorClass, - private \Closure $presetValidator + private \Closure $presetValidator, + private readonly bool $fast ){} /** @phpstan-return class-string */ public function getGeneratorClass() : string{ return $this->generatorClass; } + public function isFast() : bool{ return $this->fast; } + /** * @throws InvalidGeneratorOptionsException */ diff --git a/src/world/generator/PopulationTask.php b/src/world/generator/PopulationTask.php index a8366a306..971349a5b 100644 --- a/src/world/generator/PopulationTask.php +++ b/src/world/generator/PopulationTask.php @@ -27,11 +27,18 @@ use pocketmine\scheduler\AsyncTask; use pocketmine\utils\AssumptionFailedError; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\FastChunkSerializer; +use pocketmine\world\generator\executor\ThreadLocalGeneratorContext; use function array_map; use function igbinary_serialize; use function igbinary_unserialize; /** + * @internal + * + * TODO: this should be moved to the executor namespace, but plugins have unfortunately used it directly due to the + * difficulty of regenerating chunks. This should be addressed in the future. + * For the remainder of PM5, we can't relocate this class. + * * @phpstan-type OnCompletion \Closure(Chunk $centerChunk, array $adjacentChunks) : void */ class PopulationTask extends AsyncTask{ diff --git a/src/world/generator/executor/AsyncGeneratorExecutor.php b/src/world/generator/executor/AsyncGeneratorExecutor.php new file mode 100644 index 000000000..d19b6e661 --- /dev/null +++ b/src/world/generator/executor/AsyncGeneratorExecutor.php @@ -0,0 +1,106 @@ + + */ + private array $generatorRegisteredWorkers = []; + + public function __construct( + \Logger $logger, + private readonly AsyncPool $workerPool, + private readonly GeneratorExecutorSetupParameters $setupParameters, + int $asyncContextId = null + ){ + $this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor"); + + //TODO: we only allow setting this for PM5 because of PopulationTask uses in plugins + $this->asyncContextId = $asyncContextId ?? self::$nextAsyncContextId++; + + $this->workerStartHook = function(int $workerId) : void{ + if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){ + $this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered"); + unset($this->generatorRegisteredWorkers[$workerId]); + } + }; + $this->workerPool->addWorkerStartHook($this->workerStartHook); + } + + private function registerGeneratorToWorker(int $worker) : void{ + $this->logger->debug("Registering generator on worker $worker"); + $this->workerPool->submitTaskToWorker(new AsyncGeneratorRegisterTask($this->setupParameters, $this->asyncContextId), $worker); + $this->generatorRegisteredWorkers[$worker] = true; + } + + private function unregisterGenerator() : void{ + foreach($this->workerPool->getRunningWorkers() as $i){ + if(isset($this->generatorRegisteredWorkers[$i])){ + $this->workerPool->submitTaskToWorker(new AsyncGeneratorUnregisterTask($this->asyncContextId), $i); + } + } + $this->generatorRegisteredWorkers = []; + } + + public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void{ + $task = new PopulationTask( + $this->asyncContextId, + $chunkX, + $chunkZ, + $centerChunk, + $adjacentChunks, + $onCompletion + ); + $workerId = $this->workerPool->selectWorker(); + if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){ + $this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline"); + unset($this->generatorRegisteredWorkers[$workerId]); + } + if(!isset($this->generatorRegisteredWorkers[$workerId])){ + $this->registerGeneratorToWorker($workerId); + } + $this->workerPool->submitTaskToWorker($task, $workerId); + } + + public function shutdown() : void{ + $this->unregisterGenerator(); + $this->workerPool->removeWorkerStartHook($this->workerStartHook); + } +} diff --git a/src/world/generator/GeneratorRegisterTask.php b/src/world/generator/executor/AsyncGeneratorRegisterTask.php similarity index 54% rename from src/world/generator/GeneratorRegisterTask.php rename to src/world/generator/executor/AsyncGeneratorRegisterTask.php index e2e773a35..5bc67834d 100644 --- a/src/world/generator/GeneratorRegisterTask.php +++ b/src/world/generator/executor/AsyncGeneratorRegisterTask.php @@ -21,37 +21,20 @@ declare(strict_types=1); -namespace pocketmine\world\generator; +namespace pocketmine\world\generator\executor; use pocketmine\scheduler\AsyncTask; -use pocketmine\world\World; -class GeneratorRegisterTask extends AsyncTask{ - public int $seed; - public int $worldId; - public int $worldMinY; - public int $worldMaxY; +class AsyncGeneratorRegisterTask extends AsyncTask{ - /** - * @phpstan-param class-string $generatorClass - */ public function __construct( - World $world, - public string $generatorClass, - public string $generatorSettings - ){ - $this->seed = $world->getSeed(); - $this->worldId = $world->getId(); - $this->worldMinY = $world->getMinY(); - $this->worldMaxY = $world->getMaxY(); - } + private readonly GeneratorExecutorSetupParameters $setupParameters, + private readonly int $contextId + ){} public function onRun() : void{ - /** - * @var Generator $generator - * @see Generator::__construct() - */ - $generator = new $this->generatorClass($this->seed, $this->generatorSettings); - ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldMinY, $this->worldMaxY), $this->worldId); + $setupParameters = $this->setupParameters; + $generator = $setupParameters->createGenerator(); + ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $setupParameters->worldMinY, $setupParameters->worldMaxY), $this->contextId); } } diff --git a/src/world/generator/GeneratorUnregisterTask.php b/src/world/generator/executor/AsyncGeneratorUnregisterTask.php similarity index 74% rename from src/world/generator/GeneratorUnregisterTask.php rename to src/world/generator/executor/AsyncGeneratorUnregisterTask.php index 41b4cd808..c771903f5 100644 --- a/src/world/generator/GeneratorUnregisterTask.php +++ b/src/world/generator/executor/AsyncGeneratorUnregisterTask.php @@ -21,19 +21,16 @@ declare(strict_types=1); -namespace pocketmine\world\generator; +namespace pocketmine\world\generator\executor; use pocketmine\scheduler\AsyncTask; -use pocketmine\world\World; -class GeneratorUnregisterTask extends AsyncTask{ - public int $worldId; - - public function __construct(World $world){ - $this->worldId = $world->getId(); - } +class AsyncGeneratorUnregisterTask extends AsyncTask{ + public function __construct( + private readonly int $contextId + ){} public function onRun() : void{ - ThreadLocalGeneratorContext::unregister($this->worldId); + ThreadLocalGeneratorContext::unregister($this->contextId); } } diff --git a/src/world/generator/executor/GeneratorExecutor.php b/src/world/generator/executor/GeneratorExecutor.php new file mode 100644 index 000000000..d3f62d410 --- /dev/null +++ b/src/world/generator/executor/GeneratorExecutor.php @@ -0,0 +1,38 @@ + $adjacentChunks + * @phpstan-param \Closure(Chunk $centerChunk, array $adjacentChunks) : void $onCompletion + */ + public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void; + + public function shutdown() : void; + +} diff --git a/src/world/generator/executor/GeneratorExecutorSetupParameters.php b/src/world/generator/executor/GeneratorExecutorSetupParameters.php new file mode 100644 index 000000000..b5fdb7bf9 --- /dev/null +++ b/src/world/generator/executor/GeneratorExecutorSetupParameters.php @@ -0,0 +1,50 @@ + $generatorClass + */ + public function __construct( + public readonly int $worldMinY, + public readonly int $worldMaxY, + public readonly int $generatorSeed, + public readonly string $generatorClass, + public readonly string $generatorSettings, + ){} + + public function createGenerator() : Generator{ + /** + * @var Generator $generator + * @see Generator::__construct() + */ + $generator = new $this->generatorClass($this->generatorSeed, $this->generatorSettings); + return $generator; + } +} diff --git a/src/world/generator/executor/SyncGeneratorExecutor.php b/src/world/generator/executor/SyncGeneratorExecutor.php new file mode 100644 index 000000000..79b5fdd00 --- /dev/null +++ b/src/world/generator/executor/SyncGeneratorExecutor.php @@ -0,0 +1,61 @@ +generator = $setupParameters->createGenerator(); + $this->worldMinY = $setupParameters->worldMinY; + $this->worldMaxY = $setupParameters->worldMaxY; + } + + public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void{ + [$centerChunk, $adjacentChunks] = PopulationUtils::populateChunkWithAdjacents( + $this->worldMinY, + $this->worldMaxY, + $this->generator, + $chunkX, + $chunkZ, + $centerChunk, + $adjacentChunks + ); + + $onCompletion($centerChunk, $adjacentChunks); + } + + public function shutdown() : void{ + //NOOP + } +} diff --git a/src/world/generator/ThreadLocalGeneratorContext.php b/src/world/generator/executor/ThreadLocalGeneratorContext.php similarity index 94% rename from src/world/generator/ThreadLocalGeneratorContext.php rename to src/world/generator/executor/ThreadLocalGeneratorContext.php index bcf99882b..bea8bb032 100644 --- a/src/world/generator/ThreadLocalGeneratorContext.php +++ b/src/world/generator/executor/ThreadLocalGeneratorContext.php @@ -21,7 +21,9 @@ declare(strict_types=1); -namespace pocketmine\world\generator; +namespace pocketmine\world\generator\executor; + +use pocketmine\world\generator\Generator; /** * Manages thread-local caches for generators and the things needed to support them diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index d3adde422..2030a0dad 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -1272,18 +1272,18 @@ parameters: count: 1 path: ../../../src/world/format/io/region/RegionLoader.php - - - message: '#^Dynamic new is not allowed\.$#' - identifier: pocketmine.new.dynamic - count: 1 - path: ../../../src/world/generator/GeneratorRegisterTask.php - - message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' identifier: return.type count: 1 path: ../../../src/world/generator/biome/BiomeSelector.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/world/generator/executor/GeneratorExecutorSetupParameters.php + - message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' identifier: method.nonObject From bf33a625c93469b4f158e292c5868a161603daf0 Mon Sep 17 00:00:00 2001 From: Adam <116978956+b1zeyofficial@users.noreply.github.com> Date: Wed, 28 May 2025 02:57:28 +0600 Subject: [PATCH 211/334] Implemented Respawn Anchor (#6646) PlayerRespawnAnchorUseEvent is also added with options SET_SPAWN and EXPLODE, which allows plugins to customise the outcome of using the anchor in PM, which currently doesn't support dimensions. The event is also cancellable. --- src/block/BlockTypeIds.php | 3 +- src/block/RespawnAnchor.php | 123 +++++++++++++++++ src/block/VanillaBlocks.php | 3 + .../convert/BlockObjectToStateSerializer.php | 5 + .../BlockStateToObjectDeserializer.php | 4 + src/entity/object/EndCrystal.php | 2 +- src/entity/object/PrimedTNT.php | 2 +- src/event/block/BlockExplodeEvent.php | 122 +++++++++++++++++ src/event/block/BlockPreExplodeEvent.php | 129 ++++++++++++++++++ src/event/entity/EntityExplodeEvent.php | 25 +++- src/event/entity/EntityPreExplodeEvent.php | 50 ++++++- .../player/PlayerRespawnAnchorUseEvent.php | 56 ++++++++ src/item/StringToItemParser.php | 1 + src/player/Player.php | 17 +++ src/world/Explosion.php | 77 +++++++++-- src/world/sound/RespawnAnchorChargeSound.php | 35 +++++ src/world/sound/RespawnAnchorDepleteSound.php | 35 +++++ .../sound/RespawnAnchorSetSpawnSound.php | 35 +++++ .../block_factory_consistency_check.json | 1 + 19 files changed, 711 insertions(+), 14 deletions(-) create mode 100644 src/block/RespawnAnchor.php create mode 100644 src/event/block/BlockExplodeEvent.php create mode 100644 src/event/block/BlockPreExplodeEvent.php create mode 100644 src/event/player/PlayerRespawnAnchorUseEvent.php create mode 100644 src/world/sound/RespawnAnchorChargeSound.php create mode 100644 src/world/sound/RespawnAnchorDepleteSound.php create mode 100644 src/world/sound/RespawnAnchorSetSpawnSound.php diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index c440cefdc..4af1894bd 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -786,8 +786,9 @@ final class BlockTypeIds{ public const RESIN_BRICKS = 10756; public const RESIN_CLUMP = 10757; public const CHISELED_RESIN_BRICKS = 10758; + public const RESPAWN_ANCHOR = 10759; - public const FIRST_UNUSED_BLOCK_ID = 10759; + public const FIRST_UNUSED_BLOCK_ID = 10760; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/RespawnAnchor.php b/src/block/RespawnAnchor.php new file mode 100644 index 000000000..f702d240d --- /dev/null +++ b/src/block/RespawnAnchor.php @@ -0,0 +1,123 @@ +boundedIntAuto(self::MIN_CHARGES, self::MAX_CHARGES, $this->charges); + } + + public function getCharges() : int{ + return $this->charges; + } + + /** @return $this */ + public function setCharges(int $charges) : self{ + if($charges < self::MIN_CHARGES || $charges > self::MAX_CHARGES){ + throw new \InvalidArgumentException("Charges must be between " . self::MIN_CHARGES . " and " . self::MAX_CHARGES . ", given: $charges"); + } + $this->charges = $charges; + return $this; + } + + public function getLightLevel() : int{ + return $this->charges > 0 ? ($this->charges * 4) - 1 : 0; + } + + public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + if($item->getTypeId() === ItemTypeIds::fromBlockTypeId(BlockTypeIds::GLOWSTONE) && $this->charges < self::MAX_CHARGES){ + $this->position->getWorld()->setBlock($this->position, $this->setCharges($this->charges + 1)); + $this->position->getWorld()->addSound($this->position, new RespawnAnchorChargeSound()); + return true; + } + + if($this->charges > self::MIN_CHARGES){ + if($player === null){ + return false; + } + + $ev = new PlayerRespawnAnchorUseEvent($player, $this, PlayerRespawnAnchorUseEvent::ACTION_EXPLODE); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + + switch($ev->getAction()){ + case PlayerRespawnAnchorUseEvent::ACTION_EXPLODE: + $this->explode($player); + return false; + + case PlayerRespawnAnchorUseEvent::ACTION_SET_SPAWN: + if($player->getSpawn() !== null && $player->getSpawn()->equals($this->position)){ + return true; + } + + $player->setSpawn($this->position); + $this->position->getWorld()->addSound($this->position, new RespawnAnchorSetSpawnSound()); + $player->sendMessage(KnownTranslationFactory::tile_respawn_anchor_respawnSet()->prefix(TextFormat::GRAY)); + return true; + } + } + return false; + } + + private function explode(?Player $player) : void{ + $ev = new BlockPreExplodeEvent($this, 5, $player); + $ev->setIncendiary(true); + + $ev->call(); + if($ev->isCancelled()){ + return; + } + + $this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR()); + + $explosion = new Explosion(Position::fromObject($this->position->add(0.5, 0.5, 0.5), $this->position->getWorld()), $ev->getRadius(), $this); + $explosion->setFireChance($ev->getFireChance()); + + if($ev->isBlockBreaking()){ + $explosion->explodeA(); + } + $explosion->explodeB(); + } +} diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 0a6d4b31c..54ec27fc2 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -694,6 +694,7 @@ use function strtolower; * @method static Stair RESIN_BRICK_STAIRS() * @method static Wall RESIN_BRICK_WALL() * @method static ResinClump RESIN_CLUMP() + * @method static RespawnAnchor RESPAWN_ANCHOR() * @method static DoublePlant ROSE_BUSH() * @method static Sand SAND() * @method static Opaque SANDSTONE() @@ -1647,6 +1648,8 @@ final class VanillaBlocks{ self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo)); self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0)))); + + self::register("respawn_anchor", fn(BID $id) => new RespawnAnchor($id, "Respawn Anchor", new Info(BreakInfo::pickaxe(50.0, ToolTier::DIAMOND, 6000.0)))); } private static function registerBlocksR17() : void{ diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 45784d409..27d550f13 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -122,6 +122,7 @@ use pocketmine\block\RedstoneRepeater; use pocketmine\block\RedstoneTorch; use pocketmine\block\RedstoneWire; use pocketmine\block\ResinClump; +use pocketmine\block\RespawnAnchor; use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\block\Sapling; use pocketmine\block\SeaPickle; @@ -1754,6 +1755,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return Writer::create(Ids::RESIN_CLUMP) ->writeFacingFlags($block->getFaces()); }); + $this->map(Blocks::RESPAWN_ANCHOR(), function(RespawnAnchor $block) : Writer{ + return Writer::create(Ids::RESPAWN_ANCHOR) + ->writeInt(StateNames::RESPAWN_ANCHOR_CHARGE, $block->getCharges()); + }); $this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH))); $this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB); $this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index ed45a47d3..c55fde77a 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -1717,6 +1717,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS()); $this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in)); $this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags())); + $this->map(Ids::RESPAWN_ANCHOR, function(Reader $in) : Block{ + return Blocks::RESPAWN_ANCHOR() + ->setCharges($in->readBoundedInt(StateNames::RESPAWN_ANCHOR_CHARGE, 0, 4)); + }); $this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB()); $this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS()); $this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in)); diff --git a/src/entity/object/EndCrystal.php b/src/entity/object/EndCrystal.php index afaeb6769..74c7664bf 100644 --- a/src/entity/object/EndCrystal.php +++ b/src/entity/object/EndCrystal.php @@ -129,7 +129,7 @@ class EndCrystal extends Entity implements Explosive{ $ev = new EntityPreExplodeEvent($this, 6); $ev->call(); if(!$ev->isCancelled()){ - $explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this); + $explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this, $ev->getFireChance()); if($ev->isBlockBreaking()){ $explosion->explodeA(); } diff --git a/src/entity/object/PrimedTNT.php b/src/entity/object/PrimedTNT.php index af3c97922..f3f299d70 100644 --- a/src/entity/object/PrimedTNT.php +++ b/src/entity/object/PrimedTNT.php @@ -121,7 +121,7 @@ class PrimedTNT extends Entity implements Explosive{ $ev->call(); if(!$ev->isCancelled()){ //TODO: deal with underwater TNT (underwater TNT treats water as if it has a blast resistance of 0) - $explosion = new Explosion(Position::fromObject($this->location->add(0, $this->size->getHeight() / 2, 0), $this->getWorld()), $ev->getRadius(), $this); + $explosion = new Explosion(Position::fromObject($this->location->add(0, $this->size->getHeight() / 2, 0), $this->getWorld()), $ev->getRadius(), $this, $ev->getFireChance()); if($ev->isBlockBreaking()){ $explosion->explodeA(); } diff --git a/src/event/block/BlockExplodeEvent.php b/src/event/block/BlockExplodeEvent.php new file mode 100644 index 000000000..a8501d475 --- /dev/null +++ b/src/event/block/BlockExplodeEvent.php @@ -0,0 +1,122 @@ + 100.0){ + throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0"); + } + } + + public function getPosition() : Position{ + return $this->position; + } + + /** + * Returns the percentage chance of drops from each block destroyed by the explosion. + * + * @return float 0-100 + */ + public function getYield() : float{ + return $this->yield; + } + + /** + * Sets the percentage chance of drops from each block destroyed by the explosion. + * + * @param float $yield 0-100 + */ + public function setYield(float $yield) : void{ + Utils::checkFloatNotInfOrNaN("yield", $yield); + if($yield < 0.0 || $yield > 100.0){ + throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0"); + } + $this->yield = $yield; + } + + /** + * Returns a list of blocks destroyed by the explosion. + * + * @return Block[] + */ + public function getAffectedBlocks() : array{ + return $this->blocks; + } + + /** + * Sets the blocks destroyed by the explosion. + * + * @param Block[] $blocks + */ + public function setAffectedBlocks(array $blocks) : void{ + Utils::validateArrayValueType($blocks, fn(Block $block) => null); + $this->blocks = $blocks; + } + + /** + * Returns a list of affected blocks that will be replaced by fire. + * + * @return Block[] + */ + public function getIgnitions() : array{ + return $this->ignitions; + } + + /** + * Set the list of blocks that will be replaced by fire. + * + * @param Block[] $ignitions + */ + public function setIgnitions(array $ignitions) : void{ + Utils::validateArrayValueType($ignitions, fn(Block $block) => null); + $this->ignitions = $ignitions; + } +} diff --git a/src/event/block/BlockPreExplodeEvent.php b/src/event/block/BlockPreExplodeEvent.php new file mode 100644 index 000000000..f41cb8a63 --- /dev/null +++ b/src/event/block/BlockPreExplodeEvent.php @@ -0,0 +1,129 @@ + 1.0){ + throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1."); + } + parent::__construct($block); + } + + public function getRadius() : float{ + return $this->radius; + } + + public function setRadius(float $radius) : void{ + Utils::checkFloatNotInfOrNaN("radius", $radius); + if($radius <= 0){ + throw new \InvalidArgumentException("Explosion radius must be positive"); + } + $this->radius = $radius; + } + + public function isBlockBreaking() : bool{ + return $this->blockBreaking; + } + + public function setBlockBreaking(bool $affectsBlocks) : void{ + $this->blockBreaking = $affectsBlocks; + } + + /** + * Returns whether the explosion will create a fire. + */ + public function isIncendiary() : bool{ + return $this->fireChance > 0; + } + + /** + * Sets whether the explosion will create a fire by filling fireChance with default values. + * + * If $incendiary is true, the fire chance will be filled only if explosion isn't currently creating a fire (if fire chance is 0). + */ + public function setIncendiary(bool $incendiary) : void{ + if(!$incendiary){ + $this->fireChance = 0; + }elseif($this->fireChance <= 0){ + $this->fireChance = Explosion::DEFAULT_FIRE_CHANCE; + } + } + + /** + * Returns a chance between 0 and 1 of creating a fire. + */ + public function getFireChance() : float{ + return $this->fireChance; + } + + /** + * Sets a chance between 0 and 1 of creating a fire. + * For example, if the chance is 1/3, then that amount of affected blocks will be ignited. + * + * @param float $fireChance 0 ... 1 + */ + public function setFireChance(float $fireChance) : void{ + Utils::checkFloatNotInfOrNaN("fireChance", $fireChance); + if($fireChance < 0.0 || $fireChance > 1.0){ + throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1."); + } + $this->fireChance = $fireChance; + } + + /** + * Returns the player who triggered the block explosion. + * Returns null if the block was exploded by other means. + */ + public function getPlayer() : ?Player{ + return $this->player; + } +} diff --git a/src/event/entity/EntityExplodeEvent.php b/src/event/entity/EntityExplodeEvent.php index 0a0e4f696..c1750ccb3 100644 --- a/src/event/entity/EntityExplodeEvent.php +++ b/src/event/entity/EntityExplodeEvent.php @@ -43,13 +43,15 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{ /** * @param Block[] $blocks - * @param float $yield 0-100 + * @param float $yield 0-100 + * @param Block[] $ignitions */ public function __construct( Entity $entity, protected Position $position, protected array $blocks, - protected float $yield + protected float $yield, + private array $ignitions ){ $this->entity = $entity; if($yield < 0.0 || $yield > 100.0){ @@ -98,4 +100,23 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{ } $this->yield = $yield; } + + /** + * Set the list of blocks that will be replaced by fire. + * + * @param Block[] $ignitions + */ + public function setIgnitions(array $ignitions) : void{ + Utils::validateArrayValueType($ignitions, fn(Block $block) => null); + $this->ignitions = $ignitions; + } + + /** + * Returns a list of affected blocks that will be replaced by fire. + * + * @return Block[] + */ + public function getIgnitions() : array{ + return $this->ignitions; + } } diff --git a/src/event/entity/EntityPreExplodeEvent.php b/src/event/entity/EntityPreExplodeEvent.php index f02a85ecd..c88e83304 100644 --- a/src/event/entity/EntityPreExplodeEvent.php +++ b/src/event/entity/EntityPreExplodeEvent.php @@ -26,6 +26,8 @@ namespace pocketmine\event\entity; use pocketmine\entity\Entity; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; +use pocketmine\utils\Utils; +use pocketmine\world\Explosion; /** * Called when an entity decides to explode, before the explosion's impact is calculated. @@ -42,11 +44,16 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{ public function __construct( Entity $entity, - protected float $radius + protected float $radius, + private float $fireChance = 0.0, ){ if($radius <= 0){ throw new \InvalidArgumentException("Explosion radius must be positive"); } + Utils::checkFloatNotInfOrNaN("fireChance", $fireChance); + if($fireChance < 0.0 || $fireChance > 1.0){ + throw new \InvalidArgumentException("Fire chance must be between 0 and 1."); + } $this->entity = $entity; } @@ -61,6 +68,47 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{ $this->radius = $radius; } + /** + * Returns whether the explosion will create a fire. + */ + public function isIncendiary() : bool{ + return $this->fireChance > 0; + } + + /** + * Sets whether the explosion will create a fire by filling fireChance with default values. + * + * If $incendiary is true, the fire chance will be filled only if explosion isn't currently creating a fire (if fire chance is 0). + */ + public function setIncendiary(bool $incendiary) : void{ + if(!$incendiary){ + $this->fireChance = 0; + }elseif($this->fireChance <= 0){ + $this->fireChance = Explosion::DEFAULT_FIRE_CHANCE; + } + } + + /** + * Returns a chance between 0 and 1 of creating a fire. + */ + public function getFireChance() : float{ + return $this->fireChance; + } + + /** + * Sets a chance between 0 and 1 of creating a fire. + * For example, if the chance is 1/3, then that amount of affected blocks will be ignited. + * + * @param float $fireChance 0 ... 1 + */ + public function setFireChance(float $fireChance) : void{ + Utils::checkFloatNotInfOrNaN("fireChance", $fireChance); + if($fireChance < 0.0 || $fireChance > 1.0){ + throw new \InvalidArgumentException("Fire chance must be between 0 and 1."); + } + $this->fireChance = $fireChance; + } + public function isBlockBreaking() : bool{ return $this->blockBreaking; } diff --git a/src/event/player/PlayerRespawnAnchorUseEvent.php b/src/event/player/PlayerRespawnAnchorUseEvent.php new file mode 100644 index 000000000..be7697f11 --- /dev/null +++ b/src/event/player/PlayerRespawnAnchorUseEvent.php @@ -0,0 +1,56 @@ +player = $player; + } + + public function getBlock() : Block{ + return $this->block; + } + + public function getAction() : int{ + return $this->action; + } + + public function setAction(int $action) : void{ + $this->action = $action; + } +} diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index a3bd7b872..7a90babed 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -993,6 +993,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("resin_brick_wall", fn() => Blocks::RESIN_BRICK_WALL()); $result->registerBlock("resin_bricks", fn() => Blocks::RESIN_BRICKS()); $result->registerBlock("resin_clump", fn() => Blocks::RESIN_CLUMP()); + $result->registerBlock("respawn_anchor", fn() => Blocks::RESPAWN_ANCHOR()); $result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED)); $result->registerBlock("rose", fn() => Blocks::POPPY()); $result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH()); diff --git a/src/player/Player.php b/src/player/Player.php index 3c494b980..6429d1281 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -26,6 +26,7 @@ namespace pocketmine\player; use pocketmine\block\BaseSign; use pocketmine\block\Bed; use pocketmine\block\BlockTypeTags; +use pocketmine\block\RespawnAnchor; use pocketmine\block\UnknownBlock; use pocketmine\block\VanillaBlocks; use pocketmine\command\CommandSender; @@ -136,6 +137,7 @@ use pocketmine\world\sound\EntityAttackNoDamageSound; use pocketmine\world\sound\EntityAttackSound; use pocketmine\world\sound\FireExtinguishSound; use pocketmine\world\sound\ItemBreakSound; +use pocketmine\world\sound\RespawnAnchorDepleteSound; use pocketmine\world\sound\Sound; use pocketmine\world\World; use pocketmine\YmlServerProperties; @@ -2538,6 +2540,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } $this->logger->debug("Respawn position located, completing respawn"); $ev = new PlayerRespawnEvent($this, $safeSpawn); + $spawnPosition = $ev->getRespawnPosition(); + $spawnBlock = $spawnPosition->getWorld()->getBlock($spawnPosition); + if($spawnBlock instanceof RespawnAnchor){ + if($spawnBlock->getCharges() > 0){ + $spawnPosition->getWorld()->setBlock($spawnPosition, $spawnBlock->setCharges($spawnBlock->getCharges() - 1)); + $spawnPosition->getWorld()->addSound($spawnPosition, new RespawnAnchorDepleteSound()); + }else{ + $defaultSpawn = $this->server->getWorldManager()->getDefaultWorld()?->getSpawnLocation(); + if($defaultSpawn !== null){ + $this->setSpawn($defaultSpawn); + $ev->setRespawnPosition($defaultSpawn); + $this->sendMessage(KnownTranslationFactory::tile_respawn_anchor_notValid()->prefix(TextFormat::GRAY)); + } + } + } $ev->call(); $realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld()); diff --git a/src/world/Explosion.php b/src/world/Explosion.php index 601f9109e..9e83d06be 100644 --- a/src/world/Explosion.php +++ b/src/world/Explosion.php @@ -26,16 +26,20 @@ namespace pocketmine\world; use pocketmine\block\Block; use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\block\TNT; +use pocketmine\block\utils\SupportType; use pocketmine\block\VanillaBlocks; use pocketmine\entity\Entity; +use pocketmine\event\block\BlockExplodeEvent; use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityExplodeEvent; use pocketmine\item\VanillaItems; use pocketmine\math\AxisAlignedBB; +use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Utils; use pocketmine\world\format\SubChunk; use pocketmine\world\particle\HugeExplodeSeedParticle; use pocketmine\world\sound\ExplodeSound; @@ -48,25 +52,36 @@ use function mt_rand; use function sqrt; class Explosion{ + public const DEFAULT_FIRE_CHANCE = 1.0 / 3.0; + private int $rays = 16; public World $world; - /** @var Block[] */ + /** + * @var Block[] + * @phpstan-var array + */ public array $affectedBlocks = []; public float $stepLen = 0.3; + /** @var Block[] */ + private array $fireIgnitions = []; private SubChunkExplorer $subChunkExplorer; public function __construct( public Position $source, public float $radius, - private Entity|Block|null $what = null + private Entity|Block|null $what = null, + private float $fireChance = 0.0 ){ if(!$this->source->isValid()){ throw new \InvalidArgumentException("Position does not have a valid world"); } $this->world = $this->source->getWorld(); - + Utils::checkFloatNotInfOrNaN("fireChance", $fireChance); + if($fireChance < 0.0 || $fireChance > 1.0){ + throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1."); + } if($radius <= 0){ throw new \InvalidArgumentException("Explosion radius must be greater than 0, got $radius"); } @@ -85,6 +100,7 @@ class Explosion{ $blockFactory = RuntimeBlockStateRegistry::getInstance(); $mRays = $this->rays - 1; + $incendiary = $this->fireChance > 0; for($i = 0; $i < $this->rays; ++$i){ for($j = 0; $j < $this->rays; ++$j){ for($k = 0; $k < $this->rays; ++$k){ @@ -127,7 +143,12 @@ class Explosion{ $_block = $this->world->getBlockAt($vBlockX, $vBlockY, $vBlockZ, true, false); foreach($_block->getAffectedBlocks() as $_affectedBlock){ $_affectedBlockPos = $_affectedBlock->getPosition(); - $this->affectedBlocks[World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z)] = $_affectedBlock; + $posHash = World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z); + $this->affectedBlocks[$posHash] = $_affectedBlock; + + if($incendiary && Utils::getRandomFloat() <= $this->fireChance){ + $this->fireIgnitions[$posHash] = $_affectedBlock; + } } } } @@ -150,13 +171,32 @@ class Explosion{ $yield = min(100, (1 / $this->radius) * 100); if($this->what instanceof Entity){ - $ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield); + $ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield, $this->fireIgnitions); + + $ev->call(); + if($ev->isCancelled()){ + return false; + } + + $yield = $ev->getYield(); + $this->affectedBlocks = $ev->getBlockList(); + $this->fireIgnitions = $ev->getIgnitions(); + }elseif($this->what instanceof Block){ + $ev = new BlockExplodeEvent( + $this->what, + $this->source, + $this->affectedBlocks, + $yield, + $this->fireIgnitions, + ); + $ev->call(); if($ev->isCancelled()){ return false; }else{ $yield = $ev->getYield(); - $this->affectedBlocks = $ev->getBlockList(); + $this->affectedBlocks = $ev->getAffectedBlocks(); + $this->fireIgnitions = $ev->getIgnitions(); } } @@ -198,8 +238,9 @@ class Explosion{ $air = VanillaItems::AIR(); $airBlock = VanillaBlocks::AIR(); + $fireBlock = VanillaBlocks::FIRE(); - foreach($this->affectedBlocks as $block){ + foreach($this->affectedBlocks as $hash => $block){ $pos = $block->getPosition(); if($block instanceof TNT){ $block->ignite(mt_rand(10, 30)); @@ -212,7 +253,13 @@ class Explosion{ if(($t = $this->world->getTileAt($pos->x, $pos->y, $pos->z)) !== null){ $t->onBlockDestroyed(); //needed to create drops for inventories } - $this->world->setBlockAt($pos->x, $pos->y, $pos->z, $airBlock); + $targetBlock = + isset($this->fireIgnitions[$hash]) && + $block->getSide(Facing::DOWN)->getSupportType(Facing::UP) === SupportType::FULL ? + $fireBlock : + $airBlock; + + $this->world->setBlockAt($pos->x, $pos->y, $pos->z, $targetBlock); } } @@ -221,4 +268,18 @@ class Explosion{ return true; } + + /** + * Sets a chance between 0 and 1 of creating a fire. + * For example, if the chance is 1/3, then that amount of affected blocks will be ignited. + * + * @param float $fireChance 0 ... 1 + */ + public function setFireChance(float $fireChance) : void{ + Utils::checkFloatNotInfOrNaN("fireChance", $fireChance); + if($fireChance < 0.0 || $fireChance > 1.0){ + throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1."); + } + $this->fireChance = $fireChance; + } } diff --git a/src/world/sound/RespawnAnchorChargeSound.php b/src/world/sound/RespawnAnchorChargeSound.php new file mode 100644 index 000000000..5a5731262 --- /dev/null +++ b/src/world/sound/RespawnAnchorChargeSound.php @@ -0,0 +1,35 @@ + Date: Wed, 28 May 2025 21:00:22 +0100 Subject: [PATCH 212/334] Stem drops seeds according to binomial distribution fixes #6709 we really need a better way to reverse-engineer the chance parameter for these as the wiki just gives a probability table, which is quite tiresome to extract patterns from. --- src/block/Stem.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/block/Stem.php b/src/block/Stem.php index 2ac95aa3f..2b6f2150c 100644 --- a/src/block/Stem.php +++ b/src/block/Stem.php @@ -25,11 +25,12 @@ namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\CropGrowthHelper; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; +use pocketmine\item\VanillaItems; use pocketmine\math\Facing; use function array_rand; -use function mt_rand; abstract class Stem extends Crops{ protected int $facing = Facing::UP; @@ -90,8 +91,10 @@ abstract class Stem extends Crops{ } public function getDropsForCompatibleTool(Item $item) : array{ + //TODO: bit annoying we have to pass an Item instance here + //this should not be affected by Fortune, but still follows a binomial distribution return [ - $this->asItem()->setCount(mt_rand(0, 2)) + $this->asItem()->setCount(FortuneDropHelper::binomial(VanillaItems::AIR(), 0, chance: ($this->age + 1) / 15)) ]; } } From b40b99fe72cec89b0b785e904cbaff9664c6fb0c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 28 May 2025 21:32:48 +0100 Subject: [PATCH 213/334] Player: fixed crash on action item return this can happen if the old item had a lower max damage than the new one, and the new one has a damage higher than the old one's max damage. it can also happen if the damage was overridden to some illegal value by a custom item as seen in https://crash.pmmp.io/view/12754811 --- src/player/Player.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/player/Player.php b/src/player/Player.php index 1c67b7182..e0a42ed1d 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1641,7 +1641,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $newReplica = clone $oldHeldItem; $newReplica->setCount($newHeldItem->getCount()); if($newReplica instanceof Durable && $newHeldItem instanceof Durable){ - $newReplica->setDamage($newHeldItem->getDamage()); + $newDamage = $newHeldItem->getDamage(); + if($newDamage >= 0 && $newDamage <= $newReplica->getMaxDurability()){ + $newReplica->setDamage($newDamage); + } } $damagedOrDeducted = $newReplica->equalsExact($newHeldItem); From 035d2dec230d14ec06887e3d49f4526a619fd0c2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 28 May 2025 22:03:29 +0100 Subject: [PATCH 214/334] LevelDB: make unknown block errors way less annoying these would previously generate a new line for every error. since errors are often repeated for different offsets (e.g. different states of the same block), we can save a lot of spam by deduplicating them and telling which offsets the errors occurred in. --- src/world/format/io/leveldb/LevelDB.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index 6223e66b8..3a64f93f6 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -36,6 +36,7 @@ use pocketmine\nbt\TreeRoot; use pocketmine\utils\Binary; use pocketmine\utils\BinaryDataException; use pocketmine\utils\BinaryStream; +use pocketmine\utils\Utils; use pocketmine\VersionInfo; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\BaseWorldProvider; @@ -204,23 +205,29 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt); }catch(BlockStateDeserializeException $e){ //while not ideal, this is not a fatal error - $blockDecodeErrors[] = "Palette offset $i / Upgrade error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString(); + $errorMessage = "Upgrade error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString(); + $blockDecodeErrors[$errorMessage][] = $i; $palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); continue; } try{ $palette[] = $this->blockStateDeserializer->deserialize($blockStateData); }catch(UnsupportedBlockStateException $e){ - $blockDecodeErrors[] = "Palette offset $i / " . $e->getMessage(); + $blockDecodeErrors[$e->getMessage()][] = $i; $palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); }catch(BlockStateDeserializeException $e){ - $blockDecodeErrors[] = "Palette offset $i / Deserialize error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString(); + $errorMessage = "Deserialize error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString(); + $blockDecodeErrors[$errorMessage][] = $i; $palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); } } if(count($blockDecodeErrors) > 0){ - $logger->error("Errors decoding blocks:\n - " . implode("\n - ", $blockDecodeErrors)); + $finalErrors = []; + foreach(Utils::promoteKeys($blockDecodeErrors) as $errorMessage => $paletteOffsets){ + $finalErrors[] = "$errorMessage (palette offsets: " . implode(", ", $paletteOffsets) . ")"; + } + $logger->error("Errors decoding blocks:\n - " . implode("\n - ", $finalErrors)); } //TODO: exceptions From 56da492e48bf94cf5da4153f48f6d872ba8c0f21 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 28 May 2025 22:10:20 +0100 Subject: [PATCH 215/334] World: make less noise about deleted tile entities no need to repeat the same message 100 times --- src/world/World.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/world/World.php b/src/world/World.php index 792681a89..5c5e4cfbf 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2982,6 +2982,8 @@ class World implements ChunkManager{ if(count($chunkData->getEntityNBT()) !== 0){ $this->timings->syncChunkLoadEntities->startTiming(); $entityFactory = EntityFactory::getInstance(); + + $deletedEntities = []; foreach($chunkData->getEntityNBT() as $k => $nbt){ try{ $entity = $entityFactory->createFromData($this, $nbt); @@ -2998,18 +3000,23 @@ class World implements ChunkManager{ }elseif($saveIdTag instanceof IntTag){ //legacy MCPE format $saveId = "legacy(" . $saveIdTag->getValue() . ")"; } - $logger->warning("Deleted unknown entity type $saveId"); + $deletedEntities[$saveId] = ($deletedEntities[$saveId] ?? 0) + 1; } //TODO: we can't prevent entities getting added to unloaded chunks if they were saved in the wrong place //here, because entities currently add themselves to the world } + foreach(Utils::promoteKeys($deletedEntities) as $saveId => $count){ + $logger->warning("Deleted unknown entity type $saveId x$count"); + } $this->timings->syncChunkLoadEntities->stopTiming(); } if(count($chunkData->getTileNBT()) !== 0){ $this->timings->syncChunkLoadTileEntities->startTiming(); $tileFactory = TileFactory::getInstance(); + + $deletedTiles = []; foreach($chunkData->getTileNBT() as $k => $nbt){ try{ $tile = $tileFactory->createFromData($this, $nbt); @@ -3019,7 +3026,8 @@ class World implements ChunkManager{ continue; } if($tile === null){ - $logger->warning("Deleted unknown tile entity type " . $nbt->getString("id", "")); + $saveId = $nbt->getString("id", ""); + $deletedTiles[$saveId] = ($deletedTiles[$saveId] ?? 0) + 1; continue; } @@ -3035,6 +3043,10 @@ class World implements ChunkManager{ } } + foreach(Utils::promoteKeys($deletedTiles) as $saveId => $count){ + $logger->warning("Deleted unknown tile entity type $saveId x$count"); + } + $this->timings->syncChunkLoadTileEntities->stopTiming(); } } From 0910a219d4b66d53f1387eea27f25efbc4e34572 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 28 May 2025 23:29:37 +0100 Subject: [PATCH 216/334] Fixed improper pre-checking of PlayerAuthInputPacket flags --- composer.json | 2 +- composer.lock | 14 +++++++------- src/network/mcpe/handler/InGamePacketHandler.php | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 979973893..d2064bcd6 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", "pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~38.0.0+bedrock-1.21.80", + "pocketmine/bedrock-protocol": "~38.1.0+bedrock-1.21.80", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index 9cb0721fc..2e2e5a600 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": "ceb98091ac3f61f1a4b87708c48dc75a", + "content-hash": "fe62caebfdb35cd8bd57c8e61879b7c0", "packages": [ { "name": "adhocore/json-comment", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "38.0.1+bedrock-1.21.80", + "version": "38.1.0+bedrock-1.21.80", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f" + "reference": "a1fa215563517050045309bb779a67f75843b867" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f", - "reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1fa215563517050045309bb779a67f75843b867", + "reference": "a1fa215563517050045309bb779a67f75843b867", "shasum": "" }, "require": { @@ -296,9 +296,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/38.0.1+bedrock-1.21.80" + "source": "https://github.com/pmmp/BedrockProtocol/tree/38.1.0+bedrock-1.21.80" }, - "time": "2025-05-17T11:56:33+00:00" + "time": "2025-05-28T22:19:59+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index eec200e4b..927ba38fa 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -211,7 +211,7 @@ class InGamePacketHandler extends PacketHandler{ } $inputFlags = $packet->getInputFlags(); - if($inputFlags !== $this->lastPlayerAuthInputFlags){ + if($this->lastPlayerAuthInputFlags === null || !$inputFlags->equals($this->lastPlayerAuthInputFlags)){ $this->lastPlayerAuthInputFlags = $inputFlags; $sneaking = $inputFlags->get(PlayerAuthInputFlags::SNEAKING); From b4b6bbe29f21754039db11ab8ca7d0758e4b43b5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 29 May 2025 17:18:45 +0100 Subject: [PATCH 217/334] BaseInventory: fixed internalAddItem() setting air slots to air this bug was introduced in #4237, but it was unnoticed due to having no adverse effects other than noisy debugs and network traffic. --- src/inventory/BaseInventory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 0d5d1ffe6..c4afda43a 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -256,7 +256,7 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{ $slotItem->setCount($slotItem->getCount() + $amount); $this->setItem($i, $slotItem); if($newItem->getCount() <= 0){ - break; + return $newItem; } } } @@ -270,7 +270,7 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{ $slotItem->setCount($amount); $this->setItem($slotIndex, $slotItem); if($newItem->getCount() <= 0){ - break; + return $newItem; } } } From e99665fb12299519ad50f0da7f08000af9e2bc45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 14:08:14 +0000 Subject: [PATCH 218/334] Bump docker/build-push-action in the github-actions group (#6719) --- .github/workflows/build-docker-image.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index dc282ab71..a3921f820 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.18.0 with: push: true context: ./pocketmine-mp @@ -66,7 +66,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.18.0 with: push: true context: ./pocketmine-mp @@ -79,7 +79,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.18.0 with: push: true context: ./pocketmine-mp @@ -92,7 +92,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.18.0 with: push: true context: ./pocketmine-mp From a4ac28592c6c5f5876927aa2a140b65dad63d786 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 2 Jun 2025 15:17:00 +0100 Subject: [PATCH 219/334] Updated dependencies --- composer.json | 2 +- composer.lock | 24 +++++++++++----------- tests/phpstan/configs/actual-problems.neon | 6 ------ tests/phpstan/configs/phpstan-bugs.neon | 12 ----------- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/composer.json b/composer.json index d2064bcd6..1935bc290 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.16", + "phpstan/phpstan": "2.1.17", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index 2e2e5a600..cb60c7ace 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": "fe62caebfdb35cd8bd57c8e61879b7c0", + "content-hash": "69921783f476a0704fa1f8924b901a89", "packages": [ { "name": "adhocore/json-comment", @@ -1038,16 +1038,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -1090,9 +1090,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "phar-io/manifest", @@ -1214,16 +1214,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.16", + "version": "2.1.17", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9" + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", - "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", "shasum": "" }, "require": { @@ -1268,7 +1268,7 @@ "type": "github" } ], - "time": "2025-05-16T09:40:10+00:00" + "time": "2025-05-21T20:55:28+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index d3adde422..39ae4948c 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -702,12 +702,6 @@ parameters: count: 1 path: ../../../src/inventory/transaction/InventoryTransaction.php - - - message: '#^Cannot cast mixed to int\.$#' - identifier: cast.int - count: 2 - path: ../../../src/item/Item.php - - message: '#^Parameter \#1 \$buffer of method pocketmine\\nbt\\BaseNbtSerializer\:\:read\(\) expects string, mixed given\.$#' identifier: argument.type diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index aeb3fae29..75b1e82a7 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -18,12 +18,6 @@ parameters: count: 1 path: ../../../src/Server.php - - - message: '#^Method pocketmine\\block\\Block\:\:readStateFromWorld\(\) is marked as impure but does not have any side effects\.$#' - identifier: impureMethod.pure - count: 1 - path: ../../../src/block/Block.php - - message: '#^Method pocketmine\\block\\DoubleTallGrass\:\:traitGetDropsForIncompatibleTool\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -252,9 +246,3 @@ parameters: count: 2 path: ../../phpunit/promise/PromiseTest.php - - - message: '#^Strict comparison using \=\=\= between 0 and 0 will always evaluate to true\.$#' - identifier: identical.alwaysTrue - count: 1 - path: ../rules/UnsafeForeachArrayOfStringRule.php - From 5ebbcd5d33286d37f311a1c9f8a69100dcaf87e9 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 2 Jun 2025 15:24:25 +0100 Subject: [PATCH 220/334] Move to newer systems for movement and block break handling (#6718) MS is due to remove the non-server-auth versions of all of this stuff. Fortunately v3 server auth movement works just fine without any changes, although we will need to start sending player tick in some packets if someone wants to actually use the rewind stuff. --- src/network/mcpe/InventoryManager.php | 19 +++-- .../mcpe/handler/InGamePacketHandler.php | 85 +++++++++++-------- .../mcpe/handler/ItemStackRequestExecutor.php | 26 +++++- .../mcpe/handler/PreSpawnPacketHandler.php | 2 +- 4 files changed, 89 insertions(+), 43 deletions(-) diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 2ff23a73a..19bd94fce 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -41,6 +41,7 @@ use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\item\enchantment\EnchantingOption; use pocketmine\item\enchantment\EnchantmentInstance; +use pocketmine\item\Item; use pocketmine\network\mcpe\cache\CreativeInventoryCache; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; @@ -228,17 +229,25 @@ class InventoryManager{ return null; } - private function addPredictedSlotChange(Inventory $inventory, int $slot, ItemStack $item) : void{ + private function addPredictedSlotChangeInternal(Inventory $inventory, int $slot, ItemStack $item) : void{ $this->inventories[spl_object_id($inventory)]->predictions[$slot] = $item; } - public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : void{ + public function addPredictedSlotChange(Inventory $inventory, int $slot, Item $item) : void{ $typeConverter = $this->session->getTypeConverter(); + $itemStack = $typeConverter->coreItemStackToNet($item); + $this->addPredictedSlotChangeInternal($inventory, $slot, $itemStack); + } + + public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : void{ foreach($tx->getActions() as $action){ if($action instanceof SlotChangeAction){ //TODO: ItemStackRequestExecutor can probably build these predictions with much lower overhead - $itemStack = $typeConverter->coreItemStackToNet($action->getTargetItem()); - $this->addPredictedSlotChange($action->getInventory(), $action->getSlot(), $itemStack); + $this->addPredictedSlotChange( + $action->getInventory(), + $action->getSlot(), + $action->getTargetItem() + ); } } } @@ -267,7 +276,7 @@ class InventoryManager{ } [$inventory, $slot] = $info; - $this->addPredictedSlotChange($inventory, $slot, $action->newItem->getItemStack()); + $this->addPredictedSlotChangeInternal($inventory, $slot, $action->newItem->getItemStack()); } } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index eec200e4b..c2ca3cb5c 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -136,6 +136,8 @@ class InGamePacketHandler extends PacketHandler{ protected ?float $lastPlayerAuthInputPitch = null; protected ?BitSet $lastPlayerAuthInputFlags = null; + protected ?BlockPosition $lastBlockAttacked = null; + public bool $forceMoveSync = false; protected ?string $lastRequestedFullSkinId = null; @@ -248,6 +250,28 @@ class InGamePacketHandler extends PacketHandler{ $packetHandled = true; + $useItemTransaction = $packet->getItemInteractionData(); + if($useItemTransaction !== null){ + if(count($useItemTransaction->getTransactionData()->getActions()) > 100){ + throw new PacketHandlingException("Too many actions in item use transaction"); + } + + $this->inventoryManager->setCurrentItemStackRequestId($useItemTransaction->getRequestId()); + $this->inventoryManager->addRawPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions()); + if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){ + $packetHandled = false; + $this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")"); + }else{ + $this->inventoryManager->syncMismatchedPredictedSlotChanges(); + } + $this->inventoryManager->setCurrentItemStackRequestId(null); + } + + $itemStackRequest = $packet->getItemStackRequest(); + $itemStackResponseBuilder = $itemStackRequest !== null ? $this->handleSingleItemStackRequest($itemStackRequest) : null; + + //itemstack request or transaction may set predictions for the outcome of these actions, so these need to be + //processed last $blockActions = $packet->getBlockActions(); if($blockActions !== null){ if(count($blockActions) > 100){ @@ -268,27 +292,9 @@ class InGamePacketHandler extends PacketHandler{ } } - $useItemTransaction = $packet->getItemInteractionData(); - if($useItemTransaction !== null){ - if(count($useItemTransaction->getTransactionData()->getActions()) > 100){ - throw new PacketHandlingException("Too many actions in item use transaction"); - } - - $this->inventoryManager->setCurrentItemStackRequestId($useItemTransaction->getRequestId()); - $this->inventoryManager->addRawPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions()); - if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){ - $packetHandled = false; - $this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")"); - }else{ - $this->inventoryManager->syncMismatchedPredictedSlotChanges(); - } - $this->inventoryManager->setCurrentItemStackRequestId(null); - } - - $itemStackRequest = $packet->getItemStackRequest(); if($itemStackRequest !== null){ - $result = $this->handleSingleItemStackRequest($itemStackRequest); - $this->session->sendDataPacket(ItemStackResponsePacket::create([$result])); + $itemStackResponse = $itemStackResponseBuilder?->build() ?? new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $itemStackRequest->getRequestId()); + $this->session->sendDataPacket(ItemStackResponsePacket::create([$itemStackResponse])); } return $packetHandled; @@ -498,13 +504,6 @@ class InGamePacketHandler extends PacketHandler{ //if only the client would tell us what blocks it thinks changed... $this->syncBlocksNearby($vBlockPos, $data->getFace()); return true; - case UseItemTransactionData::ACTION_BREAK_BLOCK: - $blockPos = $data->getBlockPosition(); - $vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ()); - if(!$this->player->breakBlock($vBlockPos)){ - $this->syncBlocksNearby($vBlockPos, null); - } - return true; case UseItemTransactionData::ACTION_CLICK_AIR: if($this->player->isUsingItem()){ if(!$this->player->consumeHeldItem()){ @@ -580,7 +579,7 @@ class InGamePacketHandler extends PacketHandler{ return false; } - private function handleSingleItemStackRequest(ItemStackRequest $request) : ItemStackResponse{ + private function handleSingleItemStackRequest(ItemStackRequest $request) : ?ItemStackResponseBuilder{ if(count($request->getActions()) > 60){ //recipe book auto crafting can affect all slots of the inventory when consuming inputs or producing outputs //this means there could be as many as 50 CraftingConsumeInput actions or Place (taking the result) actions @@ -597,7 +596,11 @@ class InGamePacketHandler extends PacketHandler{ $executor = new ItemStackRequestExecutor($this->player, $this->inventoryManager, $request); try{ $transaction = $executor->generateInventoryTransaction(); - $result = $this->executeInventoryTransaction($transaction, $request->getRequestId()); + if($transaction !== null){ + $result = $this->executeInventoryTransaction($transaction, $request->getRequestId()); + }else{ + $result = true; //predictions only, just send responses + } }catch(ItemStackRequestProcessException $e){ $result = false; $this->session->getLogger()->debug("ItemStackRequest #" . $request->getRequestId() . " failed: " . $e->getMessage()); @@ -605,10 +608,7 @@ class InGamePacketHandler extends PacketHandler{ $this->inventoryManager->requestSyncAll(); } - if(!$result){ - return new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $request->getRequestId()); - } - return $executor->buildItemStackResponse(); + return $result ? $executor->getItemStackResponseBuilder() : null; } public function handleItemStackRequest(ItemStackRequestPacket $packet) : bool{ @@ -618,7 +618,7 @@ class InGamePacketHandler extends PacketHandler{ throw new PacketHandlingException("Too many requests in ItemStackRequestPacket"); } foreach($packet->getRequests() as $request){ - $responses[] = $this->handleSingleItemStackRequest($request); + $responses[] = $this->handleSingleItemStackRequest($request)?->build() ?? new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $request->getRequestId()); } $this->session->sendDataPacket(ItemStackResponsePacket::create($responses)); @@ -681,16 +681,27 @@ class InGamePacketHandler extends PacketHandler{ switch($action){ case PlayerAction::START_BREAK: + case PlayerAction::CONTINUE_DESTROY_BLOCK: //destroy the next block while holding down left click self::validateFacing($face); + if($this->lastBlockAttacked !== null && $blockPosition->equals($this->lastBlockAttacked)){ + //the client will send CONTINUE_DESTROY_BLOCK for the currently targeted block directly before it + //sends PREDICT_DESTROY_BLOCK, but also when it starts to break the block + //this seems like a bug in the client and would cause spurious left-click events if we allowed it to + //be delivered to the player + $this->session->getLogger()->debug("Ignoring PlayerAction $action on $pos because we were already destroying this block"); + break; + } if(!$this->player->attackBlock($pos, $face)){ $this->syncBlocksNearby($pos, $face); } + $this->lastBlockAttacked = $blockPosition; break; case PlayerAction::ABORT_BREAK: case PlayerAction::STOP_BREAK: $this->player->stopBreakBlock($pos); + $this->lastBlockAttacked = null; break; case PlayerAction::START_SLEEPING: //unused @@ -701,11 +712,17 @@ class InGamePacketHandler extends PacketHandler{ case PlayerAction::CRACK_BREAK: self::validateFacing($face); $this->player->continueBreakBlock($pos, $face); + $this->lastBlockAttacked = $blockPosition; break; case PlayerAction::INTERACT_BLOCK: //TODO: ignored (for now) break; case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK: //TODO: do we need to handle this? + case PlayerAction::PREDICT_DESTROY_BLOCK: + if(!$this->player->breakBlock($pos)){ + $this->syncBlocksNearby($pos, $face); + } + $this->lastBlockAttacked = null; break; case PlayerAction::START_ITEM_USE_ON: case PlayerAction::STOP_ITEM_USE_ON: diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index 6db8f1e12..d71a1c6bf 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -33,9 +33,11 @@ use pocketmine\inventory\transaction\EnchantingTransaction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionBuilder; use pocketmine\inventory\transaction\TransactionBuilderInventory; +use pocketmine\item\Durable; use pocketmine\item\Item; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds; +use pocketmine\network\mcpe\protocol\types\inventory\FullContainerName; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingConsumeInputStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingCreateSpecificResultStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeAutoStackRequestAction; @@ -47,6 +49,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DropStackReque use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequestSlotInfo; +use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\MineBlockStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\PlaceStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\SwapStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\TakeStackRequestAction; @@ -362,6 +365,16 @@ class ItemStackRequestExecutor{ $this->setNextCreatedItem($nextResultItem); }elseif($action instanceof DeprecatedCraftingResultsStackRequestAction){ //no obvious use + }elseif($action instanceof MineBlockStackRequestAction){ + $slot = $action->getHotbarSlot(); + $this->requestSlotInfos[] = new ItemStackRequestSlotInfo(new FullContainerName(ContainerUIIds::HOTBAR), $slot, $action->getStackId()); + $inventory = $this->player->getInventory(); + $usedItem = $inventory->slotExists($slot) ? $inventory->getItem($slot) : null; + $predictedDamage = $action->getPredictedDurability(); + if($usedItem instanceof Durable && $predictedDamage >= 0 && $predictedDamage <= $usedItem->getMaxDurability()){ + $usedItem->setDamage($predictedDamage); + $this->inventoryManager->addPredictedSlotChange($inventory, $slot, $usedItem); + } }else{ throw new ItemStackRequestProcessException("Unhandled item stack request action"); } @@ -370,7 +383,7 @@ class ItemStackRequestExecutor{ /** * @throws ItemStackRequestProcessException */ - public function generateInventoryTransaction() : InventoryTransaction{ + public function generateInventoryTransaction() : ?InventoryTransaction{ foreach(Utils::promoteKeys($this->request->getActions()) as $k => $action){ try{ $this->processItemStackRequestAction($action); @@ -380,6 +393,9 @@ class ItemStackRequestExecutor{ } $this->setNextCreatedItem(null); $inventoryActions = $this->builder->generateActions(); + if(count($inventoryActions) === 0){ + return null; + } $transaction = $this->specialTransaction ?? new InventoryTransaction($this->player); foreach($inventoryActions as $action){ @@ -389,12 +405,16 @@ class ItemStackRequestExecutor{ return $transaction; } - public function buildItemStackResponse() : ItemStackResponse{ + public function getItemStackResponseBuilder() : ItemStackResponseBuilder{ $builder = new ItemStackResponseBuilder($this->request->getRequestId(), $this->inventoryManager); foreach($this->requestSlotInfos as $requestInfo){ $builder->addSlot($requestInfo->getContainerName()->getContainerId(), $requestInfo->getSlotId()); } - return $builder->build(); + return $builder; + } + + public function buildItemStackResponse() : ItemStackResponse{ + return $this->getItemStackResponseBuilder()->build(); } } diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 9aa302c0c..161a679d6 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -99,7 +99,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $this->server->getMotd(), "", false, - new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V2, 0, false), + new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V3, 0, true), 0, 0, "", From 4c3a2ef46ed15dbdccec415d78603a23fea41fb1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 8 Jun 2025 18:44:37 +0100 Subject: [PATCH 221/334] Update dependencies (minor-next) --- composer.json | 4 +-- composer.lock | 83 ++++++++++++++++++++++----------------------------- 2 files changed, 37 insertions(+), 50 deletions(-) diff --git a/composer.json b/composer.json index 8c56bbf81..e65a6fb8e 100644 --- a/composer.json +++ b/composer.json @@ -45,10 +45,10 @@ "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", "pocketmine/nbt": "~1.1.0", - "pocketmine/raklib": "~1.1.2", + "pocketmine/raklib": "~1.2.0", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", - "ramsey/uuid": "~4.7.0", + "ramsey/uuid": "~4.8.0", "symfony/filesystem": "~6.4.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 10fb2a6ab..c01e7a299 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": "3db02308c5b44b94e1c990d60ffd2f28", + "content-hash": "7c3052613e98e566d8b00ae3c9119057", "packages": [ { "name": "adhocore/json-comment", @@ -67,16 +67,16 @@ }, { "name": "brick/math", - "version": "0.12.3", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "shasum": "" }, "require": { @@ -115,7 +115,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.13.1" }, "funding": [ { @@ -123,7 +123,7 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2025-03-29T13:50:30+00:00" }, { "name": "netresearch/jsonmapper", @@ -618,16 +618,16 @@ }, { "name": "pocketmine/raklib", - "version": "1.1.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/pmmp/RakLib.git", - "reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f" + "reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/RakLib/zipball/4145a31cd812fe8931c3c9c691fcd2ded2f47e7f", - "reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f", + "url": "https://api.github.com/repos/pmmp/RakLib/zipball/a28d05216d34dbd00e8aed827a58df6b4c11510b", + "reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b", "shasum": "" }, "require": { @@ -655,9 +655,9 @@ "description": "A RakNet server implementation written in PHP", "support": { "issues": "https://github.com/pmmp/RakLib/issues", - "source": "https://github.com/pmmp/RakLib/tree/1.1.2" + "source": "https://github.com/pmmp/RakLib/tree/1.2.0" }, - "time": "2025-04-06T03:38:21+00:00" + "time": "2025-06-08T17:36:06+00:00" }, { "name": "pocketmine/raklib-ipc", @@ -818,20 +818,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.8.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -840,26 +840,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -894,19 +891,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.8.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-06-01T06:28:46+00:00" }, { "name": "symfony/filesystem", From 48b80ecf78400f8644ab6f67e40aac702ffd65d1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 8 Jun 2025 19:01:11 +0100 Subject: [PATCH 222/334] Change crashdump file name format this has bothered me for ages since it sorts into some absurd order by default due to the name starting with the day of the week. this way it'll ensure that the files are always alphanumerically ordered, which means the most recent crashdump should always be at the bottom. --- src/Server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server.php b/src/Server.php index 679a0ef0b..d6f0a8415 100644 --- a/src/Server.php +++ b/src/Server.php @@ -1618,7 +1618,7 @@ class Server{ if(!is_dir($crashFolder)){ mkdir($crashFolder); } - $crashDumpPath = Path::join($crashFolder, date("D_M_j-H.i.s-T_Y", (int) $dump->getData()->time) . ".log"); + $crashDumpPath = Path::join($crashFolder, date("Y-m-d_H.i.s_T", (int) $dump->getData()->time) . ".log"); $fp = @fopen($crashDumpPath, "wb"); if(!is_resource($fp)){ From 95b4db5169db52777b826f3ea804c42c52c42100 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 11 Jun 2025 21:29:03 +0100 Subject: [PATCH 223/334] Fix slow SubChunk garbage collection check, closes #6574 (#6731) --- src/world/format/SubChunk.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/world/format/SubChunk.php b/src/world/format/SubChunk.php index d8546e7e9..cc6673430 100644 --- a/src/world/format/SubChunk.php +++ b/src/world/format/SubChunk.php @@ -134,11 +134,8 @@ class SubChunk{ foreach($this->blockLayers as $layer){ $layer->collectGarbage(); - foreach($layer->getPalette() as $p){ - if($p !== $this->emptyBlockId){ - $cleanedLayers[] = $layer; - continue 2; - } + if($layer->getBitsPerBlock() !== 0 || $layer->get(0, 0, 0) !== $this->emptyBlockId){ + $cleanedLayers[] = $layer; } } $this->blockLayers = $cleanedLayers; From 9c71f4fc1ce6ed1beeff9f55b8b209fa361983cb Mon Sep 17 00:00:00 2001 From: Dries C <15795262+dries-c@users.noreply.github.com> Date: Wed, 18 Jun 2025 02:15:00 +0200 Subject: [PATCH 224/334] Assemble 1.21.90 (#6736) --- changelogs/5.29.md | 25 +++++++ composer.json | 4 +- composer.lock | 26 +++---- src/VersionInfo.php | 4 +- src/data/bedrock/item/ItemTypeNames.php | 1 + .../mcpe/handler/LoginPacketHandler.php | 71 +++++++++++++++++-- .../mcpe/handler/PreSpawnPacketHandler.php | 3 +- .../handler/ResourcePacksPacketHandler.php | 3 +- src/world/format/io/data/BedrockWorldData.php | 4 +- 9 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 changelogs/5.29.md diff --git a/changelogs/5.29.md b/changelogs/5.29.md new file mode 100644 index 000000000..cb6e50da3 --- /dev/null +++ b/changelogs/5.29.md @@ -0,0 +1,25 @@ +# 5.29.0 +Released 18th June 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.90. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.90. +- Removed support for earlier versions. + +## Fixes +- Fixed thread crashes sometimes not reporting proper cause information in crashdumps. +- Fixed crash when a plugin replaced a player's held tool with a different tool with a damage exceeding the old tool's max damage during an action. +- Fixed performance issue of `PlayerAuthInputPacket` input flags handling (broken change detection). +- Fixed `BaseInventory->addItem()` triggering updates on empty slots when no items were added. +- Fixed slow check in `SubChunk` block layer garbage collection. + +## Internals +- `LoginPacketHandler->processLogin()` signature has changed. This will break any plugins overriding `LoginPacketHandler`. As noted above, this is _not_ covered by the API version guarantee. +- Automated branch sync for `minor-next` and `major-next` is now triggered by `repository_dispatch` from a cron job in this repository instead of `RestrictedActions`. The `RestrictedActions` cron job was getting automatically disabled by GitHub due to repo inactivity. diff --git a/composer.json b/composer.json index 1935bc290..1decaac39 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80", + "pocketmine/bedrock-data": "~5.1.0+bedrock-1.21.90", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~38.1.0+bedrock-1.21.80", + "pocketmine/bedrock-protocol": "~39.0.0+bedrock-1.21.90", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index cb60c7ace..b6db8fe7d 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": "69921783f476a0704fa1f8924b901a89", + "content-hash": "bde74cbb65c043a2acf6f62b5b328e67", "packages": [ { "name": "adhocore/json-comment", @@ -204,16 +204,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "5.0.0+bedrock-1.21.80", + "version": "5.1.0+bedrock-1.21.90", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080" + "reference": "89ed34957aeccc63e517aa849af593adae958e98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/e38d5ea19f794ec5216e5f96742237e8c4e7f080", - "reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/89ed34957aeccc63e517aa849af593adae958e98", + "reference": "89ed34957aeccc63e517aa849af593adae958e98", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.80" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.90" }, - "time": "2025-05-09T14:15:18+00:00" + "time": "2025-06-17T23:44:21+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "38.1.0+bedrock-1.21.80", + "version": "39.0.0+bedrock-1.21.90", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "a1fa215563517050045309bb779a67f75843b867" + "reference": "2b088183d12fc003523400867ee398e3893899ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1fa215563517050045309bb779a67f75843b867", - "reference": "a1fa215563517050045309bb779a67f75843b867", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2b088183d12fc003523400867ee398e3893899ed", + "reference": "2b088183d12fc003523400867ee398e3893899ed", "shasum": "" }, "require": { @@ -296,9 +296,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/38.1.0+bedrock-1.21.80" + "source": "https://github.com/pmmp/BedrockProtocol/tree/39.0.0+bedrock-1.21.90" }, - "time": "2025-05-28T22:19:59+00:00" + "time": "2025-06-17T23:46:38+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 615024656..7033c2707 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.28.3"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.29.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php index 5f86cde96..d2ab0996b 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -380,6 +380,7 @@ final class ItemTypeNames{ public const MUSIC_DISC_RELIC = "minecraft:music_disc_relic"; public const MUSIC_DISC_STAL = "minecraft:music_disc_stal"; public const MUSIC_DISC_STRAD = "minecraft:music_disc_strad"; + public const MUSIC_DISC_TEARS = "minecraft:music_disc_tears"; public const MUSIC_DISC_WAIT = "minecraft:music_disc_wait"; public const MUSIC_DISC_WARD = "minecraft:music_disc_ward"; public const MUTTON = "minecraft:mutton"; diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index c15753dad..5c467f2d4 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -33,6 +33,8 @@ use pocketmine\network\mcpe\JwtUtils; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\LoginPacket; use pocketmine\network\mcpe\protocol\types\login\AuthenticationData; +use pocketmine\network\mcpe\protocol\types\login\AuthenticationInfo; +use pocketmine\network\mcpe\protocol\types\login\AuthenticationType; use pocketmine\network\mcpe\protocol\types\login\ClientData; use pocketmine\network\mcpe\protocol\types\login\ClientDataToSkinDataHelper; use pocketmine\network\mcpe\protocol\types\login\JwtChain; @@ -42,7 +44,11 @@ use pocketmine\player\PlayerInfo; use pocketmine\player\XboxLivePlayerInfo; use pocketmine\Server; use Ramsey\Uuid\Uuid; +use function gettype; use function is_array; +use function is_object; +use function json_decode; +use const JSON_THROW_ON_ERROR; /** * Handles the initial login phase of the session. This handler is used as the initial state. @@ -60,7 +66,9 @@ class LoginPacketHandler extends PacketHandler{ ){} public function handleLogin(LoginPacket $packet) : bool{ - $extraData = $this->fetchAuthData($packet->chainDataJwt); + $authInfo = $this->parseAuthInfo($packet->authInfoJson); + $jwtChain = $this->parseJwtChain($authInfo->Certificate); + $extraData = $this->fetchAuthData($jwtChain); if(!Player::isValidUserName($extraData->displayName)){ $this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_invalidName()); @@ -139,11 +147,61 @@ class LoginPacketHandler extends PacketHandler{ return true; } - $this->processLogin($packet, $ev->isAuthRequired()); + $this->processLogin($authInfo->Token, AuthenticationType::from($authInfo->AuthenticationType), $jwtChain->chain, $packet->clientDataJwt, $ev->isAuthRequired()); return true; } + /** + * @throws PacketHandlingException + */ + protected function parseAuthInfo(string $authInfo) : AuthenticationInfo{ + try{ + $authInfoJson = json_decode($authInfo, associative: false, flags: JSON_THROW_ON_ERROR); + }catch(\JsonException $e){ + throw PacketHandlingException::wrap($e); + } + if(!is_object($authInfoJson)){ + throw new \RuntimeException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object"); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnMissingData = true; + $mapper->bExceptionOnUndefinedProperty = true; + $mapper->bStrictObjectTypeChecking = true; + try{ + $clientData = $mapper->map($authInfoJson, new AuthenticationInfo()); + }catch(\JsonMapper_Exception $e){ + throw PacketHandlingException::wrap($e); + } + return $clientData; + } + + /** + * @throws PacketHandlingException + */ + protected function parseJwtChain(string $chainDataJwt) : JwtChain{ + try{ + $jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR); + }catch(\JsonException $e){ + throw PacketHandlingException::wrap($e); + } + if(!is_object($jwtChainJson)){ + throw new \RuntimeException("Unexpected type for JWT chain data: " . gettype($jwtChainJson) . ", expected object"); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnMissingData = true; + $mapper->bExceptionOnUndefinedProperty = true; + $mapper->bStrictObjectTypeChecking = true; + try{ + $clientData = $mapper->map($jwtChainJson, new JwtChain()); + }catch(\JsonMapper_Exception $e){ + throw PacketHandlingException::wrap($e); + } + return $clientData; + } + /** * @throws PacketHandlingException */ @@ -211,10 +269,15 @@ class LoginPacketHandler extends PacketHandler{ * TODO: This is separated for the purposes of allowing plugins (like Specter) to hack it and bypass authentication. * In the future this won't be necessary. * + * @param null|string[] $legacyCertificate + * * @throws \InvalidArgumentException */ - protected function processLogin(LoginPacket $packet, bool $authRequired) : void{ - $this->server->getAsyncPool()->submitTask(new ProcessLoginTask($packet->chainDataJwt->chain, $packet->clientDataJwt, $authRequired, $this->authCallback)); + protected function processLogin(string $token, AuthenticationType $authType, ?array $legacyCertificate, string $clientData, bool $authRequired) : void{ + if($legacyCertificate === null){ + throw new PacketHandlingException("Legacy certificate cannot be null"); + } + $this->server->getAsyncPool()->submitTask(new ProcessLoginTask($legacyCertificate, $clientData, $authRequired, $this->authCallback)); $this->session->setHandler(null); //drop packets received during login verification } } diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 9aa302c0c..e528a5201 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -40,7 +40,6 @@ use pocketmine\network\mcpe\protocol\types\Experiments; use pocketmine\network\mcpe\protocol\types\LevelSettings; use pocketmine\network\mcpe\protocol\types\NetworkPermissions; use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings; -use pocketmine\network\mcpe\protocol\types\ServerAuthMovementMode; use pocketmine\network\mcpe\protocol\types\SpawnSettings; use pocketmine\player\Player; use pocketmine\Server; @@ -99,7 +98,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $this->server->getMotd(), "", false, - new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V2, 0, false), + new PlayerMovementSettings(0, false), 0, 0, "", diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index a1df394da..a9ffae6f7 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -120,7 +120,8 @@ class ResourcePacksPacketHandler extends PacketHandler{ hasAddons: false, hasScripts: false, worldTemplateId: Uuid::fromString(Uuid::NIL), - worldTemplateVersion: "" + worldTemplateVersion: "", + forceDisableVibrantVisuals: true, )); $this->session->getLogger()->debug("Waiting for client to accept resource packs"); } diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index d39c17a47..a971920ec 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -51,11 +51,11 @@ use function time; class BedrockWorldData extends BaseNbtWorldData{ public const CURRENT_STORAGE_VERSION = 10; - public const CURRENT_STORAGE_NETWORK_VERSION = 800; + public const CURRENT_STORAGE_NETWORK_VERSION = 818; public const CURRENT_CLIENT_VERSION_TARGET = [ 1, //major 21, //minor - 80, //patch + 90, //patch 3, //revision 0 //is beta ]; From 8843b1b5685a07f626bf9f62be7d0b6ce12411b4 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 00:16:13 +0000 Subject: [PATCH 225/334] 5.29.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15720776704 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 7033c2707..ec525d6c3 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.29.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.29.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 670d3fb9971c0622666894da359ad2bd0aa34d3d Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 18 Jun 2025 19:29:28 +0100 Subject: [PATCH 226/334] Mention developer team in draft release notification --- .github/workflows/draft-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index fa20d1912..ca48ab8e0 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -188,4 +188,4 @@ jobs: if: github.event_name == 'pull_request_target' uses: thollander/actions-comment-pull-request@v3 with: - message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it." + message: "${{ vars.DRAFT_RELEASE_NOTIFICATION_MENTION }} [Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it." From 3643d3aeb81baf8ad2cc4e8b8409e6dd3ab1b5f5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 18 Jun 2025 20:42:10 +0100 Subject: [PATCH 227/334] Ready 5.30.0 release --- changelogs/5.30.md | 55 +++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 ++-- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.30.md diff --git a/changelogs/5.30.md b/changelogs/5.30.md new file mode 100644 index 000000000..50889df26 --- /dev/null +++ b/changelogs/5.30.md @@ -0,0 +1,55 @@ +# 5.30.0 +Released 18th June 2025. + +This is a minor feature release containing API additions, internals cleanup and user experience improvements. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Significantly reduced log spam when unknown blocks, tiles and entities are found in saved worlds. +- The file name structure for crashdumps has been changed to improve sorting order in file browsers. +- Buffering is now skipped on the RakLib layer. In theory this could reduce player network latency by 10 ms (YMMV). + +## Gameplay +### Blocks +- Many blocks have had their hardness and blast resistance updated to match vanilla. +- Implemented Respawn Anchor. +- Melon Stem and Pumpkin Stem drop amounts should now match vanilla (using binomial distribution). + +## API +## General +- Verification of save registration has been added for blocks, entities and tiles. This is intended to make it easier to find mistakes when registering custom things, which previously would produce obscure core crashes. + +### `pocketmine\event\entity` +- The following classes have been added: + - `EntityExtinguishEvent` - called when a burning entity is extinguished by water or other sources + - `EntityFrostWalkerEvent` - called every tick upon which an entity wearing Frost Walker boots moves; this can be used to customise or cancel the behaviour of the Frost Walker enchantment + +### `pocketmine\entity` +- The following methods have been added: + - `public Entity->getStepHeight() : float` + - `public Entity->setStepHeight(float $stepHeight) : void` + +### `pocketmine\world\generator` +- Generator execution has been decoupled from `PopulationTask` and async tasks in general. The following classes have been added: + - `executor\GeneratorExecutor` + - `executor\SyncGeneratorExecutor` - runs a generator on the main thread (used for flat world generation, which doesn't need threads) + - `executor\AsyncGeneratorExecutor` - runs a generator inside an async task, as before + - `PopulationUtils` - contains population business logic previously baked into `PopulationTask` - this permits the reuse of that logic outside async tasks +- The following methods have signature changes: + - `GeneratorManager->addGenerator()` now accepts an optional `bool $fast` parameter, defaulting to `false`; setting this to `true` will cause your generator to run on the main thread +- The following methods have been added: + - `public GeneratorManagerEntry->isFast() : bool` - returns whether this generator should run on the main thread +- `PopulationTask` has been marked as `@internal`. In the next major version, it will move to the `generator\executor` namespace; however, for now it stays put because plugins currently have no other way to regenerate chunks. + +## Internals +- World data version numbers have been consolidated in `pocketmine\data\bedrock\WorldDataVersions`. This removes the need to modify several different files to support new world versions, and reduces the chances of things getting missed. +- Block hardness and blast resistance is now unit-tested against `block_properties_table.json` in `BedrockData`. This file comes from vanilla BDS, so we can use it to verify compliance. +- Protocol-layer "server auth block breaking" has been enabled. Functionally, this is no different from the previous system, it just works differently on the network layer. +- Various internal classes in the `pocketmine\world\generator` namespace have been moved to the `generator\executor` namespace. +- Removed `World->registerGenerator()` and `World->unregisterGenerator()`. +- Removed redundant calls to `curl_close()` (obsolete since PHP 8.0). diff --git a/src/VersionInfo.php b/src/VersionInfo.php index ec525d6c3..0b6a601bc 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.29.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.30.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 682642087613f7c4ef5b34194253e2cd088fa3ce Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 20:49:31 +0000 Subject: [PATCH 228/334] 5.30.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15743323722 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 0b6a601bc..e6b323974 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.30.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.30.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 0e511ff783e5a5a3443622835fc775eecaf96c12 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 18 Jun 2025 21:55:53 +0100 Subject: [PATCH 229/334] smh --- changelogs/5.30.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/changelogs/5.30.md b/changelogs/5.30.md index 50889df26..6e9762ea9 100644 --- a/changelogs/5.30.md +++ b/changelogs/5.30.md @@ -24,11 +24,20 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if ## General - Verification of save registration has been added for blocks, entities and tiles. This is intended to make it easier to find mistakes when registering custom things, which previously would produce obscure core crashes. +### `pocketmine\event\block` +- The following classes have been added: + - `BlockPreExplodeEvent` - called before a block tries to explode + - `BlockExplodeEvent` - called when after a block's explosion calculation has been done, but before any changes are applied + ### `pocketmine\event\entity` - The following classes have been added: - `EntityExtinguishEvent` - called when a burning entity is extinguished by water or other sources - `EntityFrostWalkerEvent` - called every tick upon which an entity wearing Frost Walker boots moves; this can be used to customise or cancel the behaviour of the Frost Walker enchantment +### `pocketmine\event\player` +- The following classes have been added: + - `PlayerRespawnAnchorUseEvent` - called when a player interacts with a charged respawn anchor + ### `pocketmine\entity` - The following methods have been added: - `public Entity->getStepHeight() : float` From 04494e845c8ec9ae174604b6dde6c1cc6c22953a Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Fri, 20 Jun 2025 15:26:42 +0100 Subject: [PATCH 230/334] EntityExplodeEvent: Fixed accidental BC break introduced by #6646 thanks @Yexeed --- src/event/entity/EntityExplodeEvent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/entity/EntityExplodeEvent.php b/src/event/entity/EntityExplodeEvent.php index c1750ccb3..779ab879a 100644 --- a/src/event/entity/EntityExplodeEvent.php +++ b/src/event/entity/EntityExplodeEvent.php @@ -51,7 +51,7 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{ protected Position $position, protected array $blocks, protected float $yield, - private array $ignitions + private array $ignitions = [] ){ $this->entity = $entity; if($yield < 0.0 || $yield > 100.0){ From 258923cc78bc2fc5f9504e450cb3ba43a484d2c3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Jun 2025 23:05:51 +0100 Subject: [PATCH 231/334] World: verify blockstate IDs in setChunk() I think I've finally traced the source of these problems back to BuilderTools setting bad values in async tasks :) --- src/world/World.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/world/World.php b/src/world/World.php index 5c5e4cfbf..ecec3d3b9 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2625,6 +2625,16 @@ class World implements ChunkManager{ } public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void{ + foreach($chunk->getSubChunks() as $subChunk){ + foreach($subChunk->getBlockLayers() as $blockLayer){ + foreach($blockLayer->getPalette() as $blockStateId){ + if(!$this->blockStateRegistry->hasStateId($blockStateId)){ + throw new \InvalidArgumentException("Provided chunk contains unknown/unregistered blocks (found unknown state ID $blockStateId)"); + } + } + } + } + $chunkHash = World::chunkHash($chunkX, $chunkZ); $oldChunk = $this->loadChunk($chunkX, $chunkZ); if($oldChunk !== null && $oldChunk !== $chunk){ From 2a97b4294d90fcd01c520fb016db6ae11fa0159b Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:59:40 +0300 Subject: [PATCH 232/334] Fixed held block placement after respawn anchor explosion (#6742) --- src/block/RespawnAnchor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/RespawnAnchor.php b/src/block/RespawnAnchor.php index f702d240d..e19ea8f6d 100644 --- a/src/block/RespawnAnchor.php +++ b/src/block/RespawnAnchor.php @@ -85,7 +85,7 @@ final class RespawnAnchor extends Opaque{ switch($ev->getAction()){ case PlayerRespawnAnchorUseEvent::ACTION_EXPLODE: $this->explode($player); - return false; + return true; case PlayerRespawnAnchorUseEvent::ACTION_SET_SPAWN: if($player->getSpawn() !== null && $player->getSpawn()->equals($this->position)){ From 177fa7643493a0f2fda13daf9c583e9b6d468609 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Mon, 23 Jun 2025 22:57:33 +0300 Subject: [PATCH 233/334] Disable client-side locator bar (#6743) Without a propper server-side implementation, it just a mess of white dots of nearby players --- src/network/mcpe/handler/PreSpawnPacketHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index ad3cafd39..99f65e78f 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -81,7 +81,8 @@ class PreSpawnPacketHandler extends PacketHandler{ $levelSettings->lightningLevel = 0; $levelSettings->commandsEnabled = true; $levelSettings->gameRules = [ - "naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration + "naturalregeneration" => new BoolGameRule(false, false), //Hack for client side regeneration + "locatorbar" => new BoolGameRule(false, false) //Disable client-side tracking of nearby players ]; $levelSettings->experiments = new Experiments([], false); From cb508f43823db5f5e8c7a1829dddae0f0959bae8 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:30:48 +0300 Subject: [PATCH 234/334] Release 5.30.1 (#6744) --- changelogs/5.30.md | 9 +++++++++ composer.lock | 24 ++++++++++++------------ src/VersionInfo.php | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/changelogs/5.30.md b/changelogs/5.30.md index 6e9762ea9..3ecfd285e 100644 --- a/changelogs/5.30.md +++ b/changelogs/5.30.md @@ -62,3 +62,12 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - Various internal classes in the `pocketmine\world\generator` namespace have been moved to the `generator\executor` namespace. - Removed `World->registerGenerator()` and `World->unregisterGenerator()`. - Removed redundant calls to `curl_close()` (obsolete since PHP 8.0). + +# 5.30.1 +Released 23rd June 2025. + +## Fixes +- Fixed accidental break of backwards compatability in `EntityExplodeEvent` introduced in the previous release. +- Fixed placement of player holding block when exploding respawn anchor. +- Updated BedrockProtocol to fix incorrect encoding of `ServerScriptDebugDrawerPacket`. +- Disabled client-side locator bar, allowing plugins to write their own implementations. diff --git a/composer.lock b/composer.lock index e8ca2f08d..2967a967f 100644 --- a/composer.lock +++ b/composer.lock @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "39.0.0+bedrock-1.21.90", + "version": "39.0.1+bedrock-1.21.90", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "2b088183d12fc003523400867ee398e3893899ed" + "reference": "fd231bad0d94024ff50169dc06e8c4fca5aa2eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2b088183d12fc003523400867ee398e3893899ed", - "reference": "2b088183d12fc003523400867ee398e3893899ed", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/fd231bad0d94024ff50169dc06e8c4fca5aa2eb3", + "reference": "fd231bad0d94024ff50169dc06e8c4fca5aa2eb3", "shasum": "" }, "require": { @@ -296,9 +296,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/39.0.0+bedrock-1.21.90" + "source": "https://github.com/pmmp/BedrockProtocol/tree/39.0.1+bedrock-1.21.90" }, - "time": "2025-06-17T23:46:38+00:00" + "time": "2025-06-23T13:22:50+00:00" }, { "name": "pocketmine/binaryutils", @@ -1681,16 +1681,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.46", + "version": "10.5.47", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" + "reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", - "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", + "reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", "shasum": "" }, "require": { @@ -1762,7 +1762,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.5.46" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.47" }, "funding": [ { @@ -1786,7 +1786,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T06:46:24+00:00" + "time": "2025-06-20T11:29:11+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index e6b323974..ee9effeb0 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.30.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From e41551843598411e256f60e7f42f66f57d42f243 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:31:58 +0000 Subject: [PATCH 235/334] 5.30.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15834488047 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index ee9effeb0..cb4ee32ca 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.30.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.30.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 40a3ee68dd7ca1ba0121a81abb0441a01f288543 Mon Sep 17 00:00:00 2001 From: ItzxDwi <107537435+ItzxDwi@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:35:32 +0800 Subject: [PATCH 236/334] fix typo in changelog (#6745) --- changelogs/5.30.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/5.30.md b/changelogs/5.30.md index 3ecfd285e..cc2ecbc1f 100644 --- a/changelogs/5.30.md +++ b/changelogs/5.30.md @@ -67,7 +67,7 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if Released 23rd June 2025. ## Fixes -- Fixed accidental break of backwards compatability in `EntityExplodeEvent` introduced in the previous release. +- Fixed accidental break of backwards compatibility in `EntityExplodeEvent` introduced in the previous release. - Fixed placement of player holding block when exploding respawn anchor. - Updated BedrockProtocol to fix incorrect encoding of `ServerScriptDebugDrawerPacket`. - Disabled client-side locator bar, allowing plugins to write their own implementations. From 3a5432b3162bab3e3119587a913c56d94555cd98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 00:16:12 +0000 Subject: [PATCH 237/334] Bump build/php from `1549433` to `ce1b095` (#6741) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 154943379..ce1b095a9 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 15494337976e645499e2e3e8c8b491227522be91 +Subproject commit ce1b095a9c6f47dadc7b5812da4e469d52f272bc From 92c3ce7f02c8eb7412f61fb279373bacd10dc6e4 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 26 Jun 2025 00:10:46 +0100 Subject: [PATCH 238/334] Create copilot-setup-steps.yml --- .github/workflows/copilot-setup-steps.yml | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 000000000..a9dc4606c --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,41 @@ +name: "Copilot Agent environment setup" + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: pmmp/setup-php-action@3.2.0 + with: + php-version: ${{ inputs.php }} + install-path: "./bin" + pm-version-major: ${{ inputs.pm-version-major }} + + - name: Restore Composer package cache + uses: actions/cache@v4 + with: + path: | + ~/.cache/composer/files + ~/.cache/composer/vcs + key: "composer-v2-cache-${{ inputs.php }}-${{ hashFiles('./composer.lock') }}" + restore-keys: | + composer-v2-cache- + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-interaction From 3176e7549e887748ceb445be4819065c2de1fee9 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 26 Jun 2025 00:11:26 +0100 Subject: [PATCH 239/334] woops --- .github/workflows/copilot-setup-steps.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index a9dc4606c..b777f4ea0 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -23,9 +23,9 @@ jobs: - name: Setup PHP uses: pmmp/setup-php-action@3.2.0 with: - php-version: ${{ inputs.php }} + php-version: 8.3 install-path: "./bin" - pm-version-major: ${{ inputs.pm-version-major }} + pm-version-major: 5 - name: Restore Composer package cache uses: actions/cache@v4 From 6dbd4282cbe23ce389309639c9b4a4181e96ee77 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 26 Jun 2025 00:12:12 +0100 Subject: [PATCH 240/334] fix cache key --- .github/workflows/copilot-setup-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index b777f4ea0..702860569 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -33,7 +33,7 @@ jobs: path: | ~/.cache/composer/files ~/.cache/composer/vcs - key: "composer-v2-cache-${{ inputs.php }}-${{ hashFiles('./composer.lock') }}" + key: "composer-v2-cache-8.3-${{ hashFiles('./composer.lock') }}" restore-keys: | composer-v2-cache- From 7ea0f2ff43c2edebc694604f3a82b029d9fa51b5 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 26 Jun 2025 00:14:34 +0100 Subject: [PATCH 241/334] copilot-setup-steps: also add extension stubs --- .github/workflows/copilot-setup-steps.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 702860569..ef0b122e1 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -39,3 +39,9 @@ jobs: - name: Install Composer dependencies run: composer install --prefer-dist --no-interaction + + - name: Clone extension stubs + uses: actions/checkout@v4 + with: + repository: pmmp/phpstorm-stubs + path: extension-stubs From f1f6e796a424086647ea8ff647bbaa5f60104950 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 10:28:28 +0000 Subject: [PATCH 242/334] Bump the github-actions group with 2 updates (#6749) --- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release-pr-check.yml | 2 +- .github/workflows/draft-release.yml | 4 ++-- .github/workflows/main.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 93b2978aa..906f227ea 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.33.0 + uses: shivammathur/setup-php@2.34.1 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index 20b2200e6..b2575f9be 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@2.33.0 + uses: shivammathur/setup-php@2.34.1 with: php-version: 8.2 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index ca48ab8e0..014ea531c 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -87,7 +87,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.33.0 + uses: shivammathur/setup-php@2.34.1 with: php-version: ${{ env.PHP_VERSION }} @@ -165,7 +165,7 @@ jobs: ${{ github.workspace }}/core-permissions.rst - name: Create draft release - uses: ncipollo/release-action@v1.16.0 + uses: ncipollo/release-action@v1.18.0 id: create-draft with: artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cabda54be..4d020c10b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.33.0 + uses: shivammathur/setup-php@2.34.1 with: php-version: 8.3 tools: php-cs-fixer:3.75 From 50e15db9ac280f4646f33fc05283190092c53e28 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 8 Jul 2025 13:41:59 +0100 Subject: [PATCH 243/334] Prepare 5.31.0 release (#6752) --- changelogs/5.31.md | 14 +++++++++++++ composer.json | 4 ++-- composer.lock | 26 ++++++++++++------------- src/VersionInfo.php | 4 ++-- src/data/bedrock/item/ItemTypeNames.php | 1 + 5 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 changelogs/5.31.md diff --git a/changelogs/5.31.md b/changelogs/5.31.md new file mode 100644 index 000000000..60e797425 --- /dev/null +++ b/changelogs/5.31.md @@ -0,0 +1,14 @@ +# 5.31.0 +Released 8th July 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.93. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.93. +- Removed support for earlier versions. diff --git a/composer.json b/composer.json index 051550de6..419f29a4a 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "~5.1.0+bedrock-1.21.90", + "pocketmine/bedrock-data": "~5.2.0+bedrock-1.21.93", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~39.0.0+bedrock-1.21.90", + "pocketmine/bedrock-protocol": "~39.1.0+bedrock-1.21.93", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index 2967a967f..9f550b715 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": "9bf4984c23f688264d3ce6a729a6ec17", + "content-hash": "679ab8fc31e55b5170daa34258dc0fd4", "packages": [ { "name": "adhocore/json-comment", @@ -204,16 +204,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "5.1.0+bedrock-1.21.90", + "version": "5.2.0+bedrock-1.21.93", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "89ed34957aeccc63e517aa849af593adae958e98" + "reference": "740e18e490c6a102b774518ff2224a06762bcaf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/89ed34957aeccc63e517aa849af593adae958e98", - "reference": "89ed34957aeccc63e517aa849af593adae958e98", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/740e18e490c6a102b774518ff2224a06762bcaf8", + "reference": "740e18e490c6a102b774518ff2224a06762bcaf8", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.90" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.93" }, - "time": "2025-06-17T23:44:21+00:00" + "time": "2025-07-08T12:30:28+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "39.0.1+bedrock-1.21.90", + "version": "39.1.0+bedrock-1.21.93", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "fd231bad0d94024ff50169dc06e8c4fca5aa2eb3" + "reference": "e9bc5fb691d18dab229a158462c13f0c6fea79c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/fd231bad0d94024ff50169dc06e8c4fca5aa2eb3", - "reference": "fd231bad0d94024ff50169dc06e8c4fca5aa2eb3", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e9bc5fb691d18dab229a158462c13f0c6fea79c8", + "reference": "e9bc5fb691d18dab229a158462c13f0c6fea79c8", "shasum": "" }, "require": { @@ -296,9 +296,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/39.0.1+bedrock-1.21.90" + "source": "https://github.com/pmmp/BedrockProtocol/tree/39.1.0+bedrock-1.21.93" }, - "time": "2025-06-23T13:22:50+00:00" + "time": "2025-07-08T12:31:39+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index cb4ee32ca..499833d34 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.30.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.31.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php index d2ab0996b..3178386ca 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -372,6 +372,7 @@ final class ItemTypeNames{ public const MUSIC_DISC_CREATOR = "minecraft:music_disc_creator"; public const MUSIC_DISC_CREATOR_MUSIC_BOX = "minecraft:music_disc_creator_music_box"; public const MUSIC_DISC_FAR = "minecraft:music_disc_far"; + public const MUSIC_DISC_LAVA_CHICKEN = "minecraft:music_disc_lava_chicken"; public const MUSIC_DISC_MALL = "minecraft:music_disc_mall"; public const MUSIC_DISC_MELLOHI = "minecraft:music_disc_mellohi"; public const MUSIC_DISC_OTHERSIDE = "minecraft:music_disc_otherside"; From a1d74b57109a7c91ffab73718182f3b1e530fae3 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:43:11 +0000 Subject: [PATCH 244/334] 5.31.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/16143550499 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 499833d34..aeb4d9ff8 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.31.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.31.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 4047cbaafe1685211c8a1eaf87f8780bbb7b1ad9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:07:07 +0000 Subject: [PATCH 245/334] Bump phpstan/phpstan-strict-rules in the development-patch-updates group (#6756) --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 9f550b715..5981f64b3 100644 --- a/composer.lock +++ b/composer.lock @@ -1312,16 +1312,16 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a" + "reference": "1f1358da2f8e1317478c63c21beb9918c9821f6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", - "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/1f1358da2f8e1317478c63c21beb9918c9821f6f", + "reference": "1f1358da2f8e1317478c63c21beb9918c9821f6f", "shasum": "" }, "require": { @@ -1354,9 +1354,9 @@ "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/2.0.4" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.5" }, - "time": "2025-03-18T11:42:40+00:00" + "time": "2025-07-17T12:01:44+00:00" }, { "name": "phpunit/php-code-coverage", From d41f1b288978109c69a58924baae3e0d93c3a534 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 25 Jul 2025 18:01:02 +0100 Subject: [PATCH 246/334] World: avoid hammering the disk looking for known ungenerated chunks closes #6679 judging by the debug logs, this actually happens a lot during initial world generation, which I suppose isn't that surprising. --- src/world/World.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/world/World.php b/src/world/World.php index ecec3d3b9..8602c5803 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -289,6 +289,12 @@ class World implements ChunkManager{ */ private array $chunks = []; + /** + * @var true[] + * @phpstan-var array + */ + private array $knownUngeneratedChunks = []; + /** * @var Vector3[][] chunkHash => [relativeBlockHash => Vector3] * @phpstan-var array> @@ -625,6 +631,7 @@ class World implements ChunkManager{ self::getXZ($chunkHash, $chunkX, $chunkZ); $this->unloadChunk($chunkX, $chunkZ, false); } + $this->knownUngeneratedChunks = []; foreach($this->entitiesByChunk as $chunkHash => $entities){ self::getXZ($chunkHash, $chunkX, $chunkZ); @@ -2667,6 +2674,7 @@ class World implements ChunkManager{ } $this->chunks[$chunkHash] = $chunk; + unset($this->knownUngeneratedChunks[$chunkHash]); $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); @@ -2931,6 +2939,9 @@ class World implements ChunkManager{ if(isset($this->chunks[$chunkHash = World::chunkHash($x, $z)])){ return $this->chunks[$chunkHash]; } + if(isset($this->knownUngeneratedChunks[$chunkHash])){ + return null; + } $this->timings->syncChunkLoad->startTiming(); @@ -2950,6 +2961,7 @@ class World implements ChunkManager{ if($loadedChunkData === null){ $this->timings->syncChunkLoad->stopTiming(); + $this->knownUngeneratedChunks[$chunkHash] = true; return null; } From 866df55edf17d845fff362ff351e055e5d9b8fc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:25:19 +0000 Subject: [PATCH 247/334] Bump ramsey/uuid from 4.8.1 to 4.9.0 (#6748) --- composer.json | 2 +- composer.lock | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 419f29a4a..ce4994d4f 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "pocketmine/raklib": "~1.2.0", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", - "ramsey/uuid": "~4.8.0", + "ramsey/uuid": "~4.9.0", "symfony/filesystem": "~6.4.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 5981f64b3..0180fd0a6 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": "679ab8fc31e55b5170daa34258dc0fd4", + "content-hash": "4dc5ea726d881d8c52d1b5299485d940", "packages": [ { "name": "adhocore/json-comment", @@ -818,21 +818,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -891,9 +890,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "symfony/filesystem", @@ -2757,7 +2756,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -2788,9 +2787,9 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From 40ea6dd30d9f5c874be39c2f8a6f5d19755268c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:26:25 +0000 Subject: [PATCH 248/334] Bump phpstan/phpstan-strict-rules in the development-patch-updates group (#6758) --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 0180fd0a6..2e5e83a94 100644 --- a/composer.lock +++ b/composer.lock @@ -1311,16 +1311,16 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "1f1358da2f8e1317478c63c21beb9918c9821f6f" + "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/1f1358da2f8e1317478c63c21beb9918c9821f6f", - "reference": "1f1358da2f8e1317478c63c21beb9918c9821f6f", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094", + "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094", "shasum": "" }, "require": { @@ -1353,9 +1353,9 @@ "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/2.0.5" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6" }, - "time": "2025-07-17T12:01:44+00:00" + "time": "2025-07-21T12:19:29+00:00" }, { "name": "phpunit/php-code-coverage", From a83c62a4a2e5eea17fdfc3eb2bfd55f31534a9e3 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 30 Jul 2025 19:08:29 +0100 Subject: [PATCH 249/334] readme: Stop showing random PR statuses on CI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f2b715ab..98f569346 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

- CI + CI GitHub release (latest SemVer) Discord
From cc17e68072c1bcadefcab3c7ddb42cdb27be0dad Mon Sep 17 00:00:00 2001 From: Hugo_ <55756021+Dhaiven@users.noreply.github.com> Date: Thu, 31 Jul 2025 08:48:47 +0200 Subject: [PATCH 250/334] Add blocks interfaces for commons properties (#6639) --- src/block/ActivatorRail.php | 3 +- src/block/AmethystCluster.php | 3 +- src/block/Anvil.php | 3 +- src/block/Barrel.php | 3 +- src/block/BaseBanner.php | 3 +- src/block/BaseBigDripleaf.php | 3 +- src/block/BaseCoral.php | 3 +- src/block/BaseSign.php | 3 +- src/block/Bed.php | 4 +- src/block/Bell.php | 3 +- src/block/BoneBlock.php | 3 +- src/block/Button.php | 3 +- src/block/Cactus.php | 3 +- src/block/CakeWithCandle.php | 3 +- src/block/CakeWithDyedCandle.php | 3 +- src/block/Campfire.php | 4 +- src/block/Candle.php | 3 +- src/block/Carpet.php | 3 +- src/block/CarvedPumpkin.php | 3 +- src/block/CaveVines.php | 3 +- src/block/Chain.php | 3 +- src/block/ChemistryTable.php | 3 +- src/block/Chest.php | 3 +- src/block/ChiseledBookshelf.php | 3 +- src/block/ChorusFlower.php | 3 +- src/block/CocoaBlock.php | 4 +- src/block/Concrete.php | 3 +- src/block/ConcretePowder.php | 3 +- src/block/CopperBulb.php | 4 +- src/block/CoralBlock.php | 3 +- src/block/Crops.php | 3 +- src/block/DaylightSensor.php | 3 +- src/block/Door.php | 3 +- src/block/DoublePitcherCrop.php | 3 +- src/block/DyedCandle.php | 3 +- src/block/DyedShulkerBox.php | 3 +- src/block/EndPortalFrame.php | 3 +- src/block/EndRod.php | 3 +- src/block/EnderChest.php | 3 +- src/block/FenceGate.php | 4 +- src/block/Fire.php | 3 +- src/block/FloorBanner.php | 3 +- src/block/FloorSign.php | 3 +- src/block/FrostedIce.php | 3 +- src/block/Furnace.php | 3 +- src/block/GlazedTerracotta.php | 4 +- src/block/GlowLichen.php | 3 +- src/block/HayBale.php | 3 +- src/block/Hopper.php | 3 +- src/block/ItemFrame.php | 3 +- src/block/Ladder.php | 3 +- src/block/Lectern.php | 3 +- src/block/LightningRod.php | 3 +- src/block/Loom.php | 3 +- src/block/NetherVines.php | 3 +- src/block/NetherWartPlant.php | 3 +- src/block/PinkPetals.php | 3 +- src/block/PitcherCrop.php | 3 +- src/block/Planks.php | 3 +- src/block/PoweredRail.php | 3 +- src/block/RedstoneComparator.php | 5 +- src/block/RedstoneLamp.php | 3 +- src/block/RedstoneOre.php | 3 +- src/block/RedstoneRepeater.php | 4 +- src/block/RedstoneTorch.php | 3 +- src/block/RedstoneWire.php | 3 +- src/block/ResinClump.php | 3 +- src/block/ShulkerBox.php | 3 +- src/block/SimplePillar.php | 3 +- src/block/SmallDripleaf.php | 3 +- src/block/StainedGlass.php | 3 +- src/block/StainedGlassPane.php | 3 +- src/block/StainedHardenedClay.php | 3 +- src/block/StainedHardenedGlass.php | 3 +- src/block/StainedHardenedGlassPane.php | 3 +- src/block/Stair.php | 3 +- src/block/Stonecutter.php | 3 +- src/block/Sugarcane.php | 3 +- src/block/SweetBerryBush.php | 3 +- src/block/Trapdoor.php | 3 +- src/block/TripwireHook.php | 3 +- src/block/WallBanner.php | 3 +- src/block/WallCoralFan.php | 3 +- src/block/WallSign.php | 3 +- src/block/WeightedPressurePlate.php | 3 +- src/block/Wood.php | 4 +- src/block/WoodenButton.php | 3 +- src/block/WoodenDoor.php | 3 +- src/block/WoodenFence.php | 3 +- src/block/WoodenPressurePlate.php | 3 +- src/block/WoodenSlab.php | 3 +- src/block/WoodenStairs.php | 3 +- src/block/WoodenTrapdoor.php | 3 +- src/block/Wool.php | 3 +- src/block/utils/Ageable.php | 34 ++++++++++++ .../utils/AnalogRedstoneSignalEmitter.php | 34 ++++++++++++ src/block/utils/AnyFacing.php | 41 ++++++++++++++ src/block/utils/Colored.php | 34 ++++++++++++ src/block/utils/CoralMaterial.php | 41 ++++++++++++++ src/block/utils/HorizontalFacing.php | 41 ++++++++++++++ src/block/utils/Lightable.php | 34 ++++++++++++ src/block/utils/MultiFacing.php | 54 +++++++++++++++++++ src/block/utils/PillarRotation.php | 39 ++++++++++++++ src/block/utils/PoweredByRedstone.php | 34 ++++++++++++ src/block/utils/SignLikeRotation.php | 39 ++++++++++++++ src/block/utils/WoodMaterial.php | 29 ++++++++++ 106 files changed, 652 insertions(+), 94 deletions(-) create mode 100644 src/block/utils/Ageable.php create mode 100644 src/block/utils/AnalogRedstoneSignalEmitter.php create mode 100644 src/block/utils/AnyFacing.php create mode 100644 src/block/utils/Colored.php create mode 100644 src/block/utils/CoralMaterial.php create mode 100644 src/block/utils/HorizontalFacing.php create mode 100644 src/block/utils/Lightable.php create mode 100644 src/block/utils/MultiFacing.php create mode 100644 src/block/utils/PillarRotation.php create mode 100644 src/block/utils/PoweredByRedstone.php create mode 100644 src/block/utils/SignLikeRotation.php create mode 100644 src/block/utils/WoodMaterial.php diff --git a/src/block/ActivatorRail.php b/src/block/ActivatorRail.php index dcd0ef93b..da15eb1e8 100644 --- a/src/block/ActivatorRail.php +++ b/src/block/ActivatorRail.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\RailPoweredByRedstoneTrait; -class ActivatorRail extends StraightOnlyRail{ +class ActivatorRail extends StraightOnlyRail implements PoweredByRedstone{ use RailPoweredByRedstoneTrait; //TODO diff --git a/src/block/AmethystCluster.php b/src/block/AmethystCluster.php index 639490456..8a750e974 100644 --- a/src/block/AmethystCluster.php +++ b/src/block/AmethystCluster.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\AmethystTrait; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\FortuneDropHelper; use pocketmine\block\utils\SupportType; @@ -38,7 +39,7 @@ use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; -final class AmethystCluster extends Transparent{ +final class AmethystCluster extends Transparent implements AnyFacing{ use AmethystTrait; use AnyFacingTrait; diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 2c48f9a7c..fcb4d045c 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -26,6 +26,7 @@ namespace pocketmine\block; use pocketmine\block\inventory\AnvilInventory; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -41,7 +42,7 @@ use pocketmine\world\sound\AnvilFallSound; use pocketmine\world\sound\Sound; use function round; -class Anvil extends Transparent implements Fallable{ +class Anvil extends Transparent implements Fallable, HorizontalFacing{ use FallableTrait; use HorizontalFacingTrait; diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 0f0499ab9..7b2ea356e 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\tile\Barrel as TileBarrel; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -33,7 +34,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use function abs; -class Barrel extends Opaque{ +class Barrel extends Opaque implements AnyFacing{ use AnyFacingTrait; protected bool $open = false; diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index b56323453..376f1f9dc 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Banner as TileBanner; use pocketmine\block\utils\BannerPatternLayer; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Banner as ItemBanner; @@ -36,7 +37,7 @@ use pocketmine\world\BlockTransaction; use function assert; use function count; -abstract class BaseBanner extends Transparent{ +abstract class BaseBanner extends Transparent implements Colored{ use ColoredTrait; /** diff --git a/src/block/BaseBigDripleaf.php b/src/block/BaseBigDripleaf.php index f0ff59cf0..94e2c12a2 100644 --- a/src/block/BaseBigDripleaf.php +++ b/src/block/BaseBigDripleaf.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\event\block\StructureGrowEvent; @@ -33,7 +34,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -abstract class BaseBigDripleaf extends Transparent{ +abstract class BaseBigDripleaf extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; abstract protected function isHead() : bool; diff --git a/src/block/BaseCoral.php b/src/block/BaseCoral.php index b9c595a97..c1cc9bb25 100644 --- a/src/block/BaseCoral.php +++ b/src/block/BaseCoral.php @@ -24,12 +24,13 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\CoralMaterial; use pocketmine\block\utils\CoralTypeTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use function mt_rand; -abstract class BaseCoral extends Transparent{ +abstract class BaseCoral extends Transparent implements CoralMaterial{ use CoralTypeTrait; public function onNearbyBlockChange() : void{ diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 0f5d77d58..0efaa603c 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -27,6 +27,7 @@ use pocketmine\block\tile\Sign as TileSign; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\SignText; use pocketmine\block\utils\SupportType; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodType; use pocketmine\block\utils\WoodTypeTrait; use pocketmine\color\Color; @@ -44,7 +45,7 @@ use function array_map; use function assert; use function strlen; -abstract class BaseSign extends Transparent{ +abstract class BaseSign extends Transparent implements WoodMaterial{ use WoodTypeTrait; protected SignText $text; diff --git a/src/block/Bed.php b/src/block/Bed.php index 133c4a9cc..21bd3a172 100644 --- a/src/block/Bed.php +++ b/src/block/Bed.php @@ -24,8 +24,10 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\tile\Bed as TileBed; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\DyeColor; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -41,7 +43,7 @@ use pocketmine\utils\TextFormat; use pocketmine\world\BlockTransaction; use pocketmine\world\World; -class Bed extends Transparent{ +class Bed extends Transparent implements Colored, HorizontalFacing{ use ColoredTrait; use HorizontalFacingTrait; diff --git a/src/block/Bell.php b/src/block/Bell.php index 53a6fc7fb..258abc628 100644 --- a/src/block/Bell.php +++ b/src/block/Bell.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Bell as TileBell; use pocketmine\block\utils\BellAttachmentType; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -38,7 +39,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\BellRingSound; -final class Bell extends Transparent{ +final class Bell extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; private BellAttachmentType $attachmentType = BellAttachmentType::FLOOR; diff --git a/src/block/BoneBlock.php b/src/block/BoneBlock.php index 247bdb748..465d96033 100644 --- a/src/block/BoneBlock.php +++ b/src/block/BoneBlock.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\PillarRotationTrait; -class BoneBlock extends Opaque{ +class BoneBlock extends Opaque implements PillarRotation{ use PillarRotationTrait; } diff --git a/src/block/Button.php b/src/block/Button.php index 73bd1d556..4d1f1bbc5 100644 --- a/src/block/Button.php +++ b/src/block/Button.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -33,7 +34,7 @@ use pocketmine\world\BlockTransaction; use pocketmine\world\sound\RedstonePowerOffSound; use pocketmine\world\sound\RedstonePowerOnSound; -abstract class Button extends Flowable{ +abstract class Button extends Flowable implements AnyFacing{ use AnyFacingTrait; protected bool $pressed = false; diff --git a/src/block/Cactus.php b/src/block/Cactus.php index 67b15b946..51c98786b 100644 --- a/src/block/Cactus.php +++ b/src/block/Cactus.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\StaticSupportTrait; @@ -33,7 +34,7 @@ use pocketmine\event\entity\EntityDamageEvent; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; -class Cactus extends Transparent{ +class Cactus extends Transparent implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php index 546843d6c..1462776c2 100644 --- a/src/block/CakeWithCandle.php +++ b/src/block/CakeWithCandle.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\CandleTrait; +use pocketmine\block\utils\Lightable; use pocketmine\entity\Living; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -31,7 +32,7 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; -class CakeWithCandle extends BaseCake{ +class CakeWithCandle extends BaseCake implements Lightable{ use CandleTrait { onInteract as onInteractCandle; } diff --git a/src/block/CakeWithDyedCandle.php b/src/block/CakeWithDyedCandle.php index 0dff358e1..04ab0c6eb 100644 --- a/src/block/CakeWithDyedCandle.php +++ b/src/block/CakeWithDyedCandle.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\DyeColor; -class CakeWithDyedCandle extends CakeWithCandle{ +class CakeWithDyedCandle extends CakeWithCandle implements Colored{ use ColoredTrait; public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ diff --git a/src/block/Campfire.php b/src/block/Campfire.php index 9f4c42a9c..edfc87ecb 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -25,7 +25,9 @@ namespace pocketmine\block; use pocketmine\block\inventory\CampfireInventory; use pocketmine\block\tile\Campfire as TileCampfire; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\LightableTrait; use pocketmine\block\utils\SupportType; use pocketmine\crafting\FurnaceRecipe; @@ -59,7 +61,7 @@ use function count; use function min; use function mt_rand; -class Campfire extends Transparent{ +class Campfire extends Transparent implements Lightable, HorizontalFacing{ use HorizontalFacingTrait{ HorizontalFacingTrait::describeBlockOnlyState as encodeFacingState; } diff --git a/src/block/Candle.php b/src/block/Candle.php index 7f22641e1..977f13a09 100644 --- a/src/block/Candle.php +++ b/src/block/Candle.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\CandleTrait; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -35,7 +36,7 @@ use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; -class Candle extends Transparent{ +class Candle extends Transparent implements Lightable{ use CandleTrait { describeBlockOnlyState as encodeLitState; getLightLevel as getBaseLightLevel; diff --git a/src/block/Carpet.php b/src/block/Carpet.php index 2d8e7ea47..fd8b42a09 100644 --- a/src/block/Carpet.php +++ b/src/block/Carpet.php @@ -23,12 +23,13 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; -class Carpet extends Flowable{ +class Carpet extends Flowable implements Colored{ use ColoredTrait; use StaticSupportTrait; diff --git a/src/block/CarvedPumpkin.php b/src/block/CarvedPumpkin.php index 98f3c2307..cfdb8d49d 100644 --- a/src/block/CarvedPumpkin.php +++ b/src/block/CarvedPumpkin.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; -class CarvedPumpkin extends Opaque{ +class CarvedPumpkin extends Opaque implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; } diff --git a/src/block/CaveVines.php b/src/block/CaveVines.php index daa973507..84d7d3169 100644 --- a/src/block/CaveVines.php +++ b/src/block/CaveVines.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\StaticSupportTrait; @@ -39,7 +40,7 @@ use pocketmine\world\BlockTransaction; use pocketmine\world\sound\GlowBerriesPickSound; use function mt_rand; -class CaveVines extends Flowable{ +class CaveVines extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/Chain.php b/src/block/Chain.php index e9cc2c9be..5ec3b1e2a 100644 --- a/src/block/Chain.php +++ b/src/block/Chain.php @@ -23,13 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\PillarRotationTrait; use pocketmine\block\utils\SupportType; use pocketmine\math\Axis; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; -final class Chain extends Transparent{ +final class Chain extends Transparent implements PillarRotation{ use PillarRotationTrait; public function getSupportType(int $facing) : SupportType{ diff --git a/src/block/ChemistryTable.php b/src/block/ChemistryTable.php index 058e40288..9b5e78f92 100644 --- a/src/block/ChemistryTable.php +++ b/src/block/ChemistryTable.php @@ -24,11 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; -final class ChemistryTable extends Opaque{ +final class ChemistryTable extends Opaque implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ diff --git a/src/block/Chest.php b/src/block/Chest.php index 7d2650007..c0dc09d9e 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\SupportType; use pocketmine\event\block\ChestPairEvent; use pocketmine\item\Item; @@ -33,7 +34,7 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; -class Chest extends Transparent{ +class Chest extends Transparent implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; protected function recalculateCollisionBoxes() : array{ diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php index 73c4861bf..be49c7a91 100644 --- a/src/block/ChiseledBookshelf.php +++ b/src/block/ChiseledBookshelf.php @@ -26,6 +26,7 @@ namespace pocketmine\block; use pocketmine\block\tile\ChiseledBookshelf as TileChiseledBookshelf; use pocketmine\block\utils\ChiseledBookshelfSlot; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Book; @@ -38,7 +39,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use function spl_object_id; -class ChiseledBookshelf extends Opaque{ +class ChiseledBookshelf extends Opaque implements HorizontalFacing{ use HorizontalFacingTrait; use FacesOppositePlacingPlayerTrait; diff --git a/src/block/ChorusFlower.php b/src/block/ChorusFlower.php index cc3c606d9..ef48d5a89 100644 --- a/src/block/ChorusFlower.php +++ b/src/block/ChorusFlower.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\entity\projectile\Projectile; @@ -40,7 +41,7 @@ use function array_rand; use function min; use function mt_rand; -final class ChorusFlower extends Flowable{ +final class ChorusFlower extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/CocoaBlock.php b/src/block/CocoaBlock.php index 83e1de34b..ae09ccb0a 100644 --- a/src/block/CocoaBlock.php +++ b/src/block/CocoaBlock.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\WoodType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -39,7 +41,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use function mt_rand; -class CocoaBlock extends Flowable{ +class CocoaBlock extends Flowable implements Ageable, HorizontalFacing{ use HorizontalFacingTrait; use AgeableTrait; diff --git a/src/block/Concrete.php b/src/block/Concrete.php index fae6f8e2f..6cad11193 100644 --- a/src/block/Concrete.php +++ b/src/block/Concrete.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -class Concrete extends Opaque{ +class Concrete extends Opaque implements Colored{ use ColoredTrait; } diff --git a/src/block/ConcretePowder.php b/src/block/ConcretePowder.php index 59f14bc72..89b53053b 100644 --- a/src/block/ConcretePowder.php +++ b/src/block/ConcretePowder.php @@ -24,12 +24,13 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; use pocketmine\math\Facing; -class ConcretePowder extends Opaque implements Fallable{ +class ConcretePowder extends Opaque implements Fallable, Colored{ use ColoredTrait; use FallableTrait { onNearbyBlockChange as protected startFalling; diff --git a/src/block/CopperBulb.php b/src/block/CopperBulb.php index 97fc209fe..d0bdedf3c 100644 --- a/src/block/CopperBulb.php +++ b/src/block/CopperBulb.php @@ -26,11 +26,13 @@ namespace pocketmine\block; use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\CopperTrait; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\LightableTrait; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\data\runtime\RuntimeDataDescriber; -class CopperBulb extends Opaque implements CopperMaterial{ +class CopperBulb extends Opaque implements CopperMaterial, Lightable, PoweredByRedstone{ use CopperTrait; use PoweredByRedstoneTrait; use LightableTrait{ diff --git a/src/block/CoralBlock.php b/src/block/CoralBlock.php index 3e7ca8224..c21209998 100644 --- a/src/block/CoralBlock.php +++ b/src/block/CoralBlock.php @@ -24,11 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\CoralMaterial; use pocketmine\block\utils\CoralTypeTrait; use pocketmine\item\Item; use function mt_rand; -final class CoralBlock extends Opaque{ +final class CoralBlock extends Opaque implements CoralMaterial{ use CoralTypeTrait; public function onNearbyBlockChange() : void{ diff --git a/src/block/Crops.php b/src/block/Crops.php index e90ac6236..b0488d150 100644 --- a/src/block/Crops.php +++ b/src/block/Crops.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\CropGrowthHelper; @@ -34,7 +35,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use function mt_rand; -abstract class Crops extends Flowable{ +abstract class Crops extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/DaylightSensor.php b/src/block/DaylightSensor.php index 5720af529..ed56d2f44 100644 --- a/src/block/DaylightSensor.php +++ b/src/block/DaylightSensor.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\AnalogRedstoneSignalEmitter; use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -36,7 +37,7 @@ use function max; use function round; use const M_PI; -class DaylightSensor extends Transparent{ +class DaylightSensor extends Transparent implements AnalogRedstoneSignalEmitter{ use AnalogRedstoneSignalEmitterTrait; protected bool $inverted = false; diff --git a/src/block/Door.php b/src/block/Door.php index fa88267e1..2ff53645c 100644 --- a/src/block/Door.php +++ b/src/block/Door.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -34,7 +35,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\DoorSound; -class Door extends Transparent{ +class Door extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; protected bool $top = false; diff --git a/src/block/DoublePitcherCrop.php b/src/block/DoublePitcherCrop.php index 1233ed05d..89d9e98b5 100644 --- a/src/block/DoublePitcherCrop.php +++ b/src/block/DoublePitcherCrop.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\CropGrowthHelper; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -37,7 +38,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class DoublePitcherCrop extends DoublePlant{ +final class DoublePitcherCrop extends DoublePlant implements Ageable{ use AgeableTrait { describeBlockOnlyState as describeAge; } diff --git a/src/block/DyedCandle.php b/src/block/DyedCandle.php index a495e8d00..57a8b5923 100644 --- a/src/block/DyedCandle.php +++ b/src/block/DyedCandle.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -class DyedCandle extends Candle{ +class DyedCandle extends Candle implements Colored{ use ColoredTrait; protected function getCandleIfCompatibleType(Block $block) : ?Candle{ diff --git a/src/block/DyedShulkerBox.php b/src/block/DyedShulkerBox.php index 5eae9237e..8be2718a2 100644 --- a/src/block/DyedShulkerBox.php +++ b/src/block/DyedShulkerBox.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -final class DyedShulkerBox extends ShulkerBox{ +final class DyedShulkerBox extends ShulkerBox implements Colored{ use ColoredTrait; } diff --git a/src/block/EndPortalFrame.php b/src/block/EndPortalFrame.php index ed5b77433..6c4865fdd 100644 --- a/src/block/EndPortalFrame.php +++ b/src/block/EndPortalFrame.php @@ -24,11 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; -class EndPortalFrame extends Opaque{ +class EndPortalFrame extends Opaque implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; protected bool $eye = false; diff --git a/src/block/EndRod.php b/src/block/EndRod.php index a6770f370..e3d439f6a 100644 --- a/src/block/EndRod.php +++ b/src/block/EndRod.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\item\Item; use pocketmine\math\Axis; @@ -32,7 +33,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class EndRod extends Flowable{ +class EndRod extends Flowable implements AnyFacing{ use AnyFacingTrait; public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 6a8cf108c..5dec4f3af 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -26,6 +26,7 @@ namespace pocketmine\block; use pocketmine\block\inventory\EnderChestInventory; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -33,7 +34,7 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; -class EnderChest extends Transparent{ +class EnderChest extends Transparent implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; public function getLightLevel() : int{ diff --git a/src/block/FenceGate.php b/src/block/FenceGate.php index 2bbfdf892..d8cdfe4e2 100644 --- a/src/block/FenceGate.php +++ b/src/block/FenceGate.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -35,7 +37,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\DoorSound; -class FenceGate extends Transparent{ +class FenceGate extends Transparent implements HorizontalFacing, WoodMaterial{ use WoodTypeTrait; use HorizontalFacingTrait; diff --git a/src/block/Fire.php b/src/block/Fire.php index 35a7a696c..62809fb9d 100644 --- a/src/block/Fire.php +++ b/src/block/Fire.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\SupportType; @@ -35,7 +36,7 @@ use function max; use function min; use function mt_rand; -class Fire extends BaseFire{ +class Fire extends BaseFire implements Ageable{ use AgeableTrait; public const MAX_AGE = 15; diff --git a/src/block/FloorBanner.php b/src/block/FloorBanner.php index 73bc45787..ba089b6c0 100644 --- a/src/block/FloorBanner.php +++ b/src/block/FloorBanner.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\SignLikeRotation; use pocketmine\block\utils\SignLikeRotationTrait; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -30,7 +31,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class FloorBanner extends BaseBanner{ +final class FloorBanner extends BaseBanner implements SignLikeRotation{ use SignLikeRotationTrait; protected function getSupportingFace() : int{ diff --git a/src/block/FloorSign.php b/src/block/FloorSign.php index 5615d15d8..94e51ffe8 100644 --- a/src/block/FloorSign.php +++ b/src/block/FloorSign.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\SignLikeRotation; use pocketmine\block\utils\SignLikeRotationTrait; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -30,7 +31,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class FloorSign extends BaseSign{ +final class FloorSign extends BaseSign implements SignLikeRotation{ use SignLikeRotationTrait; protected function getSupportingFace() : int{ diff --git a/src/block/FrostedIce.php b/src/block/FrostedIce.php index 3e8592306..046d75811 100644 --- a/src/block/FrostedIce.php +++ b/src/block/FrostedIce.php @@ -23,11 +23,12 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use function mt_rand; -class FrostedIce extends Ice{ +class FrostedIce extends Ice implements Ageable{ use AgeableTrait; public const MAX_AGE = 3; diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 7a64e3cd3..54480e62c 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\LightableTrait; use pocketmine\crafting\FurnaceType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -33,7 +34,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use function mt_rand; -class Furnace extends Opaque{ +class Furnace extends Opaque implements Lightable{ use FacesOppositePlacingPlayerTrait; use LightableTrait; diff --git a/src/block/GlazedTerracotta.php b/src/block/GlazedTerracotta.php index 15b3254e5..ccb928875 100644 --- a/src/block/GlazedTerracotta.php +++ b/src/block/GlazedTerracotta.php @@ -23,10 +23,12 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; -class GlazedTerracotta extends Opaque{ +class GlazedTerracotta extends Opaque implements Colored, HorizontalFacing{ use ColoredTrait; use FacesOppositePlacingPlayerTrait; } diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index a44c4d035..6ad8d3748 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\MultiAnySupportTrait; +use pocketmine\block\utils\MultiFacing; use pocketmine\block\utils\SupportType; use pocketmine\item\Fertilizer; use pocketmine\item\Item; @@ -35,7 +36,7 @@ use pocketmine\world\World; use function count; use function shuffle; -class GlowLichen extends Transparent{ +class GlowLichen extends Transparent implements MultiFacing{ use MultiAnySupportTrait; public function getLightLevel() : int{ diff --git a/src/block/HayBale.php b/src/block/HayBale.php index 6fdd2cb63..dacfe92fa 100644 --- a/src/block/HayBale.php +++ b/src/block/HayBale.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\PillarRotationTrait; use pocketmine\entity\Entity; -class HayBale extends Opaque{ +class HayBale extends Opaque implements PillarRotation{ use PillarRotationTrait; public function getFlameEncouragement() : int{ diff --git a/src/block/Hopper.php b/src/block/Hopper.php index 0d823674b..4956b668f 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\tile\Hopper as TileHopper; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -34,7 +35,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class Hopper extends Transparent{ +class Hopper extends Transparent implements PoweredByRedstone{ use PoweredByRedstoneTrait; private int $facing = Facing::DOWN; diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php index c03806a3b..0fda77758 100644 --- a/src/block/ItemFrame.php +++ b/src/block/ItemFrame.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\tile\ItemFrame as TileItemFrame; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -39,7 +40,7 @@ use pocketmine\world\sound\ItemFrameRotateItemSound; use function is_infinite; use function is_nan; -class ItemFrame extends Flowable{ +class ItemFrame extends Flowable implements AnyFacing{ use AnyFacingTrait; public const ROTATIONS = 8; diff --git a/src/block/Ladder.php b/src/block/Ladder.php index 09c0b8f6b..6edaebbf2 100644 --- a/src/block/Ladder.php +++ b/src/block/Ladder.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\entity\Entity; @@ -35,7 +36,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class Ladder extends Transparent{ +class Ladder extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; public function hasEntityCollision() : bool{ diff --git a/src/block/Lectern.php b/src/block/Lectern.php index 03880b3c5..9ba01c1c5 100644 --- a/src/block/Lectern.php +++ b/src/block/Lectern.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Lectern as TileLectern; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -36,7 +37,7 @@ use pocketmine\player\Player; use pocketmine\world\sound\LecternPlaceBookSound; use function count; -class Lectern extends Transparent{ +class Lectern extends Transparent implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; protected int $viewedPage = 0; diff --git a/src/block/LightningRod.php b/src/block/LightningRod.php index a0dd50542..9c1971229 100644 --- a/src/block/LightningRod.php +++ b/src/block/LightningRod.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\item\Item; use pocketmine\math\Axis; @@ -32,7 +33,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class LightningRod extends Transparent{ +final class LightningRod extends Transparent implements AnyFacing{ use AnyFacingTrait; protected function recalculateCollisionBoxes() : array{ diff --git a/src/block/Loom.php b/src/block/Loom.php index d3dd4f3c7..a2b9fc235 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -25,11 +25,12 @@ namespace pocketmine\block; use pocketmine\block\inventory\LoomInventory; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; -final class Loom extends Opaque{ +final class Loom extends Opaque implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ diff --git a/src/block/NetherVines.php b/src/block/NetherVines.php index e8729c00f..67a0b6f94 100644 --- a/src/block/NetherVines.php +++ b/src/block/NetherVines.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\FortuneDropHelper; use pocketmine\block\utils\StaticSupportTrait; @@ -41,7 +42,7 @@ use function mt_rand; /** * This class is used for Weeping & Twisting vines, because they have same behaviour */ -class NetherVines extends Flowable{ +class NetherVines extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/NetherWartPlant.php b/src/block/NetherWartPlant.php index 34e6fd57e..df0609754 100644 --- a/src/block/NetherWartPlant.php +++ b/src/block/NetherWartPlant.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\FortuneDropHelper; @@ -31,7 +32,7 @@ use pocketmine\item\Item; use pocketmine\math\Facing; use function mt_rand; -class NetherWartPlant extends Flowable{ +class NetherWartPlant extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/PinkPetals.php b/src/block/PinkPetals.php index 17bc4c50a..b8b6e248c 100644 --- a/src/block/PinkPetals.php +++ b/src/block/PinkPetals.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -34,7 +35,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class PinkPetals extends Flowable{ +class PinkPetals extends Flowable implements HorizontalFacing{ use HorizontalFacingTrait; use StaticSupportTrait { canBePlacedAt as supportedWhenPlacedAt; diff --git a/src/block/PitcherCrop.php b/src/block/PitcherCrop.php index d41aed284..1c771bb8a 100644 --- a/src/block/PitcherCrop.php +++ b/src/block/PitcherCrop.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\CropGrowthHelper; @@ -38,7 +39,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class PitcherCrop extends Flowable{ +final class PitcherCrop extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/Planks.php b/src/block/Planks.php index 1074f8adf..ad7956361 100644 --- a/src/block/Planks.php +++ b/src/block/Planks.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class Planks extends Opaque{ +class Planks extends Opaque implements WoodMaterial{ use WoodTypeTrait; public function getFuelTime() : int{ diff --git a/src/block/PoweredRail.php b/src/block/PoweredRail.php index 25d6dc9e3..321b19a46 100644 --- a/src/block/PoweredRail.php +++ b/src/block/PoweredRail.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\RailPoweredByRedstoneTrait; -class PoweredRail extends StraightOnlyRail{ +class PoweredRail extends StraightOnlyRail implements PoweredByRedstone{ use RailPoweredByRedstoneTrait; } diff --git a/src/block/RedstoneComparator.php b/src/block/RedstoneComparator.php index 40e1ef510..88050b85a 100644 --- a/src/block/RedstoneComparator.php +++ b/src/block/RedstoneComparator.php @@ -24,8 +24,11 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\tile\Comparator; +use pocketmine\block\utils\AnalogRedstoneSignalEmitter; use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\block\utils\SupportType; @@ -38,7 +41,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use function assert; -class RedstoneComparator extends Flowable{ +class RedstoneComparator extends Flowable implements AnalogRedstoneSignalEmitter, PoweredByRedstone, HorizontalFacing{ use HorizontalFacingTrait; use AnalogRedstoneSignalEmitterTrait; use PoweredByRedstoneTrait; diff --git a/src/block/RedstoneLamp.php b/src/block/RedstoneLamp.php index 58098c395..33a97801d 100644 --- a/src/block/RedstoneLamp.php +++ b/src/block/RedstoneLamp.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\data\runtime\RuntimeDataDescriber; -class RedstoneLamp extends Opaque{ +class RedstoneLamp extends Opaque implements PoweredByRedstone{ use PoweredByRedstoneTrait; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ diff --git a/src/block/RedstoneOre.php b/src/block/RedstoneOre.php index 10e701a6f..3477a3519 100644 --- a/src/block/RedstoneOre.php +++ b/src/block/RedstoneOre.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FortuneDropHelper; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\LightableTrait; use pocketmine\item\Item; use pocketmine\item\VanillaItems; @@ -31,7 +32,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use function mt_rand; -class RedstoneOre extends Opaque{ +class RedstoneOre extends Opaque implements Lightable{ use LightableTrait; public function getLightLevel() : int{ diff --git a/src/block/RedstoneRepeater.php b/src/block/RedstoneRepeater.php index bf9d0c5da..4059ff1cc 100644 --- a/src/block/RedstoneRepeater.php +++ b/src/block/RedstoneRepeater.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; +use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\block\utils\SupportType; @@ -35,7 +37,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class RedstoneRepeater extends Flowable{ +class RedstoneRepeater extends Flowable implements PoweredByRedstone, HorizontalFacing{ use HorizontalFacingTrait; use PoweredByRedstoneTrait; use StaticSupportTrait; diff --git a/src/block/RedstoneTorch.php b/src/block/RedstoneTorch.php index 26c86038b..f73076ccc 100644 --- a/src/block/RedstoneTorch.php +++ b/src/block/RedstoneTorch.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\LightableTrait; use pocketmine\data\runtime\RuntimeDataDescriber; -class RedstoneTorch extends Torch{ +class RedstoneTorch extends Torch implements Lightable{ use LightableTrait; public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ diff --git a/src/block/RedstoneWire.php b/src/block/RedstoneWire.php index a2d293fca..6be27bcb4 100644 --- a/src/block/RedstoneWire.php +++ b/src/block/RedstoneWire.php @@ -23,13 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\AnalogRedstoneSignalEmitter; use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\math\Facing; -class RedstoneWire extends Flowable{ +class RedstoneWire extends Flowable implements AnalogRedstoneSignalEmitter{ use AnalogRedstoneSignalEmitterTrait; use StaticSupportTrait; diff --git a/src/block/ResinClump.php b/src/block/ResinClump.php index 75126edf3..2855a7cc3 100644 --- a/src/block/ResinClump.php +++ b/src/block/ResinClump.php @@ -24,9 +24,10 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\MultiAnySupportTrait; +use pocketmine\block\utils\MultiFacing; use pocketmine\block\utils\SupportType; -final class ResinClump extends Transparent{ +final class ResinClump extends Transparent implements MultiFacing{ use MultiAnySupportTrait; public function isSolid() : bool{ diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index d557401ee..52a3b83cd 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; +use pocketmine\block\utils\AnyFacing; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -32,7 +33,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class ShulkerBox extends Opaque{ +class ShulkerBox extends Opaque implements AnyFacing{ use AnyFacingTrait; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ diff --git a/src/block/SimplePillar.php b/src/block/SimplePillar.php index 98c89f89c..6a7af96ed 100644 --- a/src/block/SimplePillar.php +++ b/src/block/SimplePillar.php @@ -23,12 +23,13 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\PillarRotationTrait; /** * @internal This class provides a general base for pillar-like blocks. It **should not** be used for contract binding * in APIs, because not all pillar-like blocks extend this class. */ -class SimplePillar extends Opaque{ +class SimplePillar extends Opaque implements PillarRotation{ use PillarRotationTrait; } diff --git a/src/block/SmallDripleaf.php b/src/block/SmallDripleaf.php index d192e43db..846be5c93 100644 --- a/src/block/SmallDripleaf.php +++ b/src/block/SmallDripleaf.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -36,7 +37,7 @@ use pocketmine\world\BlockTransaction; use pocketmine\world\Position; use function mt_rand; -class SmallDripleaf extends Transparent{ +class SmallDripleaf extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; protected bool $top = false; diff --git a/src/block/StainedGlass.php b/src/block/StainedGlass.php index bc0d84877..baf4755bb 100644 --- a/src/block/StainedGlass.php +++ b/src/block/StainedGlass.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -final class StainedGlass extends Glass{ +final class StainedGlass extends Glass implements Colored{ use ColoredTrait; } diff --git a/src/block/StainedGlassPane.php b/src/block/StainedGlassPane.php index 18ecfdee0..897c291c7 100644 --- a/src/block/StainedGlassPane.php +++ b/src/block/StainedGlassPane.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -final class StainedGlassPane extends GlassPane{ +final class StainedGlassPane extends GlassPane implements Colored{ use ColoredTrait; } diff --git a/src/block/StainedHardenedClay.php b/src/block/StainedHardenedClay.php index 2c2c01ba3..765e97e4f 100644 --- a/src/block/StainedHardenedClay.php +++ b/src/block/StainedHardenedClay.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -final class StainedHardenedClay extends HardenedClay{ +final class StainedHardenedClay extends HardenedClay implements Colored{ use ColoredTrait; } diff --git a/src/block/StainedHardenedGlass.php b/src/block/StainedHardenedGlass.php index cc609a49a..c3a794e4d 100644 --- a/src/block/StainedHardenedGlass.php +++ b/src/block/StainedHardenedGlass.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -final class StainedHardenedGlass extends HardenedGlass{ +final class StainedHardenedGlass extends HardenedGlass implements Colored{ use ColoredTrait; } diff --git a/src/block/StainedHardenedGlassPane.php b/src/block/StainedHardenedGlassPane.php index 63dbe1f77..9631ff5a9 100644 --- a/src/block/StainedHardenedGlassPane.php +++ b/src/block/StainedHardenedGlassPane.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -final class StainedHardenedGlassPane extends HardenedGlassPane{ +final class StainedHardenedGlassPane extends HardenedGlassPane implements Colored{ use ColoredTrait; } diff --git a/src/block/Stair.php b/src/block/Stair.php index d66a9ce5c..56101de13 100644 --- a/src/block/Stair.php +++ b/src/block/Stair.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\StairShape; use pocketmine\block\utils\SupportType; @@ -35,7 +36,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class Stair extends Transparent{ +class Stair extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; protected bool $upsideDown = false; diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 30c19d25d..0fd259326 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\inventory\StonecutterInventory; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -32,7 +33,7 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; -class Stonecutter extends Transparent{ +class Stonecutter extends Transparent implements HorizontalFacing{ use FacesOppositePlacingPlayerTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index 2da2dc9b9..0874413c5 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\StaticSupportTrait; @@ -34,7 +35,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use pocketmine\world\Position; -class Sugarcane extends Flowable{ +class Sugarcane extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait { onNearbyBlockChange as onSupportBlockChange; diff --git a/src/block/SweetBerryBush.php b/src/block/SweetBerryBush.php index eabdde118..881b7359f 100644 --- a/src/block/SweetBerryBush.php +++ b/src/block/SweetBerryBush.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Ageable; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\FortuneDropHelper; @@ -39,7 +40,7 @@ use pocketmine\player\Player; use pocketmine\world\sound\SweetBerriesPickSound; use function mt_rand; -class SweetBerryBush extends Flowable{ +class SweetBerryBush extends Flowable implements Ageable{ use AgeableTrait; use StaticSupportTrait; diff --git a/src/block/Trapdoor.php b/src/block/Trapdoor.php index a903e1b5e..5e8a7dc3f 100644 --- a/src/block/Trapdoor.php +++ b/src/block/Trapdoor.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -34,7 +35,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\DoorSound; -class Trapdoor extends Transparent{ +class Trapdoor extends Transparent implements HorizontalFacing{ use HorizontalFacingTrait; protected bool $open = false; diff --git a/src/block/TripwireHook.php b/src/block/TripwireHook.php index 325819825..4e40cf62e 100644 --- a/src/block/TripwireHook.php +++ b/src/block/TripwireHook.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -32,7 +33,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -class TripwireHook extends Flowable{ +class TripwireHook extends Flowable implements HorizontalFacing{ use HorizontalFacingTrait; protected bool $connected = false; diff --git a/src/block/WallBanner.php b/src/block/WallBanner.php index 5182fe9f8..ddb157cda 100644 --- a/src/block/WallBanner.php +++ b/src/block/WallBanner.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Axis; @@ -31,7 +32,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class WallBanner extends BaseBanner{ +final class WallBanner extends BaseBanner implements HorizontalFacing{ use HorizontalFacingTrait; protected function getSupportingFace() : int{ diff --git a/src/block/WallCoralFan.php b/src/block/WallCoralFan.php index f9dece1cd..67745a537 100644 --- a/src/block/WallCoralFan.php +++ b/src/block/WallCoralFan.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -33,7 +34,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class WallCoralFan extends BaseCoral{ +final class WallCoralFan extends BaseCoral implements HorizontalFacing{ use HorizontalFacingTrait; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ diff --git a/src/block/WallSign.php b/src/block/WallSign.php index 07826eae7..c6b42608d 100644 --- a/src/block/WallSign.php +++ b/src/block/WallSign.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Axis; @@ -31,7 +32,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -final class WallSign extends BaseSign{ +final class WallSign extends BaseSign implements HorizontalFacing{ use HorizontalFacingTrait; protected function getSupportingFace() : int{ diff --git a/src/block/WeightedPressurePlate.php b/src/block/WeightedPressurePlate.php index 726b31f6b..065f8b2c8 100644 --- a/src/block/WeightedPressurePlate.php +++ b/src/block/WeightedPressurePlate.php @@ -23,13 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\AnalogRedstoneSignalEmitter; use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait; use function ceil; use function count; use function max; use function min; -class WeightedPressurePlate extends PressurePlate{ +class WeightedPressurePlate extends PressurePlate implements AnalogRedstoneSignalEmitter{ use AnalogRedstoneSignalEmitterTrait; private readonly float $signalStrengthFactor; diff --git a/src/block/Wood.php b/src/block/Wood.php index 127533b98..7aa667bc8 100644 --- a/src/block/Wood.php +++ b/src/block/Wood.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\PillarRotationTrait; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Axe; @@ -32,7 +34,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\sound\ItemUseOnBlockSound; -class Wood extends Opaque{ +class Wood extends Opaque implements PillarRotation, WoodMaterial{ use PillarRotationTrait; use WoodTypeTrait; diff --git a/src/block/WoodenButton.php b/src/block/WoodenButton.php index 7ba8a7af0..4eed3a50e 100644 --- a/src/block/WoodenButton.php +++ b/src/block/WoodenButton.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class WoodenButton extends Button{ +class WoodenButton extends Button implements WoodMaterial{ use WoodTypeTrait; protected function getActivationTime() : int{ diff --git a/src/block/WoodenDoor.php b/src/block/WoodenDoor.php index 96f349e49..b7918a820 100644 --- a/src/block/WoodenDoor.php +++ b/src/block/WoodenDoor.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class WoodenDoor extends Door{ +class WoodenDoor extends Door implements WoodMaterial{ use WoodTypeTrait; public function getFuelTime() : int{ diff --git a/src/block/WoodenFence.php b/src/block/WoodenFence.php index c12043a86..73c8da44d 100644 --- a/src/block/WoodenFence.php +++ b/src/block/WoodenFence.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class WoodenFence extends Fence{ +class WoodenFence extends Fence implements WoodMaterial{ use WoodTypeTrait; public function getFuelTime() : int{ diff --git a/src/block/WoodenPressurePlate.php b/src/block/WoodenPressurePlate.php index a629c2f1c..6d638788b 100644 --- a/src/block/WoodenPressurePlate.php +++ b/src/block/WoodenPressurePlate.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodType; use pocketmine\block\utils\WoodTypeTrait; -class WoodenPressurePlate extends SimplePressurePlate{ +class WoodenPressurePlate extends SimplePressurePlate implements WoodMaterial{ use WoodTypeTrait; public function __construct( diff --git a/src/block/WoodenSlab.php b/src/block/WoodenSlab.php index b388a36ea..3ed606c64 100644 --- a/src/block/WoodenSlab.php +++ b/src/block/WoodenSlab.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class WoodenSlab extends Slab{ +class WoodenSlab extends Slab implements WoodMaterial{ use WoodTypeTrait; public function getFuelTime() : int{ diff --git a/src/block/WoodenStairs.php b/src/block/WoodenStairs.php index 0d9ba62ce..8da3fceb6 100644 --- a/src/block/WoodenStairs.php +++ b/src/block/WoodenStairs.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class WoodenStairs extends Stair{ +class WoodenStairs extends Stair implements WoodMaterial{ use WoodTypeTrait; public function getFuelTime() : int{ diff --git a/src/block/WoodenTrapdoor.php b/src/block/WoodenTrapdoor.php index c0a6623a1..2b796df52 100644 --- a/src/block/WoodenTrapdoor.php +++ b/src/block/WoodenTrapdoor.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodMaterial; use pocketmine\block\utils\WoodTypeTrait; -class WoodenTrapdoor extends Trapdoor{ +class WoodenTrapdoor extends Trapdoor implements WoodMaterial{ use WoodTypeTrait; public function getFuelTime() : int{ diff --git a/src/block/Wool.php b/src/block/Wool.php index 0b008ac04..d47c27d27 100644 --- a/src/block/Wool.php +++ b/src/block/Wool.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\ColoredTrait; -class Wool extends Opaque{ +class Wool extends Opaque implements Colored{ use ColoredTrait; public function getFlameEncouragement() : int{ diff --git a/src/block/utils/Ageable.php b/src/block/utils/Ageable.php new file mode 100644 index 000000000..31fe3f459 --- /dev/null +++ b/src/block/utils/Ageable.php @@ -0,0 +1,34 @@ + Date: Sun, 3 Aug 2025 15:47:12 +0100 Subject: [PATCH 251/334] BlockStateUpgrader: All but removed dependency on BlockStateData --- .../block/upgrade/BlockStateUpgrader.php | 83 +++++++++---------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index 2dce762b8..a3e72807e 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -75,6 +75,8 @@ final class BlockStateUpgrader{ public function upgrade(BlockStateData $blockStateData) : BlockStateData{ $version = $blockStateData->getVersion(); + $name = $blockStateData->getName(); + $states = $blockStateData->getStates(); foreach($this->upgradeSchemas as $resultVersion => $schemaList){ /* * Sometimes Mojang made changes without bumping the version ID. @@ -91,57 +93,54 @@ final class BlockStateUpgrader{ } foreach($schemaList as $schema){ - $blockStateData = $this->applySchema($schema, $blockStateData); + [$name, $states] = $this->applySchema($schema, $name, $states); } } - if($this->outputVersion > $version){ - //always update the version number of the blockstate, even if it didn't change - this is needed for - //external tools - $blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $this->outputVersion); - } - return $blockStateData; + return new BlockStateData($name, $states, $this->outputVersion); } - private function applySchema(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : BlockStateData{ - $newStateData = $this->applyStateRemapped($schema, $blockStateData); - if($newStateData !== null){ - return $newStateData; + /** + * @param Tag[] $states + * @phpstan-param array $states + * + * @return (string|Tag[])[] + * @phpstan-return array{0: string, 1: array} + */ + private function applySchema(BlockStateUpgradeSchema $schema, string $oldName, array $states) : array{ + $remapped = $this->applyStateRemapped($schema, $oldName, $states); + if($remapped !== null){ + return $remapped; } - $oldName = $blockStateData->getName(); - $states = $blockStateData->getStates(); - if(isset($schema->renamedIds[$oldName]) && isset($schema->flattenedProperties[$oldName])){ //TODO: this probably ought to be validated when the schema is constructed throw new AssumptionFailedError("Both renamedIds and flattenedProperties are set for the same block ID \"$oldName\" - don't know what to do"); } if(isset($schema->renamedIds[$oldName])){ - $newName = $schema->renamedIds[$oldName] ?? null; + $newName = $schema->renamedIds[$oldName]; }elseif(isset($schema->flattenedProperties[$oldName])){ [$newName, $states] = $this->applyPropertyFlattened($schema->flattenedProperties[$oldName], $oldName, $states); }else{ - $newName = null; + $newName = $oldName; } - $stateChanges = 0; + $states = $this->applyPropertyAdded($schema, $oldName, $states); + $states = $this->applyPropertyRemoved($schema, $oldName, $states); + $states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states); + $states = $this->applyPropertyValueChanged($schema, $oldName, $states); - $states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges); - $states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges); - $states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges); - $states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges); - - if($newName !== null || $stateChanges > 0){ - return new BlockStateData($newName ?? $oldName, $states, $schema->getVersionId()); - } - - return $blockStateData; + return [$newName, $states]; } - private function applyStateRemapped(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : ?BlockStateData{ - $oldName = $blockStateData->getName(); - $oldState = $blockStateData->getStates(); - + /** + * @param Tag[] $oldState + * @phpstan-param array $oldState + * + * @return (string|Tag[])[]|null + * @phpstan-return array{0: string, 1: array}|null + */ + private function applyStateRemapped(BlockStateUpgradeSchema $schema, string $oldName, array $oldState) : ?array{ if(isset($schema->remappedStates[$oldName])){ foreach($schema->remappedStates[$oldName] as $remap){ if(count($remap->oldState) > count($oldState)){ @@ -168,7 +167,7 @@ final class BlockStateUpgrader{ } } - return new BlockStateData($newName, $newState, $schema->getVersionId()); + return [$newName, $newState]; } } @@ -182,11 +181,10 @@ final class BlockStateUpgrader{ * @return Tag[] * @phpstan-return array */ - private function applyPropertyAdded(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{ + private function applyPropertyAdded(BlockStateUpgradeSchema $schema, string $oldName, array $states) : array{ if(isset($schema->addedProperties[$oldName])){ foreach(Utils::stringifyKeys($schema->addedProperties[$oldName]) as $propertyName => $value){ if(!isset($states[$propertyName])){ - $stateChanges++; $states[$propertyName] = $value; } } @@ -202,13 +200,10 @@ final class BlockStateUpgrader{ * @return Tag[] * @phpstan-return array */ - private function applyPropertyRemoved(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{ + private function applyPropertyRemoved(BlockStateUpgradeSchema $schema, string $oldName, array $states) : array{ if(isset($schema->removedProperties[$oldName])){ foreach($schema->removedProperties[$oldName] as $propertyName){ - if(isset($states[$propertyName])){ - $stateChanges++; - unset($states[$propertyName]); - } + unset($states[$propertyName]); } } @@ -234,12 +229,11 @@ final class BlockStateUpgrader{ * @return Tag[] * @phpstan-return array */ - private function applyPropertyRenamedOrValueChanged(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{ + private function applyPropertyRenamedOrValueChanged(BlockStateUpgradeSchema $schema, string $oldName, array $states) : array{ if(isset($schema->renamedProperties[$oldName])){ foreach(Utils::stringifyKeys($schema->renamedProperties[$oldName]) as $oldPropertyName => $newPropertyName){ $oldValue = $states[$oldPropertyName] ?? null; if($oldValue !== null){ - $stateChanges++; unset($states[$oldPropertyName]); //If a value remap is needed, we need to do it here, since we won't be able to locate the property @@ -260,16 +254,13 @@ final class BlockStateUpgrader{ * @return Tag[] * @phpstan-return array */ - private function applyPropertyValueChanged(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{ + private function applyPropertyValueChanged(BlockStateUpgradeSchema $schema, string $oldName, array $states) : array{ if(isset($schema->remappedPropertyValues[$oldName])){ foreach(Utils::stringifyKeys($schema->remappedPropertyValues[$oldName]) as $oldPropertyName => $remappedValues){ $oldValue = $states[$oldPropertyName] ?? null; if($oldValue !== null){ $newValue = $this->locateNewPropertyValue($schema, $oldName, $oldPropertyName, $oldValue); - if($newValue !== $oldValue){ - $stateChanges++; - $states[$oldPropertyName] = $newValue; - } + $states[$oldPropertyName] = $newValue; } } } From 89d18f929ffdd31d7da0cedb24e120bd33170355 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 6 Aug 2025 15:48:29 +0100 Subject: [PATCH 252/334] RakLibServer: fixed deadlock on thread crash synchronized block -> getCrashInfo -> join -> synchronized on the same context on the child thread -> deadlock instead we check for isTerminated and then get the crash info outside of the synchronized block. --- src/network/mcpe/raklib/RakLibServer.php | 16 ++++++++-------- src/thread/CommonThreadPartsTrait.php | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index d5e825bee..f66734ee5 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -68,17 +68,17 @@ class RakLibServer extends Thread{ public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{ $this->start($options); $this->synchronized(function() : void{ - while(!$this->ready && $this->getCrashInfo() === null){ + while(!$this->ready && !$this->isTerminated()){ $this->wait(); } - $crashInfo = $this->getCrashInfo(); - if($crashInfo !== null){ - if($crashInfo->getType() === SocketException::class){ - throw new SocketException($crashInfo->getMessage()); - } - throw new ThreadCrashException("RakLib failed to start", $crashInfo); - } }); + $crashInfo = $this->getCrashInfo(); + if($crashInfo !== null){ + if($crashInfo->getType() === SocketException::class){ + throw new SocketException($crashInfo->getMessage()); + } + throw new ThreadCrashException("RakLib failed to start", $crashInfo); + } } protected function onRun() : void{ diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index de606a7b2..d7d51d40f 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -100,6 +100,8 @@ trait CommonThreadPartsTrait{ //*before* the shutdown handler is invoked, so we might land here before the crash info has been set. //In the future this should probably be fixed by running the shutdown handlers before setting isTerminated, //but this workaround should be good enough for now. + //WARNING: Do not call this inside a synchronized block on this thread's context. Because the shutdown handler + //runs in a synchronized block, this will result in a deadlock. if($this->isTerminated() && !$this->isJoined()){ $this->join(); } From 173b685b022d40de3c8fbe5de6a1435a81a148af Mon Sep 17 00:00:00 2001 From: Dries C <15795262+dries-c@users.noreply.github.com> Date: Wed, 6 Aug 2025 17:41:44 +0200 Subject: [PATCH 253/334] Bedrock 1.21.100 (#6760) --------- Co-authored-by: Dylan T. --- changelogs/5.32.md | 17 +++++++ composer.json | 6 +-- composer.lock | 44 +++++++++---------- src/VersionInfo.php | 4 +- src/data/bedrock/WorldDataVersions.php | 6 +-- src/data/bedrock/block/BlockTypeNames.php | 8 ++++ src/data/bedrock/item/ItemTypeNames.php | 11 +++++ .../mcpe/handler/PreSpawnPacketHandler.php | 1 + .../biome/model/BiomeDefinitionEntryData.php | 2 +- 9 files changed, 68 insertions(+), 31 deletions(-) create mode 100644 changelogs/5.32.md diff --git a/changelogs/5.32.md b/changelogs/5.32.md new file mode 100644 index 000000000..16b9aaff4 --- /dev/null +++ b/changelogs/5.32.md @@ -0,0 +1,17 @@ +# 5.32.0 +Released 6th August 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.100. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.100. +- Removed support for earlier versions. + +## Fixes +- Fixed deadlock on RakLib thread crash (e.g. due to port binding failure). diff --git a/composer.json b/composer.json index ce4994d4f..e66269b40 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,9 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "~5.2.0+bedrock-1.21.93", - "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~39.1.0+bedrock-1.21.93", + "pocketmine/bedrock-data": "~5.3.0+bedrock-1.21.100", + "pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100", + "pocketmine/bedrock-protocol": "~40.0.0+bedrock-1.21.100", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index 2e5e83a94..9a69e6e14 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": "4dc5ea726d881d8c52d1b5299485d940", + "content-hash": "402ad5667b1e636a8ec6acf2f1b5f055", "packages": [ { "name": "adhocore/json-comment", @@ -204,16 +204,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "5.2.0+bedrock-1.21.93", + "version": "5.3.0+bedrock-1.21.100", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "740e18e490c6a102b774518ff2224a06762bcaf8" + "reference": "5279e76261df158d5af187cfaafc1618c1da9e3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/740e18e490c6a102b774518ff2224a06762bcaf8", - "reference": "740e18e490c6a102b774518ff2224a06762bcaf8", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/5279e76261df158d5af187cfaafc1618c1da9e3f", + "reference": "5279e76261df158d5af187cfaafc1618c1da9e3f", "shasum": "" }, "type": "library", @@ -224,22 +224,22 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.93" + "source": "https://github.com/pmmp/BedrockData/tree/5.3.0+bedrock-1.21.100" }, - "time": "2025-07-08T12:30:28+00:00" + "time": "2025-07-30T22:07:56+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", - "version": "1.14.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git", - "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb" + "reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb", - "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb", + "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/09e0dbe9743f21a76b1fe04b2b4136785775f52b", + "reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b", "shasum": "" }, "type": "library", @@ -250,22 +250,22 @@ "description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves", "support": { "issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.14.0" + "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.15.0" }, - "time": "2024-12-04T12:22:49+00:00" + "time": "2025-08-06T15:08:48+00:00" }, { "name": "pocketmine/bedrock-protocol", - "version": "39.1.0+bedrock-1.21.93", + "version": "40.0.0+bedrock-1.21.100", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "e9bc5fb691d18dab229a158462c13f0c6fea79c8" + "reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e9bc5fb691d18dab229a158462c13f0c6fea79c8", - "reference": "e9bc5fb691d18dab229a158462c13f0c6fea79c8", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca", + "reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca", "shasum": "" }, "require": { @@ -296,9 +296,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/39.1.0+bedrock-1.21.93" + "source": "https://github.com/pmmp/BedrockProtocol/tree/40.0.0+bedrock-1.21.100" }, - "time": "2025-07-08T12:31:39+00:00" + "time": "2025-08-06T15:13:45+00:00" }, { "name": "pocketmine/binaryutils", @@ -2756,7 +2756,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -2787,9 +2787,9 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/VersionInfo.php b/src/VersionInfo.php index aeb4d9ff8..b0a533298 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.31.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.32.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** diff --git a/src/data/bedrock/WorldDataVersions.php b/src/data/bedrock/WorldDataVersions.php index ca36795fa..e682eb43a 100644 --- a/src/data/bedrock/WorldDataVersions.php +++ b/src/data/bedrock/WorldDataVersions.php @@ -54,13 +54,13 @@ final class WorldDataVersions{ * This may be lower than the current protocol version if PocketMine-MP does not yet support features of the newer * version. This allows the protocol to be updated independently of world format support. */ - public const NETWORK = 818; + public const NETWORK = 827; public const LAST_OPENED_IN = [ 1, //major 21, //minor - 90, //patch - 3, //revision + 100, //patch + 23, //revision 0 //is beta ]; } diff --git a/src/data/bedrock/block/BlockTypeNames.php b/src/data/bedrock/block/BlockTypeNames.php index 527a01345..71402c74a 100644 --- a/src/data/bedrock/block/BlockTypeNames.php +++ b/src/data/bedrock/block/BlockTypeNames.php @@ -255,6 +255,7 @@ final class BlockTypeNames{ public const CONDUIT = "minecraft:conduit"; public const COPPER_BLOCK = "minecraft:copper_block"; public const COPPER_BULB = "minecraft:copper_bulb"; + public const COPPER_CHEST = "minecraft:copper_chest"; public const COPPER_DOOR = "minecraft:copper_door"; public const COPPER_GRATE = "minecraft:copper_grate"; public const COPPER_ORE = "minecraft:copper_ore"; @@ -533,6 +534,7 @@ final class BlockTypeNames{ public const EXPOSED_CHISELED_COPPER = "minecraft:exposed_chiseled_copper"; public const EXPOSED_COPPER = "minecraft:exposed_copper"; public const EXPOSED_COPPER_BULB = "minecraft:exposed_copper_bulb"; + public const EXPOSED_COPPER_CHEST = "minecraft:exposed_copper_chest"; public const EXPOSED_COPPER_DOOR = "minecraft:exposed_copper_door"; public const EXPOSED_COPPER_GRATE = "minecraft:exposed_copper_grate"; public const EXPOSED_COPPER_TRAPDOOR = "minecraft:exposed_copper_trapdoor"; @@ -857,6 +859,7 @@ final class BlockTypeNames{ public const OXIDIZED_CHISELED_COPPER = "minecraft:oxidized_chiseled_copper"; public const OXIDIZED_COPPER = "minecraft:oxidized_copper"; public const OXIDIZED_COPPER_BULB = "minecraft:oxidized_copper_bulb"; + public const OXIDIZED_COPPER_CHEST = "minecraft:oxidized_copper_chest"; public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door"; public const OXIDIZED_COPPER_GRATE = "minecraft:oxidized_copper_grate"; public const OXIDIZED_COPPER_TRAPDOOR = "minecraft:oxidized_copper_trapdoor"; @@ -1211,6 +1214,7 @@ final class BlockTypeNames{ public const WAXED_CHISELED_COPPER = "minecraft:waxed_chiseled_copper"; public const WAXED_COPPER = "minecraft:waxed_copper"; public const WAXED_COPPER_BULB = "minecraft:waxed_copper_bulb"; + public const WAXED_COPPER_CHEST = "minecraft:waxed_copper_chest"; public const WAXED_COPPER_DOOR = "minecraft:waxed_copper_door"; public const WAXED_COPPER_GRATE = "minecraft:waxed_copper_grate"; public const WAXED_COPPER_TRAPDOOR = "minecraft:waxed_copper_trapdoor"; @@ -1221,6 +1225,7 @@ final class BlockTypeNames{ public const WAXED_EXPOSED_CHISELED_COPPER = "minecraft:waxed_exposed_chiseled_copper"; public const WAXED_EXPOSED_COPPER = "minecraft:waxed_exposed_copper"; public const WAXED_EXPOSED_COPPER_BULB = "minecraft:waxed_exposed_copper_bulb"; + public const WAXED_EXPOSED_COPPER_CHEST = "minecraft:waxed_exposed_copper_chest"; public const WAXED_EXPOSED_COPPER_DOOR = "minecraft:waxed_exposed_copper_door"; public const WAXED_EXPOSED_COPPER_GRATE = "minecraft:waxed_exposed_copper_grate"; public const WAXED_EXPOSED_COPPER_TRAPDOOR = "minecraft:waxed_exposed_copper_trapdoor"; @@ -1231,6 +1236,7 @@ final class BlockTypeNames{ public const WAXED_OXIDIZED_CHISELED_COPPER = "minecraft:waxed_oxidized_chiseled_copper"; public const WAXED_OXIDIZED_COPPER = "minecraft:waxed_oxidized_copper"; public const WAXED_OXIDIZED_COPPER_BULB = "minecraft:waxed_oxidized_copper_bulb"; + public const WAXED_OXIDIZED_COPPER_CHEST = "minecraft:waxed_oxidized_copper_chest"; public const WAXED_OXIDIZED_COPPER_DOOR = "minecraft:waxed_oxidized_copper_door"; public const WAXED_OXIDIZED_COPPER_GRATE = "minecraft:waxed_oxidized_copper_grate"; public const WAXED_OXIDIZED_COPPER_TRAPDOOR = "minecraft:waxed_oxidized_copper_trapdoor"; @@ -1241,6 +1247,7 @@ final class BlockTypeNames{ public const WAXED_WEATHERED_CHISELED_COPPER = "minecraft:waxed_weathered_chiseled_copper"; public const WAXED_WEATHERED_COPPER = "minecraft:waxed_weathered_copper"; public const WAXED_WEATHERED_COPPER_BULB = "minecraft:waxed_weathered_copper_bulb"; + public const WAXED_WEATHERED_COPPER_CHEST = "minecraft:waxed_weathered_copper_chest"; public const WAXED_WEATHERED_COPPER_DOOR = "minecraft:waxed_weathered_copper_door"; public const WAXED_WEATHERED_COPPER_GRATE = "minecraft:waxed_weathered_copper_grate"; public const WAXED_WEATHERED_COPPER_TRAPDOOR = "minecraft:waxed_weathered_copper_trapdoor"; @@ -1251,6 +1258,7 @@ final class BlockTypeNames{ public const WEATHERED_CHISELED_COPPER = "minecraft:weathered_chiseled_copper"; public const WEATHERED_COPPER = "minecraft:weathered_copper"; public const WEATHERED_COPPER_BULB = "minecraft:weathered_copper_bulb"; + public const WEATHERED_COPPER_CHEST = "minecraft:weathered_copper_chest"; public const WEATHERED_COPPER_DOOR = "minecraft:weathered_copper_door"; public const WEATHERED_COPPER_GRATE = "minecraft:weathered_copper_grate"; public const WEATHERED_COPPER_TRAPDOOR = "minecraft:weathered_copper_trapdoor"; diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php index 3178386ca..5c648ff38 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -153,8 +153,19 @@ final class ItemTypeNames{ public const COOKED_RABBIT = "minecraft:cooked_rabbit"; public const COOKED_SALMON = "minecraft:cooked_salmon"; public const COOKIE = "minecraft:cookie"; + public const COPPER_AXE = "minecraft:copper_axe"; + public const COPPER_BOOTS = "minecraft:copper_boots"; + public const COPPER_CHESTPLATE = "minecraft:copper_chestplate"; public const COPPER_DOOR = "minecraft:copper_door"; + public const COPPER_GOLEM_SPAWN_EGG = "minecraft:copper_golem_spawn_egg"; + public const COPPER_HELMET = "minecraft:copper_helmet"; + public const COPPER_HOE = "minecraft:copper_hoe"; public const COPPER_INGOT = "minecraft:copper_ingot"; + public const COPPER_LEGGINGS = "minecraft:copper_leggings"; + public const COPPER_NUGGET = "minecraft:copper_nugget"; + public const COPPER_PICKAXE = "minecraft:copper_pickaxe"; + public const COPPER_SHOVEL = "minecraft:copper_shovel"; + public const COPPER_SWORD = "minecraft:copper_sword"; public const CORAL = "minecraft:coral"; public const CORAL_BLOCK = "minecraft:coral_block"; public const CORAL_FAN = "minecraft:coral_fan"; diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 99f65e78f..4aa8be227 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -108,6 +108,7 @@ class PreSpawnPacketHandler extends PacketHandler{ Uuid::fromString(Uuid::NIL), false, false, + false, new NetworkPermissions(disableClientSounds: true), [], 0, diff --git a/src/world/biome/model/BiomeDefinitionEntryData.php b/src/world/biome/model/BiomeDefinitionEntryData.php index 8a5c3d354..bb63b36e1 100644 --- a/src/world/biome/model/BiomeDefinitionEntryData.php +++ b/src/world/biome/model/BiomeDefinitionEntryData.php @@ -28,7 +28,7 @@ namespace pocketmine\world\biome\model; */ final class BiomeDefinitionEntryData{ /** @required */ - public ?int $id; + public int $id; /** @required */ public float $temperature; From 275fdc4280bcbcf48556e4f5c282ab73f82cf56c Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:42:50 +0000 Subject: [PATCH 254/334] 5.32.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/16781699267 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index b0a533298..f28239816 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.32.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.32.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 0b9e680753270ba8ccc4f724c89b14df1b7ae2fe Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 6 Aug 2025 17:15:21 +0100 Subject: [PATCH 255/334] Fix GitHub release trigger actions borked https://github.com/actions/runner/issues/2788 --- .github/workflows/build-docker-image.yml | 31 +++++++++++++++----- .github/workflows/discord-release-notify.yml | 17 +++++++++-- .github/workflows/update-updater-api.yml | 17 +++++++++-- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index a3921f820..9319f9e2f 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -4,6 +4,11 @@ on: release: types: - published + workflow_dispatch: + inputs: + release: + description: 'Tag name to build' + required: true jobs: build: @@ -33,11 +38,23 @@ jobs: repository: pmmp/PocketMine-Docker fetch-depth: 1 - - name: Get tag names + + - name: Get tag name id: tag-name run: | - VERSION=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') - echo TAG_NAME=$VERSION >> $GITHUB_OUTPUT + if [[ "${{ github.event_name }}" == "release" ]]; then + echo TAG_NAME="${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo TAG_NAME="${{ github.event.inputs.release }}" >> $GITHUB_OUTPUT + else + echo "Unsupported event type: ${{ github.event_name }}" + exit 1 + fi + + - name: Parse version + id: version + run: | + VERSION="${{ steps.tag-name.outputs.TAG_NAME }}" echo MAJOR=$(echo $VERSION | cut -d. -f1) >> $GITHUB_OUTPUT echo MINOR=$(echo $VERSION | cut -d. -f1-2) >> $GITHUB_OUTPUT @@ -71,8 +88,8 @@ jobs: push: true context: ./pocketmine-mp tags: | - ${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }} - ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }} + ${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MAJOR }} + ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MAJOR }} build-args: | PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }} PMMP_REPO=${{ github.repository }} @@ -84,8 +101,8 @@ jobs: push: true context: ./pocketmine-mp tags: | - ${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }} - ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }} + ${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MINOR }} + ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MINOR }} build-args: | PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }} PMMP_REPO=${{ github.repository }} diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 906f227ea..697b6358b 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -4,6 +4,11 @@ on: release: types: - published + workflow_dispatch: + inputs: + release: + description: 'Release to make notification for' + required: true jobs: build: @@ -30,9 +35,17 @@ jobs: - name: Install Composer dependencies run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs - - name: Get actual tag name + - name: Get tag name id: tag-name - run: echo TAG_NAME=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') >> $GITHUB_OUTPUT + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + echo TAG_NAME="${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo TAG_NAME="${{ github.event.inputs.release }}" >> $GITHUB_OUTPUT + else + echo "Unsupported event type: ${{ github.event_name }}" + exit 1 + fi - name: Run webhook post script run: php .github/workflows/discord-release-embed.php ${{ github.repository }} ${{ steps.tag-name.outputs.TAG_NAME }} ${{ github.token }} ${{ secrets.DISCORD_RELEASE_WEBHOOK }} ${{ secrets.DISCORD_NEWS_PING_ROLE_ID }} diff --git a/.github/workflows/update-updater-api.yml b/.github/workflows/update-updater-api.yml index 3f42062fd..031950ba8 100644 --- a/.github/workflows/update-updater-api.yml +++ b/.github/workflows/update-updater-api.yml @@ -4,6 +4,11 @@ on: release: types: - published + workflow_dispatch: + inputs: + release: + description: 'Release to publish info for' + required: true jobs: build: @@ -19,9 +24,17 @@ jobs: repository: ${{ github.repository_owner }}/update.pmmp.io ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }} - - name: Get actual tag name + - name: Get tag name id: tag-name - run: echo TAG_NAME=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') >> $GITHUB_OUTPUT + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + echo TAG_NAME="${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo TAG_NAME="${{ github.event.inputs.release }}" >> $GITHUB_OUTPUT + else + echo "Unsupported event type: ${{ github.event_name }}" + exit 1 + fi - name: Download new release information run: curl -f -L ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.tag-name.outputs.TAG_NAME }}/build_info.json -o new_build_info.json From 11612ed0e2a554ed94f654a625656e5f6b9a14b3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 11 Aug 2025 00:49:37 +0100 Subject: [PATCH 256/334] Fixed content log warning about recipe with missing ID --- src/network/mcpe/cache/CraftingDataCache.php | 16 ++++++++++++---- .../mcpe/handler/ItemStackRequestExecutor.php | 6 ++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php index 14523f74c..da0f37c44 100644 --- a/src/network/mcpe/cache/CraftingDataCache.php +++ b/src/network/mcpe/cache/CraftingDataCache.php @@ -56,6 +56,12 @@ final class CraftingDataCache{ */ private array $caches = []; + /** + * The client doesn't like recipes with ID 0 (as of 1.21.100) and complains about them in the content log + * This doesn't actually affect the function of the recipe, but it is annoying, so this offset fixes it + */ + public const RECIPE_ID_OFFSET = 1; + public function getCache(CraftingManager $manager) : CraftingDataPacket{ $id = spl_object_id($manager); if(!isset($this->caches[$id])){ @@ -82,6 +88,8 @@ final class CraftingDataCache{ $noUnlockingRequirement = new RecipeUnlockingRequirement(null); foreach($manager->getCraftingRecipeIndex() as $index => $recipe){ + //the client doesn't like recipes with an ID of 0, so we need to offset them + $recipeNetId = $index + self::RECIPE_ID_OFFSET; if($recipe instanceof ShapelessRecipe){ $typeTag = match($recipe->getType()){ ShapelessRecipeType::CRAFTING => CraftingRecipeBlockName::CRAFTING_TABLE, @@ -91,14 +99,14 @@ final class CraftingDataCache{ }; $recipesWithTypeIds[] = new ProtocolShapelessRecipe( CraftingDataPacket::ENTRY_SHAPELESS, - Binary::writeInt($index), + Binary::writeInt($recipeNetId), array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()), array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, $typeTag, 50, $noUnlockingRequirement, - $index + $recipeNetId ); }elseif($recipe instanceof ShapedRecipe){ $inputs = []; @@ -110,7 +118,7 @@ final class CraftingDataCache{ } $recipesWithTypeIds[] = $r = new ProtocolShapedRecipe( CraftingDataPacket::ENTRY_SHAPED, - Binary::writeInt($index), + Binary::writeInt($recipeNetId), $inputs, array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, @@ -118,7 +126,7 @@ final class CraftingDataCache{ 50, true, $noUnlockingRequirement, - $index, + $recipeNetId, ); }else{ //TODO: probably special recipe types diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index d71a1c6bf..4eddf3100 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -35,6 +35,7 @@ use pocketmine\inventory\transaction\TransactionBuilder; use pocketmine\inventory\transaction\TransactionBuilderInventory; use pocketmine\item\Durable; use pocketmine\item\Item; +use pocketmine\network\mcpe\cache\CraftingDataCache; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds; use pocketmine\network\mcpe\protocol\types\inventory\FullContainerName; @@ -238,9 +239,10 @@ class ItemStackRequestExecutor{ throw new ItemStackRequestProcessException("Cannot craft a recipe more than 256 times"); } $craftingManager = $this->player->getServer()->getCraftingManager(); - $recipe = $craftingManager->getCraftingRecipeFromIndex($recipeId); + $recipeIndex = $recipeId - CraftingDataCache::RECIPE_ID_OFFSET; + $recipe = $craftingManager->getCraftingRecipeFromIndex($recipeIndex); if($recipe === null){ - throw new ItemStackRequestProcessException("No such crafting recipe index: $recipeId"); + throw new ItemStackRequestProcessException("No such crafting recipe index: $recipeIndex"); } $this->specialTransaction = new CraftingTransaction($this->player, $craftingManager, [], $recipe, $repetitions); From c417ecd30d20520227b15e09eda87db492ab0a6a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 12 Aug 2025 18:38:24 +0100 Subject: [PATCH 257/334] NetworkSession: Abort packet processing if handling triggered a disconnection this shows up when requesting invalid data during resource pack handling, for example --- src/network/mcpe/NetworkSession.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index bea3f8131..234ad4765 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -415,6 +415,11 @@ class NetworkSession{ $this->logger->debug($packet->getName() . ": " . base64_encode($buffer)); throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName()); } + if(!$this->isConnected()){ + //handling this packet may have caused a disconnection + $this->logger->debug("Aborting batch processing due to server-side disconnection"); + break; + } } }catch(PacketDecodeException|BinaryDataException $e){ $this->logger->logException($e); From e375437439df51f7862b6b98318394643fcd6724 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 12 Aug 2025 20:11:35 +0100 Subject: [PATCH 258/334] ResourcePacksPacketHandler: harden checks for client responses --- .../handler/ResourcePacksPacketHandler.php | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index a9ffae6f7..d98d8e9ad 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -36,6 +36,7 @@ use pocketmine\network\mcpe\protocol\types\Experiments; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType; +use pocketmine\network\PacketHandlingException; use pocketmine\resourcepacks\ResourcePack; use Ramsey\Uuid\Uuid; use function array_keys; @@ -43,6 +44,7 @@ use function array_map; use function ceil; use function count; use function implode; +use function sprintf; use function strpos; use function strtolower; use function substr; @@ -66,6 +68,9 @@ class ResourcePacksPacketHandler extends PacketHandler{ */ private array $resourcePacksById = []; + private bool $requestedMetadata = false; + private bool $requestedStack = false; + /** @var bool[][] uuid => [chunk index => hasSent] */ private array $downloadedChunks = []; @@ -140,6 +145,21 @@ class ResourcePacksPacketHandler extends PacketHandler{ $this->session->disconnect("Refused resource packs", "You must accept resource packs to join this server.", true); break; case ResourcePackClientResponsePacket::STATUS_SEND_PACKS: + if($this->requestedMetadata){ + throw new PacketHandlingException("Cannot request resource pack metadata multiple times"); + } + $this->requestedMetadata = true; + + if($this->requestedStack){ + //client already told us that they have all the packs, they shouldn't be asking for more + throw new PacketHandlingException("Cannot request resource pack metadata after resource pack stack"); + } + + if(count($packet->packIds) > count($this->resourcePacksById)){ + throw new PacketHandlingException(sprintf("Requested metadata for more resource packs (%d) than available on the server (%d)", count($packet->packIds), count($this->resourcePacksById))); + } + + $seen = []; foreach($packet->packIds as $uuid){ //dirty hack for mojang's dirty hack for versions $splitPos = strpos($uuid, "_"); @@ -153,6 +173,9 @@ class ResourcePacksPacketHandler extends PacketHandler{ $this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", array_keys($this->resourcePacksById))); return false; } + if(isset($seen[$pack->getPackId()])){ + throw new PacketHandlingException("Repeated metadata request for pack $uuid"); + } $this->session->sendDataPacket(ResourcePackDataInfoPacket::create( $pack->getPackId(), @@ -163,11 +186,16 @@ class ResourcePacksPacketHandler extends PacketHandler{ false, ResourcePackType::RESOURCES //TODO: this might be an addon (not behaviour pack), needed to properly support client-side custom items )); + $seen[$pack->getPackId()] = true; } $this->session->getLogger()->debug("Player requested download of " . count($packet->packIds) . " resource packs"); - break; case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS: + if($this->requestedStack){ + throw new PacketHandlingException("Cannot request resource pack stack multiple times"); + } + $this->requestedStack = true; + $stack = array_map(static function(ResourcePack $pack) : ResourcePackStackEntry{ return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks }, $this->resourcePackStack); From 442049d564dbf4af4d4a2c741501793b1a8b600a Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 14 Aug 2025 11:37:24 +0100 Subject: [PATCH 259/334] Prepare 5.32.1 release (#6766) --- changelogs/5.32.md | 8 ++++++++ src/VersionInfo.php | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/changelogs/5.32.md b/changelogs/5.32.md index 16b9aaff4..414330351 100644 --- a/changelogs/5.32.md +++ b/changelogs/5.32.md @@ -15,3 +15,11 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if ## Fixes - Fixed deadlock on RakLib thread crash (e.g. due to port binding failure). + +# 5.32.1 +Released 14th August 2025. + +## Fixes +- Hardened checks when processing resource pack sending during player logins. +- Fixed content log warning about crafting recipe with missing ID. +- Fixed packets in a batch still being processed after one of them caused the session to be terminated. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index f28239816..77a9a2bee 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.32.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From f633416f0595e81e4c176fc00bd85652afe3f4bb Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:38:22 +0000 Subject: [PATCH 260/334] 5.32.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/16962847004 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 77a9a2bee..b3f37677b 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.32.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.32.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From cb7a562c8b0a8577b40f8689c52bf03b13a3cf8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 00:17:24 +0000 Subject: [PATCH 261/334] Bump the github-actions group across 1 directory with 2 updates (#6767) --- .github/workflows/build-docker-image.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 4 ++-- .github/workflows/discord-release-notify.yml | 4 ++-- .github/workflows/draft-release-pr-check.yml | 6 +++--- .github/workflows/draft-release.yml | 4 ++-- .github/workflows/main-php-matrix.yml | 8 ++++---- .github/workflows/main.yml | 6 +++--- .github/workflows/update-updater-api.yml | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 9319f9e2f..acfc3d3a7 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -33,7 +33,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Clone pmmp/PocketMine-Docker repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: pmmp/PocketMine-Docker fetch-depth: 1 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index ef0b122e1..c644876e0 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -18,7 +18,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: pmmp/setup-php-action@3.2.0 @@ -41,7 +41,7 @@ jobs: run: composer install --prefer-dist --no-interaction - name: Clone extension stubs - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: pmmp/phpstorm-stubs path: extension-stubs diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 697b6358b..e15134fdb 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -15,10 +15,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.34.1 + uses: shivammathur/setup-php@2.35.3 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index b2575f9be..cf6036968 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -30,7 +30,7 @@ jobs: valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Check IS_DEVELOPMENT_BUILD flag id: validate @@ -46,10 +46,10 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP - uses: shivammathur/setup-php@2.34.1 + uses: shivammathur/setup-php@2.35.3 with: php-version: 8.2 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 014ea531c..052635234 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -82,12 +82,12 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.34.1 + uses: shivammathur/setup-php@2.35.3 with: php-version: ${{ env.PHP_VERSION }} diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml index 015a33188..7637a3956 100644 --- a/.github/workflows/main-php-matrix.yml +++ b/.github/workflows/main-php-matrix.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: pmmp/setup-php-action@3.2.0 @@ -59,7 +59,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: pmmp/setup-php-action@3.2.0 @@ -91,7 +91,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true @@ -125,7 +125,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: pmmp/setup-php-action@3.2.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d020c10b..9d5f282bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,10 +25,10 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.34.1 + uses: shivammathur/setup-php@2.35.3 with: php-version: 8.3 tools: php-cs-fixer:3.75 @@ -45,7 +45,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run ShellCheck uses: ludeeus/action-shellcheck@2.0.0 diff --git a/.github/workflows/update-updater-api.yml b/.github/workflows/update-updater-api.yml index 031950ba8..841fa7d44 100644 --- a/.github/workflows/update-updater-api.yml +++ b/.github/workflows/update-updater-api.yml @@ -19,7 +19,7 @@ jobs: - name: Install jq run: sudo apt update && sudo apt install jq -y - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: repository: ${{ github.repository_owner }}/update.pmmp.io ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }} From 1e8612cfc895d3884ecbc91f0d60c48edc959ffc Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Fri, 15 Aug 2025 21:39:13 +0200 Subject: [PATCH 262/334] BlockObjectToStateSerializer: Avoid unnecessary Writer and Closure (#6759) --------- Co-authored-by: Dylan K. Taylor --- .../convert/BlockObjectToStateSerializer.php | 264 +++++++++--------- 1 file changed, 132 insertions(+), 132 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 27d550f13..49f0269ed 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -195,8 +195,8 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ * These callables actually accept Block, but for the sake of type completeness, it has to be never, since we can't * describe the bottom type of a type hierarchy only containing Block. * - * @var \Closure[] - * @phpstan-var array + * @var (\Closure|BlockStateData)[] + * @phpstan-var array */ private array $serializers = []; @@ -233,17 +233,18 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ /** * @phpstan-template TBlockType of Block * @phpstan-param TBlockType $block - * @phpstan-param \Closure(TBlockType) : Writer $serializer + * @phpstan-param \Closure(TBlockType) : (Writer|BlockStateData)|Writer|BlockStateData $serializer */ - public function map(Block $block, \Closure $serializer) : void{ + public function map(Block $block, \Closure|Writer|BlockStateData $serializer) : void{ if(isset($this->serializers[$block->getTypeId()])){ throw new \InvalidArgumentException("Block type ID " . $block->getTypeId() . " already has a serializer registered"); } - $this->serializers[$block->getTypeId()] = $serializer; + //writer accepted for convenience only + $this->serializers[$block->getTypeId()] = $serializer instanceof Writer ? $serializer->getBlockStateData() : $serializer; } public function mapSimple(Block $block, string $id) : void{ - $this->map($block, fn() => Writer::create($id)); + $this->map($block, BlockStateData::current($id, [])); } public function mapSlab(Slab $block, string $singleId, string $doubleId) : void{ @@ -272,19 +273,21 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ throw new BlockStateSerializeException("No serializer registered for " . get_class($blockState) . " with type ID $typeId"); } + if($locatedSerializer instanceof BlockStateData){ //static data, not dependent on state + return $locatedSerializer; + } + /** * TODO: there is no guarantee that this type actually matches that of $blockState - a plugin may have stolen * the type ID of the block (which never makes sense, even in a world where overriding block types is a thing). * In the future we'll need some way to guarantee that type IDs are never reused (perhaps spl_object_id()?) * - * @var \Closure $serializer - * @phpstan-var \Closure(TBlockType) : Writer $serializer + * @var \Closure $locatedSerializer + * @phpstan-var \Closure(TBlockType) : (Writer|BlockStateData) $locatedSerializer */ - $serializer = $locatedSerializer; + $result = $locatedSerializer($blockState); - /** @var Writer $writer */ - $writer = $serializer($blockState); - return $writer->getBlockStateData(); + return $result instanceof Writer ? $result->getBlockStateData() : $result; } private function registerCandleSerializers() : void{ @@ -330,7 +333,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ } public function registerFlatColorBlockSerializers() : void{ - $this->map(Blocks::STAINED_HARDENED_GLASS(), fn(StainedHardenedGlass $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::STAINED_HARDENED_GLASS(), fn(StainedHardenedGlass $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::HARD_BLACK_STAINED_GLASS, DyeColor::BLUE => Ids::HARD_BLUE_STAINED_GLASS, DyeColor::BROWN => Ids::HARD_BROWN_STAINED_GLASS, @@ -347,9 +350,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::HARD_RED_STAINED_GLASS, DyeColor::WHITE => Ids::HARD_WHITE_STAINED_GLASS, DyeColor::YELLOW => Ids::HARD_YELLOW_STAINED_GLASS, - })); + }, [])); - $this->map(Blocks::STAINED_HARDENED_GLASS_PANE(), fn(StainedHardenedGlassPane $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::STAINED_HARDENED_GLASS_PANE(), fn(StainedHardenedGlassPane $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::HARD_BLACK_STAINED_GLASS_PANE, DyeColor::BLUE => Ids::HARD_BLUE_STAINED_GLASS_PANE, DyeColor::BROWN => Ids::HARD_BROWN_STAINED_GLASS_PANE, @@ -366,7 +369,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::HARD_RED_STAINED_GLASS_PANE, DyeColor::WHITE => Ids::HARD_WHITE_STAINED_GLASS_PANE, DyeColor::YELLOW => Ids::HARD_YELLOW_STAINED_GLASS_PANE, - })); + }, [])); $this->map(Blocks::GLAZED_TERRACOTTA(), function(GlazedTerracotta $block) : Writer{ return Writer::create(match($block->getColor()){ @@ -390,7 +393,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeHorizontalFacing($block->getFacing()); }); - $this->map(Blocks::WOOL(), fn(Wool $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::WOOL(), fn(Wool $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_WOOL, DyeColor::BLUE => Ids::BLUE_WOOL, DyeColor::BROWN => Ids::BROWN_WOOL, @@ -407,9 +410,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_WOOL, DyeColor::WHITE => Ids::WHITE_WOOL, DyeColor::YELLOW => Ids::YELLOW_WOOL, - })); + }, [])); - $this->map(Blocks::CARPET(), fn(Carpet $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::CARPET(), fn(Carpet $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_CARPET, DyeColor::BLUE => Ids::BLUE_CARPET, DyeColor::BROWN => Ids::BROWN_CARPET, @@ -426,9 +429,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_CARPET, DyeColor::WHITE => Ids::WHITE_CARPET, DyeColor::YELLOW => Ids::YELLOW_CARPET, - })); + }, [])); - $this->map(Blocks::DYED_SHULKER_BOX(), fn(DyedShulkerBox $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::DYED_SHULKER_BOX(), fn(DyedShulkerBox $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_SHULKER_BOX, DyeColor::BLUE => Ids::BLUE_SHULKER_BOX, DyeColor::BROWN => Ids::BROWN_SHULKER_BOX, @@ -445,9 +448,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_SHULKER_BOX, DyeColor::WHITE => Ids::WHITE_SHULKER_BOX, DyeColor::YELLOW => Ids::YELLOW_SHULKER_BOX, - })); + }, [])); - $this->map(Blocks::CONCRETE(), fn(Concrete $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::CONCRETE(), fn(Concrete $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_CONCRETE, DyeColor::BLUE => Ids::BLUE_CONCRETE, DyeColor::BROWN => Ids::BROWN_CONCRETE, @@ -464,9 +467,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_CONCRETE, DyeColor::WHITE => Ids::WHITE_CONCRETE, DyeColor::YELLOW => Ids::YELLOW_CONCRETE, - })); + }, [])); - $this->map(Blocks::CONCRETE_POWDER(), fn(ConcretePowder $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::CONCRETE_POWDER(), fn(ConcretePowder $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_CONCRETE_POWDER, DyeColor::BLUE => Ids::BLUE_CONCRETE_POWDER, DyeColor::BROWN => Ids::BROWN_CONCRETE_POWDER, @@ -483,9 +486,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_CONCRETE_POWDER, DyeColor::WHITE => Ids::WHITE_CONCRETE_POWDER, DyeColor::YELLOW => Ids::YELLOW_CONCRETE_POWDER, - })); + }, [])); - $this->map(Blocks::STAINED_CLAY(), fn(StainedHardenedClay $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::STAINED_CLAY(), fn(StainedHardenedClay $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_TERRACOTTA, DyeColor::BLUE => Ids::BLUE_TERRACOTTA, DyeColor::BROWN => Ids::BROWN_TERRACOTTA, @@ -502,9 +505,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_TERRACOTTA, DyeColor::WHITE => Ids::WHITE_TERRACOTTA, DyeColor::YELLOW => Ids::YELLOW_TERRACOTTA, - })); + }, [])); - $this->map(Blocks::STAINED_GLASS(), fn(StainedGlass $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::STAINED_GLASS(), fn(StainedGlass $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_STAINED_GLASS, DyeColor::BLUE => Ids::BLUE_STAINED_GLASS, DyeColor::BROWN => Ids::BROWN_STAINED_GLASS, @@ -521,9 +524,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_STAINED_GLASS, DyeColor::WHITE => Ids::WHITE_STAINED_GLASS, DyeColor::YELLOW => Ids::YELLOW_STAINED_GLASS, - })); + }, [])); - $this->map(Blocks::STAINED_GLASS_PANE(), fn(StainedGlassPane $block) => Writer::create(match($block->getColor()){ + $this->map(Blocks::STAINED_GLASS_PANE(), fn(StainedGlassPane $block) => BlockStateData::current(match($block->getColor()){ DyeColor::BLACK => Ids::BLACK_STAINED_GLASS_PANE, DyeColor::BLUE => Ids::BLUE_STAINED_GLASS_PANE, DyeColor::BROWN => Ids::BROWN_STAINED_GLASS_PANE, @@ -540,19 +543,17 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::RED => Ids::RED_STAINED_GLASS_PANE, DyeColor::WHITE => Ids::WHITE_STAINED_GLASS_PANE, DyeColor::YELLOW => Ids::YELLOW_STAINED_GLASS_PANE, - })); + }, [])); } private function registerFlatCoralSerializers() : void{ - $this->map(Blocks::CORAL(), fn(Coral $block) => Writer::create( - match($block->getCoralType()){ - CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL : Ids::BRAIN_CORAL, - CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL : Ids::BUBBLE_CORAL, - CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL : Ids::FIRE_CORAL, - CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL : Ids::HORN_CORAL, - CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL : Ids::TUBE_CORAL, - } - )); + $this->map(Blocks::CORAL(), fn(Coral $block) => BlockStateData::current(match($block->getCoralType()){ + CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL : Ids::BRAIN_CORAL, + CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL : Ids::BUBBLE_CORAL, + CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL : Ids::FIRE_CORAL, + CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL : Ids::HORN_CORAL, + CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL : Ids::TUBE_CORAL, + }, [])); $this->map(Blocks::CORAL_FAN(), fn(FloorCoralFan $block) => Writer::create( match($block->getCoralType()){ @@ -568,15 +569,13 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ default => throw new BlockStateSerializeException("Invalid axis {$axis}"), })); - $this->map(Blocks::CORAL_BLOCK(), fn(CoralBlock $block) => Writer::create( - match($block->getCoralType()){ - CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_BLOCK : Ids::BRAIN_CORAL_BLOCK, - CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_BLOCK : Ids::BUBBLE_CORAL_BLOCK, - CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_BLOCK : Ids::FIRE_CORAL_BLOCK, - CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_BLOCK : Ids::HORN_CORAL_BLOCK, - CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_BLOCK : Ids::TUBE_CORAL_BLOCK, - } - )); + $this->map(Blocks::CORAL_BLOCK(), fn(CoralBlock $block) => BlockStateData::current(match($block->getCoralType()){ + CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_BLOCK : Ids::BRAIN_CORAL_BLOCK, + CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_BLOCK : Ids::BUBBLE_CORAL_BLOCK, + CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_BLOCK : Ids::FIRE_CORAL_BLOCK, + CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_BLOCK : Ids::HORN_CORAL_BLOCK, + CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_BLOCK : Ids::TUBE_CORAL_BLOCK, + }, [])); $this->map(Blocks::WALL_CORAL_FAN(), fn(WallCoralFan $block) => Writer::create( match($block->getCoralType()){ @@ -591,7 +590,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ } private function registerCauldronSerializers() : void{ - $this->map(Blocks::CAULDRON(), fn() => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0)); + $this->map(Blocks::CAULDRON(), Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0)); $this->map(Blocks::LAVA_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_LAVA, $b->getFillLevel())); //potion cauldrons store their real information in the block actor data $this->map(Blocks::POTION_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); @@ -798,52 +797,60 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ } private function registerCopperSerializers() : void{ - $this->map(Blocks::COPPER(), function(Copper $block) : Writer{ + $this->map(Blocks::COPPER(), function(Copper $block) : BlockStateData{ $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) : - Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER) + return BlockStateData::current( + $block->isWaxed() ? + Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) : + Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER), + [] ); }); - $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{ + $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : BlockStateData{ $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_CHISELED_COPPER, - Ids::WAXED_EXPOSED_CHISELED_COPPER, - Ids::WAXED_WEATHERED_CHISELED_COPPER, - Ids::WAXED_OXIDIZED_CHISELED_COPPER - ) : - Helper::selectCopperId($oxidation, - Ids::CHISELED_COPPER, - Ids::EXPOSED_CHISELED_COPPER, - Ids::WEATHERED_CHISELED_COPPER, - Ids::OXIDIZED_CHISELED_COPPER - ) + return BlockStateData::current( + $block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_CHISELED_COPPER, + Ids::WAXED_EXPOSED_CHISELED_COPPER, + Ids::WAXED_WEATHERED_CHISELED_COPPER, + Ids::WAXED_OXIDIZED_CHISELED_COPPER + ) : + Helper::selectCopperId($oxidation, + Ids::CHISELED_COPPER, + Ids::EXPOSED_CHISELED_COPPER, + Ids::WEATHERED_CHISELED_COPPER, + Ids::OXIDIZED_CHISELED_COPPER + ), + [] ); }); - $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{ + $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : BlockStateData{ $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_COPPER_GRATE, - Ids::WAXED_EXPOSED_COPPER_GRATE, - Ids::WAXED_WEATHERED_COPPER_GRATE, - Ids::WAXED_OXIDIZED_COPPER_GRATE - ) : - Helper::selectCopperId($oxidation, - Ids::COPPER_GRATE, - Ids::EXPOSED_COPPER_GRATE, - Ids::WEATHERED_COPPER_GRATE, - Ids::OXIDIZED_COPPER_GRATE - ) + return BlockStateData::current( + $block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_COPPER_GRATE, + Ids::WAXED_EXPOSED_COPPER_GRATE, + Ids::WAXED_WEATHERED_COPPER_GRATE, + Ids::WAXED_OXIDIZED_COPPER_GRATE + ) : + Helper::selectCopperId($oxidation, + Ids::COPPER_GRATE, + Ids::EXPOSED_COPPER_GRATE, + Ids::WEATHERED_COPPER_GRATE, + Ids::OXIDIZED_COPPER_GRATE + ), + [] ); }); - $this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{ + $this->map(Blocks::CUT_COPPER(), function(Copper $block) : BlockStateData{ $oxidation = $block->getOxidation(); - return new Writer($block->isWaxed() ? - Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) : - Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER) + return BlockStateData::current( + $block->isWaxed() ? + Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) : + Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER), + [] ); }); $this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{ @@ -1279,7 +1286,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered()) ->writeInt(StateNames::RAIL_DIRECTION, $block->getShape()); }); - $this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), fn() => Writer::create(Ids::MUSHROOM_STEM) + $this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM)); $this->map(Blocks::AMETHYST_CLUSTER(), fn(AmethystCluster $block) => Writer::create( match($stage = $block->getStage()){ @@ -1476,13 +1483,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSlab(Blocks::DIORITE_SLAB(), Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB); $this->mapStairs(Blocks::DIORITE_STAIRS(), Ids::DIORITE_STAIRS); $this->map(Blocks::DIORITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::DIORITE_WALL))); - $this->map(Blocks::DIRT(), function(Dirt $block) : Writer{ - return Writer::create(match($block->getDirtType()){ - DirtType::NORMAL => Ids::DIRT, - DirtType::COARSE => Ids::COARSE_DIRT, - DirtType::ROOTED => Ids::DIRT_WITH_ROOTS, - }); - }); + $this->map(Blocks::DIRT(), fn(Dirt $block) => BlockStateData::current(match($block->getDirtType()){ + DirtType::NORMAL => Ids::DIRT, + DirtType::COARSE => Ids::COARSE_DIRT, + DirtType::ROOTED => Ids::DIRT_WITH_ROOTS, + }, [])); $this->map(Blocks::DOUBLE_TALLGRASS(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::TALL_GRASS))); $this->map(Blocks::ELEMENT_CONSTRUCTOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::ELEMENT_CONSTRUCTOR))); $this->map(Blocks::ENDER_CHEST(), function(EnderChest $block) : Writer{ @@ -1510,10 +1515,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return Writer::create(Ids::FIRE) ->writeInt(StateNames::AGE, $block->getAge()); }); - $this->map(Blocks::FLOWER_POT(), function() : Writer{ - return Writer::create(Ids::FLOWER_POT) - ->writeBool(StateNames::UPDATE_BIT, false); //to keep MCPE happy - }); + $this->map(Blocks::FLOWER_POT(), Writer::create(Ids::FLOWER_POT) + ->writeBool(StateNames::UPDATE_BIT, false) //to keep MCPE happy + ); $this->map(Blocks::FROGLIGHT(), function(Froglight $block){ return Writer::create(match($block->getFroglightType()){ FroglightType::OCHRE => Ids::OCHRE_FROGLIGHT, @@ -1579,27 +1583,25 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ LeverFacing::EAST => StringValues::LEVER_DIRECTION_EAST, }); }); - $this->map(Blocks::LIGHT(), function(Light $block) : Writer{ - return Writer::create(match($block->getLightLevel()){ - 0 => Ids::LIGHT_BLOCK_0, - 1 => Ids::LIGHT_BLOCK_1, - 2 => Ids::LIGHT_BLOCK_2, - 3 => Ids::LIGHT_BLOCK_3, - 4 => Ids::LIGHT_BLOCK_4, - 5 => Ids::LIGHT_BLOCK_5, - 6 => Ids::LIGHT_BLOCK_6, - 7 => Ids::LIGHT_BLOCK_7, - 8 => Ids::LIGHT_BLOCK_8, - 9 => Ids::LIGHT_BLOCK_9, - 10 => Ids::LIGHT_BLOCK_10, - 11 => Ids::LIGHT_BLOCK_11, - 12 => Ids::LIGHT_BLOCK_12, - 13 => Ids::LIGHT_BLOCK_13, - 14 => Ids::LIGHT_BLOCK_14, - 15 => Ids::LIGHT_BLOCK_15, - default => throw new BlockStateSerializeException("Invalid light level " . $block->getLightLevel()), - }); - }); + $this->map(Blocks::LIGHT(), fn(Light $block) => BlockStateData::current(match($block->getLightLevel()){ + 0 => Ids::LIGHT_BLOCK_0, + 1 => Ids::LIGHT_BLOCK_1, + 2 => Ids::LIGHT_BLOCK_2, + 3 => Ids::LIGHT_BLOCK_3, + 4 => Ids::LIGHT_BLOCK_4, + 5 => Ids::LIGHT_BLOCK_5, + 6 => Ids::LIGHT_BLOCK_6, + 7 => Ids::LIGHT_BLOCK_7, + 8 => Ids::LIGHT_BLOCK_8, + 9 => Ids::LIGHT_BLOCK_9, + 10 => Ids::LIGHT_BLOCK_10, + 11 => Ids::LIGHT_BLOCK_11, + 12 => Ids::LIGHT_BLOCK_12, + 13 => Ids::LIGHT_BLOCK_13, + 14 => Ids::LIGHT_BLOCK_14, + 15 => Ids::LIGHT_BLOCK_15, + default => throw new BlockStateSerializeException("Invalid light level " . $block->getLightLevel()), + }, [])); $this->map(Blocks::LIGHTNING_ROD(), function(LightningRod $block) : Writer{ return Writer::create(Ids::LIGHTNING_ROD) ->writeFacingDirection($block->getFacing()); @@ -1626,7 +1628,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::MUD_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::MUD_BRICK_WALL))); $this->map(Blocks::MUDDY_MANGROVE_ROOTS(), fn(SimplePillar $block) => Writer::create(Ids::MUDDY_MANGROVE_ROOTS) ->writePillarAxis($block->getAxis())); - $this->map(Blocks::MUSHROOM_STEM(), fn() => Writer::create(Ids::MUSHROOM_STEM) + $this->map(Blocks::MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_STEM)); $this->mapSlab(Blocks::NETHER_BRICK_SLAB(), Ids::NETHER_BRICK_SLAB, Ids::NETHER_BRICK_DOUBLE_SLAB); $this->mapStairs(Blocks::NETHER_BRICK_STAIRS(), Ids::NETHER_BRICK_STAIRS); @@ -1698,12 +1700,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSlab(Blocks::PRISMARINE_SLAB(), Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB); $this->mapStairs(Blocks::PRISMARINE_STAIRS(), Ids::PRISMARINE_STAIRS); $this->map(Blocks::PRISMARINE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::PRISMARINE_WALL))); - $this->map(Blocks::PUMPKIN(), function() : Writer{ - return Writer::create(Ids::PUMPKIN) - ->writeCardinalHorizontalFacing(Facing::SOUTH); //no longer used - }); + $this->map(Blocks::PUMPKIN(), Writer::create(Ids::PUMPKIN) + ->writeCardinalHorizontalFacing(Facing::SOUTH) //no longer used + ); $this->map(Blocks::PUMPKIN_STEM(), fn(PumpkinStem $block) => Helper::encodeStem($block, new Writer(Ids::PUMPKIN_STEM))); - $this->map(Blocks::PURPUR(), fn() => Writer::create(Ids::PURPUR_BLOCK)->writePillarAxis(Axis::Y)); + $this->map(Blocks::PURPUR(), Writer::create(Ids::PURPUR_BLOCK)->writePillarAxis(Axis::Y)); $this->map(Blocks::PURPLE_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_PURPLE))); $this->map(Blocks::PURPUR_PILLAR(), function(SimplePillar $block) : Writer{ return Writer::create(Ids::PURPUR_PILLAR) @@ -1711,7 +1712,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }); $this->mapSlab(Blocks::PURPUR_SLAB(), Ids::PURPUR_SLAB, Ids::PURPUR_DOUBLE_SLAB); $this->mapStairs(Blocks::PURPUR_STAIRS(), Ids::PURPUR_STAIRS); - $this->map(Blocks::QUARTZ(), fn() => Helper::encodeQuartz(Axis::Y, Writer::create(Ids::QUARTZ_BLOCK))); + $this->map(Blocks::QUARTZ(), Helper::encodeQuartz(Axis::Y, Writer::create(Ids::QUARTZ_BLOCK))); $this->map(Blocks::QUARTZ_PILLAR(), fn(SimplePillar $block) => Helper::encodeQuartz($block->getAxis(), Writer::create(Ids::QUARTZ_PILLAR))); $this->mapSlab(Blocks::QUARTZ_SLAB(), Ids::QUARTZ_SLAB, Ids::QUARTZ_DOUBLE_SLAB); $this->mapStairs(Blocks::QUARTZ_STAIRS(), Ids::QUARTZ_STAIRS); @@ -1774,7 +1775,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); }); $this->map(Blocks::SMOKER(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::SMOKER, Ids::LIT_SMOKER)); - $this->map(Blocks::SMOOTH_QUARTZ(), fn() => Helper::encodeQuartz(Axis::Y, Writer::create(Ids::SMOOTH_QUARTZ))); + $this->map(Blocks::SMOOTH_QUARTZ(), Helper::encodeQuartz(Axis::Y, Writer::create(Ids::SMOOTH_QUARTZ))); $this->mapSlab(Blocks::SMOOTH_QUARTZ_SLAB(), Ids::SMOOTH_QUARTZ_SLAB, Ids::SMOOTH_QUARTZ_DOUBLE_SLAB); $this->mapStairs(Blocks::SMOOTH_QUARTZ_STAIRS(), Ids::SMOOTH_QUARTZ_STAIRS); $this->mapSlab(Blocks::SMOOTH_RED_SANDSTONE_SLAB(), Ids::SMOOTH_RED_SANDSTONE_SLAB, Ids::SMOOTH_RED_SANDSTONE_DOUBLE_SLAB); @@ -1792,10 +1793,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeCardinalHorizontalFacing($block->getFacing()) ->writeBool(StateNames::EXTINGUISHED, !$block->isLit()); }); - $this->map(Blocks::SOUL_FIRE(), function() : Writer{ - return Writer::create(Ids::SOUL_FIRE) - ->writeInt(StateNames::AGE, 0); //useless for soul fire, we don't track it - }); + $this->map(Blocks::SOUL_FIRE(), Writer::create(Ids::SOUL_FIRE) + ->writeInt(StateNames::AGE, 0) //useless for soul fire, we don't track it + ); $this->map(Blocks::SOUL_LANTERN(), function(Lantern $block) : Writer{ return Writer::create(Ids::SOUL_LANTERN) ->writeBool(StateNames::HANGING, $block->isHanging()); From e89523ce66729f79ef63c2edce676e0c1e1261bd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 15 Aug 2025 22:02:12 +0100 Subject: [PATCH 263/334] First look at flattened ID specialisation for block serializers in the future we should be able to unify these, similarly to simple mappings. unifying blocks with states will, however, be considerably more work. only color benefits from this so far --- .../convert/BlockObjectToStateSerializer.php | 334 ++++++------------ .../BlockStateToObjectDeserializer.php | 330 ++++++----------- .../bedrock/block/convert/StringEnumMap.php | 78 ++++ .../bedrock/block/convert/ValueMappings.php | 83 +++++ 4 files changed, 377 insertions(+), 448 deletions(-) create mode 100644 src/data/bedrock/block/convert/StringEnumMap.php create mode 100644 src/data/bedrock/block/convert/ValueMappings.php diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 49f0269ed..6b545bd01 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -290,86 +290,72 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return $result instanceof Writer ? $result->getBlockStateData() : $result; } + /** + * @phpstan-template TBlock of Block + * @phpstan-template TEnum of \UnitEnum + * + * @phpstan-param TBlock $block + * @phpstan-param StringEnumMap $mapProperty + * @phpstan-param \Closure(TBlock) : TEnum $getProperty + * @phpstan-param ?\Closure(TBlock, Writer) : Writer $extra + */ + public function mapFlattenedEnum( + Block $block, + StringEnumMap $mapProperty, + string $prefix, + string $suffix, + \Closure $getProperty, + ?\Closure $extra = null + ) : void{ + $this->map($block, function(Block $block) use ($getProperty, $mapProperty, $prefix, $suffix, $extra) : Writer{ + $property = $getProperty($block); + $infix = $mapProperty->enumToValue($property); + $id = $prefix . $infix . $suffix; + $writer = new Writer($id); + if($extra !== null){ + $extra($block, $writer); + } + return $writer; + }); + } + private function registerCandleSerializers() : void{ $this->map(Blocks::CANDLE(), fn(Candle $block) => Helper::encodeCandle($block, new Writer(Ids::CANDLE))); - $this->map(Blocks::DYED_CANDLE(), fn(DyedCandle $block) => Helper::encodeCandle($block, new Writer(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_CANDLE, - DyeColor::BLUE => Ids::BLUE_CANDLE, - DyeColor::BROWN => Ids::BROWN_CANDLE, - DyeColor::CYAN => Ids::CYAN_CANDLE, - DyeColor::GRAY => Ids::GRAY_CANDLE, - DyeColor::GREEN => Ids::GREEN_CANDLE, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CANDLE, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CANDLE, - DyeColor::LIME => Ids::LIME_CANDLE, - DyeColor::MAGENTA => Ids::MAGENTA_CANDLE, - DyeColor::ORANGE => Ids::ORANGE_CANDLE, - DyeColor::PINK => Ids::PINK_CANDLE, - DyeColor::PURPLE => Ids::PURPLE_CANDLE, - DyeColor::RED => Ids::RED_CANDLE, - DyeColor::WHITE => Ids::WHITE_CANDLE, - DyeColor::YELLOW => Ids::YELLOW_CANDLE, - }))); + $this->mapFlattenedEnum( + Blocks::DYED_CANDLE(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_candle", + fn(DyedCandle $block) => $block->getColor(), + Helper::encodeCandle(...) + ); $this->map(Blocks::CAKE_WITH_CANDLE(), fn(CakeWithCandle $block) => Writer::create(Ids::CANDLE_CAKE) ->writeBool(StateNames::LIT, $block->isLit())); - $this->map(Blocks::CAKE_WITH_DYED_CANDLE(), fn(CakeWithDyedCandle $block) => Writer::create(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_CANDLE_CAKE, - DyeColor::BLUE => Ids::BLUE_CANDLE_CAKE, - DyeColor::BROWN => Ids::BROWN_CANDLE_CAKE, - DyeColor::CYAN => Ids::CYAN_CANDLE_CAKE, - DyeColor::GRAY => Ids::GRAY_CANDLE_CAKE, - DyeColor::GREEN => Ids::GREEN_CANDLE_CAKE, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CANDLE_CAKE, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CANDLE_CAKE, - DyeColor::LIME => Ids::LIME_CANDLE_CAKE, - DyeColor::MAGENTA => Ids::MAGENTA_CANDLE_CAKE, - DyeColor::ORANGE => Ids::ORANGE_CANDLE_CAKE, - DyeColor::PINK => Ids::PINK_CANDLE_CAKE, - DyeColor::PURPLE => Ids::PURPLE_CANDLE_CAKE, - DyeColor::RED => Ids::RED_CANDLE_CAKE, - DyeColor::WHITE => Ids::WHITE_CANDLE_CAKE, - DyeColor::YELLOW => Ids::YELLOW_CANDLE_CAKE, - })->writeBool(StateNames::LIT, $block->isLit())); + $this->mapFlattenedEnum( + Blocks::CAKE_WITH_DYED_CANDLE(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_candle_cake", + fn(CakeWithDyedCandle $block) => $block->getColor(), + fn(CakeWithDyedCandle $block, Writer $writer) => $writer->writeBool(StateNames::LIT, $block->isLit()) + ); } public function registerFlatColorBlockSerializers() : void{ - $this->map(Blocks::STAINED_HARDENED_GLASS(), fn(StainedHardenedGlass $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::HARD_BLACK_STAINED_GLASS, - DyeColor::BLUE => Ids::HARD_BLUE_STAINED_GLASS, - DyeColor::BROWN => Ids::HARD_BROWN_STAINED_GLASS, - DyeColor::CYAN => Ids::HARD_CYAN_STAINED_GLASS, - DyeColor::GRAY => Ids::HARD_GRAY_STAINED_GLASS, - DyeColor::GREEN => Ids::HARD_GREEN_STAINED_GLASS, - DyeColor::LIGHT_BLUE => Ids::HARD_LIGHT_BLUE_STAINED_GLASS, - DyeColor::LIGHT_GRAY => Ids::HARD_LIGHT_GRAY_STAINED_GLASS, - DyeColor::LIME => Ids::HARD_LIME_STAINED_GLASS, - DyeColor::MAGENTA => Ids::HARD_MAGENTA_STAINED_GLASS, - DyeColor::ORANGE => Ids::HARD_ORANGE_STAINED_GLASS, - DyeColor::PINK => Ids::HARD_PINK_STAINED_GLASS, - DyeColor::PURPLE => Ids::HARD_PURPLE_STAINED_GLASS, - DyeColor::RED => Ids::HARD_RED_STAINED_GLASS, - DyeColor::WHITE => Ids::HARD_WHITE_STAINED_GLASS, - DyeColor::YELLOW => Ids::HARD_YELLOW_STAINED_GLASS, - }, [])); - - $this->map(Blocks::STAINED_HARDENED_GLASS_PANE(), fn(StainedHardenedGlassPane $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::HARD_BLACK_STAINED_GLASS_PANE, - DyeColor::BLUE => Ids::HARD_BLUE_STAINED_GLASS_PANE, - DyeColor::BROWN => Ids::HARD_BROWN_STAINED_GLASS_PANE, - DyeColor::CYAN => Ids::HARD_CYAN_STAINED_GLASS_PANE, - DyeColor::GRAY => Ids::HARD_GRAY_STAINED_GLASS_PANE, - DyeColor::GREEN => Ids::HARD_GREEN_STAINED_GLASS_PANE, - DyeColor::LIGHT_BLUE => Ids::HARD_LIGHT_BLUE_STAINED_GLASS_PANE, - DyeColor::LIGHT_GRAY => Ids::HARD_LIGHT_GRAY_STAINED_GLASS_PANE, - DyeColor::LIME => Ids::HARD_LIME_STAINED_GLASS_PANE, - DyeColor::MAGENTA => Ids::HARD_MAGENTA_STAINED_GLASS_PANE, - DyeColor::ORANGE => Ids::HARD_ORANGE_STAINED_GLASS_PANE, - DyeColor::PINK => Ids::HARD_PINK_STAINED_GLASS_PANE, - DyeColor::PURPLE => Ids::HARD_PURPLE_STAINED_GLASS_PANE, - DyeColor::RED => Ids::HARD_RED_STAINED_GLASS_PANE, - DyeColor::WHITE => Ids::HARD_WHITE_STAINED_GLASS_PANE, - DyeColor::YELLOW => Ids::HARD_YELLOW_STAINED_GLASS_PANE, - }, [])); + $this->mapFlattenedEnum( + Blocks::STAINED_HARDENED_GLASS(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:hard_", + "_stained_glass", + fn(StainedHardenedGlass $block) => $block->getColor() + ); + $this->mapFlattenedEnum( + Blocks::STAINED_HARDENED_GLASS_PANE(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:hard_", + "_stained_glass_pane", + fn(StainedHardenedGlassPane $block) => $block->getColor(), + ); $this->map(Blocks::GLAZED_TERRACOTTA(), function(GlazedTerracotta $block) : Writer{ return Writer::create(match($block->getColor()){ @@ -393,157 +379,67 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeHorizontalFacing($block->getFacing()); }); - $this->map(Blocks::WOOL(), fn(Wool $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_WOOL, - DyeColor::BLUE => Ids::BLUE_WOOL, - DyeColor::BROWN => Ids::BROWN_WOOL, - DyeColor::CYAN => Ids::CYAN_WOOL, - DyeColor::GRAY => Ids::GRAY_WOOL, - DyeColor::GREEN => Ids::GREEN_WOOL, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_WOOL, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_WOOL, - DyeColor::LIME => Ids::LIME_WOOL, - DyeColor::MAGENTA => Ids::MAGENTA_WOOL, - DyeColor::ORANGE => Ids::ORANGE_WOOL, - DyeColor::PINK => Ids::PINK_WOOL, - DyeColor::PURPLE => Ids::PURPLE_WOOL, - DyeColor::RED => Ids::RED_WOOL, - DyeColor::WHITE => Ids::WHITE_WOOL, - DyeColor::YELLOW => Ids::YELLOW_WOOL, - }, [])); + $this->mapFlattenedEnum( + Blocks::WOOL(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_wool", + fn(Wool $block) => $block->getColor() + ); - $this->map(Blocks::CARPET(), fn(Carpet $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_CARPET, - DyeColor::BLUE => Ids::BLUE_CARPET, - DyeColor::BROWN => Ids::BROWN_CARPET, - DyeColor::CYAN => Ids::CYAN_CARPET, - DyeColor::GRAY => Ids::GRAY_CARPET, - DyeColor::GREEN => Ids::GREEN_CARPET, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CARPET, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CARPET, - DyeColor::LIME => Ids::LIME_CARPET, - DyeColor::MAGENTA => Ids::MAGENTA_CARPET, - DyeColor::ORANGE => Ids::ORANGE_CARPET, - DyeColor::PINK => Ids::PINK_CARPET, - DyeColor::PURPLE => Ids::PURPLE_CARPET, - DyeColor::RED => Ids::RED_CARPET, - DyeColor::WHITE => Ids::WHITE_CARPET, - DyeColor::YELLOW => Ids::YELLOW_CARPET, - }, [])); + $this->mapFlattenedEnum( + Blocks::CARPET(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_carpet", + fn(Carpet $block) => $block->getColor() + ); - $this->map(Blocks::DYED_SHULKER_BOX(), fn(DyedShulkerBox $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_SHULKER_BOX, - DyeColor::BLUE => Ids::BLUE_SHULKER_BOX, - DyeColor::BROWN => Ids::BROWN_SHULKER_BOX, - DyeColor::CYAN => Ids::CYAN_SHULKER_BOX, - DyeColor::GRAY => Ids::GRAY_SHULKER_BOX, - DyeColor::GREEN => Ids::GREEN_SHULKER_BOX, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_SHULKER_BOX, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_SHULKER_BOX, - DyeColor::LIME => Ids::LIME_SHULKER_BOX, - DyeColor::MAGENTA => Ids::MAGENTA_SHULKER_BOX, - DyeColor::ORANGE => Ids::ORANGE_SHULKER_BOX, - DyeColor::PINK => Ids::PINK_SHULKER_BOX, - DyeColor::PURPLE => Ids::PURPLE_SHULKER_BOX, - DyeColor::RED => Ids::RED_SHULKER_BOX, - DyeColor::WHITE => Ids::WHITE_SHULKER_BOX, - DyeColor::YELLOW => Ids::YELLOW_SHULKER_BOX, - }, [])); + $this->mapFlattenedEnum( + Blocks::DYED_SHULKER_BOX(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_shulker_box", + fn(DyedShulkerBox $block) => $block->getColor() + ); - $this->map(Blocks::CONCRETE(), fn(Concrete $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_CONCRETE, - DyeColor::BLUE => Ids::BLUE_CONCRETE, - DyeColor::BROWN => Ids::BROWN_CONCRETE, - DyeColor::CYAN => Ids::CYAN_CONCRETE, - DyeColor::GRAY => Ids::GRAY_CONCRETE, - DyeColor::GREEN => Ids::GREEN_CONCRETE, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CONCRETE, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CONCRETE, - DyeColor::LIME => Ids::LIME_CONCRETE, - DyeColor::MAGENTA => Ids::MAGENTA_CONCRETE, - DyeColor::ORANGE => Ids::ORANGE_CONCRETE, - DyeColor::PINK => Ids::PINK_CONCRETE, - DyeColor::PURPLE => Ids::PURPLE_CONCRETE, - DyeColor::RED => Ids::RED_CONCRETE, - DyeColor::WHITE => Ids::WHITE_CONCRETE, - DyeColor::YELLOW => Ids::YELLOW_CONCRETE, - }, [])); + $this->mapFlattenedEnum( + Blocks::CONCRETE(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_concrete", + fn(Concrete $block) => $block->getColor() + ); + $this->mapFlattenedEnum( + Blocks::CONCRETE_POWDER(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_concrete_powder", + fn(ConcretePowder $block) => $block->getColor() + ); - $this->map(Blocks::CONCRETE_POWDER(), fn(ConcretePowder $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_CONCRETE_POWDER, - DyeColor::BLUE => Ids::BLUE_CONCRETE_POWDER, - DyeColor::BROWN => Ids::BROWN_CONCRETE_POWDER, - DyeColor::CYAN => Ids::CYAN_CONCRETE_POWDER, - DyeColor::GRAY => Ids::GRAY_CONCRETE_POWDER, - DyeColor::GREEN => Ids::GREEN_CONCRETE_POWDER, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CONCRETE_POWDER, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CONCRETE_POWDER, - DyeColor::LIME => Ids::LIME_CONCRETE_POWDER, - DyeColor::MAGENTA => Ids::MAGENTA_CONCRETE_POWDER, - DyeColor::ORANGE => Ids::ORANGE_CONCRETE_POWDER, - DyeColor::PINK => Ids::PINK_CONCRETE_POWDER, - DyeColor::PURPLE => Ids::PURPLE_CONCRETE_POWDER, - DyeColor::RED => Ids::RED_CONCRETE_POWDER, - DyeColor::WHITE => Ids::WHITE_CONCRETE_POWDER, - DyeColor::YELLOW => Ids::YELLOW_CONCRETE_POWDER, - }, [])); + $this->mapFlattenedEnum( + Blocks::STAINED_CLAY(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_terracotta", + fn(StainedHardenedClay $block) => $block->getColor() + ); - $this->map(Blocks::STAINED_CLAY(), fn(StainedHardenedClay $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_TERRACOTTA, - DyeColor::BLUE => Ids::BLUE_TERRACOTTA, - DyeColor::BROWN => Ids::BROWN_TERRACOTTA, - DyeColor::CYAN => Ids::CYAN_TERRACOTTA, - DyeColor::GRAY => Ids::GRAY_TERRACOTTA, - DyeColor::GREEN => Ids::GREEN_TERRACOTTA, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_TERRACOTTA, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_TERRACOTTA, - DyeColor::LIME => Ids::LIME_TERRACOTTA, - DyeColor::MAGENTA => Ids::MAGENTA_TERRACOTTA, - DyeColor::ORANGE => Ids::ORANGE_TERRACOTTA, - DyeColor::PINK => Ids::PINK_TERRACOTTA, - DyeColor::PURPLE => Ids::PURPLE_TERRACOTTA, - DyeColor::RED => Ids::RED_TERRACOTTA, - DyeColor::WHITE => Ids::WHITE_TERRACOTTA, - DyeColor::YELLOW => Ids::YELLOW_TERRACOTTA, - }, [])); - - $this->map(Blocks::STAINED_GLASS(), fn(StainedGlass $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_STAINED_GLASS, - DyeColor::BLUE => Ids::BLUE_STAINED_GLASS, - DyeColor::BROWN => Ids::BROWN_STAINED_GLASS, - DyeColor::CYAN => Ids::CYAN_STAINED_GLASS, - DyeColor::GRAY => Ids::GRAY_STAINED_GLASS, - DyeColor::GREEN => Ids::GREEN_STAINED_GLASS, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_STAINED_GLASS, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_STAINED_GLASS, - DyeColor::LIME => Ids::LIME_STAINED_GLASS, - DyeColor::MAGENTA => Ids::MAGENTA_STAINED_GLASS, - DyeColor::ORANGE => Ids::ORANGE_STAINED_GLASS, - DyeColor::PINK => Ids::PINK_STAINED_GLASS, - DyeColor::PURPLE => Ids::PURPLE_STAINED_GLASS, - DyeColor::RED => Ids::RED_STAINED_GLASS, - DyeColor::WHITE => Ids::WHITE_STAINED_GLASS, - DyeColor::YELLOW => Ids::YELLOW_STAINED_GLASS, - }, [])); - - $this->map(Blocks::STAINED_GLASS_PANE(), fn(StainedGlassPane $block) => BlockStateData::current(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_STAINED_GLASS_PANE, - DyeColor::BLUE => Ids::BLUE_STAINED_GLASS_PANE, - DyeColor::BROWN => Ids::BROWN_STAINED_GLASS_PANE, - DyeColor::CYAN => Ids::CYAN_STAINED_GLASS_PANE, - DyeColor::GRAY => Ids::GRAY_STAINED_GLASS_PANE, - DyeColor::GREEN => Ids::GREEN_STAINED_GLASS_PANE, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_STAINED_GLASS_PANE, - DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_STAINED_GLASS_PANE, - DyeColor::LIME => Ids::LIME_STAINED_GLASS_PANE, - DyeColor::MAGENTA => Ids::MAGENTA_STAINED_GLASS_PANE, - DyeColor::ORANGE => Ids::ORANGE_STAINED_GLASS_PANE, - DyeColor::PINK => Ids::PINK_STAINED_GLASS_PANE, - DyeColor::PURPLE => Ids::PURPLE_STAINED_GLASS_PANE, - DyeColor::RED => Ids::RED_STAINED_GLASS_PANE, - DyeColor::WHITE => Ids::WHITE_STAINED_GLASS_PANE, - DyeColor::YELLOW => Ids::YELLOW_STAINED_GLASS_PANE, - }, [])); + $this->mapFlattenedEnum( + Blocks::STAINED_GLASS(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_stained_glass", + fn(StainedGlass $block) => $block->getColor() + ); + $this->mapFlattenedEnum( + Blocks::STAINED_GLASS_PANE(), + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_stained_glass_pane", + fn(StainedGlassPane $block) => $block->getColor() + ); } private function registerFlatCoralSerializers() : void{ diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index c55fde77a..cbeadc819 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -27,6 +27,7 @@ use pocketmine\block\AmethystCluster; use pocketmine\block\Anvil; use pocketmine\block\Bamboo; use pocketmine\block\Block; +use pocketmine\block\CakeWithDyedCandle; use pocketmine\block\CaveVines; use pocketmine\block\ChorusFlower; use pocketmine\block\DoublePitcherCrop; @@ -157,97 +158,68 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map($strippedId, fn(Reader $in) => Helper::decodeLog($getBlock(), true, $in)); } - private function registerCandleDeserializers() : void{ - $this->map(Ids::CANDLE, fn(Reader $in) => Helper::decodeCandle(Blocks::CANDLE(), $in)); - foreach([ - Ids::BLACK_CANDLE => DyeColor::BLACK, - Ids::BLUE_CANDLE => DyeColor::BLUE, - Ids::BROWN_CANDLE => DyeColor::BROWN, - Ids::CYAN_CANDLE => DyeColor::CYAN, - Ids::GRAY_CANDLE => DyeColor::GRAY, - Ids::GREEN_CANDLE => DyeColor::GREEN, - Ids::LIGHT_BLUE_CANDLE => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_CANDLE => DyeColor::LIGHT_GRAY, - Ids::LIME_CANDLE => DyeColor::LIME, - Ids::MAGENTA_CANDLE => DyeColor::MAGENTA, - Ids::ORANGE_CANDLE => DyeColor::ORANGE, - Ids::PINK_CANDLE => DyeColor::PINK, - Ids::PURPLE_CANDLE => DyeColor::PURPLE, - Ids::RED_CANDLE => DyeColor::RED, - Ids::WHITE_CANDLE => DyeColor::WHITE, - Ids::YELLOW_CANDLE => DyeColor::YELLOW, - ] as $id => $color){ - $this->map($id, fn(Reader $in) => Helper::decodeCandle(Blocks::DYED_CANDLE()->setColor($color), $in)); - } - - $this->map(Ids::CANDLE_CAKE, fn(Reader $in) => Blocks::CAKE_WITH_CANDLE()->setLit($in->readBool(StateNames::LIT))); - foreach([ - Ids::BLACK_CANDLE_CAKE => DyeColor::BLACK, - Ids::BLUE_CANDLE_CAKE => DyeColor::BLUE, - Ids::BROWN_CANDLE_CAKE => DyeColor::BROWN, - Ids::CYAN_CANDLE_CAKE => DyeColor::CYAN, - Ids::GRAY_CANDLE_CAKE => DyeColor::GRAY, - Ids::GREEN_CANDLE_CAKE => DyeColor::GREEN, - Ids::LIGHT_BLUE_CANDLE_CAKE => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_CANDLE_CAKE => DyeColor::LIGHT_GRAY, - Ids::LIME_CANDLE_CAKE => DyeColor::LIME, - Ids::MAGENTA_CANDLE_CAKE => DyeColor::MAGENTA, - Ids::ORANGE_CANDLE_CAKE => DyeColor::ORANGE, - Ids::PINK_CANDLE_CAKE => DyeColor::PINK, - Ids::PURPLE_CANDLE_CAKE => DyeColor::PURPLE, - Ids::RED_CANDLE_CAKE => DyeColor::RED, - Ids::WHITE_CANDLE_CAKE => DyeColor::WHITE, - Ids::YELLOW_CANDLE_CAKE => DyeColor::YELLOW, - ] as $id => $color){ - $this->map($id, fn(Reader $in) => Blocks::CAKE_WITH_DYED_CANDLE() - ->setColor($color) - ->setLit($in->readBool(StateNames::LIT)) - ); + /** + * @phpstan-template TBlock of Block + * @phpstan-template TEnum of \UnitEnum + * + * @phpstan-param StringEnumMap $mapProperty + * @phpstan-param \Closure(TEnum) : TBlock $getBlock + * @phpstan-param ?\Closure(TBlock, Reader) : TBlock $extra + */ + public function mapFlattenedEnum( + StringEnumMap $mapProperty, + string $prefix, + string $suffix, + \Closure $getBlock, + ?\Closure $extra = null + ) : void{ + foreach(Utils::stringifyKeys($mapProperty->getValueToEnum()) as $infix => $enumCase){ + $id = $prefix . $infix . $suffix; + if($extra === null){ + $this->map($id, fn() => $getBlock($enumCase)); + }else{ + $this->map($id, function(Reader $in) use ($enumCase, $getBlock, $extra) : Block{ + $block = $getBlock($enumCase); + $extra($block, $in); + return $block; + }); + } } } - private function registerFlatColorBlockDeserializers() : void{ - foreach([ - Ids::HARD_BLACK_STAINED_GLASS => DyeColor::BLACK, - Ids::HARD_BLUE_STAINED_GLASS => DyeColor::BLUE, - Ids::HARD_BROWN_STAINED_GLASS => DyeColor::BROWN, - Ids::HARD_CYAN_STAINED_GLASS => DyeColor::CYAN, - Ids::HARD_GRAY_STAINED_GLASS => DyeColor::GRAY, - Ids::HARD_GREEN_STAINED_GLASS => DyeColor::GREEN, - Ids::HARD_LIGHT_BLUE_STAINED_GLASS => DyeColor::LIGHT_BLUE, - Ids::HARD_LIGHT_GRAY_STAINED_GLASS => DyeColor::LIGHT_GRAY, - Ids::HARD_LIME_STAINED_GLASS => DyeColor::LIME, - Ids::HARD_MAGENTA_STAINED_GLASS => DyeColor::MAGENTA, - Ids::HARD_ORANGE_STAINED_GLASS => DyeColor::ORANGE, - Ids::HARD_PINK_STAINED_GLASS => DyeColor::PINK, - Ids::HARD_PURPLE_STAINED_GLASS => DyeColor::PURPLE, - Ids::HARD_RED_STAINED_GLASS => DyeColor::RED, - Ids::HARD_WHITE_STAINED_GLASS => DyeColor::WHITE, - Ids::HARD_YELLOW_STAINED_GLASS => DyeColor::YELLOW, - ] as $id => $color){ - $this->map($id, fn(Reader $in) => Blocks::STAINED_HARDENED_GLASS()->setColor($color)); - } + private function registerCandleDeserializers() : void{ + $this->map(Ids::CANDLE, fn(Reader $in) => Helper::decodeCandle(Blocks::CANDLE(), $in)); + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_candle", + fn(DyeColor $color) => Blocks::DYED_CANDLE()->setColor($color), + Helper::decodeCandle(...) + ); - foreach([ - Ids::HARD_BLACK_STAINED_GLASS_PANE => DyeColor::BLACK, - Ids::HARD_BLUE_STAINED_GLASS_PANE => DyeColor::BLUE, - Ids::HARD_BROWN_STAINED_GLASS_PANE => DyeColor::BROWN, - Ids::HARD_CYAN_STAINED_GLASS_PANE => DyeColor::CYAN, - Ids::HARD_GRAY_STAINED_GLASS_PANE => DyeColor::GRAY, - Ids::HARD_GREEN_STAINED_GLASS_PANE => DyeColor::GREEN, - Ids::HARD_LIGHT_BLUE_STAINED_GLASS_PANE => DyeColor::LIGHT_BLUE, - Ids::HARD_LIGHT_GRAY_STAINED_GLASS_PANE => DyeColor::LIGHT_GRAY, - Ids::HARD_LIME_STAINED_GLASS_PANE => DyeColor::LIME, - Ids::HARD_MAGENTA_STAINED_GLASS_PANE => DyeColor::MAGENTA, - Ids::HARD_ORANGE_STAINED_GLASS_PANE => DyeColor::ORANGE, - Ids::HARD_PINK_STAINED_GLASS_PANE => DyeColor::PINK, - Ids::HARD_PURPLE_STAINED_GLASS_PANE => DyeColor::PURPLE, - Ids::HARD_RED_STAINED_GLASS_PANE => DyeColor::RED, - Ids::HARD_WHITE_STAINED_GLASS_PANE => DyeColor::WHITE, - Ids::HARD_YELLOW_STAINED_GLASS_PANE => DyeColor::YELLOW, - ] as $id => $color){ - $this->map($id, fn(Reader $in) => Blocks::STAINED_HARDENED_GLASS_PANE()->setColor($color)); - } + $this->map(Ids::CANDLE_CAKE, fn(Reader $in) => Blocks::CAKE_WITH_CANDLE()->setLit($in->readBool(StateNames::LIT))); + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_candle_cake", + fn(DyeColor $color) => Blocks::CAKE_WITH_DYED_CANDLE()->setColor($color), + fn(CakeWithDyedCandle $block, Reader $in) => $block->setLit($in->readBool(StateNames::LIT)) + ); + } + + private function registerFlatColorBlockDeserializers() : void{ + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:hard_", + "_stained_glass", + fn(DyeColor $color) => Blocks::STAINED_HARDENED_GLASS()->setColor($color) + ); + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:hard_", + "_stained_glass_pane", + fn(DyeColor $color) => Blocks::STAINED_HARDENED_GLASS_PANE()->setColor($color) + ); foreach([ Ids::BLACK_GLAZED_TERRACOTTA => DyeColor::BLACK, @@ -273,110 +245,38 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ); } - foreach([ - Ids::BLACK_WOOL => DyeColor::BLACK, - Ids::BLUE_WOOL => DyeColor::BLUE, - Ids::BROWN_WOOL => DyeColor::BROWN, - Ids::CYAN_WOOL => DyeColor::CYAN, - Ids::GRAY_WOOL => DyeColor::GRAY, - Ids::GREEN_WOOL => DyeColor::GREEN, - Ids::LIGHT_BLUE_WOOL => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_WOOL => DyeColor::LIGHT_GRAY, - Ids::LIME_WOOL => DyeColor::LIME, - Ids::MAGENTA_WOOL => DyeColor::MAGENTA, - Ids::ORANGE_WOOL => DyeColor::ORANGE, - Ids::PINK_WOOL => DyeColor::PINK, - Ids::PURPLE_WOOL => DyeColor::PURPLE, - Ids::RED_WOOL => DyeColor::RED, - Ids::WHITE_WOOL => DyeColor::WHITE, - Ids::YELLOW_WOOL => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::WOOL()->setColor($color)); - } + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_wool", + fn(DyeColor $color) => Blocks::WOOL()->setColor($color) + ); + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_carpet", + fn(DyeColor $color) => Blocks::CARPET()->setColor($color) + ); - foreach([ - Ids::BLACK_CARPET => DyeColor::BLACK, - Ids::BLUE_CARPET => DyeColor::BLUE, - Ids::BROWN_CARPET => DyeColor::BROWN, - Ids::CYAN_CARPET => DyeColor::CYAN, - Ids::GRAY_CARPET => DyeColor::GRAY, - Ids::GREEN_CARPET => DyeColor::GREEN, - Ids::LIGHT_BLUE_CARPET => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_CARPET => DyeColor::LIGHT_GRAY, - Ids::LIME_CARPET => DyeColor::LIME, - Ids::MAGENTA_CARPET => DyeColor::MAGENTA, - Ids::ORANGE_CARPET => DyeColor::ORANGE, - Ids::PINK_CARPET => DyeColor::PINK, - Ids::PURPLE_CARPET => DyeColor::PURPLE, - Ids::RED_CARPET => DyeColor::RED, - Ids::WHITE_CARPET => DyeColor::WHITE, - Ids::YELLOW_CARPET => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::CARPET()->setColor($color)); - } + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_shulker_box", + fn(DyeColor $color) => Blocks::DYED_SHULKER_BOX()->setColor($color) + ); - foreach([ - Ids::BLACK_SHULKER_BOX => DyeColor::BLACK, - Ids::BLUE_SHULKER_BOX => DyeColor::BLUE, - Ids::BROWN_SHULKER_BOX => DyeColor::BROWN, - Ids::CYAN_SHULKER_BOX => DyeColor::CYAN, - Ids::GRAY_SHULKER_BOX => DyeColor::GRAY, - Ids::GREEN_SHULKER_BOX => DyeColor::GREEN, - Ids::LIGHT_BLUE_SHULKER_BOX => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_SHULKER_BOX => DyeColor::LIGHT_GRAY, - Ids::LIME_SHULKER_BOX => DyeColor::LIME, - Ids::MAGENTA_SHULKER_BOX => DyeColor::MAGENTA, - Ids::ORANGE_SHULKER_BOX => DyeColor::ORANGE, - Ids::PINK_SHULKER_BOX => DyeColor::PINK, - Ids::PURPLE_SHULKER_BOX => DyeColor::PURPLE, - Ids::RED_SHULKER_BOX => DyeColor::RED, - Ids::WHITE_SHULKER_BOX => DyeColor::WHITE, - Ids::YELLOW_SHULKER_BOX => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::DYED_SHULKER_BOX()->setColor($color)); - } - - foreach([ - Ids::BLACK_CONCRETE => DyeColor::BLACK, - Ids::BLUE_CONCRETE => DyeColor::BLUE, - Ids::BROWN_CONCRETE => DyeColor::BROWN, - Ids::CYAN_CONCRETE => DyeColor::CYAN, - Ids::GRAY_CONCRETE => DyeColor::GRAY, - Ids::GREEN_CONCRETE => DyeColor::GREEN, - Ids::LIGHT_BLUE_CONCRETE => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_CONCRETE => DyeColor::LIGHT_GRAY, - Ids::LIME_CONCRETE => DyeColor::LIME, - Ids::MAGENTA_CONCRETE => DyeColor::MAGENTA, - Ids::ORANGE_CONCRETE => DyeColor::ORANGE, - Ids::PINK_CONCRETE => DyeColor::PINK, - Ids::PURPLE_CONCRETE => DyeColor::PURPLE, - Ids::RED_CONCRETE => DyeColor::RED, - Ids::WHITE_CONCRETE => DyeColor::WHITE, - Ids::YELLOW_CONCRETE => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::CONCRETE()->setColor($color)); - } - - foreach([ - Ids::BLACK_CONCRETE_POWDER => DyeColor::BLACK, - Ids::BLUE_CONCRETE_POWDER => DyeColor::BLUE, - Ids::BROWN_CONCRETE_POWDER => DyeColor::BROWN, - Ids::CYAN_CONCRETE_POWDER => DyeColor::CYAN, - Ids::GRAY_CONCRETE_POWDER => DyeColor::GRAY, - Ids::GREEN_CONCRETE_POWDER => DyeColor::GREEN, - Ids::LIGHT_BLUE_CONCRETE_POWDER => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_CONCRETE_POWDER => DyeColor::LIGHT_GRAY, - Ids::LIME_CONCRETE_POWDER => DyeColor::LIME, - Ids::MAGENTA_CONCRETE_POWDER => DyeColor::MAGENTA, - Ids::ORANGE_CONCRETE_POWDER => DyeColor::ORANGE, - Ids::PINK_CONCRETE_POWDER => DyeColor::PINK, - Ids::PURPLE_CONCRETE_POWDER => DyeColor::PURPLE, - Ids::RED_CONCRETE_POWDER => DyeColor::RED, - Ids::WHITE_CONCRETE_POWDER => DyeColor::WHITE, - Ids::YELLOW_CONCRETE_POWDER => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::CONCRETE_POWDER()->setColor($color)); - } + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_concrete", + fn(DyeColor $color) => Blocks::CONCRETE()->setColor($color) + ); + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_concrete_powder", + fn(DyeColor $color) => Blocks::CONCRETE_POWDER()->setColor($color) + ); foreach([ Ids::BLACK_TERRACOTTA => DyeColor::BLACK, @@ -399,47 +299,19 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple($id, fn() => Blocks::STAINED_CLAY()->setColor($color)); } - foreach([ - Ids::BLACK_STAINED_GLASS => DyeColor::BLACK, - Ids::BLUE_STAINED_GLASS => DyeColor::BLUE, - Ids::BROWN_STAINED_GLASS => DyeColor::BROWN, - Ids::CYAN_STAINED_GLASS => DyeColor::CYAN, - Ids::GRAY_STAINED_GLASS => DyeColor::GRAY, - Ids::GREEN_STAINED_GLASS => DyeColor::GREEN, - Ids::LIGHT_BLUE_STAINED_GLASS => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_STAINED_GLASS => DyeColor::LIGHT_GRAY, - Ids::LIME_STAINED_GLASS => DyeColor::LIME, - Ids::MAGENTA_STAINED_GLASS => DyeColor::MAGENTA, - Ids::ORANGE_STAINED_GLASS => DyeColor::ORANGE, - Ids::PINK_STAINED_GLASS => DyeColor::PINK, - Ids::PURPLE_STAINED_GLASS => DyeColor::PURPLE, - Ids::RED_STAINED_GLASS => DyeColor::RED, - Ids::WHITE_STAINED_GLASS => DyeColor::WHITE, - Ids::YELLOW_STAINED_GLASS => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::STAINED_GLASS()->setColor($color)); - } + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_stained_glass", + fn(DyeColor $color) => Blocks::STAINED_GLASS()->setColor($color) + ); - foreach([ - Ids::BLACK_STAINED_GLASS_PANE => DyeColor::BLACK, - Ids::BLUE_STAINED_GLASS_PANE => DyeColor::BLUE, - Ids::BROWN_STAINED_GLASS_PANE => DyeColor::BROWN, - Ids::CYAN_STAINED_GLASS_PANE => DyeColor::CYAN, - Ids::GRAY_STAINED_GLASS_PANE => DyeColor::GRAY, - Ids::GREEN_STAINED_GLASS_PANE => DyeColor::GREEN, - Ids::LIGHT_BLUE_STAINED_GLASS_PANE => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_STAINED_GLASS_PANE => DyeColor::LIGHT_GRAY, - Ids::LIME_STAINED_GLASS_PANE => DyeColor::LIME, - Ids::MAGENTA_STAINED_GLASS_PANE => DyeColor::MAGENTA, - Ids::ORANGE_STAINED_GLASS_PANE => DyeColor::ORANGE, - Ids::PINK_STAINED_GLASS_PANE => DyeColor::PINK, - Ids::PURPLE_STAINED_GLASS_PANE => DyeColor::PURPLE, - Ids::RED_STAINED_GLASS_PANE => DyeColor::RED, - Ids::WHITE_STAINED_GLASS_PANE => DyeColor::WHITE, - Ids::YELLOW_STAINED_GLASS_PANE => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::STAINED_GLASS_PANE()->setColor($color)); - } + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_stained_glass_pane", + fn(DyeColor $color) => Blocks::STAINED_GLASS_PANE()->setColor($color) + ); } private function registerFlatCoralDeserializers() : void{ diff --git a/src/data/bedrock/block/convert/StringEnumMap.php b/src/data/bedrock/block/convert/StringEnumMap.php new file mode 100644 index 000000000..6171c0e71 --- /dev/null +++ b/src/data/bedrock/block/convert/StringEnumMap.php @@ -0,0 +1,78 @@ + + */ + private array $enumToValue = []; + + /** + * @var \UnitEnum[] + * @phpstan-var array + */ + private array $valueToEnum = []; + + /** + * @phpstan-param class-string $class + * @phpstan-param \Closure(TEnum) : string $mapper + */ + public function __construct( + private string $class, + \Closure $mapper + ){ + foreach($class::cases() as $case){ + $string = $mapper($case); + $this->valueToEnum[$string] = $case; + $this->enumToValue[spl_object_id($case)] = $string; + } + } + + /** + * @phpstan-param TEnum $enum + */ + public function enumToValue(\UnitEnum $enum) : string{ + return $this->enumToValue[spl_object_id($enum)]; + } + + public function valueToEnum(string $string) : ?\UnitEnum{ + return $this->valueToEnum[$string] ?? throw new BlockStateDeserializeException("No $this->class enum mapping for \"$string\""); + } + + /** + * @return \UnitEnum[] + * @phpstan-return array + */ + public function getValueToEnum() : array{ + return $this->valueToEnum; + } +} diff --git a/src/data/bedrock/block/convert/ValueMappings.php b/src/data/bedrock/block/convert/ValueMappings.php new file mode 100644 index 000000000..df57d6f53 --- /dev/null +++ b/src/data/bedrock/block/convert/ValueMappings.php @@ -0,0 +1,83 @@ +, StringEnumMap> + */ + private array $enumMappings = []; + + public function __construct(){ + $this->addEnum(DyeColor::class, fn(DyeColor $case) => match ($case) { + DyeColor::BLACK => "black", + DyeColor::BLUE => "blue", + DyeColor::BROWN => "brown", + DyeColor::CYAN => "cyan", + DyeColor::GRAY => "gray", + DyeColor::GREEN => "green", + DyeColor::LIGHT_BLUE => "light_blue", + DyeColor::LIGHT_GRAY => "light_gray", + DyeColor::LIME => "lime", + DyeColor::MAGENTA => "magenta", + DyeColor::ORANGE => "orange", + DyeColor::PINK => "pink", + DyeColor::PURPLE => "purple", + DyeColor::RED => "red", + DyeColor::WHITE => "white", + DyeColor::YELLOW => "yellow" + }); + } + + /** + * @phpstan-template TEnum of \UnitEnum + * @phpstan-param class-string $class + * @phpstan-param \Closure(TEnum): string $mapper + */ + private function addEnum(string $class, \Closure $mapper) : void{ + $this->enumMappings[$class] = new StringEnumMap($class, $mapper); + } + + /** + * @phpstan-template TEnum of \UnitEnum + * @phpstan-param class-string $class + * @phpstan-return StringEnumMap + */ + public function getEnumMap(string $class) : StringEnumMap{ + if(!isset($this->enumMappings[$class])){ + throw new \InvalidArgumentException("No enum mapping found for class: $class"); + } + /** + * @phpstan-var StringEnumMap $map + */ + $map = $this->enumMappings[$class]; + return $map; + } +} From c0fad353a2667046a073fb14f8a5a65a9f65ed7b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 15 Aug 2025 22:09:54 +0100 Subject: [PATCH 264/334] missed one sadly glazed_terracotta had to be special --- .../BlockStateToObjectDeserializer.php | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index cbeadc819..0f6d4930b 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -278,26 +278,12 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ fn(DyeColor $color) => Blocks::CONCRETE_POWDER()->setColor($color) ); - foreach([ - Ids::BLACK_TERRACOTTA => DyeColor::BLACK, - Ids::BLUE_TERRACOTTA => DyeColor::BLUE, - Ids::BROWN_TERRACOTTA => DyeColor::BROWN, - Ids::CYAN_TERRACOTTA => DyeColor::CYAN, - Ids::GRAY_TERRACOTTA => DyeColor::GRAY, - Ids::GREEN_TERRACOTTA => DyeColor::GREEN, - Ids::LIGHT_BLUE_TERRACOTTA => DyeColor::LIGHT_BLUE, - Ids::LIGHT_GRAY_TERRACOTTA => DyeColor::LIGHT_GRAY, - Ids::LIME_TERRACOTTA => DyeColor::LIME, - Ids::MAGENTA_TERRACOTTA => DyeColor::MAGENTA, - Ids::ORANGE_TERRACOTTA => DyeColor::ORANGE, - Ids::PINK_TERRACOTTA => DyeColor::PINK, - Ids::PURPLE_TERRACOTTA => DyeColor::PURPLE, - Ids::RED_TERRACOTTA => DyeColor::RED, - Ids::WHITE_TERRACOTTA => DyeColor::WHITE, - Ids::YELLOW_TERRACOTTA => DyeColor::YELLOW, - ] as $id => $color){ - $this->mapSimple($id, fn() => Blocks::STAINED_CLAY()->setColor($color)); - } + $this->mapFlattenedEnum( + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + "minecraft:", + "_terracotta", + fn(DyeColor $color) => Blocks::STAINED_CLAY()->setColor($color) + ); $this->mapFlattenedEnum( ValueMappings::getInstance()->getEnumMap(DyeColor::class), From 431790a3195ffe13859a040fa5e2b6a8dd1a5974 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 15 Aug 2025 22:24:27 +0100 Subject: [PATCH 265/334] Additional specialisation for colored blocks this reduces boilerplate even further --- .../convert/BlockObjectToStateSerializer.php | 130 +++++------------- .../convert/BlockStateDeserializerHelper.php | 8 +- .../BlockStateToObjectDeserializer.php | 106 +++++--------- 3 files changed, 76 insertions(+), 168 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 6b545bd01..1a3467fe9 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -45,7 +45,6 @@ use pocketmine\block\CakeWithCandle; use pocketmine\block\CakeWithDyedCandle; use pocketmine\block\Campfire; use pocketmine\block\Candle; -use pocketmine\block\Carpet; use pocketmine\block\Carrot; use pocketmine\block\CarvedPumpkin; use pocketmine\block\CaveVines; @@ -55,8 +54,6 @@ use pocketmine\block\Chest; use pocketmine\block\ChiseledBookshelf; use pocketmine\block\ChorusFlower; use pocketmine\block\CocoaBlock; -use pocketmine\block\Concrete; -use pocketmine\block\ConcretePowder; use pocketmine\block\Copper; use pocketmine\block\CopperBulb; use pocketmine\block\CopperDoor; @@ -73,8 +70,6 @@ use pocketmine\block\Door; use pocketmine\block\DoublePitcherCrop; use pocketmine\block\DoublePlant; use pocketmine\block\DoubleTallGrass; -use pocketmine\block\DyedCandle; -use pocketmine\block\DyedShulkerBox; use pocketmine\block\EnderChest; use pocketmine\block\EndPortalFrame; use pocketmine\block\EndRod; @@ -133,11 +128,6 @@ use pocketmine\block\SmallDripleaf; use pocketmine\block\SnowLayer; use pocketmine\block\SoulCampfire; use pocketmine\block\Sponge; -use pocketmine\block\StainedGlass; -use pocketmine\block\StainedGlassPane; -use pocketmine\block\StainedHardenedClay; -use pocketmine\block\StainedHardenedGlass; -use pocketmine\block\StainedHardenedGlassPane; use pocketmine\block\Stair; use pocketmine\block\StoneButton; use pocketmine\block\Stonecutter; @@ -153,6 +143,7 @@ use pocketmine\block\Tripwire; use pocketmine\block\TripwireHook; use pocketmine\block\UnderwaterTorch; use pocketmine\block\utils\BrewingStandSlot; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\CoralType; use pocketmine\block\utils\DirtType; use pocketmine\block\utils\DripleafState; @@ -176,7 +167,6 @@ use pocketmine\block\WoodenDoor; use pocketmine\block\WoodenPressurePlate; use pocketmine\block\WoodenStairs; use pocketmine\block\WoodenTrapdoor; -use pocketmine\block\Wool; use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\BlockStateNames as StateNames; @@ -319,43 +309,57 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }); } + /** + * @phpstan-template TBlock of Block&Colored + * @phpstan-param TBlock $block + * @phpstan-param ?\Closure(TBlock, Writer) : Writer $extra + */ + public function mapColored( + Block $block, + string $prefix, + string $suffix, + ?\Closure $extra = null + ) : void{ + $this->mapFlattenedEnum( + $block, + ValueMappings::getInstance()->getEnumMap(DyeColor::class), + $prefix, + $suffix, + fn(Colored $block) => $block->getColor(), + $extra + ); + } + private function registerCandleSerializers() : void{ $this->map(Blocks::CANDLE(), fn(Candle $block) => Helper::encodeCandle($block, new Writer(Ids::CANDLE))); - $this->mapFlattenedEnum( + $this->mapColored( Blocks::DYED_CANDLE(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), "minecraft:", "_candle", - fn(DyedCandle $block) => $block->getColor(), Helper::encodeCandle(...) ); $this->map(Blocks::CAKE_WITH_CANDLE(), fn(CakeWithCandle $block) => Writer::create(Ids::CANDLE_CAKE) ->writeBool(StateNames::LIT, $block->isLit())); - $this->mapFlattenedEnum( + $this->mapColored( Blocks::CAKE_WITH_DYED_CANDLE(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), "minecraft:", "_candle_cake", - fn(CakeWithDyedCandle $block) => $block->getColor(), fn(CakeWithDyedCandle $block, Writer $writer) => $writer->writeBool(StateNames::LIT, $block->isLit()) ); } public function registerFlatColorBlockSerializers() : void{ - $this->mapFlattenedEnum( - Blocks::STAINED_HARDENED_GLASS(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:hard_", - "_stained_glass", - fn(StainedHardenedGlass $block) => $block->getColor() - ); - $this->mapFlattenedEnum( - Blocks::STAINED_HARDENED_GLASS_PANE(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:hard_", - "_stained_glass_pane", - fn(StainedHardenedGlassPane $block) => $block->getColor(), - ); + $this->mapColored(Blocks::STAINED_HARDENED_GLASS(), "minecraft:hard_", "_stained_glass"); + $this->mapColored(Blocks::STAINED_HARDENED_GLASS_PANE(), "minecraft:hard_", "_stained_glass_pane"); + + $this->mapColored(Blocks::CARPET(), "minecraft:", "_carpet"); + $this->mapColored(Blocks::CONCRETE(), "minecraft:", "_concrete"); + $this->mapColored(Blocks::CONCRETE_POWDER(), "minecraft:", "_concrete_powder"); + $this->mapColored(Blocks::DYED_SHULKER_BOX(), "minecraft:", "_shulker_box"); + $this->mapColored(Blocks::STAINED_CLAY(), "minecraft:", "_terracotta"); + $this->mapColored(Blocks::STAINED_GLASS(), "minecraft:", "_stained_glass"); + $this->mapColored(Blocks::STAINED_GLASS_PANE(), "minecraft:", "_stained_glass_pane"); + $this->mapColored(Blocks::WOOL(), "minecraft:", "_wool"); $this->map(Blocks::GLAZED_TERRACOTTA(), function(GlazedTerracotta $block) : Writer{ return Writer::create(match($block->getColor()){ @@ -366,7 +370,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ DyeColor::GRAY => Ids::GRAY_GLAZED_TERRACOTTA, DyeColor::GREEN => Ids::GREEN_GLAZED_TERRACOTTA, DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_GLAZED_TERRACOTTA, - DyeColor::LIGHT_GRAY => Ids::SILVER_GLAZED_TERRACOTTA, + DyeColor::LIGHT_GRAY => Ids::SILVER_GLAZED_TERRACOTTA, //minecraft sadness DyeColor::LIME => Ids::LIME_GLAZED_TERRACOTTA, DyeColor::MAGENTA => Ids::MAGENTA_GLAZED_TERRACOTTA, DyeColor::ORANGE => Ids::ORANGE_GLAZED_TERRACOTTA, @@ -378,68 +382,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }) ->writeHorizontalFacing($block->getFacing()); }); - - $this->mapFlattenedEnum( - Blocks::WOOL(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_wool", - fn(Wool $block) => $block->getColor() - ); - - $this->mapFlattenedEnum( - Blocks::CARPET(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_carpet", - fn(Carpet $block) => $block->getColor() - ); - - $this->mapFlattenedEnum( - Blocks::DYED_SHULKER_BOX(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_shulker_box", - fn(DyedShulkerBox $block) => $block->getColor() - ); - - $this->mapFlattenedEnum( - Blocks::CONCRETE(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_concrete", - fn(Concrete $block) => $block->getColor() - ); - $this->mapFlattenedEnum( - Blocks::CONCRETE_POWDER(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_concrete_powder", - fn(ConcretePowder $block) => $block->getColor() - ); - - $this->mapFlattenedEnum( - Blocks::STAINED_CLAY(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_terracotta", - fn(StainedHardenedClay $block) => $block->getColor() - ); - - $this->mapFlattenedEnum( - Blocks::STAINED_GLASS(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_stained_glass", - fn(StainedGlass $block) => $block->getColor() - ); - $this->mapFlattenedEnum( - Blocks::STAINED_GLASS_PANE(), - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_stained_glass_pane", - fn(StainedGlassPane $block) => $block->getColor() - ); } private function registerFlatCoralSerializers() : void{ diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index 5cf3f7f76..3cf55429e 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -70,7 +70,13 @@ final class BlockStateDeserializerHelper{ ->setPressed($in->readBool(BlockStateNames::BUTTON_PRESSED_BIT)); } - /** @throws BlockStateDeserializeException */ + /** + * @phpstan-template TCandle of Candle + * @phpstan-param TCandle $block + * @phpstan-return TCandle + * + * @throws BlockStateDeserializeException + */ public static function decodeCandle(Candle $block, BlockStateReader $in) : Candle{ return $block ->setCount($in->readBoundedInt(StateNames::CANDLES, 0, 3) + 1) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 0f6d4930b..b21642cb4 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -40,6 +40,7 @@ use pocketmine\block\Stair; use pocketmine\block\SweetBerryBush; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\ChiseledBookshelfSlot; +use pocketmine\block\utils\Colored; use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\CoralType; @@ -187,39 +188,52 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ } } - private function registerCandleDeserializers() : void{ - $this->map(Ids::CANDLE, fn(Reader $in) => Helper::decodeCandle(Blocks::CANDLE(), $in)); + /** + * @phpstan-template TBlock of Block&Colored + * @phpstan-param \Closure() : TBlock $getBlock + * @phpstan-param ?\Closure(TBlock, Reader) : TBlock $extra + */ + public function mapColored(string $prefix, string $suffix, \Closure $getBlock, ?\Closure $extra = null) : void{ $this->mapFlattenedEnum( ValueMappings::getInstance()->getEnumMap(DyeColor::class), + $prefix, + $suffix, + fn(DyeColor $color) => $getBlock()->setColor($color), + $extra + ); + } + + private function registerCandleDeserializers() : void{ + $this->map(Ids::CANDLE, fn(Reader $in) => Helper::decodeCandle(Blocks::CANDLE(), $in)); + $this->mapColored( "minecraft:", "_candle", - fn(DyeColor $color) => Blocks::DYED_CANDLE()->setColor($color), + fn() => Blocks::DYED_CANDLE(), Helper::decodeCandle(...) ); $this->map(Ids::CANDLE_CAKE, fn(Reader $in) => Blocks::CAKE_WITH_CANDLE()->setLit($in->readBool(StateNames::LIT))); - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), + + $this->mapColored( "minecraft:", "_candle_cake", - fn(DyeColor $color) => Blocks::CAKE_WITH_DYED_CANDLE()->setColor($color), + fn() => Blocks::CAKE_WITH_DYED_CANDLE(), fn(CakeWithDyedCandle $block, Reader $in) => $block->setLit($in->readBool(StateNames::LIT)) ); } private function registerFlatColorBlockDeserializers() : void{ - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:hard_", - "_stained_glass", - fn(DyeColor $color) => Blocks::STAINED_HARDENED_GLASS()->setColor($color) - ); - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:hard_", - "_stained_glass_pane", - fn(DyeColor $color) => Blocks::STAINED_HARDENED_GLASS_PANE()->setColor($color) - ); + $this->mapColored("minecraft:hard_", "_stained_glass", fn() => Blocks::STAINED_HARDENED_GLASS()); + $this->mapColored("minecraft:hard_", "_stained_glass_pane", fn() => Blocks::STAINED_HARDENED_GLASS_PANE()); + + $this->mapColored("minecraft:", "_carpet", fn() => Blocks::CARPET()); + $this->mapColored("minecraft:", "_concrete", fn() => Blocks::CONCRETE()); + $this->mapColored("minecraft:", "_concrete_powder", fn() => Blocks::CONCRETE_POWDER()); + $this->mapColored("minecraft:", "_shulker_box", fn() => Blocks::DYED_SHULKER_BOX()); + $this->mapColored("minecraft:", "_stained_glass", fn() => Blocks::STAINED_GLASS()); + $this->mapColored("minecraft:", "_stained_glass_pane", fn() => Blocks::STAINED_GLASS_PANE()); + $this->mapColored("minecraft:", "_terracotta", fn() => Blocks::STAINED_CLAY()); + $this->mapColored("minecraft:", "_wool", fn() => Blocks::WOOL()); foreach([ Ids::BLACK_GLAZED_TERRACOTTA => DyeColor::BLACK, @@ -229,7 +243,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ Ids::GRAY_GLAZED_TERRACOTTA => DyeColor::GRAY, Ids::GREEN_GLAZED_TERRACOTTA => DyeColor::GREEN, Ids::LIGHT_BLUE_GLAZED_TERRACOTTA => DyeColor::LIGHT_BLUE, - Ids::SILVER_GLAZED_TERRACOTTA => DyeColor::LIGHT_GRAY, + Ids::SILVER_GLAZED_TERRACOTTA => DyeColor::LIGHT_GRAY, //minecraft sadness Ids::LIME_GLAZED_TERRACOTTA => DyeColor::LIME, Ids::MAGENTA_GLAZED_TERRACOTTA => DyeColor::MAGENTA, Ids::ORANGE_GLAZED_TERRACOTTA => DyeColor::ORANGE, @@ -244,60 +258,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readHorizontalFacing()) ); } - - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_wool", - fn(DyeColor $color) => Blocks::WOOL()->setColor($color) - ); - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_carpet", - fn(DyeColor $color) => Blocks::CARPET()->setColor($color) - ); - - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_shulker_box", - fn(DyeColor $color) => Blocks::DYED_SHULKER_BOX()->setColor($color) - ); - - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_concrete", - fn(DyeColor $color) => Blocks::CONCRETE()->setColor($color) - ); - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_concrete_powder", - fn(DyeColor $color) => Blocks::CONCRETE_POWDER()->setColor($color) - ); - - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_terracotta", - fn(DyeColor $color) => Blocks::STAINED_CLAY()->setColor($color) - ); - - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_stained_glass", - fn(DyeColor $color) => Blocks::STAINED_GLASS()->setColor($color) - ); - - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - "minecraft:", - "_stained_glass_pane", - fn(DyeColor $color) => Blocks::STAINED_GLASS_PANE()->setColor($color) - ); } private function registerFlatCoralDeserializers() : void{ From eea4f40138fe3af6a5061ebf5c4ef5a25f85faf0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 17 Aug 2025 15:24:40 +0100 Subject: [PATCH 266/334] BlockStateToObjectDeserializer: Remove duplicated CHISELED_COPPER registration allowing overriding of serializers by the same method as first registration was a mistake... --- .../bedrock/block/convert/BlockStateToObjectDeserializer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index c55fde77a..12c4bc43c 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -1355,7 +1355,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return $block; }); - $this->map(Ids::CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE)); $this->map(Ids::CHISELED_QUARTZ_BLOCK, function(Reader $in) : Block{ return Blocks::CHISELED_QUARTZ() ->setAxis($in->readPillarAxis()); From 2bb78f2a943c031380a733550409a6524cc918a9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 20 Aug 2025 00:58:57 +0100 Subject: [PATCH 267/334] Fixed Furnace not implementing HorizontalFacing looks like this was missed in #6639 I checked all other uses of HorizontalFacingTrait and FacesOppositePlacingPlayerTrait and this seems to be the only one. --- src/block/Furnace.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 54480e62c..43ab463a6 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\Lightable; use pocketmine\block\utils\LightableTrait; use pocketmine\crafting\FurnaceType; @@ -34,7 +35,7 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use function mt_rand; -class Furnace extends Opaque implements Lightable{ +class Furnace extends Opaque implements Lightable, HorizontalFacing{ use FacesOppositePlacingPlayerTrait; use LightableTrait; From e824266457c3207bb2565256e2f9a58652d98a51 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 22 Aug 2025 18:27:06 +0100 Subject: [PATCH 268/334] ChiseledBookshelf: add setSlots() --- src/block/ChiseledBookshelf.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php index be49c7a91..cbe22a94b 100644 --- a/src/block/ChiseledBookshelf.php +++ b/src/block/ChiseledBookshelf.php @@ -114,6 +114,18 @@ class ChiseledBookshelf extends Opaque implements HorizontalFacing{ return $this->slots; } + /** + * @param ChiseledBookshelfSlot[] $slots + * @return $this + */ + public function setSlots(array $slots) : self{ + $this->slots = []; + foreach($slots as $slot){ + $this->setSlot($slot, true); + } + return $this; + } + /** * Returns the last slot interacted by a player or null if no slot has been interacted with yet. */ From 47140cb8d7a956d767423de56410c46526c10468 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 22 Aug 2025 18:27:32 +0100 Subject: [PATCH 269/334] RedstoneLamp: implement Lightable, shimmed to powered --- src/block/RedstoneLamp.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/block/RedstoneLamp.php b/src/block/RedstoneLamp.php index 33a97801d..530fa2410 100644 --- a/src/block/RedstoneLamp.php +++ b/src/block/RedstoneLamp.php @@ -23,11 +23,12 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\Lightable; use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\data\runtime\RuntimeDataDescriber; -class RedstoneLamp extends Opaque implements PoweredByRedstone{ +class RedstoneLamp extends Opaque implements PoweredByRedstone, Lightable{ use PoweredByRedstoneTrait; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ @@ -37,4 +38,14 @@ class RedstoneLamp extends Opaque implements PoweredByRedstone{ public function getLightLevel() : int{ return $this->powered ? 15 : 0; } + + public function isLit() : bool{ + return $this->powered; + } + + /** @return $this */ + public function setLit(bool $lit = true) : self{ + $this->powered = $lit; + return $this; + } } From 7c521b456e181fed271b4e050731ed4ebc8faf1f Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 24 Aug 2025 14:12:18 +0100 Subject: [PATCH 270/334] Unify block serializers (#6769) This has several advantages: Easier to implement new blocks (one less file to modify) Easier to adjust serialization of existing blocks Guaranteed consistency between serializers and deserializers Potentially, exposes more metadata for programmatic analysis, instead of having everything baked inside opaque Closures There are some exceptions which still use the old approach: big dripleaf, cauldrons, mushroom stems, and pitcher crops. These all have multiple PM block types for a single ID, with relatively complex logic to select which to use. These weren't worth the effort to unify due to their small number. I may revisit this in the future, but I already spent a lot of brainpower on it. --- src/data/bedrock/MushroomBlockTypeIdMap.php | 20 +- .../bedrock/block/BlockLegacyMetadata.php | 2 + .../convert/BlockObjectToStateSerializer.php | 1619 +---------------- .../BlockSerializerDeserializerRegistrar.php | 237 +++ .../convert/BlockStateDeserializerHelper.php | 52 +- .../block/convert/BlockStateReader.php | 228 ++- .../convert/BlockStateSerializerHelper.php | 33 + .../BlockStateToObjectDeserializer.php | 1593 +--------------- .../block/convert/BlockStateWriter.php | 228 +-- .../block/convert/FlattenedIdModel.php | 107 ++ src/data/bedrock/block/convert/Model.php | 82 + .../bedrock/block/convert/StringEnumMap.php | 78 - .../bedrock/block/convert/ValueMappings.php | 83 - .../block/convert/VanillaBlockMappings.php | 1561 ++++++++++++++++ .../property/BoolFromStringProperty.php | 78 + .../block/convert/property/BoolProperty.php | 71 + .../convert/property/CommonProperties.php | 429 +++++ .../block/convert/property/DummyProperty.php | 61 + .../convert/property/EnumFromRawStateMap.php | 109 ++ .../property/FlattenedCaveVinesVariant.php | 35 + .../convert/property/IntFromRawStateMap.php | 104 ++ .../block/convert/property/IntProperty.php | 70 + .../property/OptionSetFromIntProperty.php | 93 + .../block/convert/property/Property.php | 44 + .../block/convert/property/StateMap.php | 53 + .../block/convert/property/StringProperty.php | 50 + .../convert/property/ValueFromIntProperty.php | 75 + .../property/ValueFromStringProperty.php | 81 + .../block/convert/property/ValueMappings.php | 305 ++++ .../property/WallConnectionTypeShim.php | 66 + .../format/io/GlobalBlockStateHandlers.php | 22 +- tests/phpstan/configs/actual-problems.neon | 6 + .../BlockSerializerDeserializerTest.php | 6 +- 33 files changed, 4072 insertions(+), 3609 deletions(-) create mode 100644 src/data/bedrock/block/convert/BlockSerializerDeserializerRegistrar.php create mode 100644 src/data/bedrock/block/convert/FlattenedIdModel.php create mode 100644 src/data/bedrock/block/convert/Model.php delete mode 100644 src/data/bedrock/block/convert/StringEnumMap.php delete mode 100644 src/data/bedrock/block/convert/ValueMappings.php create mode 100644 src/data/bedrock/block/convert/VanillaBlockMappings.php create mode 100644 src/data/bedrock/block/convert/property/BoolFromStringProperty.php create mode 100644 src/data/bedrock/block/convert/property/BoolProperty.php create mode 100644 src/data/bedrock/block/convert/property/CommonProperties.php create mode 100644 src/data/bedrock/block/convert/property/DummyProperty.php create mode 100644 src/data/bedrock/block/convert/property/EnumFromRawStateMap.php create mode 100644 src/data/bedrock/block/convert/property/FlattenedCaveVinesVariant.php create mode 100644 src/data/bedrock/block/convert/property/IntFromRawStateMap.php create mode 100644 src/data/bedrock/block/convert/property/IntProperty.php create mode 100644 src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php create mode 100644 src/data/bedrock/block/convert/property/Property.php create mode 100644 src/data/bedrock/block/convert/property/StateMap.php create mode 100644 src/data/bedrock/block/convert/property/StringProperty.php create mode 100644 src/data/bedrock/block/convert/property/ValueFromIntProperty.php create mode 100644 src/data/bedrock/block/convert/property/ValueFromStringProperty.php create mode 100644 src/data/bedrock/block/convert/property/ValueMappings.php create mode 100644 src/data/bedrock/block/convert/property/WallConnectionTypeShim.php diff --git a/src/data/bedrock/MushroomBlockTypeIdMap.php b/src/data/bedrock/MushroomBlockTypeIdMap.php index a25336d89..6357d3e14 100644 --- a/src/data/bedrock/MushroomBlockTypeIdMap.php +++ b/src/data/bedrock/MushroomBlockTypeIdMap.php @@ -24,29 +24,21 @@ declare(strict_types=1); namespace pocketmine\data\bedrock; use pocketmine\block\utils\MushroomBlockType; -use pocketmine\data\bedrock\block\BlockLegacyMetadata as LegacyMeta; +use pocketmine\data\bedrock\block\convert\property\ValueMappings; use pocketmine\utils\SingletonTrait; +/** + * @deprecated + */ final class MushroomBlockTypeIdMap{ use SingletonTrait; /** @phpstan-use IntSaveIdMapTrait */ use IntSaveIdMapTrait; public function __construct(){ + $newMapping = ValueMappings::getInstance()->mushroomBlockType; foreach(MushroomBlockType::cases() as $case){ - $this->register(match($case){ - MushroomBlockType::PORES => LegacyMeta::MUSHROOM_BLOCK_ALL_PORES, - MushroomBlockType::CAP_NORTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER, - MushroomBlockType::CAP_NORTH => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE, - MushroomBlockType::CAP_NORTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER, - MushroomBlockType::CAP_WEST => LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE, - MushroomBlockType::CAP_MIDDLE => LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY, - MushroomBlockType::CAP_EAST => LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE, - MushroomBlockType::CAP_SOUTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER, - MushroomBlockType::CAP_SOUTH => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE, - MushroomBlockType::CAP_SOUTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER, - MushroomBlockType::ALL_CAP => LegacyMeta::MUSHROOM_BLOCK_ALL_CAP, - }, $case); + $this->register($newMapping->valueToRaw($case), $case); } } } diff --git a/src/data/bedrock/block/BlockLegacyMetadata.php b/src/data/bedrock/block/BlockLegacyMetadata.php index e094bd3bd..d324a7ccc 100644 --- a/src/data/bedrock/block/BlockLegacyMetadata.php +++ b/src/data/bedrock/block/BlockLegacyMetadata.php @@ -38,6 +38,8 @@ final class BlockLegacyMetadata{ public const CORAL_VARIANT_FIRE = 3; public const CORAL_VARIANT_HORN = 4; + public const LIQUID_FALLING_FLAG = 0x08; + public const MULTI_FACE_DIRECTION_FLAG_DOWN = 0x01; public const MULTI_FACE_DIRECTION_FLAG_UP = 0x02; public const MULTI_FACE_DIRECTION_FLAG_SOUTH = 0x04; diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 1a3467fe9..90f6af97a 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -23,161 +23,17 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\convert; -use pocketmine\block\ActivatorRail; -use pocketmine\block\AmethystCluster; -use pocketmine\block\Anvil; -use pocketmine\block\Bamboo; -use pocketmine\block\BambooSapling; -use pocketmine\block\Barrel; -use pocketmine\block\Bed; -use pocketmine\block\Beetroot; -use pocketmine\block\Bell; -use pocketmine\block\BigDripleafHead; -use pocketmine\block\BigDripleafStem; use pocketmine\block\Block; -use pocketmine\block\BoneBlock; -use pocketmine\block\BrewingStand; -use pocketmine\block\BrownMushroomBlock; -use pocketmine\block\Button; -use pocketmine\block\Cactus; -use pocketmine\block\Cake; -use pocketmine\block\CakeWithCandle; -use pocketmine\block\CakeWithDyedCandle; -use pocketmine\block\Campfire; -use pocketmine\block\Candle; -use pocketmine\block\Carrot; -use pocketmine\block\CarvedPumpkin; -use pocketmine\block\CaveVines; -use pocketmine\block\Chain; -use pocketmine\block\ChemistryTable; -use pocketmine\block\Chest; -use pocketmine\block\ChiseledBookshelf; -use pocketmine\block\ChorusFlower; -use pocketmine\block\CocoaBlock; -use pocketmine\block\Copper; -use pocketmine\block\CopperBulb; -use pocketmine\block\CopperDoor; -use pocketmine\block\CopperGrate; -use pocketmine\block\CopperSlab; -use pocketmine\block\CopperStairs; -use pocketmine\block\CopperTrapdoor; -use pocketmine\block\Coral; -use pocketmine\block\CoralBlock; -use pocketmine\block\DaylightSensor; -use pocketmine\block\DetectorRail; -use pocketmine\block\Dirt; -use pocketmine\block\Door; -use pocketmine\block\DoublePitcherCrop; -use pocketmine\block\DoublePlant; -use pocketmine\block\DoubleTallGrass; -use pocketmine\block\EnderChest; -use pocketmine\block\EndPortalFrame; -use pocketmine\block\EndRod; -use pocketmine\block\Farmland; -use pocketmine\block\FenceGate; -use pocketmine\block\FillableCauldron; -use pocketmine\block\Fire; -use pocketmine\block\FloorBanner; -use pocketmine\block\FloorCoralFan; -use pocketmine\block\FloorSign; -use pocketmine\block\Froglight; -use pocketmine\block\FrostedIce; -use pocketmine\block\Furnace; -use pocketmine\block\GlazedTerracotta; -use pocketmine\block\GlowLichen; -use pocketmine\block\HayBale; -use pocketmine\block\Hopper; -use pocketmine\block\ItemFrame; -use pocketmine\block\Ladder; -use pocketmine\block\Lantern; -use pocketmine\block\Lava; -use pocketmine\block\Leaves; -use pocketmine\block\Lectern; -use pocketmine\block\Lever; -use pocketmine\block\Light; -use pocketmine\block\LightningRod; -use pocketmine\block\LitPumpkin; -use pocketmine\block\Loom; -use pocketmine\block\MelonStem; -use pocketmine\block\MobHead; -use pocketmine\block\NetherPortal; -use pocketmine\block\NetherVines; -use pocketmine\block\NetherWartPlant; -use pocketmine\block\PinkPetals; -use pocketmine\block\PitcherCrop; -use pocketmine\block\Potato; -use pocketmine\block\PoweredRail; -use pocketmine\block\PumpkinStem; -use pocketmine\block\Rail; -use pocketmine\block\RedMushroomBlock; -use pocketmine\block\RedstoneComparator; -use pocketmine\block\RedstoneLamp; -use pocketmine\block\RedstoneOre; -use pocketmine\block\RedstoneRepeater; -use pocketmine\block\RedstoneTorch; -use pocketmine\block\RedstoneWire; -use pocketmine\block\ResinClump; -use pocketmine\block\RespawnAnchor; use pocketmine\block\RuntimeBlockStateRegistry; -use pocketmine\block\Sapling; -use pocketmine\block\SeaPickle; -use pocketmine\block\SimplePillar; -use pocketmine\block\SimplePressurePlate; use pocketmine\block\Slab; -use pocketmine\block\SmallDripleaf; -use pocketmine\block\SnowLayer; -use pocketmine\block\SoulCampfire; -use pocketmine\block\Sponge; use pocketmine\block\Stair; -use pocketmine\block\StoneButton; -use pocketmine\block\Stonecutter; -use pocketmine\block\StonePressurePlate; -use pocketmine\block\Sugarcane; -use pocketmine\block\SweetBerryBush; -use pocketmine\block\TNT; -use pocketmine\block\Torch; -use pocketmine\block\TorchflowerCrop; -use pocketmine\block\Trapdoor; -use pocketmine\block\TrappedChest; -use pocketmine\block\Tripwire; -use pocketmine\block\TripwireHook; -use pocketmine\block\UnderwaterTorch; -use pocketmine\block\utils\BrewingStandSlot; -use pocketmine\block\utils\Colored; -use pocketmine\block\utils\CoralType; -use pocketmine\block\utils\DirtType; -use pocketmine\block\utils\DripleafState; -use pocketmine\block\utils\DyeColor; -use pocketmine\block\utils\FroglightType; -use pocketmine\block\utils\LeverFacing; -use pocketmine\block\utils\MobHeadType; -use pocketmine\block\VanillaBlocks as Blocks; -use pocketmine\block\Vine; -use pocketmine\block\Wall; -use pocketmine\block\WallBanner; -use pocketmine\block\WallCoralFan; -use pocketmine\block\WallSign; -use pocketmine\block\Water; -use pocketmine\block\WeightedPressurePlateHeavy; -use pocketmine\block\WeightedPressurePlateLight; -use pocketmine\block\Wheat; use pocketmine\block\Wood; -use pocketmine\block\WoodenButton; -use pocketmine\block\WoodenDoor; -use pocketmine\block\WoodenPressurePlate; -use pocketmine\block\WoodenStairs; -use pocketmine\block\WoodenTrapdoor; -use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateData; -use pocketmine\data\bedrock\block\BlockStateNames as StateNames; use pocketmine\data\bedrock\block\BlockStateSerializeException; use pocketmine\data\bedrock\block\BlockStateSerializer; -use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues; use pocketmine\data\bedrock\block\BlockTypeNames as Ids; use pocketmine\data\bedrock\block\convert\BlockStateSerializerHelper as Helper; use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer; -use pocketmine\math\Axis; -use pocketmine\math\Facing; use function get_class; final class BlockObjectToStateSerializer implements BlockStateSerializer{ @@ -196,20 +52,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ */ private array $cache = []; - public function __construct(){ - $this->registerCandleSerializers(); - $this->registerFlatColorBlockSerializers(); - $this->registerFlatCoralSerializers(); - $this->registerCauldronSerializers(); - $this->registerFlatWoodBlockSerializers(); - $this->registerLeavesSerializers(); - $this->registerSaplingSerializers(); - $this->registerMobHeadSerializers(); - $this->registerCopperSerializers(); - $this->registerSimpleSerializers(); - $this->registerSerializers(); - } - public function serialize(int $stateId) : BlockStateData{ //TODO: singleton usage not ideal //TODO: we may want to deduplicate cache entries to avoid wasting memory @@ -227,24 +69,36 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ */ public function map(Block $block, \Closure|Writer|BlockStateData $serializer) : void{ if(isset($this->serializers[$block->getTypeId()])){ - throw new \InvalidArgumentException("Block type ID " . $block->getTypeId() . " already has a serializer registered"); + throw new \InvalidArgumentException("Block type ID " . $block->getTypeId() . " (" . $block->getName() . ") already has a serializer registered"); } //writer accepted for convenience only $this->serializers[$block->getTypeId()] = $serializer instanceof Writer ? $serializer->getBlockStateData() : $serializer; } + /** + * @deprecated + */ public function mapSimple(Block $block, string $id) : void{ $this->map($block, BlockStateData::current($id, [])); } + /** + * @deprecated + */ public function mapSlab(Slab $block, string $singleId, string $doubleId) : void{ $this->map($block, fn(Slab $block) => Helper::encodeSlab($block, $singleId, $doubleId)); } + /** + * @deprecated + */ public function mapStairs(Stair $block, string $id) : void{ $this->map($block, fn(Stair $block) => Helper::encodeStairs($block, Writer::create($id))); } + /** + * @deprecated + */ public function mapLog(Wood $block, string $unstrippedId, string $strippedId) : void{ $this->map($block, fn(Wood $block) => Helper::encodeLog($block, $unstrippedId, $strippedId)); } @@ -279,1451 +133,4 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return $result instanceof Writer ? $result->getBlockStateData() : $result; } - - /** - * @phpstan-template TBlock of Block - * @phpstan-template TEnum of \UnitEnum - * - * @phpstan-param TBlock $block - * @phpstan-param StringEnumMap $mapProperty - * @phpstan-param \Closure(TBlock) : TEnum $getProperty - * @phpstan-param ?\Closure(TBlock, Writer) : Writer $extra - */ - public function mapFlattenedEnum( - Block $block, - StringEnumMap $mapProperty, - string $prefix, - string $suffix, - \Closure $getProperty, - ?\Closure $extra = null - ) : void{ - $this->map($block, function(Block $block) use ($getProperty, $mapProperty, $prefix, $suffix, $extra) : Writer{ - $property = $getProperty($block); - $infix = $mapProperty->enumToValue($property); - $id = $prefix . $infix . $suffix; - $writer = new Writer($id); - if($extra !== null){ - $extra($block, $writer); - } - return $writer; - }); - } - - /** - * @phpstan-template TBlock of Block&Colored - * @phpstan-param TBlock $block - * @phpstan-param ?\Closure(TBlock, Writer) : Writer $extra - */ - public function mapColored( - Block $block, - string $prefix, - string $suffix, - ?\Closure $extra = null - ) : void{ - $this->mapFlattenedEnum( - $block, - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - $prefix, - $suffix, - fn(Colored $block) => $block->getColor(), - $extra - ); - } - - private function registerCandleSerializers() : void{ - $this->map(Blocks::CANDLE(), fn(Candle $block) => Helper::encodeCandle($block, new Writer(Ids::CANDLE))); - $this->mapColored( - Blocks::DYED_CANDLE(), - "minecraft:", - "_candle", - Helper::encodeCandle(...) - ); - $this->map(Blocks::CAKE_WITH_CANDLE(), fn(CakeWithCandle $block) => Writer::create(Ids::CANDLE_CAKE) - ->writeBool(StateNames::LIT, $block->isLit())); - $this->mapColored( - Blocks::CAKE_WITH_DYED_CANDLE(), - "minecraft:", - "_candle_cake", - fn(CakeWithDyedCandle $block, Writer $writer) => $writer->writeBool(StateNames::LIT, $block->isLit()) - ); - } - - public function registerFlatColorBlockSerializers() : void{ - $this->mapColored(Blocks::STAINED_HARDENED_GLASS(), "minecraft:hard_", "_stained_glass"); - $this->mapColored(Blocks::STAINED_HARDENED_GLASS_PANE(), "minecraft:hard_", "_stained_glass_pane"); - - $this->mapColored(Blocks::CARPET(), "minecraft:", "_carpet"); - $this->mapColored(Blocks::CONCRETE(), "minecraft:", "_concrete"); - $this->mapColored(Blocks::CONCRETE_POWDER(), "minecraft:", "_concrete_powder"); - $this->mapColored(Blocks::DYED_SHULKER_BOX(), "minecraft:", "_shulker_box"); - $this->mapColored(Blocks::STAINED_CLAY(), "minecraft:", "_terracotta"); - $this->mapColored(Blocks::STAINED_GLASS(), "minecraft:", "_stained_glass"); - $this->mapColored(Blocks::STAINED_GLASS_PANE(), "minecraft:", "_stained_glass_pane"); - $this->mapColored(Blocks::WOOL(), "minecraft:", "_wool"); - - $this->map(Blocks::GLAZED_TERRACOTTA(), function(GlazedTerracotta $block) : Writer{ - return Writer::create(match($block->getColor()){ - DyeColor::BLACK => Ids::BLACK_GLAZED_TERRACOTTA, - DyeColor::BLUE => Ids::BLUE_GLAZED_TERRACOTTA, - DyeColor::BROWN => Ids::BROWN_GLAZED_TERRACOTTA, - DyeColor::CYAN => Ids::CYAN_GLAZED_TERRACOTTA, - DyeColor::GRAY => Ids::GRAY_GLAZED_TERRACOTTA, - DyeColor::GREEN => Ids::GREEN_GLAZED_TERRACOTTA, - DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_GLAZED_TERRACOTTA, - DyeColor::LIGHT_GRAY => Ids::SILVER_GLAZED_TERRACOTTA, //minecraft sadness - DyeColor::LIME => Ids::LIME_GLAZED_TERRACOTTA, - DyeColor::MAGENTA => Ids::MAGENTA_GLAZED_TERRACOTTA, - DyeColor::ORANGE => Ids::ORANGE_GLAZED_TERRACOTTA, - DyeColor::PINK => Ids::PINK_GLAZED_TERRACOTTA, - DyeColor::PURPLE => Ids::PURPLE_GLAZED_TERRACOTTA, - DyeColor::RED => Ids::RED_GLAZED_TERRACOTTA, - DyeColor::WHITE => Ids::WHITE_GLAZED_TERRACOTTA, - DyeColor::YELLOW => Ids::YELLOW_GLAZED_TERRACOTTA, - }) - ->writeHorizontalFacing($block->getFacing()); - }); - } - - private function registerFlatCoralSerializers() : void{ - $this->map(Blocks::CORAL(), fn(Coral $block) => BlockStateData::current(match($block->getCoralType()){ - CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL : Ids::BRAIN_CORAL, - CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL : Ids::BUBBLE_CORAL, - CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL : Ids::FIRE_CORAL, - CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL : Ids::HORN_CORAL, - CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL : Ids::TUBE_CORAL, - }, [])); - - $this->map(Blocks::CORAL_FAN(), fn(FloorCoralFan $block) => Writer::create( - match($block->getCoralType()){ - CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_FAN : Ids::BRAIN_CORAL_FAN, - CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_FAN : Ids::BUBBLE_CORAL_FAN, - CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_FAN : Ids::FIRE_CORAL_FAN, - CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_FAN : Ids::HORN_CORAL_FAN, - CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_FAN : Ids::TUBE_CORAL_FAN, - }) - ->writeInt(StateNames::CORAL_FAN_DIRECTION, match($axis = $block->getAxis()){ - Axis::X => 0, - Axis::Z => 1, - default => throw new BlockStateSerializeException("Invalid axis {$axis}"), - })); - - $this->map(Blocks::CORAL_BLOCK(), fn(CoralBlock $block) => BlockStateData::current(match($block->getCoralType()){ - CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_BLOCK : Ids::BRAIN_CORAL_BLOCK, - CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_BLOCK : Ids::BUBBLE_CORAL_BLOCK, - CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_BLOCK : Ids::FIRE_CORAL_BLOCK, - CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_BLOCK : Ids::HORN_CORAL_BLOCK, - CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_BLOCK : Ids::TUBE_CORAL_BLOCK, - }, [])); - - $this->map(Blocks::WALL_CORAL_FAN(), fn(WallCoralFan $block) => Writer::create( - match($block->getCoralType()){ - CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_WALL_FAN : Ids::TUBE_CORAL_WALL_FAN, - CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_WALL_FAN : Ids::BRAIN_CORAL_WALL_FAN, - CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_WALL_FAN : Ids::BUBBLE_CORAL_WALL_FAN, - CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_WALL_FAN : Ids::FIRE_CORAL_WALL_FAN, - CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_WALL_FAN : Ids::HORN_CORAL_WALL_FAN, - }) - ->writeCoralFacing($block->getFacing()) - ); - } - - private function registerCauldronSerializers() : void{ - $this->map(Blocks::CAULDRON(), Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0)); - $this->map(Blocks::LAVA_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_LAVA, $b->getFillLevel())); - //potion cauldrons store their real information in the block actor data - $this->map(Blocks::POTION_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); - $this->map(Blocks::WATER_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); - } - - private function registerFlatWoodBlockSerializers() : void{ - $this->map(Blocks::ACACIA_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::ACACIA_BUTTON))); - $this->map(Blocks::ACACIA_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::ACACIA_DOOR))); - $this->map(Blocks::ACACIA_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::ACACIA_FENCE_GATE))); - $this->map(Blocks::ACACIA_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::ACACIA_PRESSURE_PLATE))); - $this->map(Blocks::ACACIA_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::ACACIA_STANDING_SIGN))); - $this->map(Blocks::ACACIA_STAIRS(), fn(WoodenStairs $block) => Helper::encodeStairs($block, new Writer(Ids::ACACIA_STAIRS))); - $this->map(Blocks::ACACIA_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::ACACIA_TRAPDOOR))); - $this->map(Blocks::ACACIA_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::ACACIA_WALL_SIGN))); - $this->mapLog(Blocks::ACACIA_LOG(), Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG); - $this->mapLog(Blocks::ACACIA_WOOD(), Ids::ACACIA_WOOD, Ids::STRIPPED_ACACIA_WOOD); - $this->mapSimple(Blocks::ACACIA_FENCE(), Ids::ACACIA_FENCE); - $this->mapSimple(Blocks::ACACIA_PLANKS(), Ids::ACACIA_PLANKS); - $this->mapSlab(Blocks::ACACIA_SLAB(), Ids::ACACIA_SLAB, Ids::ACACIA_DOUBLE_SLAB); - - $this->map(Blocks::BIRCH_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::BIRCH_BUTTON))); - $this->map(Blocks::BIRCH_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::BIRCH_DOOR))); - $this->map(Blocks::BIRCH_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::BIRCH_FENCE_GATE))); - $this->map(Blocks::BIRCH_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::BIRCH_PRESSURE_PLATE))); - $this->map(Blocks::BIRCH_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::BIRCH_STANDING_SIGN))); - $this->map(Blocks::BIRCH_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::BIRCH_TRAPDOOR))); - $this->map(Blocks::BIRCH_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::BIRCH_WALL_SIGN))); - $this->mapLog(Blocks::BIRCH_LOG(), Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG); - $this->mapLog(Blocks::BIRCH_WOOD(), Ids::BIRCH_WOOD, Ids::STRIPPED_BIRCH_WOOD); - $this->mapSimple(Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE); - $this->mapSimple(Blocks::BIRCH_PLANKS(), Ids::BIRCH_PLANKS); - $this->mapSlab(Blocks::BIRCH_SLAB(), Ids::BIRCH_SLAB, Ids::BIRCH_DOUBLE_SLAB); - $this->mapStairs(Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS); - - $this->map(Blocks::CHERRY_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CHERRY_BUTTON))); - $this->map(Blocks::CHERRY_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CHERRY_DOOR))); - $this->map(Blocks::CHERRY_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CHERRY_FENCE_GATE))); - $this->map(Blocks::CHERRY_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::CHERRY_LOG, Ids::STRIPPED_CHERRY_LOG)); - $this->map(Blocks::CHERRY_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CHERRY_PRESSURE_PLATE))); - $this->map(Blocks::CHERRY_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CHERRY_STANDING_SIGN))); - $this->map(Blocks::CHERRY_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CHERRY_TRAPDOOR))); - $this->map(Blocks::CHERRY_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CHERRY_WALL_SIGN))); - $this->mapSimple(Blocks::CHERRY_FENCE(), Ids::CHERRY_FENCE); - $this->mapSimple(Blocks::CHERRY_PLANKS(), Ids::CHERRY_PLANKS); - $this->mapSlab(Blocks::CHERRY_SLAB(), Ids::CHERRY_SLAB, Ids::CHERRY_DOUBLE_SLAB); - $this->mapStairs(Blocks::CHERRY_STAIRS(), Ids::CHERRY_STAIRS); - $this->mapLog(Blocks::CHERRY_WOOD(), Ids::CHERRY_WOOD, Ids::STRIPPED_CHERRY_WOOD); - - $this->map(Blocks::CRIMSON_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CRIMSON_BUTTON))); - $this->map(Blocks::CRIMSON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CRIMSON_DOOR))); - $this->map(Blocks::CRIMSON_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CRIMSON_FENCE_GATE))); - $this->map(Blocks::CRIMSON_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE)); - $this->map(Blocks::CRIMSON_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CRIMSON_PRESSURE_PLATE))); - $this->map(Blocks::CRIMSON_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CRIMSON_STANDING_SIGN))); - $this->map(Blocks::CRIMSON_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM)); - $this->map(Blocks::CRIMSON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CRIMSON_TRAPDOOR))); - $this->map(Blocks::CRIMSON_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CRIMSON_WALL_SIGN))); - $this->mapSimple(Blocks::CRIMSON_FENCE(), Ids::CRIMSON_FENCE); - $this->mapSimple(Blocks::CRIMSON_PLANKS(), Ids::CRIMSON_PLANKS); - $this->mapSlab(Blocks::CRIMSON_SLAB(), Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB); - $this->mapStairs(Blocks::CRIMSON_STAIRS(), Ids::CRIMSON_STAIRS); - - $this->map(Blocks::DARK_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::DARK_OAK_BUTTON))); - $this->map(Blocks::DARK_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::DARK_OAK_DOOR))); - $this->map(Blocks::DARK_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::DARK_OAK_FENCE_GATE))); - $this->map(Blocks::DARK_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::DARK_OAK_PRESSURE_PLATE))); - $this->map(Blocks::DARK_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::DARKOAK_STANDING_SIGN))); - $this->map(Blocks::DARK_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::DARK_OAK_TRAPDOOR))); - $this->map(Blocks::DARK_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::DARKOAK_WALL_SIGN))); - $this->mapLog(Blocks::DARK_OAK_LOG(), Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG); - $this->mapLog(Blocks::DARK_OAK_WOOD(), Ids::DARK_OAK_WOOD, Ids::STRIPPED_DARK_OAK_WOOD); - $this->mapSimple(Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE); - $this->mapSimple(Blocks::DARK_OAK_PLANKS(), Ids::DARK_OAK_PLANKS); - $this->mapSlab(Blocks::DARK_OAK_SLAB(), Ids::DARK_OAK_SLAB, Ids::DARK_OAK_DOUBLE_SLAB); - $this->mapStairs(Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS); - - $this->map(Blocks::JUNGLE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::JUNGLE_BUTTON))); - $this->map(Blocks::JUNGLE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::JUNGLE_DOOR))); - $this->map(Blocks::JUNGLE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::JUNGLE_FENCE_GATE))); - $this->map(Blocks::JUNGLE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::JUNGLE_PRESSURE_PLATE))); - $this->map(Blocks::JUNGLE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::JUNGLE_STANDING_SIGN))); - $this->map(Blocks::JUNGLE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::JUNGLE_TRAPDOOR))); - $this->map(Blocks::JUNGLE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::JUNGLE_WALL_SIGN))); - $this->mapLog(Blocks::JUNGLE_LOG(), Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG); - $this->mapLog(Blocks::JUNGLE_WOOD(), Ids::JUNGLE_WOOD, Ids::STRIPPED_JUNGLE_WOOD); - $this->mapSimple(Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE); - $this->mapSimple(Blocks::JUNGLE_PLANKS(), Ids::JUNGLE_PLANKS); - $this->mapSlab(Blocks::JUNGLE_SLAB(), Ids::JUNGLE_SLAB, Ids::JUNGLE_DOUBLE_SLAB); - $this->mapStairs(Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS); - - $this->map(Blocks::MANGROVE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::MANGROVE_BUTTON))); - $this->map(Blocks::MANGROVE_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::MANGROVE_DOOR))); - $this->map(Blocks::MANGROVE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::MANGROVE_FENCE_GATE))); - $this->map(Blocks::MANGROVE_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG)); - $this->map(Blocks::MANGROVE_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::MANGROVE_PRESSURE_PLATE))); - $this->map(Blocks::MANGROVE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::MANGROVE_STANDING_SIGN))); - $this->map(Blocks::MANGROVE_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::MANGROVE_TRAPDOOR))); - $this->map(Blocks::MANGROVE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::MANGROVE_WALL_SIGN))); - $this->mapSimple(Blocks::MANGROVE_FENCE(), Ids::MANGROVE_FENCE); - $this->mapSimple(Blocks::MANGROVE_PLANKS(), Ids::MANGROVE_PLANKS); - $this->mapSlab(Blocks::MANGROVE_SLAB(), Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB); - $this->mapStairs(Blocks::MANGROVE_STAIRS(), Ids::MANGROVE_STAIRS); - $this->mapLog(Blocks::MANGROVE_WOOD(), Ids::MANGROVE_WOOD, Ids::STRIPPED_MANGROVE_WOOD); - - $this->map(Blocks::OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::WOODEN_BUTTON))); - $this->map(Blocks::OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::WOODEN_DOOR))); - $this->map(Blocks::OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::FENCE_GATE))); - $this->map(Blocks::OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WOODEN_PRESSURE_PLATE))); - $this->map(Blocks::OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::STANDING_SIGN))); - $this->map(Blocks::OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::TRAPDOOR))); - $this->map(Blocks::OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WALL_SIGN))); - $this->mapLog(Blocks::OAK_LOG(), Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG); - $this->mapLog(Blocks::OAK_WOOD(), Ids::OAK_WOOD, Ids::STRIPPED_OAK_WOOD); - $this->mapSimple(Blocks::OAK_FENCE(), Ids::OAK_FENCE); - $this->mapSimple(Blocks::OAK_PLANKS(), Ids::OAK_PLANKS); - $this->mapSlab(Blocks::OAK_SLAB(), Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB); - $this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS); - - $this->map(Blocks::PALE_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::PALE_OAK_BUTTON))); - $this->map(Blocks::PALE_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::PALE_OAK_DOOR))); - $this->map(Blocks::PALE_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::PALE_OAK_FENCE_GATE))); - $this->map(Blocks::PALE_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::PALE_OAK_PRESSURE_PLATE))); - $this->map(Blocks::PALE_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::PALE_OAK_STANDING_SIGN))); - $this->map(Blocks::PALE_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::PALE_OAK_TRAPDOOR))); - $this->map(Blocks::PALE_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::PALE_OAK_WALL_SIGN))); - $this->mapLog(Blocks::PALE_OAK_LOG(), Ids::PALE_OAK_LOG, Ids::STRIPPED_PALE_OAK_LOG); - $this->mapLog(Blocks::PALE_OAK_WOOD(), Ids::PALE_OAK_WOOD, Ids::STRIPPED_PALE_OAK_WOOD); - $this->mapSimple(Blocks::PALE_OAK_FENCE(), Ids::PALE_OAK_FENCE); - $this->mapSimple(Blocks::PALE_OAK_PLANKS(), Ids::PALE_OAK_PLANKS); - $this->mapSlab(Blocks::PALE_OAK_SLAB(), Ids::PALE_OAK_SLAB, Ids::PALE_OAK_DOUBLE_SLAB); - $this->mapStairs(Blocks::PALE_OAK_STAIRS(), Ids::PALE_OAK_STAIRS); - - $this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON))); - $this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR))); - $this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE))); - $this->map(Blocks::SPRUCE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::SPRUCE_PRESSURE_PLATE))); - $this->map(Blocks::SPRUCE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::SPRUCE_STANDING_SIGN))); - $this->map(Blocks::SPRUCE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::SPRUCE_TRAPDOOR))); - $this->map(Blocks::SPRUCE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::SPRUCE_WALL_SIGN))); - $this->mapLog(Blocks::SPRUCE_LOG(), Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG); - $this->mapLog(Blocks::SPRUCE_WOOD(), Ids::SPRUCE_WOOD, Ids::STRIPPED_SPRUCE_WOOD); - $this->mapSimple(Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE); - $this->mapSimple(Blocks::SPRUCE_PLANKS(), Ids::SPRUCE_PLANKS); - $this->mapSlab(Blocks::SPRUCE_SLAB(), Ids::SPRUCE_SLAB, Ids::SPRUCE_DOUBLE_SLAB); - $this->mapStairs(Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS); - //wood and slabs still use the old way of storing wood type - - $this->map(Blocks::WARPED_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::WARPED_BUTTON))); - $this->map(Blocks::WARPED_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::WARPED_DOOR))); - $this->map(Blocks::WARPED_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::WARPED_FENCE_GATE))); - $this->map(Blocks::WARPED_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE)); - $this->map(Blocks::WARPED_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WARPED_PRESSURE_PLATE))); - $this->map(Blocks::WARPED_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::WARPED_STANDING_SIGN))); - $this->map(Blocks::WARPED_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM)); - $this->map(Blocks::WARPED_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::WARPED_TRAPDOOR))); - $this->map(Blocks::WARPED_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WARPED_WALL_SIGN))); - $this->mapSimple(Blocks::WARPED_FENCE(), Ids::WARPED_FENCE); - $this->mapSimple(Blocks::WARPED_PLANKS(), Ids::WARPED_PLANKS); - $this->mapSlab(Blocks::WARPED_SLAB(), Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB); - $this->mapStairs(Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS); - } - - private function registerLeavesSerializers() : void{ - //flattened IDs - $this->map(Blocks::AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES))); - $this->map(Blocks::CHERRY_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::CHERRY_LEAVES))); - $this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED))); - $this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES))); - $this->map(Blocks::PALE_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::PALE_OAK_LEAVES))); - - //legacy mess - $this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::ACACIA_LEAVES))); - $this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::BIRCH_LEAVES))); - $this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::DARK_OAK_LEAVES))); - $this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::JUNGLE_LEAVES))); - $this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::OAK_LEAVES))); - $this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::SPRUCE_LEAVES))); - } - - private function registerSaplingSerializers() : void{ - foreach([ - Ids::ACACIA_SAPLING => Blocks::ACACIA_SAPLING(), - Ids::BIRCH_SAPLING => Blocks::BIRCH_SAPLING(), - Ids::DARK_OAK_SAPLING => Blocks::DARK_OAK_SAPLING(), - Ids::JUNGLE_SAPLING => Blocks::JUNGLE_SAPLING(), - Ids::OAK_SAPLING => Blocks::OAK_SAPLING(), - Ids::SPRUCE_SAPLING => Blocks::SPRUCE_SAPLING(), - ] as $id => $block){ - $this->map($block, fn(Sapling $block) => Helper::encodeSapling($block, new Writer($id))); - } - } - - private function registerMobHeadSerializers() : void{ - $this->map(Blocks::MOB_HEAD(), fn(MobHead $block) => Writer::create(match ($block->getMobHeadType()){ - MobHeadType::CREEPER => Ids::CREEPER_HEAD, - MobHeadType::DRAGON => Ids::DRAGON_HEAD, - MobHeadType::PIGLIN => Ids::PIGLIN_HEAD, - MobHeadType::PLAYER => Ids::PLAYER_HEAD, - MobHeadType::SKELETON => Ids::SKELETON_SKULL, - MobHeadType::WITHER_SKELETON => Ids::WITHER_SKELETON_SKULL, - MobHeadType::ZOMBIE => Ids::ZOMBIE_HEAD, - })->writeFacingWithoutDown($block->getFacing())); - } - - private function registerCopperSerializers() : void{ - $this->map(Blocks::COPPER(), function(Copper $block) : BlockStateData{ - $oxidation = $block->getOxidation(); - return BlockStateData::current( - $block->isWaxed() ? - Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) : - Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER), - [] - ); - }); - $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : BlockStateData{ - $oxidation = $block->getOxidation(); - return BlockStateData::current( - $block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_CHISELED_COPPER, - Ids::WAXED_EXPOSED_CHISELED_COPPER, - Ids::WAXED_WEATHERED_CHISELED_COPPER, - Ids::WAXED_OXIDIZED_CHISELED_COPPER - ) : - Helper::selectCopperId($oxidation, - Ids::CHISELED_COPPER, - Ids::EXPOSED_CHISELED_COPPER, - Ids::WEATHERED_CHISELED_COPPER, - Ids::OXIDIZED_CHISELED_COPPER - ), - [] - ); - }); - $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : BlockStateData{ - $oxidation = $block->getOxidation(); - return BlockStateData::current( - $block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_COPPER_GRATE, - Ids::WAXED_EXPOSED_COPPER_GRATE, - Ids::WAXED_WEATHERED_COPPER_GRATE, - Ids::WAXED_OXIDIZED_COPPER_GRATE - ) : - Helper::selectCopperId($oxidation, - Ids::COPPER_GRATE, - Ids::EXPOSED_COPPER_GRATE, - Ids::WEATHERED_COPPER_GRATE, - Ids::OXIDIZED_COPPER_GRATE - ), - [] - ); - }); - $this->map(Blocks::CUT_COPPER(), function(Copper $block) : BlockStateData{ - $oxidation = $block->getOxidation(); - return BlockStateData::current( - $block->isWaxed() ? - Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) : - Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER), - [] - ); - }); - $this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeSlab( - $block, - ($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_CUT_COPPER_SLAB, - Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, - Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, - Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB - ) : - Helper::selectCopperId( - $oxidation, - Ids::CUT_COPPER_SLAB, - Ids::EXPOSED_CUT_COPPER_SLAB, - Ids::WEATHERED_CUT_COPPER_SLAB, - Ids::OXIDIZED_CUT_COPPER_SLAB - ) - ), - ($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB - ) : - Helper::selectCopperId( - $oxidation, - Ids::DOUBLE_CUT_COPPER_SLAB, - Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, - Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, - Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB - ) - ) - ); - }); - $this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeStairs( - $block, - new Writer($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_CUT_COPPER_STAIRS, - Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, - Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, - Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS - ) : - Helper::selectCopperId( - $oxidation, - Ids::CUT_COPPER_STAIRS, - Ids::EXPOSED_CUT_COPPER_STAIRS, - Ids::WEATHERED_CUT_COPPER_STAIRS, - Ids::OXIDIZED_CUT_COPPER_STAIRS - ) - ) - ); - }); - $this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{ - $oxidation = $block->getOxidation(); - return Writer::create($block->isWaxed() ? - Helper::selectCopperId($oxidation, - Ids::WAXED_COPPER_BULB, - Ids::WAXED_EXPOSED_COPPER_BULB, - Ids::WAXED_WEATHERED_COPPER_BULB, - Ids::WAXED_OXIDIZED_COPPER_BULB) : - Helper::selectCopperId($oxidation, - Ids::COPPER_BULB, - Ids::EXPOSED_COPPER_BULB, - Ids::WEATHERED_COPPER_BULB, - Ids::OXIDIZED_COPPER_BULB - )) - ->writeBool(StateNames::LIT, $block->isLit()) - ->writeBool(StateNames::POWERED_BIT, $block->isPowered()); - }); - $this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeDoor( - $block, - new Writer($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_COPPER_DOOR, - Ids::WAXED_EXPOSED_COPPER_DOOR, - Ids::WAXED_WEATHERED_COPPER_DOOR, - Ids::WAXED_OXIDIZED_COPPER_DOOR - ) : - Helper::selectCopperId( - $oxidation, - Ids::COPPER_DOOR, - Ids::EXPOSED_COPPER_DOOR, - Ids::WEATHERED_COPPER_DOOR, - Ids::OXIDIZED_COPPER_DOOR - ) - ) - ); - }); - $this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{ - $oxidation = $block->getOxidation(); - return Helper::encodeTrapdoor( - $block, - new Writer($block->isWaxed() ? - Helper::selectCopperId( - $oxidation, - Ids::WAXED_COPPER_TRAPDOOR, - Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, - Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, - Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR - ) : - Helper::selectCopperId( - $oxidation, - Ids::COPPER_TRAPDOOR, - Ids::EXPOSED_COPPER_TRAPDOOR, - Ids::WEATHERED_COPPER_TRAPDOOR, - Ids::OXIDIZED_COPPER_TRAPDOOR - ) - ) - ); - }); - } - - private function registerSimpleSerializers() : void{ - $this->mapSimple(Blocks::AIR(), Ids::AIR); - $this->mapSimple(Blocks::AMETHYST(), Ids::AMETHYST_BLOCK); - $this->mapSimple(Blocks::ANCIENT_DEBRIS(), Ids::ANCIENT_DEBRIS); - $this->mapSimple(Blocks::ANDESITE(), Ids::ANDESITE); - $this->mapSimple(Blocks::BARRIER(), Ids::BARRIER); - $this->mapSimple(Blocks::BEACON(), Ids::BEACON); - $this->mapSimple(Blocks::BLACKSTONE(), Ids::BLACKSTONE); - $this->mapSimple(Blocks::BLUE_ICE(), Ids::BLUE_ICE); - $this->mapSimple(Blocks::BOOKSHELF(), Ids::BOOKSHELF); - $this->mapSimple(Blocks::BRICKS(), Ids::BRICK_BLOCK); - $this->mapSimple(Blocks::BROWN_MUSHROOM(), Ids::BROWN_MUSHROOM); - $this->mapSimple(Blocks::BUDDING_AMETHYST(), Ids::BUDDING_AMETHYST); - $this->mapSimple(Blocks::CALCITE(), Ids::CALCITE); - $this->mapSimple(Blocks::CARTOGRAPHY_TABLE(), Ids::CARTOGRAPHY_TABLE); - $this->mapSimple(Blocks::CHEMICAL_HEAT(), Ids::CHEMICAL_HEAT); - $this->mapSimple(Blocks::CHISELED_DEEPSLATE(), Ids::CHISELED_DEEPSLATE); - $this->mapSimple(Blocks::CHISELED_NETHER_BRICKS(), Ids::CHISELED_NETHER_BRICKS); - $this->mapSimple(Blocks::CHISELED_POLISHED_BLACKSTONE(), Ids::CHISELED_POLISHED_BLACKSTONE); - $this->mapSimple(Blocks::CHISELED_RED_SANDSTONE(), Ids::CHISELED_RED_SANDSTONE); - $this->mapSimple(Blocks::CHISELED_RESIN_BRICKS(), Ids::CHISELED_RESIN_BRICKS); - $this->mapSimple(Blocks::CHISELED_SANDSTONE(), Ids::CHISELED_SANDSTONE); - $this->mapSimple(Blocks::CHISELED_STONE_BRICKS(), Ids::CHISELED_STONE_BRICKS); - $this->mapSimple(Blocks::CHISELED_TUFF(), Ids::CHISELED_TUFF); - $this->mapSimple(Blocks::CHISELED_TUFF_BRICKS(), Ids::CHISELED_TUFF_BRICKS); - $this->mapSimple(Blocks::CHORUS_PLANT(), Ids::CHORUS_PLANT); - $this->mapSimple(Blocks::CLAY(), Ids::CLAY); - $this->mapSimple(Blocks::COAL(), Ids::COAL_BLOCK); - $this->mapSimple(Blocks::COAL_ORE(), Ids::COAL_ORE); - $this->mapSimple(Blocks::COBBLED_DEEPSLATE(), Ids::COBBLED_DEEPSLATE); - $this->mapSimple(Blocks::COBBLESTONE(), Ids::COBBLESTONE); - $this->mapSimple(Blocks::COBWEB(), Ids::WEB); - $this->mapSimple(Blocks::COPPER_ORE(), Ids::COPPER_ORE); - $this->mapSimple(Blocks::CRACKED_DEEPSLATE_BRICKS(), Ids::CRACKED_DEEPSLATE_BRICKS); - $this->mapSimple(Blocks::CRACKED_DEEPSLATE_TILES(), Ids::CRACKED_DEEPSLATE_TILES); - $this->mapSimple(Blocks::CRACKED_NETHER_BRICKS(), Ids::CRACKED_NETHER_BRICKS); - $this->mapSimple(Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS(), Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS); - $this->mapSimple(Blocks::CRACKED_STONE_BRICKS(), Ids::CRACKED_STONE_BRICKS); - $this->mapSimple(Blocks::CRAFTING_TABLE(), Ids::CRAFTING_TABLE); - $this->mapSimple(Blocks::CRIMSON_ROOTS(), Ids::CRIMSON_ROOTS); - $this->mapSimple(Blocks::CRYING_OBSIDIAN(), Ids::CRYING_OBSIDIAN); - $this->mapSimple(Blocks::DANDELION(), Ids::DANDELION); - $this->mapSimple(Blocks::CUT_RED_SANDSTONE(), Ids::CUT_RED_SANDSTONE); - $this->mapSimple(Blocks::CUT_SANDSTONE(), Ids::CUT_SANDSTONE); - $this->mapSimple(Blocks::DARK_PRISMARINE(), Ids::DARK_PRISMARINE); - $this->mapSimple(Blocks::DEAD_BUSH(), Ids::DEADBUSH); - $this->mapSimple(Blocks::DEEPSLATE_BRICKS(), Ids::DEEPSLATE_BRICKS); - $this->mapSimple(Blocks::DEEPSLATE_COAL_ORE(), Ids::DEEPSLATE_COAL_ORE); - $this->mapSimple(Blocks::DEEPSLATE_COPPER_ORE(), Ids::DEEPSLATE_COPPER_ORE); - $this->mapSimple(Blocks::DEEPSLATE_DIAMOND_ORE(), Ids::DEEPSLATE_DIAMOND_ORE); - $this->mapSimple(Blocks::DEEPSLATE_EMERALD_ORE(), Ids::DEEPSLATE_EMERALD_ORE); - $this->mapSimple(Blocks::DEEPSLATE_GOLD_ORE(), Ids::DEEPSLATE_GOLD_ORE); - $this->mapSimple(Blocks::DEEPSLATE_IRON_ORE(), Ids::DEEPSLATE_IRON_ORE); - $this->mapSimple(Blocks::DEEPSLATE_LAPIS_LAZULI_ORE(), Ids::DEEPSLATE_LAPIS_ORE); - $this->mapSimple(Blocks::DEEPSLATE_TILES(), Ids::DEEPSLATE_TILES); - $this->mapSimple(Blocks::DIAMOND(), Ids::DIAMOND_BLOCK); - $this->mapSimple(Blocks::DIAMOND_ORE(), Ids::DIAMOND_ORE); - $this->mapSimple(Blocks::DIORITE(), Ids::DIORITE); - $this->mapSimple(Blocks::DRAGON_EGG(), Ids::DRAGON_EGG); - $this->mapSimple(Blocks::DRIED_KELP(), Ids::DRIED_KELP_BLOCK); - $this->mapSimple(Blocks::ELEMENT_ACTINIUM(), Ids::ELEMENT_89); - $this->mapSimple(Blocks::ELEMENT_ALUMINUM(), Ids::ELEMENT_13); - $this->mapSimple(Blocks::ELEMENT_AMERICIUM(), Ids::ELEMENT_95); - $this->mapSimple(Blocks::ELEMENT_ANTIMONY(), Ids::ELEMENT_51); - $this->mapSimple(Blocks::ELEMENT_ARGON(), Ids::ELEMENT_18); - $this->mapSimple(Blocks::ELEMENT_ARSENIC(), Ids::ELEMENT_33); - $this->mapSimple(Blocks::ELEMENT_ASTATINE(), Ids::ELEMENT_85); - $this->mapSimple(Blocks::ELEMENT_BARIUM(), Ids::ELEMENT_56); - $this->mapSimple(Blocks::ELEMENT_BERKELIUM(), Ids::ELEMENT_97); - $this->mapSimple(Blocks::ELEMENT_BERYLLIUM(), Ids::ELEMENT_4); - $this->mapSimple(Blocks::ELEMENT_BISMUTH(), Ids::ELEMENT_83); - $this->mapSimple(Blocks::ELEMENT_BOHRIUM(), Ids::ELEMENT_107); - $this->mapSimple(Blocks::ELEMENT_BORON(), Ids::ELEMENT_5); - $this->mapSimple(Blocks::ELEMENT_BROMINE(), Ids::ELEMENT_35); - $this->mapSimple(Blocks::ELEMENT_CADMIUM(), Ids::ELEMENT_48); - $this->mapSimple(Blocks::ELEMENT_CALCIUM(), Ids::ELEMENT_20); - $this->mapSimple(Blocks::ELEMENT_CALIFORNIUM(), Ids::ELEMENT_98); - $this->mapSimple(Blocks::ELEMENT_CARBON(), Ids::ELEMENT_6); - $this->mapSimple(Blocks::ELEMENT_CERIUM(), Ids::ELEMENT_58); - $this->mapSimple(Blocks::ELEMENT_CESIUM(), Ids::ELEMENT_55); - $this->mapSimple(Blocks::ELEMENT_CHLORINE(), Ids::ELEMENT_17); - $this->mapSimple(Blocks::ELEMENT_CHROMIUM(), Ids::ELEMENT_24); - $this->mapSimple(Blocks::ELEMENT_COBALT(), Ids::ELEMENT_27); - $this->mapSimple(Blocks::ELEMENT_COPERNICIUM(), Ids::ELEMENT_112); - $this->mapSimple(Blocks::ELEMENT_COPPER(), Ids::ELEMENT_29); - $this->mapSimple(Blocks::ELEMENT_CURIUM(), Ids::ELEMENT_96); - $this->mapSimple(Blocks::ELEMENT_DARMSTADTIUM(), Ids::ELEMENT_110); - $this->mapSimple(Blocks::ELEMENT_DUBNIUM(), Ids::ELEMENT_105); - $this->mapSimple(Blocks::ELEMENT_DYSPROSIUM(), Ids::ELEMENT_66); - $this->mapSimple(Blocks::ELEMENT_EINSTEINIUM(), Ids::ELEMENT_99); - $this->mapSimple(Blocks::ELEMENT_ERBIUM(), Ids::ELEMENT_68); - $this->mapSimple(Blocks::ELEMENT_EUROPIUM(), Ids::ELEMENT_63); - $this->mapSimple(Blocks::ELEMENT_FERMIUM(), Ids::ELEMENT_100); - $this->mapSimple(Blocks::ELEMENT_FLEROVIUM(), Ids::ELEMENT_114); - $this->mapSimple(Blocks::ELEMENT_FLUORINE(), Ids::ELEMENT_9); - $this->mapSimple(Blocks::ELEMENT_FRANCIUM(), Ids::ELEMENT_87); - $this->mapSimple(Blocks::ELEMENT_GADOLINIUM(), Ids::ELEMENT_64); - $this->mapSimple(Blocks::ELEMENT_GALLIUM(), Ids::ELEMENT_31); - $this->mapSimple(Blocks::ELEMENT_GERMANIUM(), Ids::ELEMENT_32); - $this->mapSimple(Blocks::ELEMENT_GOLD(), Ids::ELEMENT_79); - $this->mapSimple(Blocks::ELEMENT_HAFNIUM(), Ids::ELEMENT_72); - $this->mapSimple(Blocks::ELEMENT_HASSIUM(), Ids::ELEMENT_108); - $this->mapSimple(Blocks::ELEMENT_HELIUM(), Ids::ELEMENT_2); - $this->mapSimple(Blocks::ELEMENT_HOLMIUM(), Ids::ELEMENT_67); - $this->mapSimple(Blocks::ELEMENT_HYDROGEN(), Ids::ELEMENT_1); - $this->mapSimple(Blocks::ELEMENT_INDIUM(), Ids::ELEMENT_49); - $this->mapSimple(Blocks::ELEMENT_IODINE(), Ids::ELEMENT_53); - $this->mapSimple(Blocks::ELEMENT_IRIDIUM(), Ids::ELEMENT_77); - $this->mapSimple(Blocks::ELEMENT_IRON(), Ids::ELEMENT_26); - $this->mapSimple(Blocks::ELEMENT_KRYPTON(), Ids::ELEMENT_36); - $this->mapSimple(Blocks::ELEMENT_LANTHANUM(), Ids::ELEMENT_57); - $this->mapSimple(Blocks::ELEMENT_LAWRENCIUM(), Ids::ELEMENT_103); - $this->mapSimple(Blocks::ELEMENT_LEAD(), Ids::ELEMENT_82); - $this->mapSimple(Blocks::ELEMENT_LITHIUM(), Ids::ELEMENT_3); - $this->mapSimple(Blocks::ELEMENT_LIVERMORIUM(), Ids::ELEMENT_116); - $this->mapSimple(Blocks::ELEMENT_LUTETIUM(), Ids::ELEMENT_71); - $this->mapSimple(Blocks::ELEMENT_MAGNESIUM(), Ids::ELEMENT_12); - $this->mapSimple(Blocks::ELEMENT_MANGANESE(), Ids::ELEMENT_25); - $this->mapSimple(Blocks::ELEMENT_MEITNERIUM(), Ids::ELEMENT_109); - $this->mapSimple(Blocks::ELEMENT_MENDELEVIUM(), Ids::ELEMENT_101); - $this->mapSimple(Blocks::ELEMENT_MERCURY(), Ids::ELEMENT_80); - $this->mapSimple(Blocks::ELEMENT_MOLYBDENUM(), Ids::ELEMENT_42); - $this->mapSimple(Blocks::ELEMENT_MOSCOVIUM(), Ids::ELEMENT_115); - $this->mapSimple(Blocks::ELEMENT_NEODYMIUM(), Ids::ELEMENT_60); - $this->mapSimple(Blocks::ELEMENT_NEON(), Ids::ELEMENT_10); - $this->mapSimple(Blocks::ELEMENT_NEPTUNIUM(), Ids::ELEMENT_93); - $this->mapSimple(Blocks::ELEMENT_NICKEL(), Ids::ELEMENT_28); - $this->mapSimple(Blocks::ELEMENT_NIHONIUM(), Ids::ELEMENT_113); - $this->mapSimple(Blocks::ELEMENT_NIOBIUM(), Ids::ELEMENT_41); - $this->mapSimple(Blocks::ELEMENT_NITROGEN(), Ids::ELEMENT_7); - $this->mapSimple(Blocks::ELEMENT_NOBELIUM(), Ids::ELEMENT_102); - $this->mapSimple(Blocks::ELEMENT_OGANESSON(), Ids::ELEMENT_118); - $this->mapSimple(Blocks::ELEMENT_OSMIUM(), Ids::ELEMENT_76); - $this->mapSimple(Blocks::ELEMENT_OXYGEN(), Ids::ELEMENT_8); - $this->mapSimple(Blocks::ELEMENT_PALLADIUM(), Ids::ELEMENT_46); - $this->mapSimple(Blocks::ELEMENT_PHOSPHORUS(), Ids::ELEMENT_15); - $this->mapSimple(Blocks::ELEMENT_PLATINUM(), Ids::ELEMENT_78); - $this->mapSimple(Blocks::ELEMENT_PLUTONIUM(), Ids::ELEMENT_94); - $this->mapSimple(Blocks::ELEMENT_POLONIUM(), Ids::ELEMENT_84); - $this->mapSimple(Blocks::ELEMENT_POTASSIUM(), Ids::ELEMENT_19); - $this->mapSimple(Blocks::ELEMENT_PRASEODYMIUM(), Ids::ELEMENT_59); - $this->mapSimple(Blocks::ELEMENT_PROMETHIUM(), Ids::ELEMENT_61); - $this->mapSimple(Blocks::ELEMENT_PROTACTINIUM(), Ids::ELEMENT_91); - $this->mapSimple(Blocks::ELEMENT_RADIUM(), Ids::ELEMENT_88); - $this->mapSimple(Blocks::ELEMENT_RADON(), Ids::ELEMENT_86); - $this->mapSimple(Blocks::ELEMENT_RHENIUM(), Ids::ELEMENT_75); - $this->mapSimple(Blocks::ELEMENT_RHODIUM(), Ids::ELEMENT_45); - $this->mapSimple(Blocks::ELEMENT_ROENTGENIUM(), Ids::ELEMENT_111); - $this->mapSimple(Blocks::ELEMENT_RUBIDIUM(), Ids::ELEMENT_37); - $this->mapSimple(Blocks::ELEMENT_RUTHENIUM(), Ids::ELEMENT_44); - $this->mapSimple(Blocks::ELEMENT_RUTHERFORDIUM(), Ids::ELEMENT_104); - $this->mapSimple(Blocks::ELEMENT_SAMARIUM(), Ids::ELEMENT_62); - $this->mapSimple(Blocks::ELEMENT_SCANDIUM(), Ids::ELEMENT_21); - $this->mapSimple(Blocks::ELEMENT_SEABORGIUM(), Ids::ELEMENT_106); - $this->mapSimple(Blocks::ELEMENT_SELENIUM(), Ids::ELEMENT_34); - $this->mapSimple(Blocks::ELEMENT_SILICON(), Ids::ELEMENT_14); - $this->mapSimple(Blocks::ELEMENT_SILVER(), Ids::ELEMENT_47); - $this->mapSimple(Blocks::ELEMENT_SODIUM(), Ids::ELEMENT_11); - $this->mapSimple(Blocks::ELEMENT_STRONTIUM(), Ids::ELEMENT_38); - $this->mapSimple(Blocks::ELEMENT_SULFUR(), Ids::ELEMENT_16); - $this->mapSimple(Blocks::ELEMENT_TANTALUM(), Ids::ELEMENT_73); - $this->mapSimple(Blocks::ELEMENT_TECHNETIUM(), Ids::ELEMENT_43); - $this->mapSimple(Blocks::ELEMENT_TELLURIUM(), Ids::ELEMENT_52); - $this->mapSimple(Blocks::ELEMENT_TENNESSINE(), Ids::ELEMENT_117); - $this->mapSimple(Blocks::ELEMENT_TERBIUM(), Ids::ELEMENT_65); - $this->mapSimple(Blocks::ELEMENT_THALLIUM(), Ids::ELEMENT_81); - $this->mapSimple(Blocks::ELEMENT_THORIUM(), Ids::ELEMENT_90); - $this->mapSimple(Blocks::ELEMENT_THULIUM(), Ids::ELEMENT_69); - $this->mapSimple(Blocks::ELEMENT_TIN(), Ids::ELEMENT_50); - $this->mapSimple(Blocks::ELEMENT_TITANIUM(), Ids::ELEMENT_22); - $this->mapSimple(Blocks::ELEMENT_TUNGSTEN(), Ids::ELEMENT_74); - $this->mapSimple(Blocks::ELEMENT_URANIUM(), Ids::ELEMENT_92); - $this->mapSimple(Blocks::ELEMENT_VANADIUM(), Ids::ELEMENT_23); - $this->mapSimple(Blocks::ELEMENT_XENON(), Ids::ELEMENT_54); - $this->mapSimple(Blocks::ELEMENT_YTTERBIUM(), Ids::ELEMENT_70); - $this->mapSimple(Blocks::ELEMENT_YTTRIUM(), Ids::ELEMENT_39); - $this->mapSimple(Blocks::ELEMENT_ZERO(), Ids::ELEMENT_0); - $this->mapSimple(Blocks::ELEMENT_ZINC(), Ids::ELEMENT_30); - $this->mapSimple(Blocks::ELEMENT_ZIRCONIUM(), Ids::ELEMENT_40); - $this->mapSimple(Blocks::EMERALD(), Ids::EMERALD_BLOCK); - $this->mapSimple(Blocks::EMERALD_ORE(), Ids::EMERALD_ORE); - $this->mapSimple(Blocks::ENCHANTING_TABLE(), Ids::ENCHANTING_TABLE); - $this->mapSimple(Blocks::END_STONE(), Ids::END_STONE); - $this->mapSimple(Blocks::END_STONE_BRICKS(), Ids::END_BRICKS); - $this->mapSimple(Blocks::FERN(), Ids::FERN); - $this->mapSimple(Blocks::FLETCHING_TABLE(), Ids::FLETCHING_TABLE); - $this->mapSimple(Blocks::GILDED_BLACKSTONE(), Ids::GILDED_BLACKSTONE); - $this->mapSimple(Blocks::GLASS(), Ids::GLASS); - $this->mapSimple(Blocks::GLASS_PANE(), Ids::GLASS_PANE); - $this->mapSimple(Blocks::GLOWING_OBSIDIAN(), Ids::GLOWINGOBSIDIAN); - $this->mapSimple(Blocks::GLOWSTONE(), Ids::GLOWSTONE); - $this->mapSimple(Blocks::GOLD(), Ids::GOLD_BLOCK); - $this->mapSimple(Blocks::GOLD_ORE(), Ids::GOLD_ORE); - $this->mapSimple(Blocks::GRANITE(), Ids::GRANITE); - $this->mapSimple(Blocks::GRASS(), Ids::GRASS_BLOCK); - $this->mapSimple(Blocks::GRASS_PATH(), Ids::GRASS_PATH); - $this->mapSimple(Blocks::GRAVEL(), Ids::GRAVEL); - $this->mapSimple(Blocks::HANGING_ROOTS(), Ids::HANGING_ROOTS); - $this->mapSimple(Blocks::HARDENED_CLAY(), Ids::HARDENED_CLAY); - $this->mapSimple(Blocks::HARDENED_GLASS(), Ids::HARD_GLASS); - $this->mapSimple(Blocks::HARDENED_GLASS_PANE(), Ids::HARD_GLASS_PANE); - $this->mapSimple(Blocks::HONEYCOMB(), Ids::HONEYCOMB_BLOCK); - $this->mapSimple(Blocks::ICE(), Ids::ICE); - $this->mapSimple(Blocks::INFESTED_CHISELED_STONE_BRICK(), Ids::INFESTED_CHISELED_STONE_BRICKS); - $this->mapSimple(Blocks::INFESTED_COBBLESTONE(), Ids::INFESTED_COBBLESTONE); - $this->mapSimple(Blocks::INFESTED_CRACKED_STONE_BRICK(), Ids::INFESTED_CRACKED_STONE_BRICKS); - $this->mapSimple(Blocks::INFESTED_MOSSY_STONE_BRICK(), Ids::INFESTED_MOSSY_STONE_BRICKS); - $this->mapSimple(Blocks::INFESTED_STONE(), Ids::INFESTED_STONE); - $this->mapSimple(Blocks::INFESTED_STONE_BRICK(), Ids::INFESTED_STONE_BRICKS); - $this->mapSimple(Blocks::INFO_UPDATE(), Ids::INFO_UPDATE); - $this->mapSimple(Blocks::INFO_UPDATE2(), Ids::INFO_UPDATE2); - $this->mapSimple(Blocks::INVISIBLE_BEDROCK(), Ids::INVISIBLE_BEDROCK); - $this->mapSimple(Blocks::IRON(), Ids::IRON_BLOCK); - $this->mapSimple(Blocks::IRON_BARS(), Ids::IRON_BARS); - $this->mapSimple(Blocks::IRON_ORE(), Ids::IRON_ORE); - $this->mapSimple(Blocks::JUKEBOX(), Ids::JUKEBOX); - $this->mapSimple(Blocks::LAPIS_LAZULI(), Ids::LAPIS_BLOCK); - $this->mapSimple(Blocks::LAPIS_LAZULI_ORE(), Ids::LAPIS_ORE); - $this->mapSimple(Blocks::LEGACY_STONECUTTER(), Ids::STONECUTTER); - $this->mapSimple(Blocks::LILY_PAD(), Ids::WATERLILY); - $this->mapSimple(Blocks::MAGMA(), Ids::MAGMA); - $this->mapSimple(Blocks::MANGROVE_ROOTS(), Ids::MANGROVE_ROOTS); - $this->mapSimple(Blocks::MELON(), Ids::MELON_BLOCK); - $this->mapSimple(Blocks::MONSTER_SPAWNER(), Ids::MOB_SPAWNER); - $this->mapSimple(Blocks::MOSSY_COBBLESTONE(), Ids::MOSSY_COBBLESTONE); - $this->mapSimple(Blocks::MOSSY_STONE_BRICKS(), Ids::MOSSY_STONE_BRICKS); - $this->mapSimple(Blocks::MUD(), Ids::MUD); - $this->mapSimple(Blocks::MUD_BRICKS(), Ids::MUD_BRICKS); - $this->mapSimple(Blocks::MYCELIUM(), Ids::MYCELIUM); - $this->mapSimple(Blocks::NETHERITE(), Ids::NETHERITE_BLOCK); - $this->mapSimple(Blocks::NETHERRACK(), Ids::NETHERRACK); - $this->mapSimple(Blocks::NETHER_BRICKS(), Ids::NETHER_BRICK); - $this->mapSimple(Blocks::NETHER_BRICK_FENCE(), Ids::NETHER_BRICK_FENCE); - $this->mapSimple(Blocks::NETHER_GOLD_ORE(), Ids::NETHER_GOLD_ORE); - $this->mapSimple(Blocks::NETHER_QUARTZ_ORE(), Ids::QUARTZ_ORE); - $this->mapSimple(Blocks::NETHER_REACTOR_CORE(), Ids::NETHERREACTOR); - $this->mapSimple(Blocks::NETHER_WART_BLOCK(), Ids::NETHER_WART_BLOCK); - $this->mapSimple(Blocks::NOTE_BLOCK(), Ids::NOTEBLOCK); - $this->mapSimple(Blocks::OBSIDIAN(), Ids::OBSIDIAN); - $this->mapSimple(Blocks::PACKED_ICE(), Ids::PACKED_ICE); - $this->mapSimple(Blocks::PACKED_MUD(), Ids::PACKED_MUD); - $this->mapSimple(Blocks::PODZOL(), Ids::PODZOL); - $this->mapSimple(Blocks::POLISHED_ANDESITE(), Ids::POLISHED_ANDESITE); - $this->mapSimple(Blocks::POLISHED_BLACKSTONE(), Ids::POLISHED_BLACKSTONE); - $this->mapSimple(Blocks::POLISHED_BLACKSTONE_BRICKS(), Ids::POLISHED_BLACKSTONE_BRICKS); - $this->mapSimple(Blocks::POLISHED_DEEPSLATE(), Ids::POLISHED_DEEPSLATE); - $this->mapSimple(Blocks::POLISHED_DIORITE(), Ids::POLISHED_DIORITE); - $this->mapSimple(Blocks::POLISHED_GRANITE(), Ids::POLISHED_GRANITE); - $this->mapSimple(Blocks::POLISHED_TUFF(), Ids::POLISHED_TUFF); - $this->mapSimple(Blocks::PRISMARINE(), Ids::PRISMARINE); - $this->mapSimple(Blocks::PRISMARINE_BRICKS(), Ids::PRISMARINE_BRICKS); - $this->mapSimple(Blocks::QUARTZ_BRICKS(), Ids::QUARTZ_BRICKS); - $this->mapSimple(Blocks::RAW_COPPER(), Ids::RAW_COPPER_BLOCK); - $this->mapSimple(Blocks::RAW_GOLD(), Ids::RAW_GOLD_BLOCK); - $this->mapSimple(Blocks::RAW_IRON(), Ids::RAW_IRON_BLOCK); - $this->mapSimple(Blocks::REDSTONE(), Ids::REDSTONE_BLOCK); - $this->mapSimple(Blocks::RED_MUSHROOM(), Ids::RED_MUSHROOM); - $this->mapSimple(Blocks::RED_NETHER_BRICKS(), Ids::RED_NETHER_BRICK); - $this->mapSimple(Blocks::RED_SAND(), Ids::RED_SAND); - $this->mapSimple(Blocks::RED_SANDSTONE(), Ids::RED_SANDSTONE); - $this->mapSimple(Blocks::REINFORCED_DEEPSLATE(), Ids::REINFORCED_DEEPSLATE); - $this->mapSimple(Blocks::RESERVED6(), Ids::RESERVED6); - $this->mapSimple(Blocks::RESIN(), Ids::RESIN_BLOCK); - $this->mapSimple(Blocks::RESIN_BRICKS(), Ids::RESIN_BRICKS); - $this->mapSimple(Blocks::SAND(), Ids::SAND); - $this->mapSimple(Blocks::SANDSTONE(), Ids::SANDSTONE); - $this->mapSimple(Blocks::SCULK(), Ids::SCULK); - $this->mapSimple(Blocks::SEA_LANTERN(), Ids::SEA_LANTERN); - $this->mapSimple(Blocks::SHROOMLIGHT(), Ids::SHROOMLIGHT); - $this->mapSimple(Blocks::SHULKER_BOX(), Ids::UNDYED_SHULKER_BOX); - $this->mapSimple(Blocks::SLIME(), Ids::SLIME); - $this->mapSimple(Blocks::SMITHING_TABLE(), Ids::SMITHING_TABLE); - $this->mapSimple(Blocks::SMOOTH_BASALT(), Ids::SMOOTH_BASALT); - $this->mapSimple(Blocks::SMOOTH_RED_SANDSTONE(), Ids::SMOOTH_RED_SANDSTONE); - $this->mapSimple(Blocks::SMOOTH_SANDSTONE(), Ids::SMOOTH_SANDSTONE); - $this->mapSimple(Blocks::SMOOTH_STONE(), Ids::SMOOTH_STONE); - $this->mapSimple(Blocks::SNOW(), Ids::SNOW); - $this->mapSimple(Blocks::SOUL_SAND(), Ids::SOUL_SAND); - $this->mapSimple(Blocks::SOUL_SOIL(), Ids::SOUL_SOIL); - $this->mapSimple(Blocks::SPORE_BLOSSOM(), Ids::SPORE_BLOSSOM); - $this->mapSimple(Blocks::STONE(), Ids::STONE); - $this->mapSimple(Blocks::STONE_BRICKS(), Ids::STONE_BRICKS); - $this->mapSimple(Blocks::TALL_GRASS(), Ids::SHORT_GRASS); //no, this is not a typo - tall_grass is now the double block, just to be confusing :( - $this->mapSimple(Blocks::TINTED_GLASS(), Ids::TINTED_GLASS); - $this->mapSimple(Blocks::TORCHFLOWER(), Ids::TORCHFLOWER); - $this->mapSimple(Blocks::TUFF(), Ids::TUFF); - $this->mapSimple(Blocks::TUFF_BRICKS(), Ids::TUFF_BRICKS); - $this->mapSimple(Blocks::WARPED_WART_BLOCK(), Ids::WARPED_WART_BLOCK); - $this->mapSimple(Blocks::WARPED_ROOTS(), Ids::WARPED_ROOTS); - $this->mapSimple(Blocks::WITHER_ROSE(), Ids::WITHER_ROSE); - - $this->mapSimple(Blocks::ALLIUM(), Ids::ALLIUM); - $this->mapSimple(Blocks::CORNFLOWER(), Ids::CORNFLOWER); - $this->mapSimple(Blocks::AZURE_BLUET(), Ids::AZURE_BLUET); - $this->mapSimple(Blocks::LILY_OF_THE_VALLEY(), Ids::LILY_OF_THE_VALLEY); - $this->mapSimple(Blocks::BLUE_ORCHID(), Ids::BLUE_ORCHID); - $this->mapSimple(Blocks::OXEYE_DAISY(), Ids::OXEYE_DAISY); - $this->mapSimple(Blocks::POPPY(), Ids::POPPY); - $this->mapSimple(Blocks::ORANGE_TULIP(), Ids::ORANGE_TULIP); - $this->mapSimple(Blocks::PINK_TULIP(), Ids::PINK_TULIP); - $this->mapSimple(Blocks::RED_TULIP(), Ids::RED_TULIP); - $this->mapSimple(Blocks::WHITE_TULIP(), Ids::WHITE_TULIP); - } - - private function registerSerializers() : void{ - $this->map(Blocks::ACTIVATOR_RAIL(), function(ActivatorRail $block) : Writer{ - return Writer::create(Ids::ACTIVATOR_RAIL) - ->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered()) - ->writeInt(StateNames::RAIL_DIRECTION, $block->getShape()); - }); - $this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) - ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM)); - $this->map(Blocks::AMETHYST_CLUSTER(), fn(AmethystCluster $block) => Writer::create( - match($stage = $block->getStage()){ - AmethystCluster::STAGE_SMALL_BUD => Ids::SMALL_AMETHYST_BUD, - AmethystCluster::STAGE_MEDIUM_BUD => Ids::MEDIUM_AMETHYST_BUD, - AmethystCluster::STAGE_LARGE_BUD => Ids::LARGE_AMETHYST_BUD, - AmethystCluster::STAGE_CLUSTER => Ids::AMETHYST_CLUSTER, - default => throw new BlockStateSerializeException("Invalid Amethyst Cluster stage $stage"), - }) - ->writeBlockFace($block->getFacing()) - ); - $this->mapSlab(Blocks::ANDESITE_SLAB(), Ids::ANDESITE_SLAB, Ids::ANDESITE_DOUBLE_SLAB); - $this->map(Blocks::ANDESITE_STAIRS(), fn(Stair $block) => Helper::encodeStairs($block, new Writer(Ids::ANDESITE_STAIRS))); - $this->map(Blocks::ANDESITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::ANDESITE_WALL))); - $this->map(Blocks::ANVIL(), fn(Anvil $block) : Writer => Writer::create( - match($damage = $block->getDamage()){ - 0 => Ids::ANVIL, - 1 => Ids::CHIPPED_ANVIL, - 2 => Ids::DAMAGED_ANVIL, - default => throw new BlockStateSerializeException("Invalid Anvil damage {$damage}"), - }) - ->writeCardinalHorizontalFacing($block->getFacing()) - ); - $this->map(Blocks::BAMBOO(), function(Bamboo $block) : Writer{ - return Writer::create(Ids::BAMBOO) - ->writeBool(StateNames::AGE_BIT, $block->isReady()) - ->writeString(StateNames::BAMBOO_LEAF_SIZE, match($block->getLeafSize()){ - Bamboo::NO_LEAVES => StringValues::BAMBOO_LEAF_SIZE_NO_LEAVES, - Bamboo::SMALL_LEAVES => StringValues::BAMBOO_LEAF_SIZE_SMALL_LEAVES, - Bamboo::LARGE_LEAVES => StringValues::BAMBOO_LEAF_SIZE_LARGE_LEAVES, - default => throw new BlockStateSerializeException("Invalid Bamboo leaf thickness " . $block->getLeafSize()), - }) - ->writeString(StateNames::BAMBOO_STALK_THICKNESS, $block->isThick() ? StringValues::BAMBOO_STALK_THICKNESS_THICK : StringValues::BAMBOO_STALK_THICKNESS_THIN); - }); - $this->map(Blocks::BAMBOO_SAPLING(), function(BambooSapling $block) : Writer{ - return Writer::create(Ids::BAMBOO_SAPLING) - ->writeBool(StateNames::AGE_BIT, $block->isReady()); - }); - $this->map(Blocks::BANNER(), function(FloorBanner $block) : Writer{ - return Writer::create(Ids::STANDING_BANNER) - ->writeInt(StateNames::GROUND_SIGN_DIRECTION, $block->getRotation()); - }); - $this->map(Blocks::BARREL(), function(Barrel $block) : Writer{ - return Writer::create(Ids::BARREL) - ->writeBool(StateNames::OPEN_BIT, $block->isOpen()) - ->writeFacingDirection($block->getFacing()); - }); - $this->map(Blocks::BASALT(), function(SimplePillar $block) : Writer{ - return Writer::create(Ids::BASALT) - ->writePillarAxis($block->getAxis()); - }); - $this->map(Blocks::BED(), function(Bed $block) : Writer{ - return Writer::create(Ids::BED) - ->writeBool(StateNames::HEAD_PIECE_BIT, $block->isHeadPart()) - ->writeBool(StateNames::OCCUPIED_BIT, $block->isOccupied()) - ->writeLegacyHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::BEDROCK(), function(Block $block) : Writer{ - return Writer::create(Ids::BEDROCK) - ->writeBool(StateNames::INFINIBURN_BIT, $block->burnsForever()); - }); - $this->map(Blocks::BEETROOTS(), fn(Beetroot $block) => Helper::encodeCrops($block, new Writer(Ids::BEETROOT))); - $this->map(Blocks::BELL(), function(Bell $block) : Writer{ - return Writer::create(Ids::BELL) - ->writeBellAttachmentType($block->getAttachmentType()) - ->writeBool(StateNames::TOGGLE_BIT, false) //we don't care about this; it's just to keep MCPE happy - ->writeLegacyHorizontalFacing($block->getFacing()); - - }); - $this->map(Blocks::BIG_DRIPLEAF_HEAD(), function(BigDripleafHead $block) : Writer{ - return Writer::create(Ids::BIG_DRIPLEAF) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeString(StateNames::BIG_DRIPLEAF_TILT, match($block->getLeafState()){ - DripleafState::STABLE => StringValues::BIG_DRIPLEAF_TILT_NONE, - DripleafState::UNSTABLE => StringValues::BIG_DRIPLEAF_TILT_UNSTABLE, - DripleafState::PARTIAL_TILT => StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT, - DripleafState::FULL_TILT => StringValues::BIG_DRIPLEAF_TILT_FULL_TILT, - }) - ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, true); - }); - $this->map(Blocks::BIG_DRIPLEAF_STEM(), function(BigDripleafStem $block) : Writer{ - return Writer::create(Ids::BIG_DRIPLEAF) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeString(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE) - ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, false); - }); - $this->mapSlab(Blocks::BLACKSTONE_SLAB(), Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS); - $this->map(Blocks::BLACKSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::BLACKSTONE_WALL))); - $this->map(Blocks::BLAST_FURNACE(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::BLAST_FURNACE, Ids::LIT_BLAST_FURNACE)); - $this->map(Blocks::BLUE_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_BLUE))); - $this->map(Blocks::BONE_BLOCK(), function(BoneBlock $block) : Writer{ - return Writer::create(Ids::BONE_BLOCK) - ->writeInt(StateNames::DEPRECATED, 0) - ->writePillarAxis($block->getAxis()); - }); - $this->map(Blocks::BREWING_STAND(), function(BrewingStand $block) : Writer{ - return Writer::create(Ids::BREWING_STAND) - ->writeBool(StateNames::BREWING_STAND_SLOT_A_BIT, $block->hasSlot(BrewingStandSlot::EAST)) - ->writeBool(StateNames::BREWING_STAND_SLOT_B_BIT, $block->hasSlot(BrewingStandSlot::SOUTHWEST)) - ->writeBool(StateNames::BREWING_STAND_SLOT_C_BIT, $block->hasSlot(BrewingStandSlot::NORTHWEST)); - }); - $this->mapSlab(Blocks::BRICK_SLAB(), Ids::BRICK_SLAB, Ids::BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::BRICK_STAIRS(), Ids::BRICK_STAIRS); - $this->map(Blocks::BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::BRICK_WALL))); - $this->map(Blocks::BROWN_MUSHROOM_BLOCK(), fn(BrownMushroomBlock $block) => Helper::encodeMushroomBlock($block, new Writer(Ids::BROWN_MUSHROOM_BLOCK))); - $this->map(Blocks::CACTUS(), function(Cactus $block) : Writer{ - return Writer::create(Ids::CACTUS) - ->writeInt(StateNames::AGE, $block->getAge()); - }); - $this->map(Blocks::CAKE(), function(Cake $block) : Writer{ - return Writer::create(Ids::CAKE) - ->writeInt(StateNames::BITE_COUNTER, $block->getBites()); - }); - $this->map(Blocks::CAMPFIRE(), function(Campfire $block) : Writer{ - return Writer::create(Ids::CAMPFIRE) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeBool(StateNames::EXTINGUISHED, !$block->isLit()); - }); - $this->map(Blocks::CARROTS(), fn(Carrot $block) => Helper::encodeCrops($block, new Writer(Ids::CARROTS))); - $this->map(Blocks::CARVED_PUMPKIN(), function(CarvedPumpkin $block) : Writer{ - return Writer::create(Ids::CARVED_PUMPKIN) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::CAVE_VINES(), function(CaveVines $block) : Writer{ - //I have no idea why this only has 3 IDs - there are 4 in Java and 4 visually distinct states in Bedrock - return Writer::create($block->hasBerries() ? - ($block->isHead() ? - Ids::CAVE_VINES_HEAD_WITH_BERRIES : - Ids::CAVE_VINES_BODY_WITH_BERRIES - ) : - Ids::CAVE_VINES - ) - ->writeInt(StateNames::GROWING_PLANT_AGE, $block->getAge()); - }); - $this->map(Blocks::CHAIN(), function(Chain $block) : Writer{ - return Writer::create(Ids::CHAIN) - ->writePillarAxis($block->getAxis()); - }); - $this->map(Blocks::CHEST(), function(Chest $block) : Writer{ - return Writer::create(Ids::CHEST) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::CHISELED_BOOKSHELF(), function(ChiseledBookshelf $block) : Writer{ - $flags = 0; - foreach($block->getSlots() as $slot){ - $flags |= 1 << $slot->value; - } - return Writer::create(Ids::CHISELED_BOOKSHELF) - ->writeLegacyHorizontalFacing($block->getFacing()) - ->writeInt(StateNames::BOOKS_STORED, $flags); - }); - $this->map(Blocks::CHISELED_QUARTZ(), fn(SimplePillar $block) => Helper::encodeQuartz($block->getAxis(), Writer::create(Ids::CHISELED_QUARTZ_BLOCK))); - $this->map(Blocks::CHORUS_FLOWER(), function(ChorusFlower $block) : Writer{ - return Writer::create(Ids::CHORUS_FLOWER) - ->writeInt(StateNames::AGE, $block->getAge()); - }); - $this->mapSlab(Blocks::COBBLED_DEEPSLATE_SLAB(), Ids::COBBLED_DEEPSLATE_SLAB, Ids::COBBLED_DEEPSLATE_DOUBLE_SLAB); - $this->mapStairs(Blocks::COBBLED_DEEPSLATE_STAIRS(), Ids::COBBLED_DEEPSLATE_STAIRS); - $this->map(Blocks::COBBLED_DEEPSLATE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::COBBLED_DEEPSLATE_WALL))); - $this->mapSlab(Blocks::COBBLESTONE_SLAB(), Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS); - $this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::COBBLESTONE_WALL))); - $this->map(Blocks::COCOA_POD(), function(CocoaBlock $block) : Writer{ - return Writer::create(Ids::COCOA) - ->writeInt(StateNames::AGE, $block->getAge()) - ->writeLegacyHorizontalFacing(Facing::opposite($block->getFacing())); - }); - $this->map(Blocks::COMPOUND_CREATOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::COMPOUND_CREATOR))); - $this->mapSlab(Blocks::CUT_RED_SANDSTONE_SLAB(), Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB); - $this->mapSlab(Blocks::CUT_SANDSTONE_SLAB(), Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB); - $this->mapSlab(Blocks::DARK_PRISMARINE_SLAB(), Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB); - $this->mapStairs(Blocks::DARK_PRISMARINE_STAIRS(), Ids::DARK_PRISMARINE_STAIRS); - $this->map(Blocks::DAYLIGHT_SENSOR(), function(DaylightSensor $block) : Writer{ - return Writer::create($block->isInverted() ? Ids::DAYLIGHT_DETECTOR_INVERTED : Ids::DAYLIGHT_DETECTOR) - ->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength()); - }); - $this->map(Blocks::DEEPSLATE(), function(SimplePillar $block) : Writer{ - return Writer::create(Ids::DEEPSLATE) - ->writePillarAxis($block->getAxis()); - }); - $this->mapSlab(Blocks::DEEPSLATE_BRICK_SLAB(), Ids::DEEPSLATE_BRICK_SLAB, Ids::DEEPSLATE_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::DEEPSLATE_BRICK_STAIRS(), Ids::DEEPSLATE_BRICK_STAIRS); - $this->map(Blocks::DEEPSLATE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::DEEPSLATE_BRICK_WALL))); - $this->map(Blocks::DEEPSLATE_REDSTONE_ORE(), fn(RedstoneOre $block) => new Writer($block->isLit() ? Ids::LIT_DEEPSLATE_REDSTONE_ORE : Ids::DEEPSLATE_REDSTONE_ORE)); - $this->mapSlab(Blocks::DEEPSLATE_TILE_SLAB(), Ids::DEEPSLATE_TILE_SLAB, Ids::DEEPSLATE_TILE_DOUBLE_SLAB); - $this->mapStairs(Blocks::DEEPSLATE_TILE_STAIRS(), Ids::DEEPSLATE_TILE_STAIRS); - $this->map(Blocks::DEEPSLATE_TILE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::DEEPSLATE_TILE_WALL))); - $this->map(Blocks::DETECTOR_RAIL(), function(DetectorRail $block) : Writer{ - return Writer::create(Ids::DETECTOR_RAIL) - ->writeBool(StateNames::RAIL_DATA_BIT, $block->isActivated()) - ->writeInt(StateNames::RAIL_DIRECTION, $block->getShape()); - }); - $this->mapSlab(Blocks::DIORITE_SLAB(), Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB); - $this->mapStairs(Blocks::DIORITE_STAIRS(), Ids::DIORITE_STAIRS); - $this->map(Blocks::DIORITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::DIORITE_WALL))); - $this->map(Blocks::DIRT(), fn(Dirt $block) => BlockStateData::current(match($block->getDirtType()){ - DirtType::NORMAL => Ids::DIRT, - DirtType::COARSE => Ids::COARSE_DIRT, - DirtType::ROOTED => Ids::DIRT_WITH_ROOTS, - }, [])); - $this->map(Blocks::DOUBLE_TALLGRASS(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::TALL_GRASS))); - $this->map(Blocks::ELEMENT_CONSTRUCTOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::ELEMENT_CONSTRUCTOR))); - $this->map(Blocks::ENDER_CHEST(), function(EnderChest $block) : Writer{ - return Writer::create(Ids::ENDER_CHEST) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::END_PORTAL_FRAME(), function(EndPortalFrame $block) : Writer{ - return Writer::create(Ids::END_PORTAL_FRAME) - ->writeBool(StateNames::END_PORTAL_EYE_BIT, $block->hasEye()) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::END_ROD(), function(EndRod $block) : Writer{ - return Writer::create(Ids::END_ROD) - ->writeEndRodFacingDirection($block->getFacing()); - }); - $this->mapSlab(Blocks::END_STONE_BRICK_SLAB(), Ids::END_STONE_BRICK_SLAB, Ids::END_STONE_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::END_STONE_BRICK_STAIRS(), Ids::END_BRICK_STAIRS); - $this->map(Blocks::END_STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::END_STONE_BRICK_WALL))); - $this->mapSlab(Blocks::FAKE_WOODEN_SLAB(), Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB); - $this->map(Blocks::FARMLAND(), function(Farmland $block) : Writer{ - return Writer::create(Ids::FARMLAND) - ->writeInt(StateNames::MOISTURIZED_AMOUNT, $block->getWetness()); - }); - $this->map(Blocks::FIRE(), function(Fire $block) : Writer{ - return Writer::create(Ids::FIRE) - ->writeInt(StateNames::AGE, $block->getAge()); - }); - $this->map(Blocks::FLOWER_POT(), Writer::create(Ids::FLOWER_POT) - ->writeBool(StateNames::UPDATE_BIT, false) //to keep MCPE happy - ); - $this->map(Blocks::FROGLIGHT(), function(Froglight $block){ - return Writer::create(match($block->getFroglightType()){ - FroglightType::OCHRE => Ids::OCHRE_FROGLIGHT, - FroglightType::PEARLESCENT => Ids::PEARLESCENT_FROGLIGHT, - FroglightType::VERDANT => Ids::VERDANT_FROGLIGHT, - }) - ->writePillarAxis($block->getAxis()); - }); - $this->map(Blocks::FROSTED_ICE(), function(FrostedIce $block) : Writer{ - return Writer::create(Ids::FROSTED_ICE) - ->writeInt(StateNames::AGE, $block->getAge()); - }); - $this->map(Blocks::FURNACE(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::FURNACE, Ids::LIT_FURNACE)); - $this->map(Blocks::GLOW_LICHEN(), function(GlowLichen $block) : Writer{ - return Writer::create(Ids::GLOW_LICHEN) - ->writeFacingFlags($block->getFaces()); - }); - $this->map(Blocks::GLOWING_ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::GLOW_FRAME)); - $this->mapSlab(Blocks::GRANITE_SLAB(), Ids::GRANITE_SLAB, Ids::GRANITE_DOUBLE_SLAB); - $this->mapStairs(Blocks::GRANITE_STAIRS(), Ids::GRANITE_STAIRS); - $this->map(Blocks::GRANITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::GRANITE_WALL))); - $this->map(Blocks::GREEN_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_GREEN))); - $this->map(Blocks::HAY_BALE(), function(HayBale $block) : Writer{ - return Writer::create(Ids::HAY_BLOCK) - ->writeInt(StateNames::DEPRECATED, 0) - ->writePillarAxis($block->getAxis()); - }); - $this->map(Blocks::HOPPER(), function(Hopper $block) : Writer{ - return Writer::create(Ids::HOPPER) - ->writeBool(StateNames::TOGGLE_BIT, $block->isPowered()) - ->writeFacingWithoutUp($block->getFacing()); - }); - $this->map(Blocks::IRON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::IRON_DOOR))); - $this->map(Blocks::IRON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::IRON_TRAPDOOR))); - $this->map(Blocks::ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::FRAME)); - $this->map(Blocks::LAB_TABLE(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::LAB_TABLE))); - $this->map(Blocks::LADDER(), function(Ladder $block) : Writer{ - return Writer::create(Ids::LADDER) - ->writeHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::LANTERN(), function(Lantern $block) : Writer{ - return Writer::create(Ids::LANTERN) - ->writeBool(StateNames::HANGING, $block->isHanging()); - }); - $this->map(Blocks::LARGE_FERN(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::LARGE_FERN))); - $this->map(Blocks::LAVA(), fn(Lava $block) => Helper::encodeLiquid($block, Ids::LAVA, Ids::FLOWING_LAVA)); - $this->map(Blocks::LECTERN(), function(Lectern $block) : Writer{ - return Writer::create(Ids::LECTERN) - ->writeBool(StateNames::POWERED_BIT, $block->isProducingSignal()) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::LEVER(), function(Lever $block) : Writer{ - return Writer::create(Ids::LEVER) - ->writeBool(StateNames::OPEN_BIT, $block->isActivated()) - ->writeString(StateNames::LEVER_DIRECTION, match($block->getFacing()){ - LeverFacing::DOWN_AXIS_Z => StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH, - LeverFacing::DOWN_AXIS_X => StringValues::LEVER_DIRECTION_DOWN_EAST_WEST, - LeverFacing::UP_AXIS_Z => StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH, - LeverFacing::UP_AXIS_X => StringValues::LEVER_DIRECTION_UP_EAST_WEST, - LeverFacing::NORTH => StringValues::LEVER_DIRECTION_NORTH, - LeverFacing::SOUTH => StringValues::LEVER_DIRECTION_SOUTH, - LeverFacing::WEST => StringValues::LEVER_DIRECTION_WEST, - LeverFacing::EAST => StringValues::LEVER_DIRECTION_EAST, - }); - }); - $this->map(Blocks::LIGHT(), fn(Light $block) => BlockStateData::current(match($block->getLightLevel()){ - 0 => Ids::LIGHT_BLOCK_0, - 1 => Ids::LIGHT_BLOCK_1, - 2 => Ids::LIGHT_BLOCK_2, - 3 => Ids::LIGHT_BLOCK_3, - 4 => Ids::LIGHT_BLOCK_4, - 5 => Ids::LIGHT_BLOCK_5, - 6 => Ids::LIGHT_BLOCK_6, - 7 => Ids::LIGHT_BLOCK_7, - 8 => Ids::LIGHT_BLOCK_8, - 9 => Ids::LIGHT_BLOCK_9, - 10 => Ids::LIGHT_BLOCK_10, - 11 => Ids::LIGHT_BLOCK_11, - 12 => Ids::LIGHT_BLOCK_12, - 13 => Ids::LIGHT_BLOCK_13, - 14 => Ids::LIGHT_BLOCK_14, - 15 => Ids::LIGHT_BLOCK_15, - default => throw new BlockStateSerializeException("Invalid light level " . $block->getLightLevel()), - }, [])); - $this->map(Blocks::LIGHTNING_ROD(), function(LightningRod $block) : Writer{ - return Writer::create(Ids::LIGHTNING_ROD) - ->writeFacingDirection($block->getFacing()); - }); - $this->map(Blocks::LILAC(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::LILAC))); - $this->map(Blocks::LIT_PUMPKIN(), function(LitPumpkin $block) : Writer{ - return Writer::create(Ids::LIT_PUMPKIN) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::LOOM(), function(Loom $block) : Writer{ - return Writer::create(Ids::LOOM) - ->writeLegacyHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::MATERIAL_REDUCER(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::MATERIAL_REDUCER))); - $this->map(Blocks::MELON_STEM(), fn(MelonStem $block) => Helper::encodeStem($block, new Writer(Ids::MELON_STEM))); - $this->mapSlab(Blocks::MOSSY_COBBLESTONE_SLAB(), Ids::MOSSY_COBBLESTONE_SLAB, Ids::MOSSY_COBBLESTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::MOSSY_COBBLESTONE_STAIRS(), Ids::MOSSY_COBBLESTONE_STAIRS); - $this->map(Blocks::MOSSY_COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::MOSSY_COBBLESTONE_WALL))); - $this->mapSlab(Blocks::MOSSY_STONE_BRICK_SLAB(), Ids::MOSSY_STONE_BRICK_SLAB, Ids::MOSSY_STONE_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::MOSSY_STONE_BRICK_STAIRS(), Ids::MOSSY_STONE_BRICK_STAIRS); - $this->map(Blocks::MOSSY_STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::MOSSY_STONE_BRICK_WALL))); - $this->mapSlab(Blocks::MUD_BRICK_SLAB(), Ids::MUD_BRICK_SLAB, Ids::MUD_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::MUD_BRICK_STAIRS(), Ids::MUD_BRICK_STAIRS); - $this->map(Blocks::MUD_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::MUD_BRICK_WALL))); - $this->map(Blocks::MUDDY_MANGROVE_ROOTS(), fn(SimplePillar $block) => Writer::create(Ids::MUDDY_MANGROVE_ROOTS) - ->writePillarAxis($block->getAxis())); - $this->map(Blocks::MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) - ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_STEM)); - $this->mapSlab(Blocks::NETHER_BRICK_SLAB(), Ids::NETHER_BRICK_SLAB, Ids::NETHER_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::NETHER_BRICK_STAIRS(), Ids::NETHER_BRICK_STAIRS); - $this->map(Blocks::NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::NETHER_BRICK_WALL))); - $this->map(Blocks::NETHER_PORTAL(), function(NetherPortal $block) : Writer{ - return Writer::create(Ids::PORTAL) - ->writeString(StateNames::PORTAL_AXIS, match($block->getAxis()){ - Axis::X => StringValues::PORTAL_AXIS_X, - Axis::Z => StringValues::PORTAL_AXIS_Z, - default => throw new BlockStateSerializeException("Invalid Nether Portal axis " . $block->getAxis()), - }); - }); - $this->map(Blocks::NETHER_WART(), function(NetherWartPlant $block) : Writer{ - return Writer::create(Ids::NETHER_WART) - ->writeInt(StateNames::AGE, $block->getAge()); - }); - $this->map(Blocks::PEONY(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::PEONY))); - $this->map(Blocks::PINK_PETALS(), function(PinkPetals $block) : Writer{ - return Writer::create(Ids::PINK_PETALS) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeInt(StateNames::GROWTH, $block->getCount() - 1); - }); - $this->map(Blocks::PITCHER_PLANT(), function(DoublePlant $block) : Writer{ - return Writer::create(Ids::PITCHER_PLANT) - ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); - }); - $this->map(Blocks::PITCHER_CROP(), function(PitcherCrop $block) : Writer{ - return Writer::create(Ids::PITCHER_CROP) - ->writeInt(StateNames::GROWTH, $block->getAge()) - ->writeBool(StateNames::UPPER_BLOCK_BIT, false); - }); - $this->map(Blocks::DOUBLE_PITCHER_CROP(), function(DoublePitcherCrop $block) : Writer{ - return Writer::create(Ids::PITCHER_CROP) - ->writeInt(StateNames::GROWTH, $block->getAge() + 1 + PitcherCrop::MAX_AGE) - ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); - }); - $this->mapSlab(Blocks::POLISHED_ANDESITE_SLAB(), Ids::POLISHED_ANDESITE_SLAB, Ids::POLISHED_ANDESITE_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_ANDESITE_STAIRS(), Ids::POLISHED_ANDESITE_STAIRS); - $this->map(Blocks::POLISHED_BASALT(), function(SimplePillar $block) : Writer{ - return Writer::create(Ids::POLISHED_BASALT) - ->writePillarAxis($block->getAxis()); - }); - $this->mapSlab(Blocks::POLISHED_BLACKSTONE_BRICK_SLAB(), Ids::POLISHED_BLACKSTONE_BRICK_SLAB, Ids::POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_BLACKSTONE_BRICK_STAIRS(), Ids::POLISHED_BLACKSTONE_BRICK_STAIRS); - $this->map(Blocks::POLISHED_BLACKSTONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_BLACKSTONE_BRICK_WALL))); - $this->map(Blocks::POLISHED_BLACKSTONE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::POLISHED_BLACKSTONE_BUTTON))); - $this->map(Blocks::POLISHED_BLACKSTONE_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE))); - $this->mapSlab(Blocks::POLISHED_BLACKSTONE_SLAB(), Ids::POLISHED_BLACKSTONE_SLAB, Ids::POLISHED_BLACKSTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_BLACKSTONE_STAIRS(), Ids::POLISHED_BLACKSTONE_STAIRS); - $this->map(Blocks::POLISHED_BLACKSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_BLACKSTONE_WALL))); - $this->mapSlab(Blocks::POLISHED_DEEPSLATE_SLAB(), Ids::POLISHED_DEEPSLATE_SLAB, Ids::POLISHED_DEEPSLATE_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_DEEPSLATE_STAIRS(), Ids::POLISHED_DEEPSLATE_STAIRS); - $this->map(Blocks::POLISHED_DEEPSLATE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_DEEPSLATE_WALL))); - $this->mapSlab(Blocks::POLISHED_DIORITE_SLAB(), Ids::POLISHED_DIORITE_SLAB, Ids::POLISHED_DIORITE_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_DIORITE_STAIRS(), Ids::POLISHED_DIORITE_STAIRS); - $this->mapSlab(Blocks::POLISHED_GRANITE_SLAB(), Ids::POLISHED_GRANITE_SLAB, Ids::POLISHED_GRANITE_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_GRANITE_STAIRS(), Ids::POLISHED_GRANITE_STAIRS); - $this->mapSlab(Blocks::POLISHED_TUFF_SLAB(), Ids::POLISHED_TUFF_SLAB, Ids::POLISHED_TUFF_DOUBLE_SLAB); - $this->mapStairs(Blocks::POLISHED_TUFF_STAIRS(), Ids::POLISHED_TUFF_STAIRS); - $this->map(Blocks::POLISHED_TUFF_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_TUFF_WALL))); - $this->map(Blocks::POTATOES(), fn(Potato $block) => Helper::encodeCrops($block, new Writer(Ids::POTATOES))); - $this->map(Blocks::POWERED_RAIL(), function(PoweredRail $block) : Writer{ - return Writer::create(Ids::GOLDEN_RAIL) - ->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered()) - ->writeInt(StateNames::RAIL_DIRECTION, $block->getShape()); - }); - $this->mapSlab(Blocks::PRISMARINE_BRICKS_SLAB(), Ids::PRISMARINE_BRICK_SLAB, Ids::PRISMARINE_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::PRISMARINE_BRICKS_STAIRS(), Ids::PRISMARINE_BRICKS_STAIRS); - $this->mapSlab(Blocks::PRISMARINE_SLAB(), Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB); - $this->mapStairs(Blocks::PRISMARINE_STAIRS(), Ids::PRISMARINE_STAIRS); - $this->map(Blocks::PRISMARINE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::PRISMARINE_WALL))); - $this->map(Blocks::PUMPKIN(), Writer::create(Ids::PUMPKIN) - ->writeCardinalHorizontalFacing(Facing::SOUTH) //no longer used - ); - $this->map(Blocks::PUMPKIN_STEM(), fn(PumpkinStem $block) => Helper::encodeStem($block, new Writer(Ids::PUMPKIN_STEM))); - $this->map(Blocks::PURPUR(), Writer::create(Ids::PURPUR_BLOCK)->writePillarAxis(Axis::Y)); - $this->map(Blocks::PURPLE_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_PURPLE))); - $this->map(Blocks::PURPUR_PILLAR(), function(SimplePillar $block) : Writer{ - return Writer::create(Ids::PURPUR_PILLAR) - ->writePillarAxis($block->getAxis()); - }); - $this->mapSlab(Blocks::PURPUR_SLAB(), Ids::PURPUR_SLAB, Ids::PURPUR_DOUBLE_SLAB); - $this->mapStairs(Blocks::PURPUR_STAIRS(), Ids::PURPUR_STAIRS); - $this->map(Blocks::QUARTZ(), Helper::encodeQuartz(Axis::Y, Writer::create(Ids::QUARTZ_BLOCK))); - $this->map(Blocks::QUARTZ_PILLAR(), fn(SimplePillar $block) => Helper::encodeQuartz($block->getAxis(), Writer::create(Ids::QUARTZ_PILLAR))); - $this->mapSlab(Blocks::QUARTZ_SLAB(), Ids::QUARTZ_SLAB, Ids::QUARTZ_DOUBLE_SLAB); - $this->mapStairs(Blocks::QUARTZ_STAIRS(), Ids::QUARTZ_STAIRS); - $this->map(Blocks::RAIL(), function(Rail $block) : Writer{ - return Writer::create(Ids::RAIL) - ->writeInt(StateNames::RAIL_DIRECTION, $block->getShape()); - }); - $this->map(Blocks::REDSTONE_COMPARATOR(), function(RedstoneComparator $block) : Writer{ - return Writer::create($block->isPowered() ? Ids::POWERED_COMPARATOR : Ids::UNPOWERED_COMPARATOR) - ->writeBool(StateNames::OUTPUT_LIT_BIT, $block->isPowered()) - ->writeBool(StateNames::OUTPUT_SUBTRACT_BIT, $block->isSubtractMode()) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::REDSTONE_LAMP(), fn(RedstoneLamp $block) => new Writer($block->isPowered() ? Ids::LIT_REDSTONE_LAMP : Ids::REDSTONE_LAMP)); - $this->map(Blocks::REDSTONE_ORE(), fn(RedstoneOre $block) => new Writer($block->isLit() ? Ids::LIT_REDSTONE_ORE : Ids::REDSTONE_ORE)); - $this->map(Blocks::REDSTONE_REPEATER(), function(RedstoneRepeater $block) : Writer{ - return Writer::create($block->isPowered() ? Ids::POWERED_REPEATER : Ids::UNPOWERED_REPEATER) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeInt(StateNames::REPEATER_DELAY, $block->getDelay() - 1); - }); - $this->map(Blocks::REDSTONE_TORCH(), function(RedstoneTorch $block) : Writer{ - return Writer::create($block->isLit() ? Ids::REDSTONE_TORCH : Ids::UNLIT_REDSTONE_TORCH) - ->writeTorchFacing($block->getFacing()); - }); - $this->map(Blocks::REDSTONE_WIRE(), function(RedstoneWire $block) : Writer{ - return Writer::create(Ids::REDSTONE_WIRE) - ->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength()); - }); - $this->map(Blocks::RED_MUSHROOM_BLOCK(), fn(RedMushroomBlock $block) => Helper::encodeMushroomBlock($block, new Writer(Ids::RED_MUSHROOM_BLOCK))); - $this->mapSlab(Blocks::RED_NETHER_BRICK_SLAB(), Ids::RED_NETHER_BRICK_SLAB, Ids::RED_NETHER_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::RED_NETHER_BRICK_STAIRS(), Ids::RED_NETHER_BRICK_STAIRS); - $this->map(Blocks::RED_NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_NETHER_BRICK_WALL))); - $this->mapSlab(Blocks::RED_SANDSTONE_SLAB(), Ids::RED_SANDSTONE_SLAB, Ids::RED_SANDSTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::RED_SANDSTONE_STAIRS(), Ids::RED_SANDSTONE_STAIRS); - $this->map(Blocks::RED_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_SANDSTONE_WALL))); - $this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_RED))); - $this->mapSlab(Blocks::RESIN_BRICK_SLAB(), Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB); - $this->map(Blocks::RESIN_BRICK_STAIRS(), fn(Stair $block) => Helper::encodeStairs($block, new Writer(Ids::RESIN_BRICK_STAIRS))); - $this->map(Blocks::RESIN_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RESIN_BRICK_WALL))); - $this->map(Blocks::RESIN_CLUMP(), function(ResinClump $block) : Writer{ - return Writer::create(Ids::RESIN_CLUMP) - ->writeFacingFlags($block->getFaces()); - }); - $this->map(Blocks::RESPAWN_ANCHOR(), function(RespawnAnchor $block) : Writer{ - return Writer::create(Ids::RESPAWN_ANCHOR) - ->writeInt(StateNames::RESPAWN_ANCHOR_CHARGE, $block->getCharges()); - }); - $this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH))); - $this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS); - $this->map(Blocks::SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::SANDSTONE_WALL))); - $this->map(Blocks::SEA_PICKLE(), function(SeaPickle $block) : Writer{ - return Writer::create(Ids::SEA_PICKLE) - ->writeBool(StateNames::DEAD_BIT, !$block->isUnderwater()) - ->writeInt(StateNames::CLUSTER_COUNT, $block->getCount() - 1); - }); - $this->map(Blocks::SMALL_DRIPLEAF(), function(SmallDripleaf $block) : Writer{ - return Writer::create(Ids::SMALL_DRIPLEAF_BLOCK) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); - }); - $this->map(Blocks::SMOKER(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::SMOKER, Ids::LIT_SMOKER)); - $this->map(Blocks::SMOOTH_QUARTZ(), Helper::encodeQuartz(Axis::Y, Writer::create(Ids::SMOOTH_QUARTZ))); - $this->mapSlab(Blocks::SMOOTH_QUARTZ_SLAB(), Ids::SMOOTH_QUARTZ_SLAB, Ids::SMOOTH_QUARTZ_DOUBLE_SLAB); - $this->mapStairs(Blocks::SMOOTH_QUARTZ_STAIRS(), Ids::SMOOTH_QUARTZ_STAIRS); - $this->mapSlab(Blocks::SMOOTH_RED_SANDSTONE_SLAB(), Ids::SMOOTH_RED_SANDSTONE_SLAB, Ids::SMOOTH_RED_SANDSTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::SMOOTH_RED_SANDSTONE_STAIRS(), Ids::SMOOTH_RED_SANDSTONE_STAIRS); - $this->mapSlab(Blocks::SMOOTH_SANDSTONE_SLAB(), Ids::SMOOTH_SANDSTONE_SLAB, Ids::SMOOTH_SANDSTONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::SMOOTH_SANDSTONE_STAIRS(), Ids::SMOOTH_SANDSTONE_STAIRS); - $this->mapSlab(Blocks::SMOOTH_STONE_SLAB(), Ids::SMOOTH_STONE_SLAB, Ids::SMOOTH_STONE_DOUBLE_SLAB); - $this->map(Blocks::SNOW_LAYER(), function(SnowLayer $block) : Writer{ - return Writer::create(Ids::SNOW_LAYER) - ->writeBool(StateNames::COVERED_BIT, false) - ->writeInt(StateNames::HEIGHT, $block->getLayers() - 1); - }); - $this->map(Blocks::SOUL_CAMPFIRE(), function(SoulCampfire $block) : Writer{ - return Writer::create(Ids::SOUL_CAMPFIRE) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeBool(StateNames::EXTINGUISHED, !$block->isLit()); - }); - $this->map(Blocks::SOUL_FIRE(), Writer::create(Ids::SOUL_FIRE) - ->writeInt(StateNames::AGE, 0) //useless for soul fire, we don't track it - ); - $this->map(Blocks::SOUL_LANTERN(), function(Lantern $block) : Writer{ - return Writer::create(Ids::SOUL_LANTERN) - ->writeBool(StateNames::HANGING, $block->isHanging()); - }); - $this->map(Blocks::SOUL_TORCH(), function(Torch $block) : Writer{ - return Writer::create(Ids::SOUL_TORCH) - ->writeTorchFacing($block->getFacing()); - }); - $this->map(Blocks::SPONGE(), fn(Sponge $block) => Writer::create($block->isWet() ? Ids::WET_SPONGE : Ids::SPONGE)); - $this->map(Blocks::STONECUTTER(), fn(Stonecutter $block) => Writer::create(Ids::STONECUTTER_BLOCK) - ->writeCardinalHorizontalFacing($block->getFacing())); - $this->mapSlab(Blocks::STONE_BRICK_SLAB(), Ids::STONE_BRICK_SLAB, Ids::STONE_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::STONE_BRICK_STAIRS(), Ids::STONE_BRICK_STAIRS); - $this->map(Blocks::STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::STONE_BRICK_WALL))); - $this->map(Blocks::STONE_BUTTON(), fn(StoneButton $block) => Helper::encodeButton($block, new Writer(Ids::STONE_BUTTON))); - $this->map(Blocks::STONE_PRESSURE_PLATE(), fn(StonePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::STONE_PRESSURE_PLATE))); - $this->mapSlab(Blocks::STONE_SLAB(), Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB); - $this->mapStairs(Blocks::STONE_STAIRS(), Ids::NORMAL_STONE_STAIRS); - $this->map(Blocks::SUGARCANE(), function(Sugarcane $block) : Writer{ - return Writer::create(Ids::REEDS) - ->writeInt(StateNames::AGE, $block->getAge()); - }); - $this->map(Blocks::SUNFLOWER(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::SUNFLOWER))); - $this->map(Blocks::SWEET_BERRY_BUSH(), function(SweetBerryBush $block) : Writer{ - return Writer::create(Ids::SWEET_BERRY_BUSH) - ->writeInt(StateNames::GROWTH, $block->getAge()); - }); - $this->map(Blocks::TNT(), fn(TNT $block) => Writer::create($block->worksUnderwater() ? Ids::UNDERWATER_TNT : Ids::TNT) - ->writeBool(StateNames::EXPLODE_BIT, $block->isUnstable()) - ); - $this->map(Blocks::TORCH(), function(Torch $block) : Writer{ - return Writer::create(Ids::TORCH) - ->writeTorchFacing($block->getFacing()); - }); - $this->map(Blocks::TORCHFLOWER_CROP(), function(TorchflowerCrop $block){ - return Writer::create(Ids::TORCHFLOWER_CROP) - ->writeInt(StateNames::GROWTH, $block->isReady() ? 1 : 0); - }); - $this->map(Blocks::TRAPPED_CHEST(), function(TrappedChest $block) : Writer{ - return Writer::create(Ids::TRAPPED_CHEST) - ->writeCardinalHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::TRIPWIRE(), function(Tripwire $block) : Writer{ - return Writer::create(Ids::TRIP_WIRE) - ->writeBool(StateNames::ATTACHED_BIT, $block->isConnected()) - ->writeBool(StateNames::DISARMED_BIT, $block->isDisarmed()) - ->writeBool(StateNames::POWERED_BIT, $block->isTriggered()) - ->writeBool(StateNames::SUSPENDED_BIT, $block->isSuspended()); - }); - $this->map(Blocks::TRIPWIRE_HOOK(), function(TripwireHook $block) : Writer{ - return Writer::create(Ids::TRIPWIRE_HOOK) - ->writeBool(StateNames::ATTACHED_BIT, $block->isConnected()) - ->writeBool(StateNames::POWERED_BIT, $block->isPowered()) - ->writeLegacyHorizontalFacing($block->getFacing()); - }); - $this->mapSlab(Blocks::TUFF_BRICK_SLAB(), Ids::TUFF_BRICK_SLAB, Ids::TUFF_BRICK_DOUBLE_SLAB); - $this->mapStairs(Blocks::TUFF_BRICK_STAIRS(), Ids::TUFF_BRICK_STAIRS); - $this->map(Blocks::TUFF_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::TUFF_BRICK_WALL))); - $this->mapSlab(Blocks::TUFF_SLAB(), Ids::TUFF_SLAB, Ids::TUFF_DOUBLE_SLAB); - $this->mapStairs(Blocks::TUFF_STAIRS(), Ids::TUFF_STAIRS); - $this->map(Blocks::TUFF_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::TUFF_WALL))); - $this->map(Blocks::TWISTING_VINES(), function(NetherVines $block) : Writer{ - return Writer::create(Ids::TWISTING_VINES) - ->writeInt(StateNames::TWISTING_VINES_AGE, $block->getAge()); - }); - $this->map(Blocks::UNDERWATER_TORCH(), function(UnderwaterTorch $block) : Writer{ - return Writer::create(Ids::UNDERWATER_TORCH) - ->writeTorchFacing($block->getFacing()); - }); - $this->map(Blocks::VINES(), function(Vine $block) : Writer{ - return Writer::create(Ids::VINE) - ->writeInt(StateNames::VINE_DIRECTION_BITS, ($block->hasFace(Facing::NORTH) ? BlockLegacyMetadata::VINE_FLAG_NORTH : 0) | ($block->hasFace(Facing::SOUTH) ? BlockLegacyMetadata::VINE_FLAG_SOUTH : 0) | ($block->hasFace(Facing::WEST) ? BlockLegacyMetadata::VINE_FLAG_WEST : 0) | ($block->hasFace(Facing::EAST) ? BlockLegacyMetadata::VINE_FLAG_EAST : 0)); - }); - $this->map(Blocks::WALL_BANNER(), function(WallBanner $block) : Writer{ - return Writer::create(Ids::WALL_BANNER) - ->writeHorizontalFacing($block->getFacing()); - }); - $this->map(Blocks::WATER(), fn(Water $block) => Helper::encodeLiquid($block, Ids::WATER, Ids::FLOWING_WATER)); - $this->map(Blocks::WEEPING_VINES(), function(NetherVines $block) : Writer{ - return Writer::create(Ids::WEEPING_VINES) - ->writeInt(StateNames::WEEPING_VINES_AGE, $block->getAge()); - }); - $this->map(Blocks::WEIGHTED_PRESSURE_PLATE_HEAVY(), function(WeightedPressurePlateHeavy $block) : Writer{ - return Writer::create(Ids::HEAVY_WEIGHTED_PRESSURE_PLATE) - ->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength()); - }); - $this->map(Blocks::WEIGHTED_PRESSURE_PLATE_LIGHT(), function(WeightedPressurePlateLight $block) : Writer{ - return Writer::create(Ids::LIGHT_WEIGHTED_PRESSURE_PLATE) - ->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength()); - }); - $this->map(Blocks::WHEAT(), fn(Wheat $block) => Helper::encodeCrops($block, new Writer(Ids::WHEAT))); - } } diff --git a/src/data/bedrock/block/convert/BlockSerializerDeserializerRegistrar.php b/src/data/bedrock/block/convert/BlockSerializerDeserializerRegistrar.php new file mode 100644 index 000000000..02491bae6 --- /dev/null +++ b/src/data/bedrock/block/convert/BlockSerializerDeserializerRegistrar.php @@ -0,0 +1,237 @@ +> $components + * + * @return string[][] + * @phpstan-return list> + */ + private static function compileFlattenedIdPartMatrix(array $components) : array{ + $result = []; + foreach($components as $component){ + $column = is_string($component) ? [$component] : $component->getPossibleValues(); + + if(count($result) === 0){ + $result = array_map(fn($value) => [$value], $column); + }else{ + $stepResult = []; + foreach($result as $parts){ + foreach($column as $value){ + $stepPart = $parts; + $stepPart[] = $value; + $stepResult[] = $stepPart; + } + } + + $result = $stepResult; + } + } + + return $result; + } + + /** + * @param string[]|StringProperty[] $idComponents + * + * @phpstan-template TBlock of Block + * + * @phpstan-param TBlock $block + * @phpstan-param list> $idComponents + */ + private static function serializeFlattenedId(Block $block, array $idComponents) : string{ + $id = ""; + foreach($idComponents as $infix){ + $id .= is_string($infix) ? $infix : $infix->serializePlain($block); + } + return $id; + } + + /** + * @param string[]|StringProperty[] $idComponents + * @param string[] $idPropertyValues + * + * @phpstan-template TBlock of Block + * + * @phpstan-param TBlock $baseBlock + * @phpstan-param list> $idComponents + * @phpstan-param list $idPropertyValues + * + * @phpstan-return TBlock + */ + private static function deserializeFlattenedId(Block $baseBlock, array $idComponents, array $idPropertyValues) : Block{ + $preparedBlock = clone $baseBlock; + foreach($idComponents as $k => $component){ + if($component instanceof StringProperty){ + $fakeValue = $idPropertyValues[$k]; + $component->deserializePlain($preparedBlock, $fakeValue); + } + } + + return $preparedBlock; + } + + public function mapSimple(Block $block, string $id) : void{ + $this->deserializer->mapSimple($id, fn() => clone $block); + $this->serializer->mapSimple($block, $id); + } + + /** + * @phpstan-template TBlock of Block + * @phpstan-param FlattenedIdModel $model + */ + public function mapFlattenedId(FlattenedIdModel $model) : void{ + $block = $model->getBlock(); + + $idComponents = $model->getIdComponents(); + if(count($idComponents) === 0){ + throw new \InvalidArgumentException("No ID components provided"); + } + $properties = $model->getProperties(); + + //This is a really cursed hack that lets us essentially write flattened properties as blockstate properties, and + //then pull them out to compile an ID :D + //This works surprisingly well and is much more elegant than I would've expected + + if(count($properties) > 0){ + $this->serializer->map($block, function(Block $block) use ($idComponents, $properties) : Writer{ + $id = self::serializeFlattenedId($block, $idComponents); + + $writer = new Writer($id); + foreach($properties as $property){ + $property->serialize($block, $writer); + } + + return $writer; + }); + }else{ + $this->serializer->map($block, function(Block $block) use ($idComponents) : BlockStateData{ + //fast path for blocks with no state properties + $id = self::serializeFlattenedId($block, $idComponents); + return BlockStateData::current($id, []); + }); + } + + $idPermutations = self::compileFlattenedIdPartMatrix($idComponents); + foreach($idPermutations as $idParts){ + //deconstruct the ID into a partial state + //we can do this at registration time since there will be multiple deserializers + $preparedBlock = self::deserializeFlattenedId($block, $idComponents, $idParts); + $id = implode("", $idParts); + + if(count($properties) > 0){ + $this->deserializer->map($id, function(Reader $reader) use ($preparedBlock, $properties) : Block{ + $block = clone $preparedBlock; + + foreach($properties as $property){ + $property->deserialize($block, $reader); + } + return $block; + }); + }else{ + //fast path for blocks with no state properties + $this->deserializer->map($id, fn() => clone $preparedBlock); + } + } + } + + /** + * @phpstan-template TBlock of Block&Colored + * @phpstan-param TBlock $block + */ + public function mapColored(Block $block, string $idPrefix, string $idSuffix) : void{ + $this->mapFlattenedId(FlattenedIdModel::create($block) + ->idComponents([ + $idPrefix, + CommonProperties::getInstance()->dyeColorIdInfix, + $idSuffix + ]) + ); + } + + public function mapSlab(Slab $block, string $type) : void{ + $commonProperties = CommonProperties::getInstance(); + $this->mapFlattenedId(FlattenedIdModel::create($block) + ->idComponents(["minecraft:", $type, "_", $commonProperties->slabIdInfix, "slab"]) + ->properties([$commonProperties->slabPositionProperty]) + ); + } + + public function mapStairs(Stair $block, string $id) : void{ + $this->mapModel(Model::create($block, $id)->properties(CommonProperties::getInstance()->stairProperties)); + } + + /** + * @phpstan-template TBlock of Block + * @phpstan-param Model $model + */ + public function mapModel(Model $model) : void{ + $id = $model->getId(); + $block = $model->getBlock(); + $propertyDescriptors = $model->getProperties(); + + $this->deserializer->map($id, static function(Reader $in) use ($block, $propertyDescriptors) : Block{ + $newBlock = clone $block; + foreach($propertyDescriptors as $descriptor){ + $descriptor->deserialize($newBlock, $in); + } + return $newBlock; + }); + $this->serializer->map($block, static function(Block $block) use ($id, $propertyDescriptors) : Writer{ + $writer = new Writer($id); + foreach($propertyDescriptors as $descriptor){ + $descriptor->serialize($block, $writer); + } + return $writer; + }); + } +} diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index 3cf55429e..1d48ec76f 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -56,11 +56,13 @@ use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\data\bedrock\block\BlockStateNames; use pocketmine\data\bedrock\block\BlockStateNames as StateNames; +use pocketmine\data\bedrock\block\convert\property\ValueMappings; use pocketmine\data\bedrock\MushroomBlockTypeIdMap; -use pocketmine\math\Axis; use pocketmine\math\Facing; -use pocketmine\utils\AssumptionFailedError; +/** + * @deprecated + */ final class BlockStateDeserializerHelper{ /** @throws BlockStateDeserializeException */ @@ -71,6 +73,7 @@ final class BlockStateDeserializerHelper{ } /** + * @deprecated * @phpstan-template TCandle of Candle * @phpstan-param TCandle $block * @phpstan-return TCandle @@ -103,6 +106,7 @@ final class BlockStateDeserializerHelper{ } /** + * @deprecated * @phpstan-template TBlock of CopperMaterial * * @phpstan-param TBlock $block @@ -115,6 +119,7 @@ final class BlockStateDeserializerHelper{ } /** + * @deprecated * @phpstan-template TBlock of CopperMaterial * * @phpstan-param TBlock $block @@ -133,6 +138,7 @@ final class BlockStateDeserializerHelper{ } /** + * @deprecated * @phpstan-template TDoor of Door * @phpstan-param TDoor $block * @phpstan-return TDoor @@ -155,7 +161,10 @@ final class BlockStateDeserializerHelper{ ->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT)); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{ return $block ->setFacing($in->readCardinalHorizontalFacing()) @@ -163,17 +172,19 @@ final class BlockStateDeserializerHelper{ ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public static function decodeFloorCoralFan(FloorCoralFan $block, BlockStateReader $in) : FloorCoralFan{ return $block - ->setAxis(match($in->readBoundedInt(BlockStateNames::CORAL_FAN_DIRECTION, 0, 1)){ - 0 => Axis::X, - 1 => Axis::Z, - default => throw new AssumptionFailedError("readBoundedInt() should have prevented this"), - }); + ->setAxis($in->mapIntFromInt(BlockStateNames::CORAL_FAN_DIRECTION, ValueMappings::getInstance()->coralAxis)); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public static function decodeFloorSign(FloorSign $block, BlockStateReader $in) : FloorSign{ return $block ->setRotation($in->readBoundedInt(BlockStateNames::GROUND_SIGN_DIRECTION, 0, 15)); @@ -186,7 +197,10 @@ final class BlockStateDeserializerHelper{ ->setHasMap($in->readBool(StateNames::ITEM_FRAME_MAP_BIT)); } - /** @throws BlockStateDeserializeException */ + /** + * @throws BlockStateDeserializeException + * @deprecated + */ public static function decodeLeaves(Leaves $block, BlockStateReader $in) : Leaves{ return $block ->setNoDecay($in->readBool(StateNames::PERSISTENT_BIT)) @@ -236,7 +250,10 @@ final class BlockStateDeserializerHelper{ ->setDelay($in->readBoundedInt(BlockStateNames::REPEATER_DELAY, 0, 3) + 1); } - /** @throws BlockStateDeserializeException */ + /** + * @throws BlockStateDeserializeException + * @deprecated + */ public static function decodeSapling(Sapling $block, BlockStateReader $in) : Sapling{ return $block ->setReady($in->readBool(BlockStateNames::AGE_BIT)); @@ -273,6 +290,7 @@ final class BlockStateDeserializerHelper{ } /** + * @deprecated * @phpstan-template TStair of Stair * @phpstan-param TStair $block * @phpstan-return TStair @@ -296,6 +314,7 @@ final class BlockStateDeserializerHelper{ } /** + * @deprecated * @phpstan-template TTrapdoor of Trapdoor * @phpstan-param TTrapdoor $block * @phpstan-return TTrapdoor @@ -320,12 +339,19 @@ final class BlockStateDeserializerHelper{ return $block; } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public static function decodeWallSign(WallSign $block, BlockStateReader $in) : WallSign{ return $block ->setFacing($in->readHorizontalFacing()); } + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public static function decodeWeightedPressurePlate(WeightedPressurePlate $block, BlockStateReader $in) : WeightedPressurePlate{ return $block ->setOutputSignalStrength($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15)); diff --git a/src/data/bedrock/block/convert/BlockStateReader.php b/src/data/bedrock/block/convert/BlockStateReader.php index e3a02885f..4d09d2f85 100644 --- a/src/data/bedrock/block/convert/BlockStateReader.php +++ b/src/data/bedrock/block/convert/BlockStateReader.php @@ -31,6 +31,9 @@ use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\data\bedrock\block\BlockStateNames; use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues; +use pocketmine\data\bedrock\block\convert\property\EnumFromRawStateMap; +use pocketmine\data\bedrock\block\convert\property\IntFromRawStateMap; +use pocketmine\data\bedrock\block\convert\property\ValueMappings; use pocketmine\math\Axis; use pocketmine\math\Facing; use pocketmine\nbt\tag\ByteTag; @@ -112,45 +115,45 @@ final class BlockStateReader{ } /** - * @param int[] $mapping - * @phpstan-param array $mapping - * @phpstan-return int + * @deprecated + * @phpstan-param IntFromRawStateMap $map * @throws BlockStateDeserializeException */ - private function parseFacingValue(int $value, array $mapping) : int{ - $result = $mapping[$value] ?? null; - if($result === null){ - throw new BlockStateDeserializeException("Unmapped facing value " . $value); - } - return $result; - } + public function mapIntFromString(string $name, IntFromRawStateMap $map) : int{ + $raw = $this->readString($name); - /** @throws BlockStateDeserializeException */ - public function readFacingDirection() : int{ - return $this->parseFacingValue($this->readInt(BlockStateNames::FACING_DIRECTION), [ - 0 => Facing::DOWN, - 1 => Facing::UP, - 2 => Facing::NORTH, - 3 => Facing::SOUTH, - 4 => Facing::WEST, - 5 => Facing::EAST - ]); - } - - /** @throws BlockStateDeserializeException */ - public function readBlockFace() : int{ - return match($raw = $this->readString(BlockStateNames::MC_BLOCK_FACE)){ - StringValues::MC_BLOCK_FACE_DOWN => Facing::DOWN, - StringValues::MC_BLOCK_FACE_UP => Facing::UP, - StringValues::MC_BLOCK_FACE_NORTH => Facing::NORTH, - StringValues::MC_BLOCK_FACE_SOUTH => Facing::SOUTH, - StringValues::MC_BLOCK_FACE_WEST => Facing::WEST, - StringValues::MC_BLOCK_FACE_EAST => Facing::EAST, - default => throw $this->badValueException(BlockStateNames::MC_BLOCK_FACE, $raw) - }; + return $map->rawToValue($raw) ?? throw $this->badValueException($name, $raw); } /** + * @deprecated + * @phpstan-param IntFromRawStateMap $map + * @throws BlockStateDeserializeException + */ + public function mapIntFromInt(string $name, IntFromRawStateMap $map) : int{ + $raw = $this->readInt($name); + + return $map->rawToValue($raw) ?? throw $this->badValueException($name, (string) $raw); + } + + /** + * @deprecated + * @throws BlockStateDeserializeException + */ + public function readFacingDirection() : int{ + return $this->mapIntFromInt(BlockStateNames::FACING_DIRECTION, ValueMappings::getInstance()->facing); + } + + /** + * @deprecated + * @throws BlockStateDeserializeException + */ + public function readBlockFace() : int{ + return $this->mapIntFromString(BlockStateNames::MC_BLOCK_FACE, ValueMappings::getInstance()->blockFace); + } + + /** + * @deprecated * @return int[] * @phpstan-return array */ @@ -173,82 +176,69 @@ final class BlockStateReader{ return $result; } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readEndRodFacingDirection() : int{ $result = $this->readFacingDirection(); return Facing::axis($result) !== Axis::Y ? Facing::opposite($result) : $result; } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readHorizontalFacing() : int{ - return $this->parseFacingValue($this->readInt(BlockStateNames::FACING_DIRECTION), [ - 0 => Facing::NORTH, //should be illegal, but 1.13 allows it - 1 => Facing::NORTH, //also should be illegal - 2 => Facing::NORTH, - 3 => Facing::SOUTH, - 4 => Facing::WEST, - 5 => Facing::EAST - ]); - } - - /** @throws BlockStateDeserializeException */ - public function readWeirdoHorizontalFacing() : int{ - return $this->parseFacingValue($this->readInt(BlockStateNames::WEIRDO_DIRECTION), [ - 0 => Facing::EAST, - 1 => Facing::WEST, - 2 => Facing::SOUTH, - 3 => Facing::NORTH - ]); - } - - /** @throws BlockStateDeserializeException */ - public function readLegacyHorizontalFacing() : int{ - return $this->parseFacingValue($this->readInt(BlockStateNames::DIRECTION), [ - 0 => Facing::SOUTH, - 1 => Facing::WEST, - 2 => Facing::NORTH, - 3 => Facing::EAST - ]); + return $this->mapIntFromInt(BlockStateNames::FACING_DIRECTION, ValueMappings::getInstance()->horizontalFacingClassic); } /** + * @deprecated + * @throws BlockStateDeserializeException + */ + public function readWeirdoHorizontalFacing() : int{ + return $this->mapIntFromInt(BlockStateNames::WEIRDO_DIRECTION, ValueMappings::getInstance()->horizontalFacing5Minus); + } + + /** + * @deprecated + * @throws BlockStateDeserializeException + */ + public function readLegacyHorizontalFacing() : int{ + return $this->mapIntFromInt(BlockStateNames::DIRECTION, ValueMappings::getInstance()->horizontalFacingSWNE); + } + + /** + * @deprecated * This is for trapdoors, because Mojang botched the conversion in 1.13 * @throws BlockStateDeserializeException */ public function read5MinusHorizontalFacing() : int{ - return $this->parseFacingValue($this->readInt(BlockStateNames::DIRECTION), [ - 0 => Facing::EAST, - 1 => Facing::WEST, - 2 => Facing::SOUTH, - 3 => Facing::NORTH - ]); + return $this->mapIntFromInt(BlockStateNames::DIRECTION, ValueMappings::getInstance()->horizontalFacing5Minus); } /** + * @deprecated * Used by pumpkins as of 1.20.0.23 beta * @throws BlockStateDeserializeException */ public function readCardinalHorizontalFacing() : int{ - return match($raw = $this->readString(BlockStateNames::MC_CARDINAL_DIRECTION)){ - StringValues::MC_CARDINAL_DIRECTION_NORTH => Facing::NORTH, - StringValues::MC_CARDINAL_DIRECTION_SOUTH => Facing::SOUTH, - StringValues::MC_CARDINAL_DIRECTION_WEST => Facing::WEST, - StringValues::MC_CARDINAL_DIRECTION_EAST => Facing::EAST, - default => throw $this->badValueException(BlockStateNames::MC_CARDINAL_DIRECTION, $raw) - }; + return $this->mapIntFromString(BlockStateNames::MC_CARDINAL_DIRECTION, ValueMappings::getInstance()->cardinalDirection); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readCoralFacing() : int{ - return $this->parseFacingValue($this->readInt(BlockStateNames::CORAL_DIRECTION), [ - 0 => Facing::WEST, - 1 => Facing::EAST, - 2 => Facing::NORTH, - 3 => Facing::SOUTH - ]); + return $this->mapIntFromInt(BlockStateNames::CORAL_DIRECTION, ValueMappings::getInstance()->horizontalFacingCoral); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readFacingWithoutDown() : int{ $result = $this->readFacingDirection(); if($result === Facing::DOWN){ //shouldn't be legal, but 1.13 allows it @@ -257,6 +247,10 @@ final class BlockStateReader{ return $result; } + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readFacingWithoutUp() : int{ $result = $this->readFacingDirection(); if($result === Facing::UP){ @@ -266,23 +260,17 @@ final class BlockStateReader{ } /** - * @phpstan-return Axis::* + * @deprecated * @throws BlockStateDeserializeException */ public function readPillarAxis() : int{ - $rawValue = $this->readString(BlockStateNames::PILLAR_AXIS); - $value = [ - StringValues::PILLAR_AXIS_X => Axis::X, - StringValues::PILLAR_AXIS_Y => Axis::Y, - StringValues::PILLAR_AXIS_Z => Axis::Z - ][$rawValue] ?? null; - if($value === null){ - throw $this->badValueException(BlockStateNames::PILLAR_AXIS, $rawValue, "Invalid axis value"); - } - return $value; + return $this->mapIntFromString(BlockStateNames::PILLAR_AXIS, ValueMappings::getInstance()->pillarAxis); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readSlabPosition() : SlabType{ return match($rawValue = $this->readString(BlockStateNames::MC_VERTICAL_HALF)){ StringValues::MC_VERTICAL_HALF_BOTTOM => SlabType::BOTTOM, @@ -292,34 +280,25 @@ final class BlockStateReader{ } /** - * @phpstan-return Facing::UP|Facing::NORTH|Facing::SOUTH|Facing::WEST|Facing::EAST + * @deprecated * @throws BlockStateDeserializeException */ public function readTorchFacing() : int{ - //TODO: horizontal directions are flipped (MCPE bug: https://bugs.mojang.com/browse/MCPE-152036) - return match($rawValue = $this->readString(BlockStateNames::TORCH_FACING_DIRECTION)){ - StringValues::TORCH_FACING_DIRECTION_EAST => Facing::WEST, - StringValues::TORCH_FACING_DIRECTION_NORTH => Facing::SOUTH, - StringValues::TORCH_FACING_DIRECTION_SOUTH => Facing::NORTH, - StringValues::TORCH_FACING_DIRECTION_TOP => Facing::UP, - StringValues::TORCH_FACING_DIRECTION_UNKNOWN => Facing::UP, //should be illegal, but 1.13 allows it - StringValues::TORCH_FACING_DIRECTION_WEST => Facing::EAST, - default => throw $this->badValueException(BlockStateNames::TORCH_FACING_DIRECTION, $rawValue, "Invalid torch facing"), - }; + return $this->mapIntFromString(BlockStateNames::TORCH_FACING_DIRECTION, ValueMappings::getInstance()->torchFacing); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readBellAttachmentType() : BellAttachmentType{ - return match($type = $this->readString(BlockStateNames::ATTACHMENT)){ - StringValues::ATTACHMENT_HANGING => BellAttachmentType::CEILING, - StringValues::ATTACHMENT_STANDING => BellAttachmentType::FLOOR, - StringValues::ATTACHMENT_SIDE => BellAttachmentType::ONE_WALL, - StringValues::ATTACHMENT_MULTIPLE => BellAttachmentType::TWO_WALLS, - default => throw $this->badValueException(BlockStateNames::ATTACHMENT, $type), - }; + return $this->readUnitEnum(BlockStateNames::ATTACHMENT, ValueMappings::getInstance()->bellAttachmentType); } - /** @throws BlockStateDeserializeException */ + /** + * @deprecated + * @throws BlockStateDeserializeException + */ public function readWallConnectionType(string $name) : ?WallConnectionType{ return match($type = $this->readString($name)){ //TODO: this looks a bit confusing due to use of EAST, but the values are the same for all connections @@ -332,6 +311,23 @@ final class BlockStateReader{ }; } + /** + * @deprecated + * @phpstan-template TEnum of \UnitEnum + * @phpstan-param EnumFromRawStateMap $map + * @phpstan-return TEnum + * @throws BlockStateDeserializeException + */ + public function readUnitEnum(string $name, EnumFromRawStateMap $map) : \UnitEnum{ + $value = $this->readString($name); + + $mapped = $map->rawToValue($value); + if($mapped === null){ + throw $this->badValueException($name, $value); + } + return $mapped; + } + /** * Explicitly mark a property as unused, so it doesn't get flagged as an error when debug mode is enabled */ diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php index a25044153..da3dbb387 100644 --- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php @@ -55,6 +55,9 @@ use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer; use pocketmine\data\bedrock\MushroomBlockTypeIdMap; use pocketmine\math\Facing; +/** + * @deprecated + */ final class BlockStateSerializerHelper{ public static function encodeButton(Button $block, Writer $out) : Writer{ return $out @@ -77,6 +80,9 @@ final class BlockStateSerializerHelper{ return $out->writeInt(BlockStateNames::GROWTH, $block->getAge()); } + /** + * @deprecated + */ public static function encodeTorch(Torch $block, Writer $out) : Writer{ return $out ->writeTorchFacing($block->getFacing()); @@ -97,6 +103,9 @@ final class BlockStateSerializerHelper{ }; } + /** + * @deprecated + */ public static function encodeDoor(Door $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop()) @@ -111,6 +120,9 @@ final class BlockStateSerializerHelper{ ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop()); } + /** + * @deprecated + */ public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{ return $out ->writeCardinalHorizontalFacing($block->getFacing()) @@ -118,6 +130,9 @@ final class BlockStateSerializerHelper{ ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } + /** + * @deprecated + */ public static function encodeFloorSign(FloorSign $block, Writer $out) : Writer{ return $out ->writeInt(BlockStateNames::GROUND_SIGN_DIRECTION, $block->getRotation()); @@ -135,6 +150,9 @@ final class BlockStateSerializerHelper{ ->writeFacingDirection($block->getFacing()); } + /** + * @deprecated + */ public static function encodeLeaves(Leaves $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::PERSISTENT_BIT, $block->isNoDecay()) @@ -159,11 +177,17 @@ final class BlockStateSerializerHelper{ ->writeInt(BlockStateNames::HUGE_MUSHROOM_BITS, MushroomBlockTypeIdMap::getInstance()->toId($block->getMushroomBlockType())); } + /** + * @deprecated + */ public static function encodeQuartz(int $axis, Writer $out) : Writer{ return $out ->writePillarAxis($axis); //this isn't needed for all types, but we have to write it anyway } + /** + * @deprecated + */ public static function encodeSapling(Sapling $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::AGE_BIT, $block->isReady()); @@ -193,6 +217,9 @@ final class BlockStateSerializerHelper{ self::encodeSingleSlab($block, $singleId); } + /** + * @deprecated + */ public static function encodeStairs(Stair $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::UPSIDE_DOWN_BIT, $block->isUpsideDown()) @@ -208,6 +235,9 @@ final class BlockStateSerializerHelper{ ->writeFacingWithoutUp($facing === Facing::UP ? Facing::DOWN : $facing); } + /** + * @deprecated + */ public static function encodeTrapdoor(Trapdoor $block, Writer $out) : Writer{ return $out ->write5MinusHorizontalFacing($block->getFacing()) @@ -224,6 +254,9 @@ final class BlockStateSerializerHelper{ ->writeWallConnectionType(BlockStateNames::WALL_CONNECTION_TYPE_WEST, $block->getConnection(Facing::WEST)); } + /** + * @deprecated + */ public static function encodeWallSign(WallSign $block, Writer $out) : Writer{ return $out ->writeHorizontalFacing($block->getFacing()); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 1e9a4041f..ca5c12412 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -23,50 +23,18 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\convert; -use pocketmine\block\AmethystCluster; -use pocketmine\block\Anvil; -use pocketmine\block\Bamboo; use pocketmine\block\Block; -use pocketmine\block\CakeWithDyedCandle; -use pocketmine\block\CaveVines; -use pocketmine\block\ChorusFlower; -use pocketmine\block\DoublePitcherCrop; -use pocketmine\block\Opaque; -use pocketmine\block\PinkPetals; -use pocketmine\block\PitcherCrop; use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\block\Slab; use pocketmine\block\Stair; -use pocketmine\block\SweetBerryBush; -use pocketmine\block\utils\BrewingStandSlot; -use pocketmine\block\utils\ChiseledBookshelfSlot; -use pocketmine\block\utils\Colored; -use pocketmine\block\utils\CopperMaterial; -use pocketmine\block\utils\CopperOxidation; -use pocketmine\block\utils\CoralType; -use pocketmine\block\utils\DirtType; -use pocketmine\block\utils\DripleafState; -use pocketmine\block\utils\DyeColor; -use pocketmine\block\utils\FroglightType; -use pocketmine\block\utils\LeverFacing; -use pocketmine\block\utils\MobHeadType; -use pocketmine\block\VanillaBlocks as Blocks; use pocketmine\block\Wood; -use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\data\bedrock\block\BlockStateDeserializer; -use pocketmine\data\bedrock\block\BlockStateNames as StateNames; -use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues; -use pocketmine\data\bedrock\block\BlockTypeNames as Ids; use pocketmine\data\bedrock\block\convert\BlockStateDeserializerHelper as Helper; use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader; -use pocketmine\math\Axis; -use pocketmine\math\Facing; -use pocketmine\utils\Utils; use function array_key_exists; use function count; -use function min; final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ @@ -82,21 +50,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ */ private array $simpleCache = []; - public function __construct(){ - $this->registerCandleDeserializers(); - $this->registerFlatColorBlockDeserializers(); - $this->registerFlatCoralDeserializers(); - $this->registerCauldronDeserializers(); - $this->registerFlatWoodBlockDeserializers(); - $this->registerLeavesDeserializers(); - $this->registerSaplingDeserializers(); - $this->registerLightDeserializers(); - $this->registerMobHeadDeserializers(); - $this->registerCopperDeserializers(); - $this->registerSimpleDeserializers(); - $this->registerDeserializers(); - } - public function deserialize(BlockStateData $stateData) : int{ if(count($stateData->getStates()) === 0){ //if a block has zero properties, we can keep a map of string ID -> internal blockstate ID @@ -133,12 +86,16 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return $this->deserializeFuncs[$id] ?? null; } - /** @phpstan-param \Closure() : Block $getBlock */ + /** + * @deprecated + * @phpstan-param \Closure() : Block $getBlock + */ public function mapSimple(string $id, \Closure $getBlock) : void{ $this->map($id, $getBlock); } /** + * @deprecated * @phpstan-param \Closure(Reader) : Slab $getBlock */ public function mapSlab(string $singleId, string $doubleId, \Closure $getBlock) : void{ @@ -147,1552 +104,22 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ } /** + * @deprecated * @phpstan-param \Closure() : Stair $getBlock */ public function mapStairs(string $id, \Closure $getBlock) : void{ $this->map($id, fn(Reader $in) : Stair => Helper::decodeStairs($getBlock(), $in)); } - /** @phpstan-param \Closure() : Wood $getBlock */ + /** + * @deprecated + * @phpstan-param \Closure() : Wood $getBlock + */ public function mapLog(string $unstrippedId, string $strippedId, \Closure $getBlock) : void{ $this->map($unstrippedId, fn(Reader $in) => Helper::decodeLog($getBlock(), false, $in)); $this->map($strippedId, fn(Reader $in) => Helper::decodeLog($getBlock(), true, $in)); } - /** - * @phpstan-template TBlock of Block - * @phpstan-template TEnum of \UnitEnum - * - * @phpstan-param StringEnumMap $mapProperty - * @phpstan-param \Closure(TEnum) : TBlock $getBlock - * @phpstan-param ?\Closure(TBlock, Reader) : TBlock $extra - */ - public function mapFlattenedEnum( - StringEnumMap $mapProperty, - string $prefix, - string $suffix, - \Closure $getBlock, - ?\Closure $extra = null - ) : void{ - foreach(Utils::stringifyKeys($mapProperty->getValueToEnum()) as $infix => $enumCase){ - $id = $prefix . $infix . $suffix; - if($extra === null){ - $this->map($id, fn() => $getBlock($enumCase)); - }else{ - $this->map($id, function(Reader $in) use ($enumCase, $getBlock, $extra) : Block{ - $block = $getBlock($enumCase); - $extra($block, $in); - return $block; - }); - } - } - } - - /** - * @phpstan-template TBlock of Block&Colored - * @phpstan-param \Closure() : TBlock $getBlock - * @phpstan-param ?\Closure(TBlock, Reader) : TBlock $extra - */ - public function mapColored(string $prefix, string $suffix, \Closure $getBlock, ?\Closure $extra = null) : void{ - $this->mapFlattenedEnum( - ValueMappings::getInstance()->getEnumMap(DyeColor::class), - $prefix, - $suffix, - fn(DyeColor $color) => $getBlock()->setColor($color), - $extra - ); - } - - private function registerCandleDeserializers() : void{ - $this->map(Ids::CANDLE, fn(Reader $in) => Helper::decodeCandle(Blocks::CANDLE(), $in)); - $this->mapColored( - "minecraft:", - "_candle", - fn() => Blocks::DYED_CANDLE(), - Helper::decodeCandle(...) - ); - - $this->map(Ids::CANDLE_CAKE, fn(Reader $in) => Blocks::CAKE_WITH_CANDLE()->setLit($in->readBool(StateNames::LIT))); - - $this->mapColored( - "minecraft:", - "_candle_cake", - fn() => Blocks::CAKE_WITH_DYED_CANDLE(), - fn(CakeWithDyedCandle $block, Reader $in) => $block->setLit($in->readBool(StateNames::LIT)) - ); - } - - private function registerFlatColorBlockDeserializers() : void{ - $this->mapColored("minecraft:hard_", "_stained_glass", fn() => Blocks::STAINED_HARDENED_GLASS()); - $this->mapColored("minecraft:hard_", "_stained_glass_pane", fn() => Blocks::STAINED_HARDENED_GLASS_PANE()); - - $this->mapColored("minecraft:", "_carpet", fn() => Blocks::CARPET()); - $this->mapColored("minecraft:", "_concrete", fn() => Blocks::CONCRETE()); - $this->mapColored("minecraft:", "_concrete_powder", fn() => Blocks::CONCRETE_POWDER()); - $this->mapColored("minecraft:", "_shulker_box", fn() => Blocks::DYED_SHULKER_BOX()); - $this->mapColored("minecraft:", "_stained_glass", fn() => Blocks::STAINED_GLASS()); - $this->mapColored("minecraft:", "_stained_glass_pane", fn() => Blocks::STAINED_GLASS_PANE()); - $this->mapColored("minecraft:", "_terracotta", fn() => Blocks::STAINED_CLAY()); - $this->mapColored("minecraft:", "_wool", fn() => Blocks::WOOL()); - - foreach([ - Ids::BLACK_GLAZED_TERRACOTTA => DyeColor::BLACK, - Ids::BLUE_GLAZED_TERRACOTTA => DyeColor::BLUE, - Ids::BROWN_GLAZED_TERRACOTTA => DyeColor::BROWN, - Ids::CYAN_GLAZED_TERRACOTTA => DyeColor::CYAN, - Ids::GRAY_GLAZED_TERRACOTTA => DyeColor::GRAY, - Ids::GREEN_GLAZED_TERRACOTTA => DyeColor::GREEN, - Ids::LIGHT_BLUE_GLAZED_TERRACOTTA => DyeColor::LIGHT_BLUE, - Ids::SILVER_GLAZED_TERRACOTTA => DyeColor::LIGHT_GRAY, //minecraft sadness - Ids::LIME_GLAZED_TERRACOTTA => DyeColor::LIME, - Ids::MAGENTA_GLAZED_TERRACOTTA => DyeColor::MAGENTA, - Ids::ORANGE_GLAZED_TERRACOTTA => DyeColor::ORANGE, - Ids::PINK_GLAZED_TERRACOTTA => DyeColor::PINK, - Ids::PURPLE_GLAZED_TERRACOTTA => DyeColor::PURPLE, - Ids::RED_GLAZED_TERRACOTTA => DyeColor::RED, - Ids::WHITE_GLAZED_TERRACOTTA => DyeColor::WHITE, - Ids::YELLOW_GLAZED_TERRACOTTA => DyeColor::YELLOW, - ] as $id => $color){ - $this->map($id, fn(Reader $in) => Blocks::GLAZED_TERRACOTTA() - ->setColor($color) - ->setFacing($in->readHorizontalFacing()) - ); - } - } - - private function registerFlatCoralDeserializers() : void{ - foreach([ - Ids::BRAIN_CORAL => CoralType::BRAIN, - Ids::BUBBLE_CORAL => CoralType::BUBBLE, - Ids::FIRE_CORAL => CoralType::FIRE, - Ids::HORN_CORAL => CoralType::HORN, - Ids::TUBE_CORAL => CoralType::TUBE, - ] as $id => $coralType){ - $this->mapSimple($id, fn() => Blocks::CORAL()->setCoralType($coralType)->setDead(false)); - } - foreach([ - Ids::DEAD_BRAIN_CORAL => CoralType::BRAIN, - Ids::DEAD_BUBBLE_CORAL => CoralType::BUBBLE, - Ids::DEAD_FIRE_CORAL => CoralType::FIRE, - Ids::DEAD_HORN_CORAL => CoralType::HORN, - Ids::DEAD_TUBE_CORAL => CoralType::TUBE, - ] as $id => $coralType){ - $this->mapSimple($id, fn() => Blocks::CORAL()->setCoralType($coralType)->setDead(true)); - } - - foreach([ - [CoralType::BRAIN, Ids::BRAIN_CORAL_FAN, Ids::DEAD_BRAIN_CORAL_FAN], - [CoralType::BUBBLE, Ids::BUBBLE_CORAL_FAN, Ids::DEAD_BUBBLE_CORAL_FAN], - [CoralType::FIRE, Ids::FIRE_CORAL_FAN, Ids::DEAD_FIRE_CORAL_FAN], - [CoralType::HORN, Ids::HORN_CORAL_FAN, Ids::DEAD_HORN_CORAL_FAN], - [CoralType::TUBE, Ids::TUBE_CORAL_FAN, Ids::DEAD_TUBE_CORAL_FAN], - ] as [$coralType, $aliveId, $deadId]){ - $this->map($aliveId, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN()->setCoralType($coralType)->setDead(false), $in)); - $this->map($deadId, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN()->setCoralType($coralType)->setDead(true), $in)); - } - - foreach([ - [CoralType::BRAIN, Ids::BRAIN_CORAL_BLOCK, Ids::DEAD_BRAIN_CORAL_BLOCK], - [CoralType::BUBBLE, Ids::BUBBLE_CORAL_BLOCK, Ids::DEAD_BUBBLE_CORAL_BLOCK], - [CoralType::FIRE, Ids::FIRE_CORAL_BLOCK, Ids::DEAD_FIRE_CORAL_BLOCK], - [CoralType::HORN, Ids::HORN_CORAL_BLOCK, Ids::DEAD_HORN_CORAL_BLOCK], - [CoralType::TUBE, Ids::TUBE_CORAL_BLOCK, Ids::DEAD_TUBE_CORAL_BLOCK], - ] as [$coralType, $aliveId, $deadId]){ - $this->map($aliveId, fn(Reader $in) => Blocks::CORAL_BLOCK()->setCoralType($coralType)->setDead(false)); - $this->map($deadId, fn(Reader $in) => Blocks::CORAL_BLOCK()->setCoralType($coralType)->setDead(true)); - } - - foreach([ - [CoralType::BRAIN, Ids::BRAIN_CORAL_WALL_FAN, Ids::DEAD_BRAIN_CORAL_WALL_FAN], - [CoralType::BUBBLE, Ids::BUBBLE_CORAL_WALL_FAN, Ids::DEAD_BUBBLE_CORAL_WALL_FAN], - [CoralType::FIRE, Ids::FIRE_CORAL_WALL_FAN, Ids::DEAD_FIRE_CORAL_WALL_FAN], - [CoralType::HORN, Ids::HORN_CORAL_WALL_FAN, Ids::DEAD_HORN_CORAL_WALL_FAN], - [CoralType::TUBE, Ids::TUBE_CORAL_WALL_FAN, Ids::DEAD_TUBE_CORAL_WALL_FAN], - ] as [$coralType, $aliveId, $deadId]){ - $this->map($aliveId, fn(Reader $in) => Blocks::WALL_CORAL_FAN()->setFacing($in->readCoralFacing())->setCoralType($coralType)->setDead(false)); - $this->map($deadId, fn(Reader $in) => Blocks::WALL_CORAL_FAN()->setFacing($in->readCoralFacing())->setCoralType($coralType)->setDead(true)); - } - } - - private function registerCauldronDeserializers() : void{ - $deserializer = function(Reader $in) : Block{ - $level = $in->readBoundedInt(StateNames::FILL_LEVEL, 0, 6); - if($level === 0){ - $in->ignored(StateNames::CAULDRON_LIQUID); - return Blocks::CAULDRON(); - } - - return (match($liquid = $in->readString(StateNames::CAULDRON_LIQUID)){ - StringValues::CAULDRON_LIQUID_WATER => Blocks::WATER_CAULDRON(), - StringValues::CAULDRON_LIQUID_LAVA => Blocks::LAVA_CAULDRON(), - StringValues::CAULDRON_LIQUID_POWDER_SNOW => throw new UnsupportedBlockStateException("Powder snow is not supported yet"), - default => throw $in->badValueException(StateNames::CAULDRON_LIQUID, $liquid) - })->setFillLevel($level); - }; - $this->map(Ids::CAULDRON, $deserializer); - } - - private function registerFlatWoodBlockDeserializers() : void{ - $this->map(Ids::ACACIA_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::ACACIA_BUTTON(), $in)); - $this->map(Ids::ACACIA_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::ACACIA_DOOR(), $in)); - $this->map(Ids::ACACIA_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::ACACIA_FENCE_GATE(), $in)); - $this->map(Ids::ACACIA_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::ACACIA_PRESSURE_PLATE(), $in)); - $this->map(Ids::ACACIA_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::ACACIA_SIGN(), $in)); - $this->map(Ids::ACACIA_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::ACACIA_TRAPDOOR(), $in)); - $this->map(Ids::ACACIA_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::ACACIA_WALL_SIGN(), $in)); - $this->mapLog(Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG, fn() => Blocks::ACACIA_LOG()); - $this->mapLog(Ids::ACACIA_WOOD, Ids::STRIPPED_ACACIA_WOOD, fn() => Blocks::ACACIA_WOOD()); - $this->mapSimple(Ids::ACACIA_FENCE, fn() => Blocks::ACACIA_FENCE()); - $this->mapSimple(Ids::ACACIA_PLANKS, fn() => Blocks::ACACIA_PLANKS()); - $this->mapSlab(Ids::ACACIA_SLAB, Ids::ACACIA_DOUBLE_SLAB, fn() => Blocks::ACACIA_SLAB()); - $this->mapStairs(Ids::ACACIA_STAIRS, fn() => Blocks::ACACIA_STAIRS()); - - $this->map(Ids::BIRCH_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::BIRCH_BUTTON(), $in)); - $this->map(Ids::BIRCH_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::BIRCH_DOOR(), $in)); - $this->map(Ids::BIRCH_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::BIRCH_FENCE_GATE(), $in)); - $this->map(Ids::BIRCH_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::BIRCH_PRESSURE_PLATE(), $in)); - $this->map(Ids::BIRCH_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::BIRCH_SIGN(), $in)); - $this->map(Ids::BIRCH_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::BIRCH_TRAPDOOR(), $in)); - $this->map(Ids::BIRCH_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::BIRCH_WALL_SIGN(), $in)); - $this->mapLog(Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG, fn() => Blocks::BIRCH_LOG()); - $this->mapLog(Ids::BIRCH_WOOD, Ids::STRIPPED_BIRCH_WOOD, fn() => Blocks::BIRCH_WOOD()); - $this->mapSimple(Ids::BIRCH_FENCE, fn() => Blocks::BIRCH_FENCE()); - $this->mapSimple(Ids::BIRCH_PLANKS, fn() => Blocks::BIRCH_PLANKS()); - $this->mapSlab(Ids::BIRCH_SLAB, Ids::BIRCH_DOUBLE_SLAB, fn() => Blocks::BIRCH_SLAB()); - $this->mapStairs(Ids::BIRCH_STAIRS, fn() => Blocks::BIRCH_STAIRS()); - - $this->map(Ids::CHERRY_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CHERRY_BUTTON(), $in)); - $this->map(Ids::CHERRY_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CHERRY_DOOR(), $in)); - $this->map(Ids::CHERRY_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CHERRY_FENCE_GATE(), $in)); - $this->map(Ids::CHERRY_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CHERRY_PRESSURE_PLATE(), $in)); - $this->map(Ids::CHERRY_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CHERRY_SIGN(), $in)); - $this->map(Ids::CHERRY_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CHERRY_TRAPDOOR(), $in)); - $this->map(Ids::CHERRY_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CHERRY_WALL_SIGN(), $in)); - $this->mapLog(Ids::CHERRY_LOG, Ids::STRIPPED_CHERRY_LOG, fn() => Blocks::CHERRY_LOG()); - $this->mapSimple(Ids::CHERRY_FENCE, fn() => Blocks::CHERRY_FENCE()); - $this->mapSimple(Ids::CHERRY_PLANKS, fn() => Blocks::CHERRY_PLANKS()); - $this->mapSlab(Ids::CHERRY_SLAB, Ids::CHERRY_DOUBLE_SLAB, fn() => Blocks::CHERRY_SLAB()); - $this->mapStairs(Ids::CHERRY_STAIRS, fn() => Blocks::CHERRY_STAIRS()); - $this->map(Ids::CHERRY_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::CHERRY_WOOD(), false, $in)); - $this->map(Ids::STRIPPED_CHERRY_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::CHERRY_WOOD(), true, $in)); - - $this->map(Ids::CRIMSON_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CRIMSON_BUTTON(), $in)); - $this->map(Ids::CRIMSON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CRIMSON_DOOR(), $in)); - $this->map(Ids::CRIMSON_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CRIMSON_FENCE_GATE(), $in)); - $this->map(Ids::CRIMSON_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CRIMSON_PRESSURE_PLATE(), $in)); - $this->map(Ids::CRIMSON_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CRIMSON_SIGN(), $in)); - $this->map(Ids::CRIMSON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CRIMSON_TRAPDOOR(), $in)); - $this->map(Ids::CRIMSON_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CRIMSON_WALL_SIGN(), $in)); - $this->mapLog(Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE, fn() => Blocks::CRIMSON_HYPHAE()); - $this->mapLog(Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM, fn() => Blocks::CRIMSON_STEM()); - $this->mapSimple(Ids::CRIMSON_FENCE, fn() => Blocks::CRIMSON_FENCE()); - $this->mapSimple(Ids::CRIMSON_PLANKS, fn() => Blocks::CRIMSON_PLANKS()); - $this->mapSlab(Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB, fn() => Blocks::CRIMSON_SLAB()); - $this->mapStairs(Ids::CRIMSON_STAIRS, fn() => Blocks::CRIMSON_STAIRS()); - - $this->map(Ids::DARKOAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::DARK_OAK_SIGN(), $in)); - $this->map(Ids::DARKOAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::DARK_OAK_WALL_SIGN(), $in)); - $this->map(Ids::DARK_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::DARK_OAK_BUTTON(), $in)); - $this->map(Ids::DARK_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::DARK_OAK_DOOR(), $in)); - $this->map(Ids::DARK_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::DARK_OAK_FENCE_GATE(), $in)); - $this->map(Ids::DARK_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::DARK_OAK_PRESSURE_PLATE(), $in)); - $this->map(Ids::DARK_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::DARK_OAK_TRAPDOOR(), $in)); - $this->mapLog(Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG, fn() => Blocks::DARK_OAK_LOG()); - $this->mapLog(Ids::DARK_OAK_WOOD, Ids::STRIPPED_DARK_OAK_WOOD, fn() => Blocks::DARK_OAK_WOOD()); - $this->mapSimple(Ids::DARK_OAK_FENCE, fn() => Blocks::DARK_OAK_FENCE()); - $this->mapSimple(Ids::DARK_OAK_PLANKS, fn() => Blocks::DARK_OAK_PLANKS()); - $this->mapSlab(Ids::DARK_OAK_SLAB, Ids::DARK_OAK_DOUBLE_SLAB, fn() => Blocks::DARK_OAK_SLAB()); - $this->mapStairs(Ids::DARK_OAK_STAIRS, fn() => Blocks::DARK_OAK_STAIRS()); - - $this->map(Ids::JUNGLE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::JUNGLE_BUTTON(), $in)); - $this->map(Ids::JUNGLE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::JUNGLE_DOOR(), $in)); - $this->map(Ids::JUNGLE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::JUNGLE_FENCE_GATE(), $in)); - $this->map(Ids::JUNGLE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::JUNGLE_PRESSURE_PLATE(), $in)); - $this->map(Ids::JUNGLE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::JUNGLE_SIGN(), $in)); - $this->map(Ids::JUNGLE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::JUNGLE_TRAPDOOR(), $in)); - $this->map(Ids::JUNGLE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::JUNGLE_WALL_SIGN(), $in)); - $this->mapLog(Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG, fn() => Blocks::JUNGLE_LOG()); - $this->mapLog(Ids::JUNGLE_WOOD, Ids::STRIPPED_JUNGLE_WOOD, fn() => Blocks::JUNGLE_WOOD()); - $this->mapSimple(Ids::JUNGLE_FENCE, fn() => Blocks::JUNGLE_FENCE()); - $this->mapSimple(Ids::JUNGLE_PLANKS, fn() => Blocks::JUNGLE_PLANKS()); - $this->mapSlab(Ids::JUNGLE_SLAB, Ids::JUNGLE_DOUBLE_SLAB, fn() => Blocks::JUNGLE_SLAB()); - $this->mapStairs(Ids::JUNGLE_STAIRS, fn() => Blocks::JUNGLE_STAIRS()); - - $this->map(Ids::MANGROVE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::MANGROVE_BUTTON(), $in)); - $this->map(Ids::MANGROVE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::MANGROVE_DOOR(), $in)); - $this->map(Ids::MANGROVE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::MANGROVE_FENCE_GATE(), $in)); - $this->map(Ids::MANGROVE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::MANGROVE_PRESSURE_PLATE(), $in)); - $this->map(Ids::MANGROVE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::MANGROVE_SIGN(), $in)); - $this->map(Ids::MANGROVE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::MANGROVE_TRAPDOOR(), $in)); - $this->map(Ids::MANGROVE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::MANGROVE_WALL_SIGN(), $in)); - $this->mapLog(Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG, fn() => Blocks::MANGROVE_LOG()); - $this->mapSimple(Ids::MANGROVE_FENCE, fn() => Blocks::MANGROVE_FENCE()); - $this->mapSimple(Ids::MANGROVE_PLANKS, fn() => Blocks::MANGROVE_PLANKS()); - $this->mapSlab(Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB, fn() => Blocks::MANGROVE_SLAB()); - $this->mapStairs(Ids::MANGROVE_STAIRS, fn() => Blocks::MANGROVE_STAIRS()); - $this->map(Ids::MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), false, $in)); - $this->map(Ids::STRIPPED_MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), true, $in)); - - //oak - due to age, many of these don't specify "oak", making for confusing reading - $this->map(Ids::WOODEN_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::OAK_BUTTON(), $in)); - $this->map(Ids::WOODEN_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::OAK_DOOR(), $in)); - $this->map(Ids::FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::OAK_FENCE_GATE(), $in)); - $this->map(Ids::WOODEN_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::OAK_PRESSURE_PLATE(), $in)); - $this->map(Ids::STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::OAK_SIGN(), $in)); - $this->map(Ids::TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::OAK_TRAPDOOR(), $in)); - $this->map(Ids::WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::OAK_WALL_SIGN(), $in)); - $this->mapLog(Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG, fn() => Blocks::OAK_LOG()); - $this->mapLog(Ids::OAK_WOOD, Ids::STRIPPED_OAK_WOOD, fn() => Blocks::OAK_WOOD()); - $this->mapSimple(Ids::OAK_FENCE, fn() => Blocks::OAK_FENCE()); - $this->mapSimple(Ids::OAK_PLANKS, fn() => Blocks::OAK_PLANKS()); - $this->mapSlab(Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB, fn() => Blocks::OAK_SLAB()); - $this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS()); - - $this->map(Ids::PALE_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::PALE_OAK_BUTTON(), $in)); - $this->map(Ids::PALE_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::PALE_OAK_DOOR(), $in)); - $this->map(Ids::PALE_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::PALE_OAK_FENCE_GATE(), $in)); - $this->map(Ids::PALE_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::PALE_OAK_PRESSURE_PLATE(), $in)); - $this->map(Ids::PALE_OAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::PALE_OAK_SIGN(), $in)); - $this->map(Ids::PALE_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::PALE_OAK_TRAPDOOR(), $in)); - $this->map(Ids::PALE_OAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::PALE_OAK_WALL_SIGN(), $in)); - $this->mapLog(Ids::PALE_OAK_LOG, Ids::STRIPPED_PALE_OAK_LOG, fn() => Blocks::PALE_OAK_LOG()); - $this->mapLog(Ids::PALE_OAK_WOOD, Ids::STRIPPED_PALE_OAK_WOOD, fn() => Blocks::PALE_OAK_WOOD()); - $this->mapSimple(Ids::PALE_OAK_FENCE, fn() => Blocks::PALE_OAK_FENCE()); - $this->mapSimple(Ids::PALE_OAK_PLANKS, fn() => Blocks::PALE_OAK_PLANKS()); - $this->mapSlab(Ids::PALE_OAK_SLAB, Ids::PALE_OAK_DOUBLE_SLAB, fn() => Blocks::PALE_OAK_SLAB()); - $this->mapStairs(Ids::PALE_OAK_STAIRS, fn() => Blocks::PALE_OAK_STAIRS()); - - $this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in)); - $this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in)); - $this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in)); - $this->map(Ids::SPRUCE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::SPRUCE_PRESSURE_PLATE(), $in)); - $this->map(Ids::SPRUCE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::SPRUCE_SIGN(), $in)); - $this->map(Ids::SPRUCE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::SPRUCE_TRAPDOOR(), $in)); - $this->map(Ids::SPRUCE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::SPRUCE_WALL_SIGN(), $in)); - $this->mapLog(Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG, fn() => Blocks::SPRUCE_LOG()); - $this->mapLog(Ids::SPRUCE_WOOD, Ids::STRIPPED_SPRUCE_WOOD, fn() => Blocks::SPRUCE_WOOD()); - $this->mapSimple(Ids::SPRUCE_FENCE, fn() => Blocks::SPRUCE_FENCE()); - $this->mapSimple(Ids::SPRUCE_PLANKS, fn() => Blocks::SPRUCE_PLANKS()); - $this->mapSlab(Ids::SPRUCE_SLAB, Ids::SPRUCE_DOUBLE_SLAB, fn() => Blocks::SPRUCE_SLAB()); - $this->mapStairs(Ids::SPRUCE_STAIRS, fn() => Blocks::SPRUCE_STAIRS()); - - $this->map(Ids::WARPED_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::WARPED_BUTTON(), $in)); - $this->map(Ids::WARPED_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::WARPED_DOOR(), $in)); - $this->map(Ids::WARPED_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::WARPED_FENCE_GATE(), $in)); - $this->map(Ids::WARPED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::WARPED_PRESSURE_PLATE(), $in)); - $this->map(Ids::WARPED_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::WARPED_SIGN(), $in)); - $this->map(Ids::WARPED_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::WARPED_TRAPDOOR(), $in)); - $this->map(Ids::WARPED_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::WARPED_WALL_SIGN(), $in)); - $this->mapLog(Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE, fn() => Blocks::WARPED_HYPHAE()); - $this->mapLog(Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM, fn() => Blocks::WARPED_STEM()); - $this->mapSimple(Ids::WARPED_FENCE, fn() => Blocks::WARPED_FENCE()); - $this->mapSimple(Ids::WARPED_PLANKS, fn() => Blocks::WARPED_PLANKS()); - $this->mapSlab(Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB, fn() => Blocks::WARPED_SLAB()); - $this->mapStairs(Ids::WARPED_STAIRS, fn() => Blocks::WARPED_STAIRS()); - } - - private function registerLeavesDeserializers() : void{ - $this->map(Ids::ACACIA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::ACACIA_LEAVES(), $in)); - $this->map(Ids::AZALEA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::AZALEA_LEAVES(), $in)); - $this->map(Ids::AZALEA_LEAVES_FLOWERED, fn(Reader $in) => Helper::decodeLeaves(Blocks::FLOWERING_AZALEA_LEAVES(), $in)); - $this->map(Ids::BIRCH_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::BIRCH_LEAVES(), $in)); - $this->map(Ids::CHERRY_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::CHERRY_LEAVES(), $in)); - $this->map(Ids::DARK_OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::DARK_OAK_LEAVES(), $in)); - $this->map(Ids::JUNGLE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::JUNGLE_LEAVES(), $in)); - $this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in)); - $this->map(Ids::OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::OAK_LEAVES(), $in)); - $this->map(Ids::PALE_OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::PALE_OAK_LEAVES(), $in)); - $this->map(Ids::SPRUCE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::SPRUCE_LEAVES(), $in)); - } - - private function registerSaplingDeserializers() : void{ - foreach([ - Ids::ACACIA_SAPLING => fn() => Blocks::ACACIA_SAPLING(), - Ids::BIRCH_SAPLING => fn() => Blocks::BIRCH_SAPLING(), - Ids::DARK_OAK_SAPLING => fn() => Blocks::DARK_OAK_SAPLING(), - Ids::JUNGLE_SAPLING => fn() => Blocks::JUNGLE_SAPLING(), - Ids::OAK_SAPLING => fn() => Blocks::OAK_SAPLING(), - Ids::SPRUCE_SAPLING => fn() => Blocks::SPRUCE_SAPLING(), - ] as $id => $getBlock){ - $this->map($id, fn(Reader $in) => Helper::decodeSapling($getBlock(), $in)); - } - } - - private function registerLightDeserializers() : void{ - foreach([ - Ids::LIGHT_BLOCK_0 => 0, - Ids::LIGHT_BLOCK_1 => 1, - Ids::LIGHT_BLOCK_2 => 2, - Ids::LIGHT_BLOCK_3 => 3, - Ids::LIGHT_BLOCK_4 => 4, - Ids::LIGHT_BLOCK_5 => 5, - Ids::LIGHT_BLOCK_6 => 6, - Ids::LIGHT_BLOCK_7 => 7, - Ids::LIGHT_BLOCK_8 => 8, - Ids::LIGHT_BLOCK_9 => 9, - Ids::LIGHT_BLOCK_10 => 10, - Ids::LIGHT_BLOCK_11 => 11, - Ids::LIGHT_BLOCK_12 => 12, - Ids::LIGHT_BLOCK_13 => 13, - Ids::LIGHT_BLOCK_14 => 14, - Ids::LIGHT_BLOCK_15 => 15, - ] as $id => $level){ - $this->mapSimple($id, fn() => Blocks::LIGHT()->setLightLevel($level)); - } - } - - private function registerMobHeadDeserializers() : void{ - foreach([ - Ids::CREEPER_HEAD => MobHeadType::CREEPER, - Ids::DRAGON_HEAD => MobHeadType::DRAGON, - Ids::PIGLIN_HEAD => MobHeadType::PIGLIN, - Ids::PLAYER_HEAD => MobHeadType::PLAYER, - Ids::SKELETON_SKULL => MobHeadType::SKELETON, - Ids::WITHER_SKELETON_SKULL => MobHeadType::WITHER_SKELETON, - Ids::ZOMBIE_HEAD => MobHeadType::ZOMBIE - ] as $id => $mobHeadType){ - $this->map($id, fn(Reader $in) => Blocks::MOB_HEAD()->setMobHeadType($mobHeadType)->setFacing($in->readFacingWithoutDown())); - } - } - - /** - * @phpstan-param \Closure(Reader) : (CopperMaterial&Block) $deserializer - */ - private function mapCopper( - string $normalId, - string $waxedNormalId, - string $exposedId, - string $waxedExposedId, - string $weatheredId, - string $waxedWeatheredId, - string $oxidizedId, - string $waxedOxidizedId, - \Closure $deserializer - ) : void{ - foreach(Utils::stringifyKeys([ - $normalId => [CopperOxidation::NONE, false], - $waxedNormalId => [CopperOxidation::NONE, true], - $exposedId => [CopperOxidation::EXPOSED, false], - $waxedExposedId => [CopperOxidation::EXPOSED, true], - $weatheredId => [CopperOxidation::WEATHERED, false], - $waxedWeatheredId => [CopperOxidation::WEATHERED, true], - $oxidizedId => [CopperOxidation::OXIDIZED, false], - $waxedOxidizedId => [CopperOxidation::OXIDIZED, true], - ]) as $id => [$oxidation, $waxed]){ - $this->map($id, fn(Reader $in) => $deserializer($in)->setOxidation($oxidation)->setWaxed($waxed)); - } - } - - private function registerCopperDeserializers() : void{ - $this->mapCopper( - Ids::CUT_COPPER_SLAB, - Ids::WAXED_CUT_COPPER_SLAB, - Ids::EXPOSED_CUT_COPPER_SLAB, - Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, - Ids::WEATHERED_CUT_COPPER_SLAB, - Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, - Ids::OXIDIZED_CUT_COPPER_SLAB, - Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, - fn(Reader $in) => Helper::decodeSingleSlab(Blocks::CUT_COPPER_SLAB(), $in) - ); - $this->mapCopper( - Ids::DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, - Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, - Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, - Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, - Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, - fn(Reader $in) => Helper::decodeDoubleSlab(Blocks::CUT_COPPER_SLAB(), $in) - ); - - $this->mapCopper( - Ids::COPPER_BULB, - Ids::WAXED_COPPER_BULB, - Ids::EXPOSED_COPPER_BULB, - Ids::WAXED_EXPOSED_COPPER_BULB, - Ids::WEATHERED_COPPER_BULB, - Ids::WAXED_WEATHERED_COPPER_BULB, - Ids::OXIDIZED_COPPER_BULB, - Ids::WAXED_OXIDIZED_COPPER_BULB, - fn(Reader $in) => Blocks::COPPER_BULB() - ->setLit($in->readBool(StateNames::LIT)) - ->setPowered($in->readBool(StateNames::POWERED_BIT)) - ); - $this->mapCopper( - Ids::COPPER_DOOR, - Ids::WAXED_COPPER_DOOR, - Ids::EXPOSED_COPPER_DOOR, - Ids::WAXED_EXPOSED_COPPER_DOOR, - Ids::WEATHERED_COPPER_DOOR, - Ids::WAXED_WEATHERED_COPPER_DOOR, - Ids::OXIDIZED_COPPER_DOOR, - Ids::WAXED_OXIDIZED_COPPER_DOOR, - fn(Reader $in) => Helper::decodeDoor(Blocks::COPPER_DOOR(), $in) - ); - $this->mapCopper( - Ids::COPPER_TRAPDOOR, - Ids::WAXED_COPPER_TRAPDOOR, - Ids::EXPOSED_COPPER_TRAPDOOR, - Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, - Ids::WEATHERED_COPPER_TRAPDOOR, - Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, - Ids::OXIDIZED_COPPER_TRAPDOOR, - Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, - fn(Reader $in) => Helper::decodeTrapdoor(Blocks::COPPER_TRAPDOOR(), $in) - ); - $this->mapCopper( - Ids::COPPER_BLOCK, - Ids::WAXED_COPPER, - Ids::EXPOSED_COPPER, - Ids::WAXED_EXPOSED_COPPER, - Ids::WEATHERED_COPPER, - Ids::WAXED_WEATHERED_COPPER, - Ids::OXIDIZED_COPPER, - Ids::WAXED_OXIDIZED_COPPER, - fn(Reader $in) => Blocks::COPPER() - ); - $this->mapCopper( - Ids::CHISELED_COPPER, - Ids::WAXED_CHISELED_COPPER, - Ids::EXPOSED_CHISELED_COPPER, - Ids::WAXED_EXPOSED_CHISELED_COPPER, - Ids::WEATHERED_CHISELED_COPPER, - Ids::WAXED_WEATHERED_CHISELED_COPPER, - Ids::OXIDIZED_CHISELED_COPPER, - Ids::WAXED_OXIDIZED_CHISELED_COPPER, - fn(Reader $in) => Blocks::CHISELED_COPPER() - ); - $this->mapCopper( - Ids::COPPER_GRATE, - Ids::WAXED_COPPER_GRATE, - Ids::EXPOSED_COPPER_GRATE, - Ids::WAXED_EXPOSED_COPPER_GRATE, - Ids::WEATHERED_COPPER_GRATE, - Ids::WAXED_WEATHERED_COPPER_GRATE, - Ids::OXIDIZED_COPPER_GRATE, - Ids::WAXED_OXIDIZED_COPPER_GRATE, - fn(Reader $in) => Blocks::COPPER_GRATE() - ); - $this->mapCopper( - Ids::CUT_COPPER, - Ids::WAXED_CUT_COPPER, - Ids::EXPOSED_CUT_COPPER, - Ids::WAXED_EXPOSED_CUT_COPPER, - Ids::WEATHERED_CUT_COPPER, - Ids::WAXED_WEATHERED_CUT_COPPER, - Ids::OXIDIZED_CUT_COPPER, - Ids::WAXED_OXIDIZED_CUT_COPPER, - fn(Reader $in) => Blocks::CUT_COPPER() - ); - $this->mapCopper( - Ids::CUT_COPPER_STAIRS, - Ids::WAXED_CUT_COPPER_STAIRS, - Ids::EXPOSED_CUT_COPPER_STAIRS, - Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, - Ids::WEATHERED_CUT_COPPER_STAIRS, - Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, - Ids::OXIDIZED_CUT_COPPER_STAIRS, - Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, - fn(Reader $in) => Helper::decodeStairs(Blocks::CUT_COPPER_STAIRS(), $in) - ); - } - - private function registerSimpleDeserializers() : void{ - $this->mapSimple(Ids::AIR, fn() => Blocks::AIR()); - $this->mapSimple(Ids::AMETHYST_BLOCK, fn() => Blocks::AMETHYST()); - $this->mapSimple(Ids::ANCIENT_DEBRIS, fn() => Blocks::ANCIENT_DEBRIS()); - $this->mapSimple(Ids::ANDESITE, fn() => Blocks::ANDESITE()); - $this->mapSimple(Ids::BARRIER, fn() => Blocks::BARRIER()); - $this->mapSimple(Ids::BEACON, fn() => Blocks::BEACON()); - $this->mapSimple(Ids::BLACKSTONE, fn() => Blocks::BLACKSTONE()); - $this->mapSimple(Ids::BLUE_ICE, fn() => Blocks::BLUE_ICE()); - $this->mapSimple(Ids::BOOKSHELF, fn() => Blocks::BOOKSHELF()); - $this->mapSimple(Ids::BRICK_BLOCK, fn() => Blocks::BRICKS()); - $this->mapSimple(Ids::BROWN_MUSHROOM, fn() => Blocks::BROWN_MUSHROOM()); - $this->mapSimple(Ids::BUDDING_AMETHYST, fn() => Blocks::BUDDING_AMETHYST()); - $this->mapSimple(Ids::CALCITE, fn() => Blocks::CALCITE()); - $this->mapSimple(Ids::CARTOGRAPHY_TABLE, fn() => Blocks::CARTOGRAPHY_TABLE()); - $this->mapSimple(Ids::CHEMICAL_HEAT, fn() => Blocks::CHEMICAL_HEAT()); - $this->mapSimple(Ids::CHISELED_DEEPSLATE, fn() => Blocks::CHISELED_DEEPSLATE()); - $this->mapSimple(Ids::CHISELED_NETHER_BRICKS, fn() => Blocks::CHISELED_NETHER_BRICKS()); - $this->mapSimple(Ids::CHISELED_POLISHED_BLACKSTONE, fn() => Blocks::CHISELED_POLISHED_BLACKSTONE()); - $this->mapSimple(Ids::CHISELED_RED_SANDSTONE, fn() => Blocks::CHISELED_RED_SANDSTONE()); - $this->mapSimple(Ids::CHISELED_RESIN_BRICKS, fn() => Blocks::CHISELED_RESIN_BRICKS()); - $this->mapSimple(Ids::CHISELED_SANDSTONE, fn() => Blocks::CHISELED_SANDSTONE()); - $this->mapSimple(Ids::CHISELED_STONE_BRICKS, fn() => Blocks::CHISELED_STONE_BRICKS()); - $this->mapSimple(Ids::CHISELED_TUFF, fn() => Blocks::CHISELED_TUFF()); - $this->mapSimple(Ids::CHISELED_TUFF_BRICKS, fn() => Blocks::CHISELED_TUFF_BRICKS()); - $this->mapSimple(Ids::CHORUS_PLANT, fn() => Blocks::CHORUS_PLANT()); - $this->mapSimple(Ids::CLAY, fn() => Blocks::CLAY()); - $this->mapSimple(Ids::COAL_BLOCK, fn() => Blocks::COAL()); - $this->mapSimple(Ids::COAL_ORE, fn() => Blocks::COAL_ORE()); - $this->mapSimple(Ids::COBBLED_DEEPSLATE, fn() => Blocks::COBBLED_DEEPSLATE()); - $this->mapSimple(Ids::COBBLESTONE, fn() => Blocks::COBBLESTONE()); - $this->mapSimple(Ids::COPPER_ORE, fn() => Blocks::COPPER_ORE()); - $this->mapSimple(Ids::CRACKED_DEEPSLATE_BRICKS, fn() => Blocks::CRACKED_DEEPSLATE_BRICKS()); - $this->mapSimple(Ids::CRACKED_DEEPSLATE_TILES, fn() => Blocks::CRACKED_DEEPSLATE_TILES()); - $this->mapSimple(Ids::CRACKED_NETHER_BRICKS, fn() => Blocks::CRACKED_NETHER_BRICKS()); - $this->mapSimple(Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS, fn() => Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS()); - $this->mapSimple(Ids::CRACKED_STONE_BRICKS, fn() => Blocks::CRACKED_STONE_BRICKS()); - $this->mapSimple(Ids::CRAFTING_TABLE, fn() => Blocks::CRAFTING_TABLE()); - $this->mapSimple(Ids::CRIMSON_ROOTS, fn() => Blocks::CRIMSON_ROOTS()); - $this->mapSimple(Ids::CRYING_OBSIDIAN, fn() => Blocks::CRYING_OBSIDIAN()); - $this->mapSimple(Ids::CUT_RED_SANDSTONE, fn() => Blocks::CUT_RED_SANDSTONE()); - $this->mapSimple(Ids::CUT_SANDSTONE, fn() => Blocks::CUT_SANDSTONE()); - $this->mapSimple(Ids::DARK_PRISMARINE, fn() => Blocks::DARK_PRISMARINE()); - $this->mapSimple(Ids::DEADBUSH, fn() => Blocks::DEAD_BUSH()); - $this->mapSimple(Ids::DEEPSLATE_BRICKS, fn() => Blocks::DEEPSLATE_BRICKS()); - $this->mapSimple(Ids::DEEPSLATE_COAL_ORE, fn() => Blocks::DEEPSLATE_COAL_ORE()); - $this->mapSimple(Ids::DEEPSLATE_COPPER_ORE, fn() => Blocks::DEEPSLATE_COPPER_ORE()); - $this->mapSimple(Ids::DEEPSLATE_DIAMOND_ORE, fn() => Blocks::DEEPSLATE_DIAMOND_ORE()); - $this->mapSimple(Ids::DEEPSLATE_EMERALD_ORE, fn() => Blocks::DEEPSLATE_EMERALD_ORE()); - $this->mapSimple(Ids::DEEPSLATE_GOLD_ORE, fn() => Blocks::DEEPSLATE_GOLD_ORE()); - $this->mapSimple(Ids::DEEPSLATE_IRON_ORE, fn() => Blocks::DEEPSLATE_IRON_ORE()); - $this->mapSimple(Ids::DEEPSLATE_LAPIS_ORE, fn() => Blocks::DEEPSLATE_LAPIS_LAZULI_ORE()); - $this->mapSimple(Ids::DEEPSLATE_TILES, fn() => Blocks::DEEPSLATE_TILES()); - $this->mapSimple(Ids::DIAMOND_BLOCK, fn() => Blocks::DIAMOND()); - $this->mapSimple(Ids::DIAMOND_ORE, fn() => Blocks::DIAMOND_ORE()); - $this->mapSimple(Ids::DIORITE, fn() => Blocks::DIORITE()); - $this->mapSimple(Ids::DRAGON_EGG, fn() => Blocks::DRAGON_EGG()); - $this->mapSimple(Ids::DRIED_KELP_BLOCK, fn() => Blocks::DRIED_KELP()); - $this->mapSimple(Ids::ELEMENT_0, fn() => Blocks::ELEMENT_ZERO()); - $this->mapSimple(Ids::ELEMENT_1, fn() => Blocks::ELEMENT_HYDROGEN()); - $this->mapSimple(Ids::ELEMENT_10, fn() => Blocks::ELEMENT_NEON()); - $this->mapSimple(Ids::ELEMENT_100, fn() => Blocks::ELEMENT_FERMIUM()); - $this->mapSimple(Ids::ELEMENT_101, fn() => Blocks::ELEMENT_MENDELEVIUM()); - $this->mapSimple(Ids::ELEMENT_102, fn() => Blocks::ELEMENT_NOBELIUM()); - $this->mapSimple(Ids::ELEMENT_103, fn() => Blocks::ELEMENT_LAWRENCIUM()); - $this->mapSimple(Ids::ELEMENT_104, fn() => Blocks::ELEMENT_RUTHERFORDIUM()); - $this->mapSimple(Ids::ELEMENT_105, fn() => Blocks::ELEMENT_DUBNIUM()); - $this->mapSimple(Ids::ELEMENT_106, fn() => Blocks::ELEMENT_SEABORGIUM()); - $this->mapSimple(Ids::ELEMENT_107, fn() => Blocks::ELEMENT_BOHRIUM()); - $this->mapSimple(Ids::ELEMENT_108, fn() => Blocks::ELEMENT_HASSIUM()); - $this->mapSimple(Ids::ELEMENT_109, fn() => Blocks::ELEMENT_MEITNERIUM()); - $this->mapSimple(Ids::ELEMENT_11, fn() => Blocks::ELEMENT_SODIUM()); - $this->mapSimple(Ids::ELEMENT_110, fn() => Blocks::ELEMENT_DARMSTADTIUM()); - $this->mapSimple(Ids::ELEMENT_111, fn() => Blocks::ELEMENT_ROENTGENIUM()); - $this->mapSimple(Ids::ELEMENT_112, fn() => Blocks::ELEMENT_COPERNICIUM()); - $this->mapSimple(Ids::ELEMENT_113, fn() => Blocks::ELEMENT_NIHONIUM()); - $this->mapSimple(Ids::ELEMENT_114, fn() => Blocks::ELEMENT_FLEROVIUM()); - $this->mapSimple(Ids::ELEMENT_115, fn() => Blocks::ELEMENT_MOSCOVIUM()); - $this->mapSimple(Ids::ELEMENT_116, fn() => Blocks::ELEMENT_LIVERMORIUM()); - $this->mapSimple(Ids::ELEMENT_117, fn() => Blocks::ELEMENT_TENNESSINE()); - $this->mapSimple(Ids::ELEMENT_118, fn() => Blocks::ELEMENT_OGANESSON()); - $this->mapSimple(Ids::ELEMENT_12, fn() => Blocks::ELEMENT_MAGNESIUM()); - $this->mapSimple(Ids::ELEMENT_13, fn() => Blocks::ELEMENT_ALUMINUM()); - $this->mapSimple(Ids::ELEMENT_14, fn() => Blocks::ELEMENT_SILICON()); - $this->mapSimple(Ids::ELEMENT_15, fn() => Blocks::ELEMENT_PHOSPHORUS()); - $this->mapSimple(Ids::ELEMENT_16, fn() => Blocks::ELEMENT_SULFUR()); - $this->mapSimple(Ids::ELEMENT_17, fn() => Blocks::ELEMENT_CHLORINE()); - $this->mapSimple(Ids::ELEMENT_18, fn() => Blocks::ELEMENT_ARGON()); - $this->mapSimple(Ids::ELEMENT_19, fn() => Blocks::ELEMENT_POTASSIUM()); - $this->mapSimple(Ids::ELEMENT_2, fn() => Blocks::ELEMENT_HELIUM()); - $this->mapSimple(Ids::ELEMENT_20, fn() => Blocks::ELEMENT_CALCIUM()); - $this->mapSimple(Ids::ELEMENT_21, fn() => Blocks::ELEMENT_SCANDIUM()); - $this->mapSimple(Ids::ELEMENT_22, fn() => Blocks::ELEMENT_TITANIUM()); - $this->mapSimple(Ids::ELEMENT_23, fn() => Blocks::ELEMENT_VANADIUM()); - $this->mapSimple(Ids::ELEMENT_24, fn() => Blocks::ELEMENT_CHROMIUM()); - $this->mapSimple(Ids::ELEMENT_25, fn() => Blocks::ELEMENT_MANGANESE()); - $this->mapSimple(Ids::ELEMENT_26, fn() => Blocks::ELEMENT_IRON()); - $this->mapSimple(Ids::ELEMENT_27, fn() => Blocks::ELEMENT_COBALT()); - $this->mapSimple(Ids::ELEMENT_28, fn() => Blocks::ELEMENT_NICKEL()); - $this->mapSimple(Ids::ELEMENT_29, fn() => Blocks::ELEMENT_COPPER()); - $this->mapSimple(Ids::ELEMENT_3, fn() => Blocks::ELEMENT_LITHIUM()); - $this->mapSimple(Ids::ELEMENT_30, fn() => Blocks::ELEMENT_ZINC()); - $this->mapSimple(Ids::ELEMENT_31, fn() => Blocks::ELEMENT_GALLIUM()); - $this->mapSimple(Ids::ELEMENT_32, fn() => Blocks::ELEMENT_GERMANIUM()); - $this->mapSimple(Ids::ELEMENT_33, fn() => Blocks::ELEMENT_ARSENIC()); - $this->mapSimple(Ids::ELEMENT_34, fn() => Blocks::ELEMENT_SELENIUM()); - $this->mapSimple(Ids::ELEMENT_35, fn() => Blocks::ELEMENT_BROMINE()); - $this->mapSimple(Ids::ELEMENT_36, fn() => Blocks::ELEMENT_KRYPTON()); - $this->mapSimple(Ids::ELEMENT_37, fn() => Blocks::ELEMENT_RUBIDIUM()); - $this->mapSimple(Ids::ELEMENT_38, fn() => Blocks::ELEMENT_STRONTIUM()); - $this->mapSimple(Ids::ELEMENT_39, fn() => Blocks::ELEMENT_YTTRIUM()); - $this->mapSimple(Ids::ELEMENT_4, fn() => Blocks::ELEMENT_BERYLLIUM()); - $this->mapSimple(Ids::ELEMENT_40, fn() => Blocks::ELEMENT_ZIRCONIUM()); - $this->mapSimple(Ids::ELEMENT_41, fn() => Blocks::ELEMENT_NIOBIUM()); - $this->mapSimple(Ids::ELEMENT_42, fn() => Blocks::ELEMENT_MOLYBDENUM()); - $this->mapSimple(Ids::ELEMENT_43, fn() => Blocks::ELEMENT_TECHNETIUM()); - $this->mapSimple(Ids::ELEMENT_44, fn() => Blocks::ELEMENT_RUTHENIUM()); - $this->mapSimple(Ids::ELEMENT_45, fn() => Blocks::ELEMENT_RHODIUM()); - $this->mapSimple(Ids::ELEMENT_46, fn() => Blocks::ELEMENT_PALLADIUM()); - $this->mapSimple(Ids::ELEMENT_47, fn() => Blocks::ELEMENT_SILVER()); - $this->mapSimple(Ids::ELEMENT_48, fn() => Blocks::ELEMENT_CADMIUM()); - $this->mapSimple(Ids::ELEMENT_49, fn() => Blocks::ELEMENT_INDIUM()); - $this->mapSimple(Ids::ELEMENT_5, fn() => Blocks::ELEMENT_BORON()); - $this->mapSimple(Ids::ELEMENT_50, fn() => Blocks::ELEMENT_TIN()); - $this->mapSimple(Ids::ELEMENT_51, fn() => Blocks::ELEMENT_ANTIMONY()); - $this->mapSimple(Ids::ELEMENT_52, fn() => Blocks::ELEMENT_TELLURIUM()); - $this->mapSimple(Ids::ELEMENT_53, fn() => Blocks::ELEMENT_IODINE()); - $this->mapSimple(Ids::ELEMENT_54, fn() => Blocks::ELEMENT_XENON()); - $this->mapSimple(Ids::ELEMENT_55, fn() => Blocks::ELEMENT_CESIUM()); - $this->mapSimple(Ids::ELEMENT_56, fn() => Blocks::ELEMENT_BARIUM()); - $this->mapSimple(Ids::ELEMENT_57, fn() => Blocks::ELEMENT_LANTHANUM()); - $this->mapSimple(Ids::ELEMENT_58, fn() => Blocks::ELEMENT_CERIUM()); - $this->mapSimple(Ids::ELEMENT_59, fn() => Blocks::ELEMENT_PRASEODYMIUM()); - $this->mapSimple(Ids::ELEMENT_6, fn() => Blocks::ELEMENT_CARBON()); - $this->mapSimple(Ids::ELEMENT_60, fn() => Blocks::ELEMENT_NEODYMIUM()); - $this->mapSimple(Ids::ELEMENT_61, fn() => Blocks::ELEMENT_PROMETHIUM()); - $this->mapSimple(Ids::ELEMENT_62, fn() => Blocks::ELEMENT_SAMARIUM()); - $this->mapSimple(Ids::ELEMENT_63, fn() => Blocks::ELEMENT_EUROPIUM()); - $this->mapSimple(Ids::ELEMENT_64, fn() => Blocks::ELEMENT_GADOLINIUM()); - $this->mapSimple(Ids::ELEMENT_65, fn() => Blocks::ELEMENT_TERBIUM()); - $this->mapSimple(Ids::ELEMENT_66, fn() => Blocks::ELEMENT_DYSPROSIUM()); - $this->mapSimple(Ids::ELEMENT_67, fn() => Blocks::ELEMENT_HOLMIUM()); - $this->mapSimple(Ids::ELEMENT_68, fn() => Blocks::ELEMENT_ERBIUM()); - $this->mapSimple(Ids::ELEMENT_69, fn() => Blocks::ELEMENT_THULIUM()); - $this->mapSimple(Ids::ELEMENT_7, fn() => Blocks::ELEMENT_NITROGEN()); - $this->mapSimple(Ids::ELEMENT_70, fn() => Blocks::ELEMENT_YTTERBIUM()); - $this->mapSimple(Ids::ELEMENT_71, fn() => Blocks::ELEMENT_LUTETIUM()); - $this->mapSimple(Ids::ELEMENT_72, fn() => Blocks::ELEMENT_HAFNIUM()); - $this->mapSimple(Ids::ELEMENT_73, fn() => Blocks::ELEMENT_TANTALUM()); - $this->mapSimple(Ids::ELEMENT_74, fn() => Blocks::ELEMENT_TUNGSTEN()); - $this->mapSimple(Ids::ELEMENT_75, fn() => Blocks::ELEMENT_RHENIUM()); - $this->mapSimple(Ids::ELEMENT_76, fn() => Blocks::ELEMENT_OSMIUM()); - $this->mapSimple(Ids::ELEMENT_77, fn() => Blocks::ELEMENT_IRIDIUM()); - $this->mapSimple(Ids::ELEMENT_78, fn() => Blocks::ELEMENT_PLATINUM()); - $this->mapSimple(Ids::ELEMENT_79, fn() => Blocks::ELEMENT_GOLD()); - $this->mapSimple(Ids::ELEMENT_8, fn() => Blocks::ELEMENT_OXYGEN()); - $this->mapSimple(Ids::ELEMENT_80, fn() => Blocks::ELEMENT_MERCURY()); - $this->mapSimple(Ids::ELEMENT_81, fn() => Blocks::ELEMENT_THALLIUM()); - $this->mapSimple(Ids::ELEMENT_82, fn() => Blocks::ELEMENT_LEAD()); - $this->mapSimple(Ids::ELEMENT_83, fn() => Blocks::ELEMENT_BISMUTH()); - $this->mapSimple(Ids::ELEMENT_84, fn() => Blocks::ELEMENT_POLONIUM()); - $this->mapSimple(Ids::ELEMENT_85, fn() => Blocks::ELEMENT_ASTATINE()); - $this->mapSimple(Ids::ELEMENT_86, fn() => Blocks::ELEMENT_RADON()); - $this->mapSimple(Ids::ELEMENT_87, fn() => Blocks::ELEMENT_FRANCIUM()); - $this->mapSimple(Ids::ELEMENT_88, fn() => Blocks::ELEMENT_RADIUM()); - $this->mapSimple(Ids::ELEMENT_89, fn() => Blocks::ELEMENT_ACTINIUM()); - $this->mapSimple(Ids::ELEMENT_9, fn() => Blocks::ELEMENT_FLUORINE()); - $this->mapSimple(Ids::ELEMENT_90, fn() => Blocks::ELEMENT_THORIUM()); - $this->mapSimple(Ids::ELEMENT_91, fn() => Blocks::ELEMENT_PROTACTINIUM()); - $this->mapSimple(Ids::ELEMENT_92, fn() => Blocks::ELEMENT_URANIUM()); - $this->mapSimple(Ids::ELEMENT_93, fn() => Blocks::ELEMENT_NEPTUNIUM()); - $this->mapSimple(Ids::ELEMENT_94, fn() => Blocks::ELEMENT_PLUTONIUM()); - $this->mapSimple(Ids::ELEMENT_95, fn() => Blocks::ELEMENT_AMERICIUM()); - $this->mapSimple(Ids::ELEMENT_96, fn() => Blocks::ELEMENT_CURIUM()); - $this->mapSimple(Ids::ELEMENT_97, fn() => Blocks::ELEMENT_BERKELIUM()); - $this->mapSimple(Ids::ELEMENT_98, fn() => Blocks::ELEMENT_CALIFORNIUM()); - $this->mapSimple(Ids::ELEMENT_99, fn() => Blocks::ELEMENT_EINSTEINIUM()); - $this->mapSimple(Ids::EMERALD_BLOCK, fn() => Blocks::EMERALD()); - $this->mapSimple(Ids::EMERALD_ORE, fn() => Blocks::EMERALD_ORE()); - $this->mapSimple(Ids::ENCHANTING_TABLE, fn() => Blocks::ENCHANTING_TABLE()); - $this->mapSimple(Ids::END_BRICKS, fn() => Blocks::END_STONE_BRICKS()); - $this->mapSimple(Ids::END_STONE, fn() => Blocks::END_STONE()); - $this->mapSimple(Ids::FERN, fn() => Blocks::FERN()); - $this->mapSimple(Ids::FLETCHING_TABLE, fn() => Blocks::FLETCHING_TABLE()); - $this->mapSimple(Ids::GILDED_BLACKSTONE, fn() => Blocks::GILDED_BLACKSTONE()); - $this->mapSimple(Ids::GLASS, fn() => Blocks::GLASS()); - $this->mapSimple(Ids::GLASS_PANE, fn() => Blocks::GLASS_PANE()); - $this->mapSimple(Ids::GLOWINGOBSIDIAN, fn() => Blocks::GLOWING_OBSIDIAN()); - $this->mapSimple(Ids::GLOWSTONE, fn() => Blocks::GLOWSTONE()); - $this->mapSimple(Ids::GOLD_BLOCK, fn() => Blocks::GOLD()); - $this->mapSimple(Ids::GOLD_ORE, fn() => Blocks::GOLD_ORE()); - $this->mapSimple(Ids::GRANITE, fn() => Blocks::GRANITE()); - $this->mapSimple(Ids::GRASS_BLOCK, fn() => Blocks::GRASS()); - $this->mapSimple(Ids::GRASS_PATH, fn() => Blocks::GRASS_PATH()); - $this->mapSimple(Ids::GRAVEL, fn() => Blocks::GRAVEL()); - $this->mapSimple(Ids::HANGING_ROOTS, fn() => Blocks::HANGING_ROOTS()); - $this->mapSimple(Ids::HARD_GLASS, fn() => Blocks::HARDENED_GLASS()); - $this->mapSimple(Ids::HARD_GLASS_PANE, fn() => Blocks::HARDENED_GLASS_PANE()); - $this->mapSimple(Ids::HARDENED_CLAY, fn() => Blocks::HARDENED_CLAY()); - $this->mapSimple(Ids::HONEYCOMB_BLOCK, fn() => Blocks::HONEYCOMB()); - $this->mapSimple(Ids::ICE, fn() => Blocks::ICE()); - $this->mapSimple(Ids::INFESTED_CHISELED_STONE_BRICKS, fn() => Blocks::INFESTED_CHISELED_STONE_BRICK()); - $this->mapSimple(Ids::INFESTED_COBBLESTONE, fn() => Blocks::INFESTED_COBBLESTONE()); - $this->mapSimple(Ids::INFESTED_CRACKED_STONE_BRICKS, fn() => Blocks::INFESTED_CRACKED_STONE_BRICK()); - $this->mapSimple(Ids::INFESTED_MOSSY_STONE_BRICKS, fn() => Blocks::INFESTED_MOSSY_STONE_BRICK()); - $this->mapSimple(Ids::INFESTED_STONE, fn() => Blocks::INFESTED_STONE()); - $this->mapSimple(Ids::INFESTED_STONE_BRICKS, fn() => Blocks::INFESTED_STONE_BRICK()); - $this->mapSimple(Ids::INFO_UPDATE, fn() => Blocks::INFO_UPDATE()); - $this->mapSimple(Ids::INFO_UPDATE2, fn() => Blocks::INFO_UPDATE2()); - $this->mapSimple(Ids::INVISIBLE_BEDROCK, fn() => Blocks::INVISIBLE_BEDROCK()); - $this->mapSimple(Ids::IRON_BARS, fn() => Blocks::IRON_BARS()); - $this->mapSimple(Ids::IRON_BLOCK, fn() => Blocks::IRON()); - $this->mapSimple(Ids::IRON_ORE, fn() => Blocks::IRON_ORE()); - $this->mapSimple(Ids::JUKEBOX, fn() => Blocks::JUKEBOX()); - $this->mapSimple(Ids::LAPIS_BLOCK, fn() => Blocks::LAPIS_LAZULI()); - $this->mapSimple(Ids::LAPIS_ORE, fn() => Blocks::LAPIS_LAZULI_ORE()); - $this->mapSimple(Ids::MAGMA, fn() => Blocks::MAGMA()); - $this->mapSimple(Ids::MANGROVE_ROOTS, fn() => Blocks::MANGROVE_ROOTS()); - $this->mapSimple(Ids::MELON_BLOCK, fn() => Blocks::MELON()); - $this->mapSimple(Ids::MOB_SPAWNER, fn() => Blocks::MONSTER_SPAWNER()); - $this->mapSimple(Ids::MOSSY_COBBLESTONE, fn() => Blocks::MOSSY_COBBLESTONE()); - $this->mapSimple(Ids::MOSSY_STONE_BRICKS, fn() => Blocks::MOSSY_STONE_BRICKS()); - $this->mapSimple(Ids::MUD, fn() => Blocks::MUD()); - $this->mapSimple(Ids::MUD_BRICKS, fn() => Blocks::MUD_BRICKS()); - $this->mapSimple(Ids::MYCELIUM, fn() => Blocks::MYCELIUM()); - $this->mapSimple(Ids::NETHER_BRICK, fn() => Blocks::NETHER_BRICKS()); - $this->mapSimple(Ids::NETHER_BRICK_FENCE, fn() => Blocks::NETHER_BRICK_FENCE()); - $this->mapSimple(Ids::NETHER_GOLD_ORE, fn() => Blocks::NETHER_GOLD_ORE()); - $this->mapSimple(Ids::NETHER_WART_BLOCK, fn() => Blocks::NETHER_WART_BLOCK()); - $this->mapSimple(Ids::NETHERITE_BLOCK, fn() => Blocks::NETHERITE()); - $this->mapSimple(Ids::NETHERRACK, fn() => Blocks::NETHERRACK()); - $this->mapSimple(Ids::NETHERREACTOR, fn() => Blocks::NETHER_REACTOR_CORE()); - $this->mapSimple(Ids::NOTEBLOCK, fn() => Blocks::NOTE_BLOCK()); - $this->mapSimple(Ids::OBSIDIAN, fn() => Blocks::OBSIDIAN()); - $this->mapSimple(Ids::PACKED_ICE, fn() => Blocks::PACKED_ICE()); - $this->mapSimple(Ids::PACKED_MUD, fn() => Blocks::PACKED_MUD()); - $this->mapSimple(Ids::PODZOL, fn() => Blocks::PODZOL()); - $this->mapSimple(Ids::POLISHED_ANDESITE, fn() => Blocks::POLISHED_ANDESITE()); - $this->mapSimple(Ids::POLISHED_BLACKSTONE, fn() => Blocks::POLISHED_BLACKSTONE()); - $this->mapSimple(Ids::POLISHED_BLACKSTONE_BRICKS, fn() => Blocks::POLISHED_BLACKSTONE_BRICKS()); - $this->mapSimple(Ids::POLISHED_DEEPSLATE, fn() => Blocks::POLISHED_DEEPSLATE()); - $this->mapSimple(Ids::POLISHED_DIORITE, fn() => Blocks::POLISHED_DIORITE()); - $this->mapSimple(Ids::POLISHED_GRANITE, fn() => Blocks::POLISHED_GRANITE()); - $this->mapSimple(Ids::POLISHED_TUFF, fn() => Blocks::POLISHED_TUFF()); - $this->mapSimple(Ids::PRISMARINE, fn() => Blocks::PRISMARINE()); - $this->mapSimple(Ids::PRISMARINE_BRICKS, fn() => Blocks::PRISMARINE_BRICKS()); - $this->mapSimple(Ids::QUARTZ_BRICKS, fn() => Blocks::QUARTZ_BRICKS()); - $this->mapSimple(Ids::QUARTZ_ORE, fn() => Blocks::NETHER_QUARTZ_ORE()); - $this->mapSimple(Ids::RAW_COPPER_BLOCK, fn() => Blocks::RAW_COPPER()); - $this->mapSimple(Ids::RAW_GOLD_BLOCK, fn() => Blocks::RAW_GOLD()); - $this->mapSimple(Ids::RAW_IRON_BLOCK, fn() => Blocks::RAW_IRON()); - $this->mapSimple(Ids::RED_MUSHROOM, fn() => Blocks::RED_MUSHROOM()); - $this->mapSimple(Ids::RED_NETHER_BRICK, fn() => Blocks::RED_NETHER_BRICKS()); - $this->mapSimple(Ids::RED_SAND, fn() => Blocks::RED_SAND()); - $this->mapSimple(Ids::RED_SANDSTONE, fn() => Blocks::RED_SANDSTONE()); - $this->mapSimple(Ids::REDSTONE_BLOCK, fn() => Blocks::REDSTONE()); - $this->mapSimple(Ids::REINFORCED_DEEPSLATE, fn() => Blocks::REINFORCED_DEEPSLATE()); - $this->mapSimple(Ids::RESERVED6, fn() => Blocks::RESERVED6()); - $this->mapSimple(Ids::RESIN_BLOCK, fn() => Blocks::RESIN()); - $this->mapSimple(Ids::RESIN_BRICKS, fn() => Blocks::RESIN_BRICKS()); - $this->mapSimple(Ids::SAND, fn() => Blocks::SAND()); - $this->mapSimple(Ids::SANDSTONE, fn() => Blocks::SANDSTONE()); - $this->mapSimple(Ids::SCULK, fn() => Blocks::SCULK()); - $this->mapSimple(Ids::SEA_LANTERN, fn() => Blocks::SEA_LANTERN()); - $this->mapSimple(Ids::SHORT_GRASS, fn() => Blocks::TALL_GRASS()); //no, this is not a typo - tall_grass is now the double block, just to be confusing :( - $this->mapSimple(Ids::SHROOMLIGHT, fn() => Blocks::SHROOMLIGHT()); - $this->mapSimple(Ids::SLIME, fn() => Blocks::SLIME()); - $this->mapSimple(Ids::SMITHING_TABLE, fn() => Blocks::SMITHING_TABLE()); - $this->mapSimple(Ids::SMOOTH_BASALT, fn() => Blocks::SMOOTH_BASALT()); - $this->mapSimple(Ids::SMOOTH_RED_SANDSTONE, fn() => Blocks::SMOOTH_RED_SANDSTONE()); - $this->mapSimple(Ids::SMOOTH_SANDSTONE, fn() => Blocks::SMOOTH_SANDSTONE()); - $this->mapSimple(Ids::SMOOTH_STONE, fn() => Blocks::SMOOTH_STONE()); - $this->mapSimple(Ids::SNOW, fn() => Blocks::SNOW()); - $this->mapSimple(Ids::SOUL_SAND, fn() => Blocks::SOUL_SAND()); - $this->mapSimple(Ids::SOUL_SOIL, fn() => Blocks::SOUL_SOIL()); - $this->mapSimple(Ids::SPORE_BLOSSOM, fn() => Blocks::SPORE_BLOSSOM()); - $this->mapSimple(Ids::SPONGE, fn() => Blocks::SPONGE()); - $this->mapSimple(Ids::STONE, fn() => Blocks::STONE()); - $this->mapSimple(Ids::STONECUTTER, fn() => Blocks::LEGACY_STONECUTTER()); - $this->mapSimple(Ids::STONE_BRICKS, fn() => Blocks::STONE_BRICKS()); - $this->mapSimple(Ids::TINTED_GLASS, fn() => Blocks::TINTED_GLASS()); - $this->mapSimple(Ids::TORCHFLOWER, fn() => Blocks::TORCHFLOWER()); - $this->mapSimple(Ids::TUFF, fn() => Blocks::TUFF()); - $this->mapSimple(Ids::TUFF_BRICKS, fn() => Blocks::TUFF_BRICKS()); - $this->mapSimple(Ids::UNDYED_SHULKER_BOX, fn() => Blocks::SHULKER_BOX()); - $this->mapSimple(Ids::WARPED_WART_BLOCK, fn() => Blocks::WARPED_WART_BLOCK()); - $this->mapSimple(Ids::WARPED_ROOTS, fn() => Blocks::WARPED_ROOTS()); - $this->mapSimple(Ids::WATERLILY, fn() => Blocks::LILY_PAD()); - $this->mapSimple(Ids::WEB, fn() => Blocks::COBWEB()); - $this->mapSimple(Ids::WET_SPONGE, fn() => Blocks::SPONGE()->setWet(true)); - $this->mapSimple(Ids::WITHER_ROSE, fn() => Blocks::WITHER_ROSE()); - $this->mapSimple(Ids::DANDELION, fn() => Blocks::DANDELION()); - - $this->mapSimple(Ids::ALLIUM, fn() => Blocks::ALLIUM()); - $this->mapSimple(Ids::CORNFLOWER, fn() => Blocks::CORNFLOWER()); - $this->mapSimple(Ids::AZURE_BLUET, fn() => Blocks::AZURE_BLUET()); - $this->mapSimple(Ids::LILY_OF_THE_VALLEY, fn() => Blocks::LILY_OF_THE_VALLEY()); - $this->mapSimple(Ids::BLUE_ORCHID, fn() => Blocks::BLUE_ORCHID()); - $this->mapSimple(Ids::OXEYE_DAISY, fn() => Blocks::OXEYE_DAISY()); - $this->mapSimple(Ids::POPPY, fn() => Blocks::POPPY()); - $this->mapSimple(Ids::ORANGE_TULIP, fn() => Blocks::ORANGE_TULIP()); - $this->mapSimple(Ids::PINK_TULIP, fn() => Blocks::PINK_TULIP()); - $this->mapSimple(Ids::RED_TULIP, fn() => Blocks::RED_TULIP()); - $this->mapSimple(Ids::WHITE_TULIP, fn() => Blocks::WHITE_TULIP()); - } - - private function registerDeserializers() : void{ - $this->map(Ids::ACTIVATOR_RAIL, function(Reader $in) : Block{ - return Blocks::ACTIVATOR_RAIL() - ->setPowered($in->readBool(StateNames::RAIL_DATA_BIT)) - ->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 5)); - }); - $this->map(Ids::AMETHYST_CLUSTER, function(Reader $in) : Block{ - return Blocks::AMETHYST_CLUSTER() - ->setStage(AmethystCluster::STAGE_CLUSTER) - ->setFacing($in->readBlockFace()); - }); - $this->mapSlab(Ids::ANDESITE_SLAB, Ids::ANDESITE_DOUBLE_SLAB, fn() => Blocks::ANDESITE_SLAB()); - $this->mapStairs(Ids::ANDESITE_STAIRS, fn() => Blocks::ANDESITE_STAIRS()); - $this->map(Ids::ANDESITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::ANDESITE_WALL(), $in)); - $this->map(Ids::ANVIL, function(Reader $in) : Block{ - return Blocks::ANVIL() - ->setDamage(Anvil::UNDAMAGED) - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::CHIPPED_ANVIL, function(Reader $in) : Block{ - return Blocks::ANVIL() - ->setDamage(Anvil::SLIGHTLY_DAMAGED) - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::DAMAGED_ANVIL, function(Reader $in) : Block{ - return Blocks::ANVIL() - ->setDamage(Anvil::VERY_DAMAGED) - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::BAMBOO, function(Reader $in) : Block{ - return Blocks::BAMBOO() - ->setLeafSize(match($value = $in->readString(StateNames::BAMBOO_LEAF_SIZE)){ - StringValues::BAMBOO_LEAF_SIZE_NO_LEAVES => Bamboo::NO_LEAVES, - StringValues::BAMBOO_LEAF_SIZE_SMALL_LEAVES => Bamboo::SMALL_LEAVES, - StringValues::BAMBOO_LEAF_SIZE_LARGE_LEAVES => Bamboo::LARGE_LEAVES, - default => throw $in->badValueException(StateNames::BAMBOO_LEAF_SIZE, $value), - }) - ->setReady($in->readBool(StateNames::AGE_BIT)) - ->setThick(match($value = $in->readString(StateNames::BAMBOO_STALK_THICKNESS)){ - StringValues::BAMBOO_STALK_THICKNESS_THIN => false, - StringValues::BAMBOO_STALK_THICKNESS_THICK => true, - default => throw $in->badValueException(StateNames::BAMBOO_STALK_THICKNESS, $value), - }); - }); - $this->map(Ids::BAMBOO_SAPLING, function(Reader $in) : Block{ - return Blocks::BAMBOO_SAPLING()->setReady($in->readBool(StateNames::AGE_BIT)); - }); - $this->map(Ids::BARREL, function(Reader $in) : Block{ - return Blocks::BARREL() - ->setFacing($in->readFacingDirection()) - ->setOpen($in->readBool(StateNames::OPEN_BIT)); - }); - $this->map(Ids::BASALT, function(Reader $in){ - return Blocks::BASALT() - ->setAxis($in->readPillarAxis()); - }); - $this->map(Ids::BED, function(Reader $in) : Block{ - return Blocks::BED() - ->setFacing($in->readLegacyHorizontalFacing()) - ->setHead($in->readBool(StateNames::HEAD_PIECE_BIT)) - ->setOccupied($in->readBool(StateNames::OCCUPIED_BIT)); - }); - $this->map(Ids::BEDROCK, function(Reader $in) : Block{ - return Blocks::BEDROCK() - ->setBurnsForever($in->readBool(StateNames::INFINIBURN_BIT)); - }); - $this->map(Ids::BEETROOT, fn(Reader $in) => Helper::decodeCrops(Blocks::BEETROOTS(), $in)); - $this->map(Ids::BELL, function(Reader $in) : Block{ - $in->ignored(StateNames::TOGGLE_BIT); //only useful at runtime - return Blocks::BELL() - ->setFacing($in->readLegacyHorizontalFacing()) - ->setAttachmentType($in->readBellAttachmentType()); - }); - $this->map(Ids::BIG_DRIPLEAF, function(Reader $in) : Block{ - if($in->readBool(StateNames::BIG_DRIPLEAF_HEAD)){ - return Blocks::BIG_DRIPLEAF_HEAD() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLeafState(match($type = $in->readString(StateNames::BIG_DRIPLEAF_TILT)){ - StringValues::BIG_DRIPLEAF_TILT_NONE => DripleafState::STABLE, - StringValues::BIG_DRIPLEAF_TILT_UNSTABLE => DripleafState::UNSTABLE, - StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT => DripleafState::PARTIAL_TILT, - StringValues::BIG_DRIPLEAF_TILT_FULL_TILT => DripleafState::FULL_TILT, - default => throw $in->badValueException(StateNames::BIG_DRIPLEAF_TILT, $type), - }); - }else{ - $in->ignored(StateNames::BIG_DRIPLEAF_TILT); - return Blocks::BIG_DRIPLEAF_STEM()->setFacing($in->readCardinalHorizontalFacing()); - } - }); - $this->mapSlab(Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB, fn() => Blocks::BLACKSTONE_SLAB()); - $this->mapStairs(Ids::BLACKSTONE_STAIRS, fn() => Blocks::BLACKSTONE_STAIRS()); - $this->map(Ids::BLACKSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::BLACKSTONE_WALL(), $in)); - $this->map(Ids::BLAST_FURNACE, function(Reader $in) : Block{ - return Blocks::BLAST_FURNACE() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(false); - }); - $this->map(Ids::BONE_BLOCK, function(Reader $in) : Block{ - $in->ignored(StateNames::DEPRECATED); - return Blocks::BONE_BLOCK()->setAxis($in->readPillarAxis()); - }); - $this->map(Ids::BREWING_STAND, function(Reader $in) : Block{ - return Blocks::BREWING_STAND() - ->setSlot(BrewingStandSlot::EAST, $in->readBool(StateNames::BREWING_STAND_SLOT_A_BIT)) - ->setSlot(BrewingStandSlot::SOUTHWEST, $in->readBool(StateNames::BREWING_STAND_SLOT_B_BIT)) - ->setSlot(BrewingStandSlot::NORTHWEST, $in->readBool(StateNames::BREWING_STAND_SLOT_C_BIT)); - }); - $this->mapSlab(Ids::BRICK_SLAB, Ids::BRICK_DOUBLE_SLAB, fn() => Blocks::BRICK_SLAB()); - $this->mapStairs(Ids::BRICK_STAIRS, fn() => Blocks::BRICK_STAIRS()); - $this->map(Ids::BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::BRICK_WALL(), $in)); - $this->map(Ids::MUSHROOM_STEM, fn(Reader $in) => match($in->readBoundedInt(StateNames::HUGE_MUSHROOM_BITS, 0, 15)){ - BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM => Blocks::ALL_SIDED_MUSHROOM_STEM(), - BlockLegacyMetadata::MUSHROOM_BLOCK_STEM => Blocks::MUSHROOM_STEM(), - default => throw new BlockStateDeserializeException("This state does not exist"), - }); - $this->map(Ids::BROWN_MUSHROOM_BLOCK, fn(Reader $in) => Helper::decodeMushroomBlock(Blocks::BROWN_MUSHROOM_BLOCK(), $in)); - $this->map(Ids::CACTUS, function(Reader $in) : Block{ - return Blocks::CACTUS() - ->setAge($in->readBoundedInt(StateNames::AGE, 0, 15)); - }); - $this->map(Ids::CAKE, function(Reader $in) : Block{ - return Blocks::CAKE() - ->setBites($in->readBoundedInt(StateNames::BITE_COUNTER, 0, 6)); - }); - $this->map(Ids::CAMPFIRE, function(Reader $in) : Block{ - return Blocks::CAMPFIRE() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(!$in->readBool(StateNames::EXTINGUISHED)); - }); - $this->map(Ids::CARROTS, fn(Reader $in) => Helper::decodeCrops(Blocks::CARROTS(), $in)); - $this->map(Ids::CARVED_PUMPKIN, function(Reader $in) : Block{ - return Blocks::CARVED_PUMPKIN() - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::CAVE_VINES, function(Reader $in) : CaveVines{ - return Blocks::CAVE_VINES() - ->setBerries(false) - ->setHead(false) - ->setAge($in->readBoundedInt(StateNames::GROWING_PLANT_AGE, 0, 25)); - }); - $this->map(Ids::CAVE_VINES_BODY_WITH_BERRIES, function(Reader $in) : CaveVines{ - return Blocks::CAVE_VINES() - ->setBerries(true) - ->setHead(false) - ->setAge($in->readBoundedInt(StateNames::GROWING_PLANT_AGE, 0, 25)); - }); - $this->map(Ids::CAVE_VINES_HEAD_WITH_BERRIES, function(Reader $in) : CaveVines{ - return Blocks::CAVE_VINES() - ->setBerries(true) - ->setHead(true) - ->setAge($in->readBoundedInt(StateNames::GROWING_PLANT_AGE, 0, 25)); - }); - $this->map(Ids::CHAIN, function(Reader $in) : Block{ - return Blocks::CHAIN() - ->setAxis($in->readPillarAxis()); - }); - $this->map(Ids::CHISELED_BOOKSHELF, function(Reader $in) : Block{ - $block = Blocks::CHISELED_BOOKSHELF() - ->setFacing($in->readLegacyHorizontalFacing()); - - //we don't use API constant for bounds here as the data bounds might be different to what we support internally - $flags = $in->readBoundedInt(StateNames::BOOKS_STORED, 0, (1 << 6) - 1); - foreach(ChiseledBookshelfSlot::cases() as $slot){ - $block->setSlot($slot, ($flags & (1 << $slot->value)) !== 0); - } - - return $block; - }); - $this->map(Ids::CHISELED_QUARTZ_BLOCK, function(Reader $in) : Block{ - return Blocks::CHISELED_QUARTZ() - ->setAxis($in->readPillarAxis()); - }); - $this->map(Ids::CHEST, function(Reader $in) : Block{ - return Blocks::CHEST() - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::CHORUS_FLOWER, function(Reader $in) : Block{ - return Blocks::CHORUS_FLOWER() - ->setAge($in->readBoundedInt(StateNames::AGE, ChorusFlower::MIN_AGE, ChorusFlower::MAX_AGE)); - }); - $this->map(Ids::COARSE_DIRT, fn() => Blocks::DIRT()->setDirtType(DirtType::COARSE)); - $this->mapSlab(Ids::COBBLED_DEEPSLATE_SLAB, Ids::COBBLED_DEEPSLATE_DOUBLE_SLAB, fn() => Blocks::COBBLED_DEEPSLATE_SLAB()); - $this->mapStairs(Ids::COBBLED_DEEPSLATE_STAIRS, fn() => Blocks::COBBLED_DEEPSLATE_STAIRS()); - $this->map(Ids::COBBLED_DEEPSLATE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::COBBLED_DEEPSLATE_WALL(), $in)); - $this->mapSlab(Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB, fn() => Blocks::COBBLESTONE_SLAB()); - $this->map(Ids::COBBLESTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::COBBLESTONE_WALL(), $in)); - $this->map(Ids::COCOA, function(Reader $in) : Block{ - return Blocks::COCOA_POD() - ->setAge($in->readBoundedInt(StateNames::AGE, 0, 2)) - ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())); - }); - $this->map(Ids::COLORED_TORCH_BLUE, fn(Reader $in) => Blocks::BLUE_TORCH()->setFacing($in->readTorchFacing())); - $this->map(Ids::COLORED_TORCH_GREEN, fn(Reader $in) => Blocks::GREEN_TORCH()->setFacing($in->readTorchFacing())); - $this->map(Ids::COLORED_TORCH_PURPLE, fn(Reader $in) => Blocks::PURPLE_TORCH()->setFacing($in->readTorchFacing())); - $this->map(Ids::COLORED_TORCH_RED, fn(Reader $in) => Blocks::RED_TORCH()->setFacing($in->readTorchFacing())); - $this->map(Ids::COMPOUND_CREATOR, fn(Reader $in) => Blocks::COMPOUND_CREATOR() - ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) - ); - $this->mapSlab(Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_RED_SANDSTONE_SLAB()); - $this->mapSlab(Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_SANDSTONE_SLAB()); - $this->mapSlab(Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB, fn() => Blocks::DARK_PRISMARINE_SLAB()); - $this->mapStairs(Ids::DARK_PRISMARINE_STAIRS, fn() => Blocks::DARK_PRISMARINE_STAIRS()); - $this->map(Ids::DAYLIGHT_DETECTOR, fn(Reader $in) => Helper::decodeDaylightSensor(Blocks::DAYLIGHT_SENSOR(), $in) - ->setInverted(false)); - $this->map(Ids::DAYLIGHT_DETECTOR_INVERTED, fn(Reader $in) => Helper::decodeDaylightSensor(Blocks::DAYLIGHT_SENSOR(), $in) - ->setInverted(true)); - $this->map(Ids::DEEPSLATE, function(Reader $in) : Block{ - return Blocks::DEEPSLATE() - ->setAxis($in->readPillarAxis()); - }); - $this->mapSlab(Ids::DEEPSLATE_BRICK_SLAB, Ids::DEEPSLATE_BRICK_DOUBLE_SLAB, fn() => Blocks::DEEPSLATE_BRICK_SLAB()); - $this->mapStairs(Ids::DEEPSLATE_BRICK_STAIRS, fn() => Blocks::DEEPSLATE_BRICK_STAIRS()); - $this->map(Ids::DEEPSLATE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::DEEPSLATE_BRICK_WALL(), $in)); - $this->map(Ids::DEEPSLATE_REDSTONE_ORE, fn() => Blocks::DEEPSLATE_REDSTONE_ORE()->setLit(false)); - $this->mapSlab(Ids::DEEPSLATE_TILE_SLAB, Ids::DEEPSLATE_TILE_DOUBLE_SLAB, fn() => Blocks::DEEPSLATE_TILE_SLAB()); - $this->mapStairs(Ids::DEEPSLATE_TILE_STAIRS, fn() => Blocks::DEEPSLATE_TILE_STAIRS()); - $this->map(Ids::DEEPSLATE_TILE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::DEEPSLATE_TILE_WALL(), $in)); - $this->map(Ids::DETECTOR_RAIL, function(Reader $in) : Block{ - return Blocks::DETECTOR_RAIL() - ->setActivated($in->readBool(StateNames::RAIL_DATA_BIT)) - ->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 5)); - }); - $this->mapSlab(Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB, fn() => Blocks::DIORITE_SLAB()); - $this->mapStairs(Ids::DIORITE_STAIRS, fn() => Blocks::DIORITE_STAIRS()); - $this->map(Ids::DIORITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::DIORITE_WALL(), $in)); - $this->map(Ids::DIRT, fn() => Blocks::DIRT()->setDirtType(DirtType::NORMAL)); - $this->map(Ids::DIRT_WITH_ROOTS, fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED)); - $this->map(Ids::LARGE_FERN, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::LARGE_FERN(), $in)); - $this->map(Ids::TALL_GRASS, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::DOUBLE_TALLGRASS(), $in)); - $this->map(Ids::PEONY, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::PEONY(), $in)); - $this->map(Ids::ROSE_BUSH, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::ROSE_BUSH(), $in)); - $this->map(Ids::SUNFLOWER, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::SUNFLOWER(), $in)); - $this->map(Ids::LILAC, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::LILAC(), $in)); - $this->map(Ids::ELEMENT_CONSTRUCTOR, fn(Reader $in) => Blocks::ELEMENT_CONSTRUCTOR() - ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) - ); - $this->mapStairs(Ids::END_BRICK_STAIRS, fn() => Blocks::END_STONE_BRICK_STAIRS()); - $this->map(Ids::END_STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::END_STONE_BRICK_WALL(), $in)); - $this->map(Ids::END_PORTAL_FRAME, function(Reader $in) : Block{ - return Blocks::END_PORTAL_FRAME() - ->setEye($in->readBool(StateNames::END_PORTAL_EYE_BIT)) - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::END_ROD, function(Reader $in) : Block{ - return Blocks::END_ROD() - ->setFacing($in->readEndRodFacingDirection()); - }); - $this->mapSlab(Ids::END_STONE_BRICK_SLAB, Ids::END_STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::END_STONE_BRICK_SLAB()); - $this->map(Ids::ENDER_CHEST, function(Reader $in) : Block{ - return Blocks::ENDER_CHEST() - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::FARMLAND, function(Reader $in) : Block{ - return Blocks::FARMLAND() - ->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7)); - }); - $this->map(Ids::FIRE, function(Reader $in) : Block{ - return Blocks::FIRE() - ->setAge($in->readBoundedInt(StateNames::AGE, 0, 15)); - }); - $this->map(Ids::FLOWER_POT, function(Reader $in) : Block{ - $in->ignored(StateNames::UPDATE_BIT); - return Blocks::FLOWER_POT(); - }); - $this->map(Ids::FLOWING_LAVA, fn(Reader $in) => Helper::decodeFlowingLiquid(Blocks::LAVA(), $in)); - $this->map(Ids::FLOWING_WATER, fn(Reader $in) => Helper::decodeFlowingLiquid(Blocks::WATER(), $in)); - $this->map(Ids::FRAME, fn(Reader $in) => Helper::decodeItemFrame(Blocks::ITEM_FRAME(), $in)); - $this->map(Ids::FROSTED_ICE, function(Reader $in) : Block{ - return Blocks::FROSTED_ICE() - ->setAge($in->readBoundedInt(StateNames::AGE, 0, 3)); - }); - $this->map(Ids::FURNACE, function(Reader $in) : Block{ - return Blocks::FURNACE() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(false); - }); - $this->map(Ids::GLOW_LICHEN, fn(Reader $in) => Blocks::GLOW_LICHEN()->setFaces($in->readFacingFlags())); - $this->map(Ids::GLOW_FRAME, fn(Reader $in) => Helper::decodeItemFrame(Blocks::GLOWING_ITEM_FRAME(), $in)); - $this->map(Ids::GOLDEN_RAIL, function(Reader $in) : Block{ - return Blocks::POWERED_RAIL() - ->setPowered($in->readBool(StateNames::RAIL_DATA_BIT)) - ->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 5)); - }); - $this->mapSlab(Ids::GRANITE_SLAB, Ids::GRANITE_DOUBLE_SLAB, fn() => Blocks::GRANITE_SLAB()); - $this->mapStairs(Ids::GRANITE_STAIRS, fn() => Blocks::GRANITE_STAIRS()); - $this->map(Ids::GRANITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::GRANITE_WALL(), $in)); - $this->map(Ids::HAY_BLOCK, function(Reader $in) : Block{ - $in->ignored(StateNames::DEPRECATED); - return Blocks::HAY_BALE()->setAxis($in->readPillarAxis()); - }); - $this->map(Ids::HEAVY_WEIGHTED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeWeightedPressurePlate(Blocks::WEIGHTED_PRESSURE_PLATE_HEAVY(), $in)); - $this->map(Ids::HOPPER, function(Reader $in) : Block{ - return Blocks::HOPPER() - ->setFacing($in->readFacingWithoutUp()) - ->setPowered($in->readBool(StateNames::TOGGLE_BIT)); - }); - $this->map(Ids::IRON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::IRON_DOOR(), $in)); - $this->map(Ids::IRON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::IRON_TRAPDOOR(), $in)); - $this->map(Ids::LAB_TABLE, fn(Reader $in) => Blocks::LAB_TABLE() - ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) - ); - $this->map(Ids::LADDER, function(Reader $in) : Block{ - return Blocks::LADDER() - ->setFacing($in->readHorizontalFacing()); - }); - $this->map(Ids::LANTERN, function(Reader $in) : Block{ - return Blocks::LANTERN() - ->setHanging($in->readBool(StateNames::HANGING)); - }); - $this->map(Ids::LARGE_AMETHYST_BUD, function(Reader $in) : Block{ - return Blocks::AMETHYST_CLUSTER() - ->setStage(AmethystCluster::STAGE_LARGE_BUD) - ->setFacing($in->readBlockFace()); - }); - $this->map(Ids::LAVA, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::LAVA(), $in)); - $this->map(Ids::LECTERN, function(Reader $in) : Block{ - return Blocks::LECTERN() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setProducingSignal($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::LEVER, function(Reader $in) : Block{ - return Blocks::LEVER() - ->setActivated($in->readBool(StateNames::OPEN_BIT)) - ->setFacing(match($value = $in->readString(StateNames::LEVER_DIRECTION)){ - StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH => LeverFacing::DOWN_AXIS_Z, - StringValues::LEVER_DIRECTION_DOWN_EAST_WEST => LeverFacing::DOWN_AXIS_X, - StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH => LeverFacing::UP_AXIS_Z, - StringValues::LEVER_DIRECTION_UP_EAST_WEST => LeverFacing::UP_AXIS_X, - StringValues::LEVER_DIRECTION_NORTH => LeverFacing::NORTH, - StringValues::LEVER_DIRECTION_SOUTH => LeverFacing::SOUTH, - StringValues::LEVER_DIRECTION_WEST => LeverFacing::WEST, - StringValues::LEVER_DIRECTION_EAST => LeverFacing::EAST, - default => throw $in->badValueException(StateNames::LEVER_DIRECTION, $value), - }); - }); - $this->map(Ids::LIGHTNING_ROD, function(Reader $in) : Block{ - return Blocks::LIGHTNING_ROD() - ->setFacing($in->readFacingDirection()); - }); - $this->map(Ids::LIGHT_WEIGHTED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeWeightedPressurePlate(Blocks::WEIGHTED_PRESSURE_PLATE_LIGHT(), $in)); - $this->map(Ids::LIT_BLAST_FURNACE, function(Reader $in) : Block{ - return Blocks::BLAST_FURNACE() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(true); - }); - $this->map(Ids::LIT_DEEPSLATE_REDSTONE_ORE, fn() => Blocks::DEEPSLATE_REDSTONE_ORE()->setLit(true)); - $this->map(Ids::LIT_FURNACE, function(Reader $in) : Block{ - return Blocks::FURNACE() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(true); - }); - $this->map(Ids::LIT_PUMPKIN, function(Reader $in) : Block{ - return Blocks::LIT_PUMPKIN() - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::LIT_REDSTONE_LAMP, function() : Block{ - return Blocks::REDSTONE_LAMP() - ->setPowered(true); - }); - $this->map(Ids::LIT_REDSTONE_ORE, function() : Block{ - return Blocks::REDSTONE_ORE() - ->setLit(true); - }); - $this->map(Ids::LIT_SMOKER, function(Reader $in) : Block{ - return Blocks::SMOKER() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(true); - }); - $this->map(Ids::LOOM, function(Reader $in) : Block{ - return Blocks::LOOM() - ->setFacing($in->readLegacyHorizontalFacing()); - }); - $this->map(Ids::MATERIAL_REDUCER, fn(Reader $in) => Blocks::MATERIAL_REDUCER() - ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) - ); - $this->map(Ids::MEDIUM_AMETHYST_BUD, function(Reader $in) : Block{ - return Blocks::AMETHYST_CLUSTER() - ->setStage(AmethystCluster::STAGE_MEDIUM_BUD) - ->setFacing($in->readBlockFace()); - }); - $this->map(Ids::MELON_STEM, fn(Reader $in) => Helper::decodeStem(Blocks::MELON_STEM(), $in)); - $this->mapSlab(Ids::MOSSY_COBBLESTONE_SLAB, Ids::MOSSY_COBBLESTONE_DOUBLE_SLAB, fn() => Blocks::MOSSY_COBBLESTONE_SLAB()); - $this->mapStairs(Ids::MOSSY_COBBLESTONE_STAIRS, fn() => Blocks::MOSSY_COBBLESTONE_STAIRS()); - $this->map(Ids::MOSSY_COBBLESTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MOSSY_COBBLESTONE_WALL(), $in)); - $this->mapSlab(Ids::MOSSY_STONE_BRICK_SLAB, Ids::MOSSY_STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::MOSSY_STONE_BRICK_SLAB()); - $this->mapStairs(Ids::MOSSY_STONE_BRICK_STAIRS, fn() => Blocks::MOSSY_STONE_BRICK_STAIRS()); - $this->map(Ids::MOSSY_STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MOSSY_STONE_BRICK_WALL(), $in)); - $this->mapSlab(Ids::MUD_BRICK_SLAB, Ids::MUD_BRICK_DOUBLE_SLAB, fn() => Blocks::MUD_BRICK_SLAB()); - $this->mapStairs(Ids::MUD_BRICK_STAIRS, fn() => Blocks::MUD_BRICK_STAIRS()); - $this->map(Ids::MUD_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MUD_BRICK_WALL(), $in)); - $this->map(Ids::MUDDY_MANGROVE_ROOTS, function(Reader $in) : Block{ - return Blocks::MUDDY_MANGROVE_ROOTS() - ->setAxis($in->readPillarAxis()); - }); - $this->mapSlab(Ids::NETHER_BRICK_SLAB, Ids::NETHER_BRICK_DOUBLE_SLAB, fn() => Blocks::NETHER_BRICK_SLAB()); - $this->mapStairs(Ids::NETHER_BRICK_STAIRS, fn() => Blocks::NETHER_BRICK_STAIRS()); - $this->map(Ids::NETHER_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::NETHER_BRICK_WALL(), $in)); - $this->map(Ids::NETHER_WART, function(Reader $in) : Block{ - return Blocks::NETHER_WART() - ->setAge($in->readBoundedInt(StateNames::AGE, 0, 3)); - }); - $this->mapSlab(Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB, fn() => Blocks::STONE_SLAB()); - $this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS()); - $this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis())); - $this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis())); - $this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB()); - $this->map(Ids::PINK_PETALS, function(Reader $in) : Block{ - //Pink petals only uses 0-3, but GROWTH state can go up to 7 - $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7); - return Blocks::PINK_PETALS() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setCount(min($growth + 1, PinkPetals::MAX_COUNT)); - }); - $this->map(Ids::PITCHER_CROP, function(Reader $in) : Block{ - $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7); - $top = $in->readBool(StateNames::UPPER_BLOCK_BIT); - if($growth <= PitcherCrop::MAX_AGE){ - //top pitcher crop with age 0-2 is an invalid state - //only the bottom half should exist in this case - return $top ? Blocks::AIR() : Blocks::PITCHER_CROP()->setAge($growth); - } - return Blocks::DOUBLE_PITCHER_CROP() - ->setAge(min($growth - PitcherCrop::MAX_AGE - 1, DoublePitcherCrop::MAX_AGE)) - ->setTop($top); - }); - $this->map(Ids::PITCHER_PLANT, function(Reader $in) : Block{ - return Blocks::PITCHER_PLANT() - ->setTop($in->readBool(StateNames::UPPER_BLOCK_BIT)); - }); - $this->mapSlab(Ids::POLISHED_ANDESITE_SLAB, Ids::POLISHED_ANDESITE_DOUBLE_SLAB, fn() => Blocks::POLISHED_ANDESITE_SLAB()); - $this->mapStairs(Ids::POLISHED_ANDESITE_STAIRS, fn() => Blocks::POLISHED_ANDESITE_STAIRS()); - $this->map(Ids::POLISHED_BASALT, function(Reader $in) : Block{ - return Blocks::POLISHED_BASALT() - ->setAxis($in->readPillarAxis()); - }); - $this->map(Ids::POLISHED_BLACKSTONE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::POLISHED_BLACKSTONE_BUTTON(), $in)); - $this->mapSlab(Ids::POLISHED_BLACKSTONE_SLAB, Ids::POLISHED_BLACKSTONE_DOUBLE_SLAB, fn() => Blocks::POLISHED_BLACKSTONE_SLAB()); - $this->map(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::POLISHED_BLACKSTONE_PRESSURE_PLATE(), $in)); - $this->mapStairs(Ids::POLISHED_BLACKSTONE_STAIRS, fn() => Blocks::POLISHED_BLACKSTONE_STAIRS()); - $this->map(Ids::POLISHED_BLACKSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_BLACKSTONE_WALL(), $in)); - $this->mapSlab(Ids::POLISHED_BLACKSTONE_BRICK_SLAB, Ids::POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB, fn() => Blocks::POLISHED_BLACKSTONE_BRICK_SLAB()); - $this->mapStairs(Ids::POLISHED_BLACKSTONE_BRICK_STAIRS, fn() => Blocks::POLISHED_BLACKSTONE_BRICK_STAIRS()); - $this->map(Ids::POLISHED_BLACKSTONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_BLACKSTONE_BRICK_WALL(), $in)); - $this->mapSlab(Ids::POLISHED_DEEPSLATE_SLAB, Ids::POLISHED_DEEPSLATE_DOUBLE_SLAB, fn() => Blocks::POLISHED_DEEPSLATE_SLAB()); - $this->mapStairs(Ids::POLISHED_DEEPSLATE_STAIRS, fn() => Blocks::POLISHED_DEEPSLATE_STAIRS()); - $this->map(Ids::POLISHED_DEEPSLATE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_DEEPSLATE_WALL(), $in)); - $this->mapSlab(Ids::POLISHED_DIORITE_SLAB, Ids::POLISHED_DIORITE_DOUBLE_SLAB, fn() => Blocks::POLISHED_DIORITE_SLAB()); - $this->mapStairs(Ids::POLISHED_DIORITE_STAIRS, fn() => Blocks::POLISHED_DIORITE_STAIRS()); - $this->mapSlab(Ids::POLISHED_GRANITE_SLAB, Ids::POLISHED_GRANITE_DOUBLE_SLAB, fn() => Blocks::POLISHED_GRANITE_SLAB()); - $this->mapStairs(Ids::POLISHED_GRANITE_STAIRS, fn() => Blocks::POLISHED_GRANITE_STAIRS()); - $this->mapSlab(Ids::POLISHED_TUFF_SLAB, Ids::POLISHED_TUFF_DOUBLE_SLAB, fn() => Blocks::POLISHED_TUFF_SLAB()); - $this->mapStairs(Ids::POLISHED_TUFF_STAIRS, fn() => Blocks::POLISHED_TUFF_STAIRS()); - $this->map(Ids::POLISHED_TUFF_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_TUFF_WALL(), $in)); - $this->map(Ids::PORTAL, function(Reader $in) : Block{ - return Blocks::NETHER_PORTAL() - ->setAxis(match($value = $in->readString(StateNames::PORTAL_AXIS)){ - StringValues::PORTAL_AXIS_UNKNOWN => Axis::X, - StringValues::PORTAL_AXIS_X => Axis::X, - StringValues::PORTAL_AXIS_Z => Axis::Z, - default => throw $in->badValueException(StateNames::PORTAL_AXIS, $value), - }); - }); - $this->map(Ids::POTATOES, fn(Reader $in) => Helper::decodeCrops(Blocks::POTATOES(), $in)); - $this->map(Ids::POWERED_COMPARATOR, fn(Reader $in) => Helper::decodeComparator(Blocks::REDSTONE_COMPARATOR(), $in)); - $this->map(Ids::POWERED_REPEATER, fn(Reader $in) => Helper::decodeRepeater(Blocks::REDSTONE_REPEATER(), $in) - ->setPowered(true)); - $this->mapSlab(Ids::PRISMARINE_BRICK_SLAB, Ids::PRISMARINE_BRICK_DOUBLE_SLAB, fn() => Blocks::PRISMARINE_BRICKS_SLAB()); - $this->mapStairs(Ids::PRISMARINE_BRICKS_STAIRS, fn() => Blocks::PRISMARINE_BRICKS_STAIRS()); - $this->map(Ids::PRISMARINE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::PRISMARINE_WALL(), $in)); - $this->mapSlab(Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB, fn() => Blocks::PRISMARINE_SLAB()); - $this->mapStairs(Ids::PRISMARINE_STAIRS, fn() => Blocks::PRISMARINE_STAIRS()); - $this->map(Ids::PUMPKIN, function(Reader $in) : Block{ - $in->ignored(StateNames::MC_CARDINAL_DIRECTION); //obsolete - return Blocks::PUMPKIN(); - }); - $this->map(Ids::PUMPKIN_STEM, fn(Reader $in) => Helper::decodeStem(Blocks::PUMPKIN_STEM(), $in)); - $this->map(Ids::PURPUR_BLOCK, function(Reader $in) : Block{ - $in->ignored(StateNames::PILLAR_AXIS); //??? - return Blocks::PURPUR(); - }); - $this->map(Ids::PURPUR_PILLAR, fn(Reader $in) => Blocks::PURPUR_PILLAR()->setAxis($in->readPillarAxis())); - $this->mapSlab(Ids::PURPUR_SLAB, Ids::PURPUR_DOUBLE_SLAB, fn() => Blocks::PURPUR_SLAB()); - $this->mapStairs(Ids::PURPUR_STAIRS, fn() => Blocks::PURPUR_STAIRS()); - $this->map(Ids::QUARTZ_BLOCK, function(Reader $in) : Opaque{ - $in->ignored(StateNames::PILLAR_AXIS); - return Blocks::QUARTZ(); - }); - $this->map(Ids::QUARTZ_PILLAR, function(Reader $in) : Block{ - return Blocks::QUARTZ_PILLAR() - ->setAxis($in->readPillarAxis()); - }); - $this->mapSlab(Ids::QUARTZ_SLAB, Ids::QUARTZ_DOUBLE_SLAB, fn() => Blocks::QUARTZ_SLAB()); - $this->mapStairs(Ids::QUARTZ_STAIRS, fn() => Blocks::QUARTZ_STAIRS()); - $this->map(Ids::RAIL, function(Reader $in) : Block{ - return Blocks::RAIL() - ->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 9)); - }); - $this->map(Ids::RED_MUSHROOM_BLOCK, fn(Reader $in) => Helper::decodeMushroomBlock(Blocks::RED_MUSHROOM_BLOCK(), $in)); - $this->mapSlab(Ids::RED_NETHER_BRICK_SLAB, Ids::RED_NETHER_BRICK_DOUBLE_SLAB, fn() => Blocks::RED_NETHER_BRICK_SLAB()); - $this->mapStairs(Ids::RED_NETHER_BRICK_STAIRS, fn() => Blocks::RED_NETHER_BRICK_STAIRS()); - $this->map(Ids::RED_NETHER_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RED_NETHER_BRICK_WALL(), $in)); - $this->mapSlab(Ids::RED_SANDSTONE_SLAB, Ids::RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::RED_SANDSTONE_SLAB()); - $this->mapStairs(Ids::RED_SANDSTONE_STAIRS, fn() => Blocks::RED_SANDSTONE_STAIRS()); - $this->map(Ids::RED_SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RED_SANDSTONE_WALL(), $in)); - $this->map(Ids::REDSTONE_LAMP, function() : Block{ - return Blocks::REDSTONE_LAMP() - ->setPowered(false); - }); - $this->map(Ids::REDSTONE_ORE, function() : Block{ - return Blocks::REDSTONE_ORE() - ->setLit(false); - }); - $this->map(Ids::REDSTONE_TORCH, function(Reader $in) : Block{ - return Blocks::REDSTONE_TORCH() - ->setFacing($in->readTorchFacing()) - ->setLit(true); - }); - $this->map(Ids::REDSTONE_WIRE, function(Reader $in) : Block{ - return Blocks::REDSTONE_WIRE() - ->setOutputSignalStrength($in->readBoundedInt(StateNames::REDSTONE_SIGNAL, 0, 15)); - }); - $this->map(Ids::REEDS, function(Reader $in) : Block{ - return Blocks::SUGARCANE() - ->setAge($in->readBoundedInt(StateNames::AGE, 0, 15)); - }); - $this->mapSlab(Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB, fn() => Blocks::RESIN_BRICK_SLAB()); - $this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS()); - $this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in)); - $this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags())); - $this->map(Ids::RESPAWN_ANCHOR, function(Reader $in) : Block{ - return Blocks::RESPAWN_ANCHOR() - ->setCharges($in->readBoundedInt(StateNames::RESPAWN_ANCHOR_CHARGE, 0, 4)); - }); - $this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB()); - $this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS()); - $this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in)); - $this->map(Ids::SEA_PICKLE, function(Reader $in) : Block{ - return Blocks::SEA_PICKLE() - ->setCount($in->readBoundedInt(StateNames::CLUSTER_COUNT, 0, 3) + 1) - ->setUnderwater(!$in->readBool(StateNames::DEAD_BIT)); - }); - $this->map(Ids::SMOKER, function(Reader $in) : Block{ - return Blocks::SMOKER() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(false); - }); - $this->map(Ids::SMALL_AMETHYST_BUD, function(Reader $in) : Block{ - return Blocks::AMETHYST_CLUSTER() - ->setStage(AmethystCluster::STAGE_SMALL_BUD) - ->setFacing($in->readBlockFace()); - }); - $this->map(Ids::SMALL_DRIPLEAF_BLOCK, function(Reader $in) : Block{ - return Blocks::SMALL_DRIPLEAF() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setTop($in->readBool(StateNames::UPPER_BLOCK_BIT)); - }); - $this->map(Ids::SMOOTH_QUARTZ, function(Reader $in) : Block{ - $in->ignored(StateNames::PILLAR_AXIS); - return Blocks::SMOOTH_QUARTZ(); - }); - $this->mapSlab(Ids::SMOOTH_QUARTZ_SLAB, Ids::SMOOTH_QUARTZ_DOUBLE_SLAB, fn() => Blocks::SMOOTH_QUARTZ_SLAB()); - $this->mapStairs(Ids::SMOOTH_QUARTZ_STAIRS, fn() => Blocks::SMOOTH_QUARTZ_STAIRS()); - $this->mapSlab(Ids::SMOOTH_RED_SANDSTONE_SLAB, Ids::SMOOTH_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SMOOTH_RED_SANDSTONE_SLAB()); - $this->mapStairs(Ids::SMOOTH_RED_SANDSTONE_STAIRS, fn() => Blocks::SMOOTH_RED_SANDSTONE_STAIRS()); - $this->mapSlab(Ids::SMOOTH_SANDSTONE_SLAB, Ids::SMOOTH_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SMOOTH_SANDSTONE_SLAB()); - $this->mapStairs(Ids::SMOOTH_SANDSTONE_STAIRS, fn() => Blocks::SMOOTH_SANDSTONE_STAIRS()); - $this->mapSlab(Ids::SMOOTH_STONE_SLAB, Ids::SMOOTH_STONE_DOUBLE_SLAB, fn() => Blocks::SMOOTH_STONE_SLAB()); - $this->map(Ids::SNOW_LAYER, function(Reader $in) : Block{ - $in->ignored(StateNames::COVERED_BIT); //seems to be useless - return Blocks::SNOW_LAYER()->setLayers($in->readBoundedInt(StateNames::HEIGHT, 0, 7) + 1); - }); - $this->map(Ids::SOUL_CAMPFIRE, function(Reader $in) : Block{ - return Blocks::SOUL_CAMPFIRE() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLit(!$in->readBool(StateNames::EXTINGUISHED)); - }); - $this->map(Ids::SOUL_FIRE, function(Reader $in) : Block{ - $in->ignored(StateNames::AGE); //this is useless for soul fire, since it doesn't have the logic associated - return Blocks::SOUL_FIRE(); - }); - $this->map(Ids::SOUL_LANTERN, function(Reader $in) : Block{ - return Blocks::SOUL_LANTERN() - ->setHanging($in->readBool(StateNames::HANGING)); - }); - $this->map(Ids::SOUL_TORCH, function(Reader $in) : Block{ - return Blocks::SOUL_TORCH() - ->setFacing($in->readTorchFacing()); - }); - $this->map(Ids::STANDING_BANNER, function(Reader $in) : Block{ - return Blocks::BANNER() - ->setRotation($in->readBoundedInt(StateNames::GROUND_SIGN_DIRECTION, 0, 15)); - }); - $this->mapSlab(Ids::STONE_BRICK_SLAB, Ids::STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::STONE_BRICK_SLAB()); - $this->mapStairs(Ids::STONE_BRICK_STAIRS, fn() => Blocks::STONE_BRICK_STAIRS()); - $this->map(Ids::STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::STONE_BRICK_WALL(), $in)); - $this->map(Ids::STONE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::STONE_BUTTON(), $in)); - $this->map(Ids::STONE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::STONE_PRESSURE_PLATE(), $in)); - $this->mapStairs(Ids::STONE_STAIRS, fn() => Blocks::COBBLESTONE_STAIRS()); - $this->map(Ids::STONECUTTER_BLOCK, function(Reader $in) : Block{ - return Blocks::STONECUTTER() - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::SWEET_BERRY_BUSH, function(Reader $in) : Block{ - //berry bush only wants 0-3, but it can be bigger in MCPE due to misuse of GROWTH state which goes up to 7 - $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7); - return Blocks::SWEET_BERRY_BUSH() - ->setAge(min($growth, SweetBerryBush::STAGE_MATURE)); - }); - $this->map(Ids::TNT, function(Reader $in) : Block{ - return Blocks::TNT() - ->setUnstable($in->readBool(StateNames::EXPLODE_BIT)) - ->setWorksUnderwater(false); - }); - $this->map(Ids::TORCH, function(Reader $in) : Block{ - return Blocks::TORCH() - ->setFacing($in->readTorchFacing()); - }); - $this->map(Ids::TORCHFLOWER_CROP, function(Reader $in) : Block{ - return Blocks::TORCHFLOWER_CROP() - //this property can have values 0-7, but only 0-1 are valid - ->setReady($in->readBoundedInt(StateNames::GROWTH, 0, 7) !== 0); - }); - $this->map(Ids::TRAPPED_CHEST, function(Reader $in) : Block{ - return Blocks::TRAPPED_CHEST() - ->setFacing($in->readCardinalHorizontalFacing()); - }); - $this->map(Ids::TRIP_WIRE, function(Reader $in) : Block{ - return Blocks::TRIPWIRE() - ->setConnected($in->readBool(StateNames::ATTACHED_BIT)) - ->setDisarmed($in->readBool(StateNames::DISARMED_BIT)) - ->setSuspended($in->readBool(StateNames::SUSPENDED_BIT)) - ->setTriggered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->map(Ids::TRIPWIRE_HOOK, function(Reader $in) : Block{ - return Blocks::TRIPWIRE_HOOK() - ->setConnected($in->readBool(StateNames::ATTACHED_BIT)) - ->setFacing($in->readLegacyHorizontalFacing()) - ->setPowered($in->readBool(StateNames::POWERED_BIT)); - }); - $this->mapSlab(Ids::TUFF_BRICK_SLAB, Ids::TUFF_BRICK_DOUBLE_SLAB, fn() => Blocks::TUFF_BRICK_SLAB()); - $this->mapStairs(Ids::TUFF_BRICK_STAIRS, fn() => Blocks::TUFF_BRICK_STAIRS()); - $this->map(Ids::TUFF_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::TUFF_BRICK_WALL(), $in)); - $this->mapSlab(Ids::TUFF_SLAB, Ids::TUFF_DOUBLE_SLAB, fn() => Blocks::TUFF_SLAB()); - $this->mapStairs(Ids::TUFF_STAIRS, fn() => Blocks::TUFF_STAIRS()); - $this->map(Ids::TUFF_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::TUFF_WALL(), $in)); - $this->map(Ids::TWISTING_VINES, function(Reader $in) : Block{ - return Blocks::TWISTING_VINES() - ->setAge($in->readBoundedInt(StateNames::TWISTING_VINES_AGE, 0, 25)); - }); - $this->map(Ids::UNDERWATER_TNT, function(Reader $in) : Block{ - return Blocks::TNT() - ->setUnstable($in->readBool(StateNames::EXPLODE_BIT)) - ->setWorksUnderwater(true); - }); - $this->map(Ids::UNDERWATER_TORCH, function(Reader $in) : Block{ - return Blocks::UNDERWATER_TORCH() - ->setFacing($in->readTorchFacing()); - }); - $this->map(Ids::UNLIT_REDSTONE_TORCH, function(Reader $in) : Block{ - return Blocks::REDSTONE_TORCH() - ->setFacing($in->readTorchFacing()) - ->setLit(false); - }); - $this->map(Ids::UNPOWERED_COMPARATOR, fn(Reader $in) => Helper::decodeComparator(Blocks::REDSTONE_COMPARATOR(), $in)); - $this->map(Ids::UNPOWERED_REPEATER, fn(Reader $in) => Helper::decodeRepeater(Blocks::REDSTONE_REPEATER(), $in) - ->setPowered(false)); - $this->map(Ids::VERDANT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::VERDANT)->setAxis($in->readPillarAxis())); - $this->map(Ids::VINE, function(Reader $in) : Block{ - $vineDirectionFlags = $in->readBoundedInt(StateNames::VINE_DIRECTION_BITS, 0, 15); - return Blocks::VINES() - ->setFace(Facing::NORTH, ($vineDirectionFlags & BlockLegacyMetadata::VINE_FLAG_NORTH) !== 0) - ->setFace(Facing::SOUTH, ($vineDirectionFlags & BlockLegacyMetadata::VINE_FLAG_SOUTH) !== 0) - ->setFace(Facing::WEST, ($vineDirectionFlags & BlockLegacyMetadata::VINE_FLAG_WEST) !== 0) - ->setFace(Facing::EAST, ($vineDirectionFlags & BlockLegacyMetadata::VINE_FLAG_EAST) !== 0); - }); - $this->map(Ids::WALL_BANNER, function(Reader $in) : Block{ - return Blocks::WALL_BANNER() - ->setFacing($in->readHorizontalFacing()); - }); - $this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in)); - - $this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{ - return Blocks::WEEPING_VINES() - ->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25)); - }); - $this->map(Ids::WHEAT, fn(Reader $in) => Helper::decodeCrops(Blocks::WHEAT(), $in)); - } - /** @throws BlockStateDeserializeException */ public function deserializeBlock(BlockStateData $blockStateData) : Block{ $id = $blockStateData->getName(); diff --git a/src/data/bedrock/block/convert/BlockStateWriter.php b/src/data/bedrock/block/convert/BlockStateWriter.php index 63af92d10..0119bd02e 100644 --- a/src/data/bedrock/block/convert/BlockStateWriter.php +++ b/src/data/bedrock/block/convert/BlockStateWriter.php @@ -31,6 +31,9 @@ use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\BlockStateNames; use pocketmine\data\bedrock\block\BlockStateSerializeException; use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues; +use pocketmine\data\bedrock\block\convert\property\EnumFromRawStateMap; +use pocketmine\data\bedrock\block\convert\property\IntFromRawStateMap; +use pocketmine\data\bedrock\block\convert\property\ValueMappings; use pocketmine\math\Axis; use pocketmine\math\Facing; use pocketmine\nbt\tag\ByteTag; @@ -73,35 +76,47 @@ final class BlockStateWriter{ return $this; } - /** @return $this */ - public function writeFacingDirection(int $value) : self{ - $this->writeInt(BlockStateNames::FACING_DIRECTION, match($value){ - Facing::DOWN => 0, - Facing::UP => 1, - Facing::NORTH => 2, - Facing::SOUTH => 3, - Facing::WEST => 4, - Facing::EAST => 5, - default => throw new BlockStateSerializeException("Invalid Facing $value") - }); - return $this; - } - - /** @return $this */ - public function writeBlockFace(int $value) : self{ - $this->writeString(BlockStateNames::MC_BLOCK_FACE, match($value){ - Facing::DOWN => StringValues::MC_BLOCK_FACE_DOWN, - Facing::UP => StringValues::MC_BLOCK_FACE_UP, - Facing::NORTH => StringValues::MC_BLOCK_FACE_NORTH, - Facing::SOUTH => StringValues::MC_BLOCK_FACE_SOUTH, - Facing::WEST => StringValues::MC_BLOCK_FACE_WEST, - Facing::EAST => StringValues::MC_BLOCK_FACE_EAST, - default => throw new BlockStateSerializeException("Invalid Facing $value") - }); + /** + * @deprecated + * @phpstan-param IntFromRawStateMap $map + * @return $this + */ + public function mapIntToString(string $name, IntFromRawStateMap $map, int $value) : self{ + $raw = $map->valueToRaw($value); + $this->writeString($name, $raw); return $this; } /** + * @deprecated + * @phpstan-param IntFromRawStateMap $map + * @return $this + */ + public function mapIntToInt(string $name, IntFromRawStateMap $map, int $value) : self{ + $raw = $map->valueToRaw($value); + $this->writeInt($name, $raw); + return $this; + } + + /** + * @deprecated + * @return $this + */ + public function writeFacingDirection(int $value) : self{ + return $this->mapIntToInt(BlockStateNames::FACING_DIRECTION, ValueMappings::getInstance()->facing, $value); + } + + /** + * @deprecated + * @return $this + */ + public function writeBlockFace(int $value) : self{ + $this->mapIntToString(BlockStateNames::MC_BLOCK_FACE, ValueMappings::getInstance()->blockFace, $value); + return $this; + } + + /** + * @deprecated * @param int[] $faces * @phpstan-param array $faces * @return $this @@ -123,86 +138,69 @@ final class BlockStateWriter{ return $this->writeInt(BlockStateNames::MULTI_FACE_DIRECTION_BITS, $result); } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeEndRodFacingDirection(int $value) : self{ //end rods are stupid in bedrock and have everything except up/down the wrong way round return $this->writeFacingDirection(Facing::axis($value) !== Axis::Y ? Facing::opposite($value) : $value); } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeHorizontalFacing(int $value) : self{ - if($value === Facing::UP || $value === Facing::DOWN){ - throw new BlockStateSerializeException("Y-axis facing is not allowed"); - } - - return $this->writeFacingDirection($value); - } - - /** @return $this */ - public function writeWeirdoHorizontalFacing(int $value) : self{ - $this->writeInt(BlockStateNames::WEIRDO_DIRECTION, match($value){ - Facing::EAST => 0, - Facing::WEST => 1, - Facing::SOUTH => 2, - Facing::NORTH => 3, - default => throw new BlockStateSerializeException("Invalid horizontal facing $value") - }); - return $this; - } - - /** @return $this */ - public function writeLegacyHorizontalFacing(int $value) : self{ - $this->writeInt(BlockStateNames::DIRECTION, match($value){ - Facing::SOUTH => 0, - Facing::WEST => 1, - Facing::NORTH => 2, - Facing::EAST => 3, - default => throw new BlockStateSerializeException("Invalid horizontal facing $value") - }); - return $this; + return $this->mapIntToInt(BlockStateNames::FACING_DIRECTION, ValueMappings::getInstance()->horizontalFacingClassic, $value); } /** + * @deprecated + * @return $this + */ + public function writeWeirdoHorizontalFacing(int $value) : self{ + return $this->mapIntToInt(BlockStateNames::WEIRDO_DIRECTION, ValueMappings::getInstance()->horizontalFacing5Minus, $value); + } + + /** + * @deprecated + * @return $this + */ + public function writeLegacyHorizontalFacing(int $value) : self{ + return $this->mapIntToInt(BlockStateNames::DIRECTION, ValueMappings::getInstance()->horizontalFacingSWNE, $value); + } + + /** + * @deprecated * This is for trapdoors, because Mojang botched the conversion in 1.13 * @return $this */ public function write5MinusHorizontalFacing(int $value) : self{ - return $this->writeInt(BlockStateNames::DIRECTION, match($value){ - Facing::EAST => 0, - Facing::WEST => 1, - Facing::SOUTH => 2, - Facing::NORTH => 3, - default => throw new BlockStateSerializeException("Invalid horizontal facing $value") - }); + return $this->mapIntToInt(BlockStateNames::DIRECTION, ValueMappings::getInstance()->horizontalFacing5Minus, $value); } /** + * @deprecated * Used by pumpkins as of 1.20.0.23 beta * @return $this */ public function writeCardinalHorizontalFacing(int $value) : self{ - return $this->writeString(BlockStateNames::MC_CARDINAL_DIRECTION, match($value){ - Facing::SOUTH => StringValues::MC_CARDINAL_DIRECTION_SOUTH, - Facing::WEST => StringValues::MC_CARDINAL_DIRECTION_WEST, - Facing::NORTH => StringValues::MC_CARDINAL_DIRECTION_NORTH, - Facing::EAST => StringValues::MC_CARDINAL_DIRECTION_EAST, - default => throw new BlockStateSerializeException("Invalid horizontal facing $value") - }); + return $this->mapIntToString(BlockStateNames::MC_CARDINAL_DIRECTION, ValueMappings::getInstance()->cardinalDirection, $value); } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeCoralFacing(int $value) : self{ - $this->writeInt(BlockStateNames::CORAL_DIRECTION, match($value){ - Facing::WEST => 0, - Facing::EAST => 1, - Facing::NORTH => 2, - Facing::SOUTH => 3, - default => throw new BlockStateSerializeException("Invalid horizontal facing $value") - }); - return $this; + return $this->mapIntToInt(BlockStateNames::CORAL_DIRECTION, ValueMappings::getInstance()->horizontalFacingCoral, $value); } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeFacingWithoutDown(int $value) : self{ if($value === Facing::DOWN){ throw new BlockStateSerializeException("Invalid facing DOWN"); @@ -211,7 +209,10 @@ final class BlockStateWriter{ return $this; } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeFacingWithoutUp(int $value) : self{ if($value === Facing::UP){ throw new BlockStateSerializeException("Invalid facing UP"); @@ -220,18 +221,19 @@ final class BlockStateWriter{ return $this; } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writePillarAxis(int $axis) : self{ - $this->writeString(BlockStateNames::PILLAR_AXIS, match($axis){ - Axis::X => StringValues::PILLAR_AXIS_X, - Axis::Y => StringValues::PILLAR_AXIS_Y, - Axis::Z => StringValues::PILLAR_AXIS_Z, - default => throw new BlockStateSerializeException("Invalid axis $axis") - }); + $this->mapIntToString(BlockStateNames::PILLAR_AXIS, ValueMappings::getInstance()->pillarAxis, $axis); return $this; } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeSlabPosition(SlabType $slabType) : self{ $this->writeString(BlockStateNames::MC_VERTICAL_HALF, match($slabType){ SlabType::TOP => StringValues::MC_VERTICAL_HALF_TOP, @@ -241,32 +243,27 @@ final class BlockStateWriter{ return $this; } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeTorchFacing(int $facing) : self{ - //TODO: horizontal directions are flipped (MCPE bug: https://bugs.mojang.com/browse/MCPE-152036) - $this->writeString(BlockStateNames::TORCH_FACING_DIRECTION, match($facing){ - Facing::UP => StringValues::TORCH_FACING_DIRECTION_TOP, - Facing::SOUTH => StringValues::TORCH_FACING_DIRECTION_NORTH, - Facing::NORTH => StringValues::TORCH_FACING_DIRECTION_SOUTH, - Facing::EAST => StringValues::TORCH_FACING_DIRECTION_WEST, - Facing::WEST => StringValues::TORCH_FACING_DIRECTION_EAST, - default => throw new BlockStateSerializeException("Invalid Torch facing $facing") - }); + $this->mapIntToString(BlockStateNames::TORCH_FACING_DIRECTION, ValueMappings::getInstance()->torchFacing, $facing); return $this; } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeBellAttachmentType(BellAttachmentType $attachmentType) : self{ - $this->writeString(BlockStateNames::ATTACHMENT, match($attachmentType){ - BellAttachmentType::FLOOR => StringValues::ATTACHMENT_STANDING, - BellAttachmentType::CEILING => StringValues::ATTACHMENT_HANGING, - BellAttachmentType::ONE_WALL => StringValues::ATTACHMENT_SIDE, - BellAttachmentType::TWO_WALLS => StringValues::ATTACHMENT_MULTIPLE, - }); - return $this; + return $this->writeUnitEnum(BlockStateNames::ATTACHMENT, ValueMappings::getInstance()->bellAttachmentType, $attachmentType); } - /** @return $this */ + /** + * @deprecated + * @return $this + */ public function writeWallConnectionType(string $name, ?WallConnectionType $wallConnectionType) : self{ $this->writeString($name, match($wallConnectionType){ null => StringValues::WALL_CONNECTION_TYPE_EAST_NONE, @@ -276,6 +273,21 @@ final class BlockStateWriter{ return $this; } + /** + * @deprecated + * @phpstan-template TEnum of \UnitEnum + * @phpstan-param EnumFromRawStateMap $map + * @phpstan-param TEnum $case + * + * @return $this + */ + public function writeUnitEnum(string $name, EnumFromRawStateMap $map, \UnitEnum $case) : self{ + $value = $map->valueToRaw($case); + $this->writeString($name, $value); + + return $this; + } + public function getBlockStateData() : BlockStateData{ return BlockStateData::current($this->id, $this->states); } diff --git a/src/data/bedrock/block/convert/FlattenedIdModel.php b/src/data/bedrock/block/convert/FlattenedIdModel.php new file mode 100644 index 000000000..50bf5cdd9 --- /dev/null +++ b/src/data/bedrock/block/convert/FlattenedIdModel.php @@ -0,0 +1,107 @@ +> + */ + private array $idComponents = []; + + /** + * @var Property[] + * @phpstan-var list> + */ + private array $properties = []; + + /** + * @phpstan-param TBlock $block + */ + private function __construct( + private Block $block + ){} + + /** + * @phpstan-template TBlock_ of Block + * @phpstan-param TBlock_ $block + * @return self + */ + public static function create(Block $block) : self{ + /** @phpstan-var self $result */ + $result = new self($block); + return $result; + } + + /** @phpstan-return TBlock */ + public function getBlock() : Block{ return $this->block; } + + /** + * @return string[]|StringProperty[] + * @phpstan-return list> + */ + public function getIdComponents() : array{ return $this->idComponents; } + + /** + * @return Property[] + * @phpstan-return list> + */ + public function getProperties() : array{ return $this->properties; } + + /** + * @param string[]|StringProperty[] $components + * @phpstan-param non-empty-list> $components + * @return $this + * @phpstan-this-out self + */ + public function idComponents(array $components) : self{ + $this->idComponents = $components; + return $this; + } + + /** + * @param Property[] $properties + * @phpstan-param non-empty-list> $properties + * @return $this + * @phpstan-this-out self + */ + public function properties(array $properties) : self{ + $this->properties = $properties; + return $this; + } +} diff --git a/src/data/bedrock/block/convert/Model.php b/src/data/bedrock/block/convert/Model.php new file mode 100644 index 000000000..3474a8932 --- /dev/null +++ b/src/data/bedrock/block/convert/Model.php @@ -0,0 +1,82 @@ +> + */ + private array $properties = []; + + /** + * @phpstan-param TBlock $block + */ + private function __construct( + private Block $block, + private string $id + ){} + + /** @phpstan-return TBlock */ + public function getBlock() : Block{ return $this->block; } + + public function getId() : string{ return $this->id; } + + /** + * @return Property[] + * @phpstan-return list> + */ + public function getProperties() : array{ return $this->properties; } + + /** + * @phpstan-template TBlock_ of Block + * @phpstan-param TBlock_ $block + * @phpstan-return self + */ + public static function create(Block $block, string $id) : self{ + return new self($block, $id); + } + + /** + * @param Property[] $properties + * @phpstan-param list> $properties + * @phpstan-return $this + */ + public function properties(array $properties) : self{ + $this->properties = $properties; + return $this; + } +} diff --git a/src/data/bedrock/block/convert/StringEnumMap.php b/src/data/bedrock/block/convert/StringEnumMap.php deleted file mode 100644 index 6171c0e71..000000000 --- a/src/data/bedrock/block/convert/StringEnumMap.php +++ /dev/null @@ -1,78 +0,0 @@ - - */ - private array $enumToValue = []; - - /** - * @var \UnitEnum[] - * @phpstan-var array - */ - private array $valueToEnum = []; - - /** - * @phpstan-param class-string $class - * @phpstan-param \Closure(TEnum) : string $mapper - */ - public function __construct( - private string $class, - \Closure $mapper - ){ - foreach($class::cases() as $case){ - $string = $mapper($case); - $this->valueToEnum[$string] = $case; - $this->enumToValue[spl_object_id($case)] = $string; - } - } - - /** - * @phpstan-param TEnum $enum - */ - public function enumToValue(\UnitEnum $enum) : string{ - return $this->enumToValue[spl_object_id($enum)]; - } - - public function valueToEnum(string $string) : ?\UnitEnum{ - return $this->valueToEnum[$string] ?? throw new BlockStateDeserializeException("No $this->class enum mapping for \"$string\""); - } - - /** - * @return \UnitEnum[] - * @phpstan-return array - */ - public function getValueToEnum() : array{ - return $this->valueToEnum; - } -} diff --git a/src/data/bedrock/block/convert/ValueMappings.php b/src/data/bedrock/block/convert/ValueMappings.php deleted file mode 100644 index df57d6f53..000000000 --- a/src/data/bedrock/block/convert/ValueMappings.php +++ /dev/null @@ -1,83 +0,0 @@ -, StringEnumMap> - */ - private array $enumMappings = []; - - public function __construct(){ - $this->addEnum(DyeColor::class, fn(DyeColor $case) => match ($case) { - DyeColor::BLACK => "black", - DyeColor::BLUE => "blue", - DyeColor::BROWN => "brown", - DyeColor::CYAN => "cyan", - DyeColor::GRAY => "gray", - DyeColor::GREEN => "green", - DyeColor::LIGHT_BLUE => "light_blue", - DyeColor::LIGHT_GRAY => "light_gray", - DyeColor::LIME => "lime", - DyeColor::MAGENTA => "magenta", - DyeColor::ORANGE => "orange", - DyeColor::PINK => "pink", - DyeColor::PURPLE => "purple", - DyeColor::RED => "red", - DyeColor::WHITE => "white", - DyeColor::YELLOW => "yellow" - }); - } - - /** - * @phpstan-template TEnum of \UnitEnum - * @phpstan-param class-string $class - * @phpstan-param \Closure(TEnum): string $mapper - */ - private function addEnum(string $class, \Closure $mapper) : void{ - $this->enumMappings[$class] = new StringEnumMap($class, $mapper); - } - - /** - * @phpstan-template TEnum of \UnitEnum - * @phpstan-param class-string $class - * @phpstan-return StringEnumMap - */ - public function getEnumMap(string $class) : StringEnumMap{ - if(!isset($this->enumMappings[$class])){ - throw new \InvalidArgumentException("No enum mapping found for class: $class"); - } - /** - * @phpstan-var StringEnumMap $map - */ - $map = $this->enumMappings[$class]; - return $map; - } -} diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php new file mode 100644 index 000000000..16ae1e244 --- /dev/null +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -0,0 +1,1561 @@ +mapSimple(Blocks::AIR(), Ids::AIR); + $reg->mapSimple(Blocks::AMETHYST(), Ids::AMETHYST_BLOCK); + $reg->mapSimple(Blocks::ANCIENT_DEBRIS(), Ids::ANCIENT_DEBRIS); + $reg->mapSimple(Blocks::ANDESITE(), Ids::ANDESITE); + $reg->mapSimple(Blocks::BARRIER(), Ids::BARRIER); + $reg->mapSimple(Blocks::BEACON(), Ids::BEACON); + $reg->mapSimple(Blocks::BLACKSTONE(), Ids::BLACKSTONE); + $reg->mapSimple(Blocks::BLUE_ICE(), Ids::BLUE_ICE); + $reg->mapSimple(Blocks::BOOKSHELF(), Ids::BOOKSHELF); + $reg->mapSimple(Blocks::BRICKS(), Ids::BRICK_BLOCK); + $reg->mapSimple(Blocks::BROWN_MUSHROOM(), Ids::BROWN_MUSHROOM); + $reg->mapSimple(Blocks::BUDDING_AMETHYST(), Ids::BUDDING_AMETHYST); + $reg->mapSimple(Blocks::CALCITE(), Ids::CALCITE); + $reg->mapSimple(Blocks::CARTOGRAPHY_TABLE(), Ids::CARTOGRAPHY_TABLE); + $reg->mapSimple(Blocks::CHEMICAL_HEAT(), Ids::CHEMICAL_HEAT); + $reg->mapSimple(Blocks::CHISELED_DEEPSLATE(), Ids::CHISELED_DEEPSLATE); + $reg->mapSimple(Blocks::CHISELED_NETHER_BRICKS(), Ids::CHISELED_NETHER_BRICKS); + $reg->mapSimple(Blocks::CHISELED_POLISHED_BLACKSTONE(), Ids::CHISELED_POLISHED_BLACKSTONE); + $reg->mapSimple(Blocks::CHISELED_RED_SANDSTONE(), Ids::CHISELED_RED_SANDSTONE); + $reg->mapSimple(Blocks::CHISELED_RESIN_BRICKS(), Ids::CHISELED_RESIN_BRICKS); + $reg->mapSimple(Blocks::CHISELED_SANDSTONE(), Ids::CHISELED_SANDSTONE); + $reg->mapSimple(Blocks::CHISELED_STONE_BRICKS(), Ids::CHISELED_STONE_BRICKS); + $reg->mapSimple(Blocks::CHISELED_TUFF(), Ids::CHISELED_TUFF); + $reg->mapSimple(Blocks::CHISELED_TUFF_BRICKS(), Ids::CHISELED_TUFF_BRICKS); + $reg->mapSimple(Blocks::CHORUS_PLANT(), Ids::CHORUS_PLANT); + $reg->mapSimple(Blocks::CLAY(), Ids::CLAY); + $reg->mapSimple(Blocks::COAL(), Ids::COAL_BLOCK); + $reg->mapSimple(Blocks::COAL_ORE(), Ids::COAL_ORE); + $reg->mapSimple(Blocks::COBBLED_DEEPSLATE(), Ids::COBBLED_DEEPSLATE); + $reg->mapSimple(Blocks::COBBLESTONE(), Ids::COBBLESTONE); + $reg->mapSimple(Blocks::COBWEB(), Ids::WEB); + $reg->mapSimple(Blocks::COPPER_ORE(), Ids::COPPER_ORE); + $reg->mapSimple(Blocks::CRACKED_DEEPSLATE_BRICKS(), Ids::CRACKED_DEEPSLATE_BRICKS); + $reg->mapSimple(Blocks::CRACKED_DEEPSLATE_TILES(), Ids::CRACKED_DEEPSLATE_TILES); + $reg->mapSimple(Blocks::CRACKED_NETHER_BRICKS(), Ids::CRACKED_NETHER_BRICKS); + $reg->mapSimple(Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS(), Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS); + $reg->mapSimple(Blocks::CRACKED_STONE_BRICKS(), Ids::CRACKED_STONE_BRICKS); + $reg->mapSimple(Blocks::CRAFTING_TABLE(), Ids::CRAFTING_TABLE); + $reg->mapSimple(Blocks::CRIMSON_ROOTS(), Ids::CRIMSON_ROOTS); + $reg->mapSimple(Blocks::CRYING_OBSIDIAN(), Ids::CRYING_OBSIDIAN); + $reg->mapSimple(Blocks::DANDELION(), Ids::DANDELION); + $reg->mapSimple(Blocks::CUT_RED_SANDSTONE(), Ids::CUT_RED_SANDSTONE); + $reg->mapSimple(Blocks::CUT_SANDSTONE(), Ids::CUT_SANDSTONE); + $reg->mapSimple(Blocks::DARK_PRISMARINE(), Ids::DARK_PRISMARINE); + $reg->mapSimple(Blocks::DEAD_BUSH(), Ids::DEADBUSH); + $reg->mapSimple(Blocks::DEEPSLATE_BRICKS(), Ids::DEEPSLATE_BRICKS); + $reg->mapSimple(Blocks::DEEPSLATE_COAL_ORE(), Ids::DEEPSLATE_COAL_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_COPPER_ORE(), Ids::DEEPSLATE_COPPER_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_DIAMOND_ORE(), Ids::DEEPSLATE_DIAMOND_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_EMERALD_ORE(), Ids::DEEPSLATE_EMERALD_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_GOLD_ORE(), Ids::DEEPSLATE_GOLD_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_IRON_ORE(), Ids::DEEPSLATE_IRON_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_LAPIS_LAZULI_ORE(), Ids::DEEPSLATE_LAPIS_ORE); + $reg->mapSimple(Blocks::DEEPSLATE_TILES(), Ids::DEEPSLATE_TILES); + $reg->mapSimple(Blocks::DIAMOND(), Ids::DIAMOND_BLOCK); + $reg->mapSimple(Blocks::DIAMOND_ORE(), Ids::DIAMOND_ORE); + $reg->mapSimple(Blocks::DIORITE(), Ids::DIORITE); + $reg->mapSimple(Blocks::DRAGON_EGG(), Ids::DRAGON_EGG); + $reg->mapSimple(Blocks::DRIED_KELP(), Ids::DRIED_KELP_BLOCK); + $reg->mapSimple(Blocks::ELEMENT_ACTINIUM(), Ids::ELEMENT_89); + $reg->mapSimple(Blocks::ELEMENT_ALUMINUM(), Ids::ELEMENT_13); + $reg->mapSimple(Blocks::ELEMENT_AMERICIUM(), Ids::ELEMENT_95); + $reg->mapSimple(Blocks::ELEMENT_ANTIMONY(), Ids::ELEMENT_51); + $reg->mapSimple(Blocks::ELEMENT_ARGON(), Ids::ELEMENT_18); + $reg->mapSimple(Blocks::ELEMENT_ARSENIC(), Ids::ELEMENT_33); + $reg->mapSimple(Blocks::ELEMENT_ASTATINE(), Ids::ELEMENT_85); + $reg->mapSimple(Blocks::ELEMENT_BARIUM(), Ids::ELEMENT_56); + $reg->mapSimple(Blocks::ELEMENT_BERKELIUM(), Ids::ELEMENT_97); + $reg->mapSimple(Blocks::ELEMENT_BERYLLIUM(), Ids::ELEMENT_4); + $reg->mapSimple(Blocks::ELEMENT_BISMUTH(), Ids::ELEMENT_83); + $reg->mapSimple(Blocks::ELEMENT_BOHRIUM(), Ids::ELEMENT_107); + $reg->mapSimple(Blocks::ELEMENT_BORON(), Ids::ELEMENT_5); + $reg->mapSimple(Blocks::ELEMENT_BROMINE(), Ids::ELEMENT_35); + $reg->mapSimple(Blocks::ELEMENT_CADMIUM(), Ids::ELEMENT_48); + $reg->mapSimple(Blocks::ELEMENT_CALCIUM(), Ids::ELEMENT_20); + $reg->mapSimple(Blocks::ELEMENT_CALIFORNIUM(), Ids::ELEMENT_98); + $reg->mapSimple(Blocks::ELEMENT_CARBON(), Ids::ELEMENT_6); + $reg->mapSimple(Blocks::ELEMENT_CERIUM(), Ids::ELEMENT_58); + $reg->mapSimple(Blocks::ELEMENT_CESIUM(), Ids::ELEMENT_55); + $reg->mapSimple(Blocks::ELEMENT_CHLORINE(), Ids::ELEMENT_17); + $reg->mapSimple(Blocks::ELEMENT_CHROMIUM(), Ids::ELEMENT_24); + $reg->mapSimple(Blocks::ELEMENT_COBALT(), Ids::ELEMENT_27); + $reg->mapSimple(Blocks::ELEMENT_COPERNICIUM(), Ids::ELEMENT_112); + $reg->mapSimple(Blocks::ELEMENT_COPPER(), Ids::ELEMENT_29); + $reg->mapSimple(Blocks::ELEMENT_CURIUM(), Ids::ELEMENT_96); + $reg->mapSimple(Blocks::ELEMENT_DARMSTADTIUM(), Ids::ELEMENT_110); + $reg->mapSimple(Blocks::ELEMENT_DUBNIUM(), Ids::ELEMENT_105); + $reg->mapSimple(Blocks::ELEMENT_DYSPROSIUM(), Ids::ELEMENT_66); + $reg->mapSimple(Blocks::ELEMENT_EINSTEINIUM(), Ids::ELEMENT_99); + $reg->mapSimple(Blocks::ELEMENT_ERBIUM(), Ids::ELEMENT_68); + $reg->mapSimple(Blocks::ELEMENT_EUROPIUM(), Ids::ELEMENT_63); + $reg->mapSimple(Blocks::ELEMENT_FERMIUM(), Ids::ELEMENT_100); + $reg->mapSimple(Blocks::ELEMENT_FLEROVIUM(), Ids::ELEMENT_114); + $reg->mapSimple(Blocks::ELEMENT_FLUORINE(), Ids::ELEMENT_9); + $reg->mapSimple(Blocks::ELEMENT_FRANCIUM(), Ids::ELEMENT_87); + $reg->mapSimple(Blocks::ELEMENT_GADOLINIUM(), Ids::ELEMENT_64); + $reg->mapSimple(Blocks::ELEMENT_GALLIUM(), Ids::ELEMENT_31); + $reg->mapSimple(Blocks::ELEMENT_GERMANIUM(), Ids::ELEMENT_32); + $reg->mapSimple(Blocks::ELEMENT_GOLD(), Ids::ELEMENT_79); + $reg->mapSimple(Blocks::ELEMENT_HAFNIUM(), Ids::ELEMENT_72); + $reg->mapSimple(Blocks::ELEMENT_HASSIUM(), Ids::ELEMENT_108); + $reg->mapSimple(Blocks::ELEMENT_HELIUM(), Ids::ELEMENT_2); + $reg->mapSimple(Blocks::ELEMENT_HOLMIUM(), Ids::ELEMENT_67); + $reg->mapSimple(Blocks::ELEMENT_HYDROGEN(), Ids::ELEMENT_1); + $reg->mapSimple(Blocks::ELEMENT_INDIUM(), Ids::ELEMENT_49); + $reg->mapSimple(Blocks::ELEMENT_IODINE(), Ids::ELEMENT_53); + $reg->mapSimple(Blocks::ELEMENT_IRIDIUM(), Ids::ELEMENT_77); + $reg->mapSimple(Blocks::ELEMENT_IRON(), Ids::ELEMENT_26); + $reg->mapSimple(Blocks::ELEMENT_KRYPTON(), Ids::ELEMENT_36); + $reg->mapSimple(Blocks::ELEMENT_LANTHANUM(), Ids::ELEMENT_57); + $reg->mapSimple(Blocks::ELEMENT_LAWRENCIUM(), Ids::ELEMENT_103); + $reg->mapSimple(Blocks::ELEMENT_LEAD(), Ids::ELEMENT_82); + $reg->mapSimple(Blocks::ELEMENT_LITHIUM(), Ids::ELEMENT_3); + $reg->mapSimple(Blocks::ELEMENT_LIVERMORIUM(), Ids::ELEMENT_116); + $reg->mapSimple(Blocks::ELEMENT_LUTETIUM(), Ids::ELEMENT_71); + $reg->mapSimple(Blocks::ELEMENT_MAGNESIUM(), Ids::ELEMENT_12); + $reg->mapSimple(Blocks::ELEMENT_MANGANESE(), Ids::ELEMENT_25); + $reg->mapSimple(Blocks::ELEMENT_MEITNERIUM(), Ids::ELEMENT_109); + $reg->mapSimple(Blocks::ELEMENT_MENDELEVIUM(), Ids::ELEMENT_101); + $reg->mapSimple(Blocks::ELEMENT_MERCURY(), Ids::ELEMENT_80); + $reg->mapSimple(Blocks::ELEMENT_MOLYBDENUM(), Ids::ELEMENT_42); + $reg->mapSimple(Blocks::ELEMENT_MOSCOVIUM(), Ids::ELEMENT_115); + $reg->mapSimple(Blocks::ELEMENT_NEODYMIUM(), Ids::ELEMENT_60); + $reg->mapSimple(Blocks::ELEMENT_NEON(), Ids::ELEMENT_10); + $reg->mapSimple(Blocks::ELEMENT_NEPTUNIUM(), Ids::ELEMENT_93); + $reg->mapSimple(Blocks::ELEMENT_NICKEL(), Ids::ELEMENT_28); + $reg->mapSimple(Blocks::ELEMENT_NIHONIUM(), Ids::ELEMENT_113); + $reg->mapSimple(Blocks::ELEMENT_NIOBIUM(), Ids::ELEMENT_41); + $reg->mapSimple(Blocks::ELEMENT_NITROGEN(), Ids::ELEMENT_7); + $reg->mapSimple(Blocks::ELEMENT_NOBELIUM(), Ids::ELEMENT_102); + $reg->mapSimple(Blocks::ELEMENT_OGANESSON(), Ids::ELEMENT_118); + $reg->mapSimple(Blocks::ELEMENT_OSMIUM(), Ids::ELEMENT_76); + $reg->mapSimple(Blocks::ELEMENT_OXYGEN(), Ids::ELEMENT_8); + $reg->mapSimple(Blocks::ELEMENT_PALLADIUM(), Ids::ELEMENT_46); + $reg->mapSimple(Blocks::ELEMENT_PHOSPHORUS(), Ids::ELEMENT_15); + $reg->mapSimple(Blocks::ELEMENT_PLATINUM(), Ids::ELEMENT_78); + $reg->mapSimple(Blocks::ELEMENT_PLUTONIUM(), Ids::ELEMENT_94); + $reg->mapSimple(Blocks::ELEMENT_POLONIUM(), Ids::ELEMENT_84); + $reg->mapSimple(Blocks::ELEMENT_POTASSIUM(), Ids::ELEMENT_19); + $reg->mapSimple(Blocks::ELEMENT_PRASEODYMIUM(), Ids::ELEMENT_59); + $reg->mapSimple(Blocks::ELEMENT_PROMETHIUM(), Ids::ELEMENT_61); + $reg->mapSimple(Blocks::ELEMENT_PROTACTINIUM(), Ids::ELEMENT_91); + $reg->mapSimple(Blocks::ELEMENT_RADIUM(), Ids::ELEMENT_88); + $reg->mapSimple(Blocks::ELEMENT_RADON(), Ids::ELEMENT_86); + $reg->mapSimple(Blocks::ELEMENT_RHENIUM(), Ids::ELEMENT_75); + $reg->mapSimple(Blocks::ELEMENT_RHODIUM(), Ids::ELEMENT_45); + $reg->mapSimple(Blocks::ELEMENT_ROENTGENIUM(), Ids::ELEMENT_111); + $reg->mapSimple(Blocks::ELEMENT_RUBIDIUM(), Ids::ELEMENT_37); + $reg->mapSimple(Blocks::ELEMENT_RUTHENIUM(), Ids::ELEMENT_44); + $reg->mapSimple(Blocks::ELEMENT_RUTHERFORDIUM(), Ids::ELEMENT_104); + $reg->mapSimple(Blocks::ELEMENT_SAMARIUM(), Ids::ELEMENT_62); + $reg->mapSimple(Blocks::ELEMENT_SCANDIUM(), Ids::ELEMENT_21); + $reg->mapSimple(Blocks::ELEMENT_SEABORGIUM(), Ids::ELEMENT_106); + $reg->mapSimple(Blocks::ELEMENT_SELENIUM(), Ids::ELEMENT_34); + $reg->mapSimple(Blocks::ELEMENT_SILICON(), Ids::ELEMENT_14); + $reg->mapSimple(Blocks::ELEMENT_SILVER(), Ids::ELEMENT_47); + $reg->mapSimple(Blocks::ELEMENT_SODIUM(), Ids::ELEMENT_11); + $reg->mapSimple(Blocks::ELEMENT_STRONTIUM(), Ids::ELEMENT_38); + $reg->mapSimple(Blocks::ELEMENT_SULFUR(), Ids::ELEMENT_16); + $reg->mapSimple(Blocks::ELEMENT_TANTALUM(), Ids::ELEMENT_73); + $reg->mapSimple(Blocks::ELEMENT_TECHNETIUM(), Ids::ELEMENT_43); + $reg->mapSimple(Blocks::ELEMENT_TELLURIUM(), Ids::ELEMENT_52); + $reg->mapSimple(Blocks::ELEMENT_TENNESSINE(), Ids::ELEMENT_117); + $reg->mapSimple(Blocks::ELEMENT_TERBIUM(), Ids::ELEMENT_65); + $reg->mapSimple(Blocks::ELEMENT_THALLIUM(), Ids::ELEMENT_81); + $reg->mapSimple(Blocks::ELEMENT_THORIUM(), Ids::ELEMENT_90); + $reg->mapSimple(Blocks::ELEMENT_THULIUM(), Ids::ELEMENT_69); + $reg->mapSimple(Blocks::ELEMENT_TIN(), Ids::ELEMENT_50); + $reg->mapSimple(Blocks::ELEMENT_TITANIUM(), Ids::ELEMENT_22); + $reg->mapSimple(Blocks::ELEMENT_TUNGSTEN(), Ids::ELEMENT_74); + $reg->mapSimple(Blocks::ELEMENT_URANIUM(), Ids::ELEMENT_92); + $reg->mapSimple(Blocks::ELEMENT_VANADIUM(), Ids::ELEMENT_23); + $reg->mapSimple(Blocks::ELEMENT_XENON(), Ids::ELEMENT_54); + $reg->mapSimple(Blocks::ELEMENT_YTTERBIUM(), Ids::ELEMENT_70); + $reg->mapSimple(Blocks::ELEMENT_YTTRIUM(), Ids::ELEMENT_39); + $reg->mapSimple(Blocks::ELEMENT_ZERO(), Ids::ELEMENT_0); + $reg->mapSimple(Blocks::ELEMENT_ZINC(), Ids::ELEMENT_30); + $reg->mapSimple(Blocks::ELEMENT_ZIRCONIUM(), Ids::ELEMENT_40); + $reg->mapSimple(Blocks::EMERALD(), Ids::EMERALD_BLOCK); + $reg->mapSimple(Blocks::EMERALD_ORE(), Ids::EMERALD_ORE); + $reg->mapSimple(Blocks::ENCHANTING_TABLE(), Ids::ENCHANTING_TABLE); + $reg->mapSimple(Blocks::END_STONE(), Ids::END_STONE); + $reg->mapSimple(Blocks::END_STONE_BRICKS(), Ids::END_BRICKS); + $reg->mapSimple(Blocks::FERN(), Ids::FERN); + $reg->mapSimple(Blocks::FLETCHING_TABLE(), Ids::FLETCHING_TABLE); + $reg->mapSimple(Blocks::GILDED_BLACKSTONE(), Ids::GILDED_BLACKSTONE); + $reg->mapSimple(Blocks::GLASS(), Ids::GLASS); + $reg->mapSimple(Blocks::GLASS_PANE(), Ids::GLASS_PANE); + $reg->mapSimple(Blocks::GLOWING_OBSIDIAN(), Ids::GLOWINGOBSIDIAN); + $reg->mapSimple(Blocks::GLOWSTONE(), Ids::GLOWSTONE); + $reg->mapSimple(Blocks::GOLD(), Ids::GOLD_BLOCK); + $reg->mapSimple(Blocks::GOLD_ORE(), Ids::GOLD_ORE); + $reg->mapSimple(Blocks::GRANITE(), Ids::GRANITE); + $reg->mapSimple(Blocks::GRASS(), Ids::GRASS_BLOCK); + $reg->mapSimple(Blocks::GRASS_PATH(), Ids::GRASS_PATH); + $reg->mapSimple(Blocks::GRAVEL(), Ids::GRAVEL); + $reg->mapSimple(Blocks::HANGING_ROOTS(), Ids::HANGING_ROOTS); + $reg->mapSimple(Blocks::HARDENED_CLAY(), Ids::HARDENED_CLAY); + $reg->mapSimple(Blocks::HARDENED_GLASS(), Ids::HARD_GLASS); + $reg->mapSimple(Blocks::HARDENED_GLASS_PANE(), Ids::HARD_GLASS_PANE); + $reg->mapSimple(Blocks::HONEYCOMB(), Ids::HONEYCOMB_BLOCK); + $reg->mapSimple(Blocks::ICE(), Ids::ICE); + $reg->mapSimple(Blocks::INFESTED_CHISELED_STONE_BRICK(), Ids::INFESTED_CHISELED_STONE_BRICKS); + $reg->mapSimple(Blocks::INFESTED_COBBLESTONE(), Ids::INFESTED_COBBLESTONE); + $reg->mapSimple(Blocks::INFESTED_CRACKED_STONE_BRICK(), Ids::INFESTED_CRACKED_STONE_BRICKS); + $reg->mapSimple(Blocks::INFESTED_MOSSY_STONE_BRICK(), Ids::INFESTED_MOSSY_STONE_BRICKS); + $reg->mapSimple(Blocks::INFESTED_STONE(), Ids::INFESTED_STONE); + $reg->mapSimple(Blocks::INFESTED_STONE_BRICK(), Ids::INFESTED_STONE_BRICKS); + $reg->mapSimple(Blocks::INFO_UPDATE(), Ids::INFO_UPDATE); + $reg->mapSimple(Blocks::INFO_UPDATE2(), Ids::INFO_UPDATE2); + $reg->mapSimple(Blocks::INVISIBLE_BEDROCK(), Ids::INVISIBLE_BEDROCK); + $reg->mapSimple(Blocks::IRON(), Ids::IRON_BLOCK); + $reg->mapSimple(Blocks::IRON_BARS(), Ids::IRON_BARS); + $reg->mapSimple(Blocks::IRON_ORE(), Ids::IRON_ORE); + $reg->mapSimple(Blocks::JUKEBOX(), Ids::JUKEBOX); + $reg->mapSimple(Blocks::LAPIS_LAZULI(), Ids::LAPIS_BLOCK); + $reg->mapSimple(Blocks::LAPIS_LAZULI_ORE(), Ids::LAPIS_ORE); + $reg->mapSimple(Blocks::LEGACY_STONECUTTER(), Ids::STONECUTTER); + $reg->mapSimple(Blocks::LILY_PAD(), Ids::WATERLILY); + $reg->mapSimple(Blocks::MAGMA(), Ids::MAGMA); + $reg->mapSimple(Blocks::MANGROVE_ROOTS(), Ids::MANGROVE_ROOTS); + $reg->mapSimple(Blocks::MELON(), Ids::MELON_BLOCK); + $reg->mapSimple(Blocks::MONSTER_SPAWNER(), Ids::MOB_SPAWNER); + $reg->mapSimple(Blocks::MOSSY_COBBLESTONE(), Ids::MOSSY_COBBLESTONE); + $reg->mapSimple(Blocks::MOSSY_STONE_BRICKS(), Ids::MOSSY_STONE_BRICKS); + $reg->mapSimple(Blocks::MUD(), Ids::MUD); + $reg->mapSimple(Blocks::MUD_BRICKS(), Ids::MUD_BRICKS); + $reg->mapSimple(Blocks::MYCELIUM(), Ids::MYCELIUM); + $reg->mapSimple(Blocks::NETHERITE(), Ids::NETHERITE_BLOCK); + $reg->mapSimple(Blocks::NETHERRACK(), Ids::NETHERRACK); + $reg->mapSimple(Blocks::NETHER_BRICKS(), Ids::NETHER_BRICK); + $reg->mapSimple(Blocks::NETHER_BRICK_FENCE(), Ids::NETHER_BRICK_FENCE); + $reg->mapSimple(Blocks::NETHER_GOLD_ORE(), Ids::NETHER_GOLD_ORE); + $reg->mapSimple(Blocks::NETHER_QUARTZ_ORE(), Ids::QUARTZ_ORE); + $reg->mapSimple(Blocks::NETHER_REACTOR_CORE(), Ids::NETHERREACTOR); + $reg->mapSimple(Blocks::NETHER_WART_BLOCK(), Ids::NETHER_WART_BLOCK); + $reg->mapSimple(Blocks::NOTE_BLOCK(), Ids::NOTEBLOCK); + $reg->mapSimple(Blocks::OBSIDIAN(), Ids::OBSIDIAN); + $reg->mapSimple(Blocks::PACKED_ICE(), Ids::PACKED_ICE); + $reg->mapSimple(Blocks::PACKED_MUD(), Ids::PACKED_MUD); + $reg->mapSimple(Blocks::PODZOL(), Ids::PODZOL); + $reg->mapSimple(Blocks::POLISHED_ANDESITE(), Ids::POLISHED_ANDESITE); + $reg->mapSimple(Blocks::POLISHED_BLACKSTONE(), Ids::POLISHED_BLACKSTONE); + $reg->mapSimple(Blocks::POLISHED_BLACKSTONE_BRICKS(), Ids::POLISHED_BLACKSTONE_BRICKS); + $reg->mapSimple(Blocks::POLISHED_DEEPSLATE(), Ids::POLISHED_DEEPSLATE); + $reg->mapSimple(Blocks::POLISHED_DIORITE(), Ids::POLISHED_DIORITE); + $reg->mapSimple(Blocks::POLISHED_GRANITE(), Ids::POLISHED_GRANITE); + $reg->mapSimple(Blocks::POLISHED_TUFF(), Ids::POLISHED_TUFF); + $reg->mapSimple(Blocks::PRISMARINE(), Ids::PRISMARINE); + $reg->mapSimple(Blocks::PRISMARINE_BRICKS(), Ids::PRISMARINE_BRICKS); + $reg->mapSimple(Blocks::QUARTZ_BRICKS(), Ids::QUARTZ_BRICKS); + $reg->mapSimple(Blocks::RAW_COPPER(), Ids::RAW_COPPER_BLOCK); + $reg->mapSimple(Blocks::RAW_GOLD(), Ids::RAW_GOLD_BLOCK); + $reg->mapSimple(Blocks::RAW_IRON(), Ids::RAW_IRON_BLOCK); + $reg->mapSimple(Blocks::REDSTONE(), Ids::REDSTONE_BLOCK); + $reg->mapSimple(Blocks::RED_MUSHROOM(), Ids::RED_MUSHROOM); + $reg->mapSimple(Blocks::RED_NETHER_BRICKS(), Ids::RED_NETHER_BRICK); + $reg->mapSimple(Blocks::RED_SAND(), Ids::RED_SAND); + $reg->mapSimple(Blocks::RED_SANDSTONE(), Ids::RED_SANDSTONE); + $reg->mapSimple(Blocks::REINFORCED_DEEPSLATE(), Ids::REINFORCED_DEEPSLATE); + $reg->mapSimple(Blocks::RESERVED6(), Ids::RESERVED6); + $reg->mapSimple(Blocks::RESIN(), Ids::RESIN_BLOCK); + $reg->mapSimple(Blocks::RESIN_BRICKS(), Ids::RESIN_BRICKS); + $reg->mapSimple(Blocks::SAND(), Ids::SAND); + $reg->mapSimple(Blocks::SANDSTONE(), Ids::SANDSTONE); + $reg->mapSimple(Blocks::SCULK(), Ids::SCULK); + $reg->mapSimple(Blocks::SEA_LANTERN(), Ids::SEA_LANTERN); + $reg->mapSimple(Blocks::SHROOMLIGHT(), Ids::SHROOMLIGHT); + $reg->mapSimple(Blocks::SHULKER_BOX(), Ids::UNDYED_SHULKER_BOX); + $reg->mapSimple(Blocks::SLIME(), Ids::SLIME); + $reg->mapSimple(Blocks::SMITHING_TABLE(), Ids::SMITHING_TABLE); + $reg->mapSimple(Blocks::SMOOTH_BASALT(), Ids::SMOOTH_BASALT); + $reg->mapSimple(Blocks::SMOOTH_RED_SANDSTONE(), Ids::SMOOTH_RED_SANDSTONE); + $reg->mapSimple(Blocks::SMOOTH_SANDSTONE(), Ids::SMOOTH_SANDSTONE); + $reg->mapSimple(Blocks::SMOOTH_STONE(), Ids::SMOOTH_STONE); + $reg->mapSimple(Blocks::SNOW(), Ids::SNOW); + $reg->mapSimple(Blocks::SOUL_SAND(), Ids::SOUL_SAND); + $reg->mapSimple(Blocks::SOUL_SOIL(), Ids::SOUL_SOIL); + $reg->mapSimple(Blocks::SPORE_BLOSSOM(), Ids::SPORE_BLOSSOM); + $reg->mapSimple(Blocks::STONE(), Ids::STONE); + $reg->mapSimple(Blocks::STONE_BRICKS(), Ids::STONE_BRICKS); + $reg->mapSimple(Blocks::TALL_GRASS(), Ids::SHORT_GRASS); //no, this is not a typo - tall_grass is now the double block, just to be confusing :( + $reg->mapSimple(Blocks::TINTED_GLASS(), Ids::TINTED_GLASS); + $reg->mapSimple(Blocks::TORCHFLOWER(), Ids::TORCHFLOWER); + $reg->mapSimple(Blocks::TUFF(), Ids::TUFF); + $reg->mapSimple(Blocks::TUFF_BRICKS(), Ids::TUFF_BRICKS); + $reg->mapSimple(Blocks::WARPED_WART_BLOCK(), Ids::WARPED_WART_BLOCK); + $reg->mapSimple(Blocks::WARPED_ROOTS(), Ids::WARPED_ROOTS); + $reg->mapSimple(Blocks::WITHER_ROSE(), Ids::WITHER_ROSE); + + $reg->mapSimple(Blocks::ALLIUM(), Ids::ALLIUM); + $reg->mapSimple(Blocks::CORNFLOWER(), Ids::CORNFLOWER); + $reg->mapSimple(Blocks::AZURE_BLUET(), Ids::AZURE_BLUET); + $reg->mapSimple(Blocks::LILY_OF_THE_VALLEY(), Ids::LILY_OF_THE_VALLEY); + $reg->mapSimple(Blocks::BLUE_ORCHID(), Ids::BLUE_ORCHID); + $reg->mapSimple(Blocks::OXEYE_DAISY(), Ids::OXEYE_DAISY); + $reg->mapSimple(Blocks::POPPY(), Ids::POPPY); + $reg->mapSimple(Blocks::ORANGE_TULIP(), Ids::ORANGE_TULIP); + $reg->mapSimple(Blocks::PINK_TULIP(), Ids::PINK_TULIP); + $reg->mapSimple(Blocks::RED_TULIP(), Ids::RED_TULIP); + $reg->mapSimple(Blocks::WHITE_TULIP(), Ids::WHITE_TULIP); + } + + private static function registerColoredMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + $reg->mapColored(Blocks::STAINED_HARDENED_GLASS(), "minecraft:hard_", "_stained_glass"); + $reg->mapColored(Blocks::STAINED_HARDENED_GLASS_PANE(), "minecraft:hard_", "_stained_glass_pane"); + + $reg->mapColored(Blocks::CARPET(), "minecraft:", "_carpet"); + $reg->mapColored(Blocks::CONCRETE(), "minecraft:", "_concrete"); + $reg->mapColored(Blocks::CONCRETE_POWDER(), "minecraft:", "_concrete_powder"); + $reg->mapColored(Blocks::DYED_SHULKER_BOX(), "minecraft:", "_shulker_box"); + $reg->mapColored(Blocks::STAINED_CLAY(), "minecraft:", "_terracotta"); + $reg->mapColored(Blocks::STAINED_GLASS(), "minecraft:", "_stained_glass"); + $reg->mapColored(Blocks::STAINED_GLASS_PANE(), "minecraft:", "_stained_glass_pane"); + $reg->mapColored(Blocks::WOOL(), "minecraft:", "_wool"); + + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::GLAZED_TERRACOTTA()) + ->idComponents([ + "minecraft:", + new ValueFromStringProperty("color", ValueMappings::getInstance()->dyeColorWithSilver, fn(GlazedTerracotta $b) => $b->getColor(), fn(GlazedTerracotta $b, DyeColor $v) => $b->setColor($v)), + "_glazed_terracotta" + ]) + ->properties([$commonProperties->horizontalFacingClassic]) + ); + } + + private static function registerCandleMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + $candleProperties = [ + $commonProperties->lit, + new IntProperty(StateNames::CANDLES, 0, 3, fn(Candle $b) => $b->getCount(), fn(Candle $b, int $v) => $b->setCount($v), offset: 1), + ]; + $cakeWithCandleProperties = [$commonProperties->lit]; + $reg->mapModel(Model::create(Blocks::CANDLE(), Ids::CANDLE)->properties($candleProperties)); + $reg->mapModel(Model::create(Blocks::CAKE_WITH_CANDLE(), Ids::CANDLE_CAKE)->properties($cakeWithCandleProperties)); + + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::DYED_CANDLE()) + ->idComponents([ + "minecraft:", + $commonProperties->dyeColorIdInfix, + "_candle" + ]) + ->properties($candleProperties) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CAKE_WITH_DYED_CANDLE()) + ->idComponents([ + "minecraft:", + $commonProperties->dyeColorIdInfix, + "_candle_cake" + ]) + ->properties($cakeWithCandleProperties) + ); + } + + private static function registerLeavesMappings(BlockSerializerDeserializerRegistrar $reg) : void{ + $properties = [ + new BoolProperty(StateNames::PERSISTENT_BIT, fn(Leaves $b) => $b->isNoDecay(), fn(Leaves $b, bool $v) => $b->setNoDecay($v)), + new BoolProperty(StateNames::UPDATE_BIT, fn(Leaves $b) => $b->isCheckDecay(), fn(Leaves $b, bool $v) => $b->setCheckDecay($v)), + ]; + foreach([ + Ids::ACACIA_LEAVES => Blocks::ACACIA_LEAVES(), + Ids::AZALEA_LEAVES => Blocks::AZALEA_LEAVES(), + Ids::AZALEA_LEAVES_FLOWERED => Blocks::FLOWERING_AZALEA_LEAVES(), + Ids::BIRCH_LEAVES => Blocks::BIRCH_LEAVES(), + Ids::CHERRY_LEAVES => Blocks::CHERRY_LEAVES(), + Ids::DARK_OAK_LEAVES => Blocks::DARK_OAK_LEAVES(), + Ids::JUNGLE_LEAVES => Blocks::JUNGLE_LEAVES(), + Ids::MANGROVE_LEAVES => Blocks::MANGROVE_LEAVES(), + Ids::OAK_LEAVES => Blocks::OAK_LEAVES(), + Ids::PALE_OAK_LEAVES => Blocks::PALE_OAK_LEAVES(), + Ids::SPRUCE_LEAVES => Blocks::SPRUCE_LEAVES() + ] as $id => $block){ + $reg->mapModel(Model::create($block, $id)->properties($properties)); + } + } + + private static function registerSaplingMappings(BlockSerializerDeserializerRegistrar $reg) : void{ + $properties = [ + new BoolProperty(StateNames::AGE_BIT, fn(Sapling $b) => $b->isReady(), fn(Sapling $b, bool $v) => $b->setReady($v)), + ]; + foreach([ + Ids::ACACIA_SAPLING => Blocks::ACACIA_SAPLING(), + Ids::BIRCH_SAPLING => Blocks::BIRCH_SAPLING(), + Ids::DARK_OAK_SAPLING => Blocks::DARK_OAK_SAPLING(), + Ids::JUNGLE_SAPLING => Blocks::JUNGLE_SAPLING(), + Ids::OAK_SAPLING => Blocks::OAK_SAPLING(), + Ids::SPRUCE_SAPLING => Blocks::SPRUCE_SAPLING(), + ] as $id => $block){ + $reg->mapModel(Model::create($block, $id)->properties($properties)); + } + } + + private static function registerPlantMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + $reg->mapModel(Model::create(Blocks::BEETROOTS(), Ids::BEETROOT)->properties([$commonProperties->cropAgeMax7])); + $reg->mapModel(Model::create(Blocks::CARROTS(), Ids::CARROTS)->properties([$commonProperties->cropAgeMax7])); + $reg->mapModel(Model::create(Blocks::POTATOES(), Ids::POTATOES)->properties([$commonProperties->cropAgeMax7])); + $reg->mapModel(Model::create(Blocks::WHEAT(), Ids::WHEAT)->properties([$commonProperties->cropAgeMax7])); + + $reg->mapModel(Model::create(Blocks::MELON_STEM(), Ids::MELON_STEM)->properties($commonProperties->stemProperties)); + $reg->mapModel(Model::create(Blocks::PUMPKIN_STEM(), Ids::PUMPKIN_STEM)->properties($commonProperties->stemProperties)); + + foreach([ + [Blocks::DOUBLE_TALLGRASS(), Ids::TALL_GRASS], + [Blocks::LARGE_FERN(), Ids::LARGE_FERN], + [Blocks::LILAC(), Ids::LILAC], + [Blocks::PEONY(), Ids::PEONY], + [Blocks::ROSE_BUSH(), Ids::ROSE_BUSH], + [Blocks::SUNFLOWER(), Ids::SUNFLOWER], + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties([$commonProperties->doublePlantHalf])); + } + + foreach([ + [Blocks::BROWN_MUSHROOM_BLOCK(), Ids::BROWN_MUSHROOM_BLOCK], + [Blocks::RED_MUSHROOM_BLOCK(), Ids::RED_MUSHROOM_BLOCK] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties([ + new ValueFromIntProperty(StateNames::HUGE_MUSHROOM_BITS, ValueMappings::getInstance()->mushroomBlockType, fn(RedMushroomBlock $b) => $b->getMushroomBlockType(), fn(RedMushroomBlock $b, MushroomBlockType $v) => $b->setMushroomBlockType($v)), + ])); + } + + $reg->mapModel(Model::create(Blocks::GLOW_LICHEN(), Ids::GLOW_LICHEN)->properties([$commonProperties->multiFacingFlags])); + $reg->mapModel(Model::create(Blocks::RESIN_CLUMP(), Ids::RESIN_CLUMP)->properties([$commonProperties->multiFacingFlags])); + + $reg->mapModel(Model::create(Blocks::VINES(), Ids::VINE)->properties([ + new OptionSetFromIntProperty( + StateNames::VINE_DIRECTION_BITS, + IntFromRawStateMap::int([ + Facing::NORTH => BlockLegacyMetadata::VINE_FLAG_NORTH, + Facing::SOUTH => BlockLegacyMetadata::VINE_FLAG_SOUTH, + Facing::WEST => BlockLegacyMetadata::VINE_FLAG_WEST, + Facing::EAST => BlockLegacyMetadata::VINE_FLAG_EAST, + ]), + fn(Vine $b) => $b->getFaces(), + fn(Vine $b, array $v) => $b->setFaces($v) + ) + ])); + + $reg->mapModel(Model::create(Blocks::SWEET_BERRY_BUSH(), Ids::SWEET_BERRY_BUSH)->properties([ + //TODO: berry bush only wants 0-3, but it can be bigger in MCPE due to misuse of GROWTH state which goes up to 7 + new IntProperty(StateNames::GROWTH, 0, 7, fn(SweetBerryBush $b) => $b->getAge(), fn(SweetBerryBush $b, int $v) => $b->setAge(min($v, SweetBerryBush::STAGE_MATURE))) + ])); + $reg->mapModel(Model::create(Blocks::TORCHFLOWER_CROP(), Ids::TORCHFLOWER_CROP)->properties([ + //TODO: this property can have values 0-7, but only 0-1 are valid + new IntProperty(StateNames::GROWTH, 0, 7, fn(TorchflowerCrop $b) => $b->isReady() ? 1 : 0, fn(TorchflowerCrop $b, int $v) => $b->setReady($v !== 0)) + ])); + } + + private static function registerCoralMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CORAL())->idComponents([...$commonProperties->coralIdPrefixes, "_coral"])); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CORAL_BLOCK())->idComponents([...$commonProperties->coralIdPrefixes, "_coral_block"])); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CORAL_FAN()) + ->idComponents([...$commonProperties->coralIdPrefixes, "_coral_fan"]) + ->properties([ + new ValueFromIntProperty(StateNames::CORAL_FAN_DIRECTION, ValueMappings::getInstance()->coralAxis, fn(FloorCoralFan $b) => $b->getAxis(), fn(FloorCoralFan $b, int $v) => $b->setAxis($v)) + ]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::WALL_CORAL_FAN()) + ->idComponents([...$commonProperties->coralIdPrefixes, "_coral_wall_fan"]) + ->properties([ + new ValueFromIntProperty(StateNames::CORAL_DIRECTION, ValueMappings::getInstance()->horizontalFacingCoral, fn(HorizontalFacing $b) => $b->getFacing(), fn(HorizontalFacing $b, int $v) => $b->setFacing($v)), + ]) + ); + } + + private static function registerCopperMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::COPPER_BULB()) + ->idComponents([...$commonProperties->copperIdPrefixes, "copper_bulb"]) + ->properties([ + $commonProperties->lit, + new BoolProperty(StateNames::POWERED_BIT, fn(PoweredByRedstone $b) => $b->isPowered(), fn(PoweredByRedstone $b, bool $v) => $b->setPowered($v)), + ]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::COPPER()) + ->idComponents([ + ...$commonProperties->copperIdPrefixes, + "copper", + //HACK: the non-waxed, non-oxidised variant has a _block suffix, but none of the others do + new BoolFromStringProperty("bruhhhh", "", "_block", fn(Copper $b) => !$b->isWaxed() && $b->getOxidation() === CopperOxidation::NONE, fn() => null) + ]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CHISELED_COPPER())->idComponents([...$commonProperties->copperIdPrefixes, "chiseled_copper"])); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::COPPER_GRATE())->idComponents([...$commonProperties->copperIdPrefixes, "copper_grate"])); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CUT_COPPER())->idComponents([...$commonProperties->copperIdPrefixes, "cut_copper"])); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CUT_COPPER_STAIRS()) + ->idComponents([...$commonProperties->copperIdPrefixes, "cut_copper_stairs"]) + ->properties($commonProperties->stairProperties) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::COPPER_TRAPDOOR()) + ->idComponents([...$commonProperties->copperIdPrefixes, "copper_trapdoor"]) + ->properties($commonProperties->trapdoorProperties) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::COPPER_DOOR()) + ->idComponents([...$commonProperties->copperIdPrefixes, "copper_door"]) + ->properties($commonProperties->doorProperties) + ); + + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CUT_COPPER_SLAB()) + ->idComponents([ + ...$commonProperties->copperIdPrefixes, + $commonProperties->slabIdInfix, + "cut_copper_slab" + ]) + ->properties([$commonProperties->slabPositionProperty]) + ); + } + + private static function registerFlattenedEnumMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + //A + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::ANVIL()) + ->idComponents([ + new ValueFromStringProperty("id", IntFromRawStateMap::string([ + 0 => Ids::ANVIL, + 1 => Ids::CHIPPED_ANVIL, + 2 => Ids::DAMAGED_ANVIL, + ]), fn(Anvil $b) => $b->getDamage(), fn(Anvil $b, int $v) => $b->setDamage($v)) + ]) + ->properties([$commonProperties->horizontalFacingCardinal]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::AMETHYST_CLUSTER()) + ->idComponents([ + new ValueFromStringProperty("id", IntFromRawStateMap::string([ + AmethystCluster::STAGE_SMALL_BUD => Ids::SMALL_AMETHYST_BUD, + AmethystCluster::STAGE_MEDIUM_BUD => Ids::MEDIUM_AMETHYST_BUD, + AmethystCluster::STAGE_LARGE_BUD => Ids::LARGE_AMETHYST_BUD, + AmethystCluster::STAGE_CLUSTER => Ids::AMETHYST_CLUSTER + ]), fn(AmethystCluster $b) => $b->getStage(), fn(AmethystCluster $b, int $v) => $b->setStage($v)) + ]) + ->properties([$commonProperties->blockFace]) + ); + + //C + //This one is a special offender :< + //I have no idea why this only has 3 IDs - there are 4 in Java and 4 visually distinct states in Bedrock + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::CAVE_VINES()) + ->idComponents([ + "minecraft:cave_vines", + new ValueFromStringProperty( + "variant", + EnumFromRawStateMap::string(FlattenedCaveVinesVariant::class, fn(FlattenedCaveVinesVariant $case) => $case->value), + fn(CaveVines $b) => $b->hasBerries() ? + ($b->isHead() ? + FlattenedCaveVinesVariant::HEAD_WITH_BERRIES : + FlattenedCaveVinesVariant::BODY_WITH_BERRIES) : + FlattenedCaveVinesVariant::NO_BERRIES, + fn(CaveVines $b, FlattenedCaveVinesVariant $v) => match($v){ + FlattenedCaveVinesVariant::HEAD_WITH_BERRIES => $b->setBerries(true)->setHead(true), + FlattenedCaveVinesVariant::BODY_WITH_BERRIES => $b->setBerries(true)->setHead(false), + FlattenedCaveVinesVariant::NO_BERRIES => $b->setBerries(false)->setHead(false), //assume this isn't a head, since we don't have enough information + } + ) + ]) + ->properties([ + new IntProperty(StateNames::GROWING_PLANT_AGE, 0, 25, fn(CaveVines $b) => $b->getAge(), fn(CaveVines $b, int $v) => $b->setAge($v)), + ]) + ); + + //D + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::DIRT()) + ->idComponents([ + new ValueFromStringProperty("id", EnumFromRawStateMap::string(DirtType::class, fn(DirtType $case) => match ($case) { + DirtType::NORMAL => Ids::DIRT, + DirtType::COARSE => Ids::COARSE_DIRT, + DirtType::ROOTED => Ids::DIRT_WITH_ROOTS, + }), fn(Dirt $b) => $b->getDirtType(), fn(Dirt $b, DirtType $v) => $b->setDirtType($v)) + ]) + ); + + //F + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::FROGLIGHT()) + ->idComponents([ + new ValueFromStringProperty("id", ValueMappings::getInstance()->froglightType, fn(Froglight $b) => $b->getFroglightType(), fn(Froglight $b, FroglightType $v) => $b->setFroglightType($v)), + ]) + ->properties([$commonProperties->pillarAxis]) + ); + + //L + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::LIGHT()) + ->idComponents([ + "minecraft:light_block_", + //this is a bit shit but it's easier than adapting IntProperty to support flattening :D + new ValueFromStringProperty( + "light_level", + IntFromRawStateMap::string(array_map(strval(...), range(0, 15))), + fn(Light $b) => $b->getLightLevel(), + fn(Light $b, int $v) => $b->setLightLevel($v) + ) + ]) + ); + + //M + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::MOB_HEAD()) + ->idComponents([ + new ValueFromStringProperty("id", ValueMappings::getInstance()->mobHeadType, fn(MobHead $b) => $b->getMobHeadType(), fn(MobHead $b, MobHeadType $v) => $b->setMobHeadType($v)), + ]) + ->properties([ + new ValueFromIntProperty(StateNames::FACING_DIRECTION, ValueMappings::getInstance()->facingExceptDown, fn(MobHead $b) => $b->getFacing(), fn(MobHead $b, int $v) => $b->setFacing($v)) + ]) + ); + + foreach([ + [Blocks::LAVA(), "lava"], + [Blocks::WATER(), "water"] + ] as [$block, $idSuffix]){ + $reg->mapFlattenedId(FlattenedIdModel::create($block) + ->idComponents([...$commonProperties->liquidIdPrefixes, $idSuffix]) + ->properties([$commonProperties->liquidData]) + ); + } + } + + private static function registerFlattenedBoolMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + foreach([ + [Blocks::BLAST_FURNACE(), "blast_furnace"], + [Blocks::FURNACE(), "furnace"], + [Blocks::SMOKER(), "smoker"] + ] as [$block, $idSuffix]){ + $reg->mapFlattenedId(FlattenedIdModel::create($block) + ->idComponents([...$commonProperties->furnaceIdPrefixes, $idSuffix]) + ->properties([$commonProperties->horizontalFacingCardinal]) + ); + } + + foreach([ + [Blocks::REDSTONE_LAMP(), "redstone_lamp"], + [Blocks::REDSTONE_ORE(), "redstone_ore"], + [Blocks::DEEPSLATE_REDSTONE_ORE(), "deepslate_redstone_ore"] + ] as [$block, $idSuffix]){ + $reg->mapFlattenedId(FlattenedIdModel::create($block)->idComponents(["minecraft:", $commonProperties->litIdInfix, $idSuffix])); + } + + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::DAYLIGHT_SENSOR()) + ->idComponents([ + "minecraft:daylight_detector", + new BoolFromStringProperty("inverted", "", "_inverted", fn(DaylightSensor $b) => $b->isInverted(), fn(DaylightSensor $b, bool $v) => $b->setInverted($v)) + ]) + ->properties([$commonProperties->analogRedstoneSignal]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::REDSTONE_REPEATER()) + ->idComponents([ + "minecraft:", + new BoolFromStringProperty("powered", "un", "", fn(RedstoneRepeater $b) => $b->isPowered(), fn(RedstoneRepeater $b, bool $v) => $b->setPowered($v)), + "powered_repeater" + ]) + ->properties([ + $commonProperties->horizontalFacingCardinal, + new IntProperty(StateNames::REPEATER_DELAY, 0, 3, fn(RedstoneRepeater $b) => $b->getDelay(), fn(RedstoneRepeater $b, int $v) => $b->setDelay($v), offset: 1), + ]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::REDSTONE_COMPARATOR()) + ->idComponents([ + "minecraft:", + //this property also appears in the state, so we ignore it in the ID + //this is baked here purely to keep minecraft happy + new BoolFromStringProperty("dummy_powered", "un", "", fn(RedstoneComparator $b) => $b->isPowered(), fn() => null), + "powered_comparator" + ]) + ->properties([ + $commonProperties->horizontalFacingCardinal, + new BoolProperty(StateNames::OUTPUT_LIT_BIT, fn(RedstoneComparator $b) => $b->isPowered(), fn(RedstoneComparator $b, bool $v) => $b->setPowered($v)), + new BoolProperty(StateNames::OUTPUT_SUBTRACT_BIT, fn(RedstoneComparator $b) => $b->isSubtractMode(), fn(RedstoneComparator $b, bool $v) => $b->setSubtractMode($v)), + ]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::REDSTONE_TORCH()) + ->idComponents([ + "minecraft:", + new BoolFromStringProperty("lit", "unlit_", "", fn(RedstoneTorch $b) => $b->isLit(), fn(RedstoneTorch $b, bool $v) => $b->setLit($v)), + "redstone_torch" + ]) + ->properties([$commonProperties->torchFacing]) + ); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::SPONGE())->idComponents([ + "minecraft:", + new BoolFromStringProperty("wet", "", "wet_", fn(Sponge $b) => $b->isWet(), fn(Sponge $b, bool $v) => $b->setWet($v)), + "sponge" + ])); + $reg->mapFlattenedId(FlattenedIdModel::create(Blocks::TNT()) + ->idComponents([ + "minecraft:", + new BoolFromStringProperty("underwater", "", "underwater_", fn(TNT $b) => $b->worksUnderwater(), fn(TNT $b, bool $v) => $b->setWorksUnderwater($v)), + "tnt" + ]) + ->properties([ + new BoolProperty(StateNames::EXPLODE_BIT, fn(TNT $b) => $b->isUnstable(), fn(TNT $b, bool $v) => $b->setUnstable($v)), + ]) + ); + } + + private static function registerStoneLikeSlabMappings(BlockSerializerDeserializerRegistrar $reg) : void{ + $reg->mapSlab(Blocks::ANDESITE_SLAB(), "andesite"); + $reg->mapSlab(Blocks::BLACKSTONE_SLAB(), "blackstone"); + $reg->mapSlab(Blocks::BRICK_SLAB(), "brick"); + $reg->mapSlab(Blocks::COBBLED_DEEPSLATE_SLAB(), "cobbled_deepslate"); + $reg->mapSlab(Blocks::COBBLESTONE_SLAB(), "cobblestone"); + $reg->mapSlab(Blocks::CUT_RED_SANDSTONE_SLAB(), "cut_red_sandstone"); + $reg->mapSlab(Blocks::CUT_SANDSTONE_SLAB(), "cut_sandstone"); + $reg->mapSlab(Blocks::DARK_PRISMARINE_SLAB(), "dark_prismarine"); + $reg->mapSlab(Blocks::DEEPSLATE_BRICK_SLAB(), "deepslate_brick"); + $reg->mapSlab(Blocks::DEEPSLATE_TILE_SLAB(), "deepslate_tile"); + $reg->mapSlab(Blocks::DIORITE_SLAB(), "diorite"); + $reg->mapSlab(Blocks::END_STONE_BRICK_SLAB(), "end_stone_brick"); + $reg->mapSlab(Blocks::FAKE_WOODEN_SLAB(), "petrified_oak"); + $reg->mapSlab(Blocks::GRANITE_SLAB(), "granite"); + $reg->mapSlab(Blocks::MOSSY_COBBLESTONE_SLAB(), "mossy_cobblestone"); + $reg->mapSlab(Blocks::MOSSY_STONE_BRICK_SLAB(), "mossy_stone_brick"); + $reg->mapSlab(Blocks::MUD_BRICK_SLAB(), "mud_brick"); + $reg->mapSlab(Blocks::NETHER_BRICK_SLAB(), "nether_brick"); + $reg->mapSlab(Blocks::POLISHED_ANDESITE_SLAB(), "polished_andesite"); + $reg->mapSlab(Blocks::POLISHED_BLACKSTONE_BRICK_SLAB(), "polished_blackstone_brick"); + $reg->mapSlab(Blocks::POLISHED_BLACKSTONE_SLAB(), "polished_blackstone"); + $reg->mapSlab(Blocks::POLISHED_DEEPSLATE_SLAB(), "polished_deepslate"); + $reg->mapSlab(Blocks::POLISHED_DIORITE_SLAB(), "polished_diorite"); + $reg->mapSlab(Blocks::POLISHED_GRANITE_SLAB(), "polished_granite"); + $reg->mapSlab(Blocks::POLISHED_TUFF_SLAB(), "polished_tuff"); + $reg->mapSlab(Blocks::PRISMARINE_BRICKS_SLAB(), "prismarine_brick"); + $reg->mapSlab(Blocks::PRISMARINE_SLAB(), "prismarine"); + $reg->mapSlab(Blocks::PURPUR_SLAB(), "purpur"); + $reg->mapSlab(Blocks::QUARTZ_SLAB(), "quartz"); + $reg->mapSlab(Blocks::RED_NETHER_BRICK_SLAB(), "red_nether_brick"); + $reg->mapSlab(Blocks::RED_SANDSTONE_SLAB(), "red_sandstone"); + $reg->mapSlab(Blocks::RESIN_BRICK_SLAB(), "resin_brick"); + $reg->mapSlab(Blocks::SANDSTONE_SLAB(), "sandstone"); + $reg->mapSlab(Blocks::SMOOTH_QUARTZ_SLAB(), "smooth_quartz"); + $reg->mapSlab(Blocks::SMOOTH_RED_SANDSTONE_SLAB(), "smooth_red_sandstone"); + $reg->mapSlab(Blocks::SMOOTH_SANDSTONE_SLAB(), "smooth_sandstone"); + $reg->mapSlab(Blocks::SMOOTH_STONE_SLAB(), "smooth_stone"); + $reg->mapSlab(Blocks::STONE_BRICK_SLAB(), "stone_brick"); + $reg->mapSlab(Blocks::STONE_SLAB(), "normal_stone"); + $reg->mapSlab(Blocks::TUFF_BRICK_SLAB(), "tuff_brick"); + $reg->mapSlab(Blocks::TUFF_SLAB(), "tuff"); + } + + private static function registerStoneLikeStairMappings(BlockSerializerDeserializerRegistrar $reg) : void{ + $reg->mapStairs(Blocks::ANDESITE_STAIRS(), Ids::ANDESITE_STAIRS); + $reg->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS); + $reg->mapStairs(Blocks::BRICK_STAIRS(), Ids::BRICK_STAIRS); + $reg->mapStairs(Blocks::COBBLED_DEEPSLATE_STAIRS(), Ids::COBBLED_DEEPSLATE_STAIRS); + $reg->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS); + $reg->mapStairs(Blocks::DARK_PRISMARINE_STAIRS(), Ids::DARK_PRISMARINE_STAIRS); + $reg->mapStairs(Blocks::DEEPSLATE_BRICK_STAIRS(), Ids::DEEPSLATE_BRICK_STAIRS); + $reg->mapStairs(Blocks::DEEPSLATE_TILE_STAIRS(), Ids::DEEPSLATE_TILE_STAIRS); + $reg->mapStairs(Blocks::DIORITE_STAIRS(), Ids::DIORITE_STAIRS); + $reg->mapStairs(Blocks::END_STONE_BRICK_STAIRS(), Ids::END_BRICK_STAIRS); + $reg->mapStairs(Blocks::GRANITE_STAIRS(), Ids::GRANITE_STAIRS); + $reg->mapStairs(Blocks::MOSSY_COBBLESTONE_STAIRS(), Ids::MOSSY_COBBLESTONE_STAIRS); + $reg->mapStairs(Blocks::MOSSY_STONE_BRICK_STAIRS(), Ids::MOSSY_STONE_BRICK_STAIRS); + $reg->mapStairs(Blocks::MUD_BRICK_STAIRS(), Ids::MUD_BRICK_STAIRS); + $reg->mapStairs(Blocks::NETHER_BRICK_STAIRS(), Ids::NETHER_BRICK_STAIRS); + $reg->mapStairs(Blocks::POLISHED_ANDESITE_STAIRS(), Ids::POLISHED_ANDESITE_STAIRS); + $reg->mapStairs(Blocks::POLISHED_BLACKSTONE_BRICK_STAIRS(), Ids::POLISHED_BLACKSTONE_BRICK_STAIRS); + $reg->mapStairs(Blocks::POLISHED_BLACKSTONE_STAIRS(), Ids::POLISHED_BLACKSTONE_STAIRS); + $reg->mapStairs(Blocks::POLISHED_DEEPSLATE_STAIRS(), Ids::POLISHED_DEEPSLATE_STAIRS); + $reg->mapStairs(Blocks::POLISHED_DIORITE_STAIRS(), Ids::POLISHED_DIORITE_STAIRS); + $reg->mapStairs(Blocks::POLISHED_GRANITE_STAIRS(), Ids::POLISHED_GRANITE_STAIRS); + $reg->mapStairs(Blocks::POLISHED_TUFF_STAIRS(), Ids::POLISHED_TUFF_STAIRS); + $reg->mapStairs(Blocks::PRISMARINE_BRICKS_STAIRS(), Ids::PRISMARINE_BRICKS_STAIRS); + $reg->mapStairs(Blocks::PRISMARINE_STAIRS(), Ids::PRISMARINE_STAIRS); + $reg->mapStairs(Blocks::PURPUR_STAIRS(), Ids::PURPUR_STAIRS); + $reg->mapStairs(Blocks::QUARTZ_STAIRS(), Ids::QUARTZ_STAIRS); + $reg->mapStairs(Blocks::RED_NETHER_BRICK_STAIRS(), Ids::RED_NETHER_BRICK_STAIRS); + $reg->mapStairs(Blocks::RED_SANDSTONE_STAIRS(), Ids::RED_SANDSTONE_STAIRS); + $reg->mapStairs(Blocks::RESIN_BRICK_STAIRS(), Ids::RESIN_BRICK_STAIRS); + $reg->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS); + $reg->mapStairs(Blocks::SMOOTH_QUARTZ_STAIRS(), Ids::SMOOTH_QUARTZ_STAIRS); + $reg->mapStairs(Blocks::SMOOTH_RED_SANDSTONE_STAIRS(), Ids::SMOOTH_RED_SANDSTONE_STAIRS); + $reg->mapStairs(Blocks::SMOOTH_SANDSTONE_STAIRS(), Ids::SMOOTH_SANDSTONE_STAIRS); + $reg->mapStairs(Blocks::STONE_BRICK_STAIRS(), Ids::STONE_BRICK_STAIRS); + $reg->mapStairs(Blocks::STONE_STAIRS(), Ids::NORMAL_STONE_STAIRS); + $reg->mapStairs(Blocks::TUFF_BRICK_STAIRS(), Ids::TUFF_BRICK_STAIRS); + $reg->mapStairs(Blocks::TUFF_STAIRS(), Ids::TUFF_STAIRS); + } + + private static function registerStoneLikeWallMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + foreach([ + Ids::ANDESITE_WALL => Blocks::ANDESITE_WALL(), + Ids::BLACKSTONE_WALL => Blocks::BLACKSTONE_WALL(), + Ids::BRICK_WALL => Blocks::BRICK_WALL(), + Ids::COBBLED_DEEPSLATE_WALL => Blocks::COBBLED_DEEPSLATE_WALL(), + Ids::COBBLESTONE_WALL => Blocks::COBBLESTONE_WALL(), + Ids::DEEPSLATE_BRICK_WALL => Blocks::DEEPSLATE_BRICK_WALL(), + Ids::DEEPSLATE_TILE_WALL => Blocks::DEEPSLATE_TILE_WALL(), + Ids::DIORITE_WALL => Blocks::DIORITE_WALL(), + Ids::END_STONE_BRICK_WALL => Blocks::END_STONE_BRICK_WALL(), + Ids::GRANITE_WALL => Blocks::GRANITE_WALL(), + Ids::MOSSY_COBBLESTONE_WALL => Blocks::MOSSY_COBBLESTONE_WALL(), + Ids::MOSSY_STONE_BRICK_WALL => Blocks::MOSSY_STONE_BRICK_WALL(), + Ids::MUD_BRICK_WALL => Blocks::MUD_BRICK_WALL(), + Ids::NETHER_BRICK_WALL => Blocks::NETHER_BRICK_WALL(), + Ids::POLISHED_BLACKSTONE_BRICK_WALL => Blocks::POLISHED_BLACKSTONE_BRICK_WALL(), + Ids::POLISHED_BLACKSTONE_WALL => Blocks::POLISHED_BLACKSTONE_WALL(), + Ids::POLISHED_DEEPSLATE_WALL => Blocks::POLISHED_DEEPSLATE_WALL(), + Ids::POLISHED_TUFF_WALL => Blocks::POLISHED_TUFF_WALL(), + Ids::PRISMARINE_WALL => Blocks::PRISMARINE_WALL(), + Ids::RED_NETHER_BRICK_WALL => Blocks::RED_NETHER_BRICK_WALL(), + Ids::RED_SANDSTONE_WALL => Blocks::RED_SANDSTONE_WALL(), + Ids::RESIN_BRICK_WALL => Blocks::RESIN_BRICK_WALL(), + Ids::SANDSTONE_WALL => Blocks::SANDSTONE_WALL(), + Ids::STONE_BRICK_WALL => Blocks::STONE_BRICK_WALL(), + Ids::TUFF_BRICK_WALL => Blocks::TUFF_BRICK_WALL(), + Ids::TUFF_WALL => Blocks::TUFF_WALL() + ] as $id => $block){ + $reg->mapModel(Model::create($block, $id)->properties($commonProperties->wallProperties)); + } + } + + private static function registerWoodMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + //buttons + foreach([ + [Blocks::ACACIA_BUTTON(), Ids::ACACIA_BUTTON], + [Blocks::BIRCH_BUTTON(), Ids::BIRCH_BUTTON], + [Blocks::CHERRY_BUTTON(), Ids::CHERRY_BUTTON], + [Blocks::CRIMSON_BUTTON(), Ids::CRIMSON_BUTTON], + [Blocks::DARK_OAK_BUTTON(), Ids::DARK_OAK_BUTTON], + [Blocks::JUNGLE_BUTTON(), Ids::JUNGLE_BUTTON], + [Blocks::MANGROVE_BUTTON(), Ids::MANGROVE_BUTTON], + [Blocks::OAK_BUTTON(), Ids::WOODEN_BUTTON], + [Blocks::PALE_OAK_BUTTON(), Ids::PALE_OAK_BUTTON], + [Blocks::SPRUCE_BUTTON(), Ids::SPRUCE_BUTTON], + [Blocks::WARPED_BUTTON(), Ids::WARPED_BUTTON] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties($commonProperties->buttonProperties)); + } + + //doors + foreach([ + [Blocks::ACACIA_DOOR(), Ids::ACACIA_DOOR], + [Blocks::BIRCH_DOOR(), Ids::BIRCH_DOOR], + [Blocks::CHERRY_DOOR(), Ids::CHERRY_DOOR], + [Blocks::CRIMSON_DOOR(), Ids::CRIMSON_DOOR], + [Blocks::DARK_OAK_DOOR(), Ids::DARK_OAK_DOOR], + [Blocks::JUNGLE_DOOR(), Ids::JUNGLE_DOOR], + [Blocks::MANGROVE_DOOR(), Ids::MANGROVE_DOOR], + [Blocks::OAK_DOOR(), Ids::WOODEN_DOOR], + [Blocks::PALE_OAK_DOOR(), Ids::PALE_OAK_DOOR], + [Blocks::SPRUCE_DOOR(), Ids::SPRUCE_DOOR], + [Blocks::WARPED_DOOR(), Ids::WARPED_DOOR] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties($commonProperties->doorProperties)); + } + + //fences + foreach([ + [Blocks::ACACIA_FENCE(), Ids::ACACIA_FENCE], + [Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE], + [Blocks::CHERRY_FENCE(), Ids::CHERRY_FENCE], + [Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE], + [Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE], + [Blocks::MANGROVE_FENCE(), Ids::MANGROVE_FENCE], + [Blocks::OAK_FENCE(), Ids::OAK_FENCE], + [Blocks::PALE_OAK_FENCE(), Ids::PALE_OAK_FENCE], + [Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE], + [Blocks::CRIMSON_FENCE(), Ids::CRIMSON_FENCE], + [Blocks::WARPED_FENCE(), Ids::WARPED_FENCE] + ] as [$block, $id]){ + $reg->mapSimple($block, $id); + } + + foreach([ + [Blocks::ACACIA_FENCE_GATE(), Ids::ACACIA_FENCE_GATE], + [Blocks::BIRCH_FENCE_GATE(), Ids::BIRCH_FENCE_GATE], + [Blocks::CHERRY_FENCE_GATE(), Ids::CHERRY_FENCE_GATE], + [Blocks::DARK_OAK_FENCE_GATE(), Ids::DARK_OAK_FENCE_GATE], + [Blocks::JUNGLE_FENCE_GATE(), Ids::JUNGLE_FENCE_GATE], + [Blocks::MANGROVE_FENCE_GATE(), Ids::MANGROVE_FENCE_GATE], + [Blocks::OAK_FENCE_GATE(), Ids::FENCE_GATE], + [Blocks::PALE_OAK_FENCE_GATE(), Ids::PALE_OAK_FENCE_GATE], + [Blocks::SPRUCE_FENCE_GATE(), Ids::SPRUCE_FENCE_GATE], + [Blocks::CRIMSON_FENCE_GATE(), Ids::CRIMSON_FENCE_GATE], + [Blocks::WARPED_FENCE_GATE(), Ids::WARPED_FENCE_GATE] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties($commonProperties->fenceGateProperties)); + } + + foreach([ + [Blocks::ACACIA_SIGN(), Ids::ACACIA_STANDING_SIGN], + [Blocks::BIRCH_SIGN(), Ids::BIRCH_STANDING_SIGN], + [Blocks::CHERRY_SIGN(), Ids::CHERRY_STANDING_SIGN], + [Blocks::DARK_OAK_SIGN(), Ids::DARKOAK_STANDING_SIGN], + [Blocks::JUNGLE_SIGN(), Ids::JUNGLE_STANDING_SIGN], + [Blocks::MANGROVE_SIGN(), Ids::MANGROVE_STANDING_SIGN], + [Blocks::OAK_SIGN(), Ids::STANDING_SIGN], + [Blocks::PALE_OAK_SIGN(), Ids::PALE_OAK_STANDING_SIGN], + [Blocks::SPRUCE_SIGN(), Ids::SPRUCE_STANDING_SIGN], + [Blocks::CRIMSON_SIGN(), Ids::CRIMSON_STANDING_SIGN], + [Blocks::WARPED_SIGN(), Ids::WARPED_STANDING_SIGN] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties([$commonProperties->floorSignLikeRotation])); + } + + //logs + foreach([ + [Blocks::ACACIA_LOG(), "acacia_log"], + [Blocks::BIRCH_LOG(), "birch_log"], + [Blocks::CHERRY_LOG(), "cherry_log"], + [Blocks::DARK_OAK_LOG(), "dark_oak_log"], + [Blocks::JUNGLE_LOG(), "jungle_log"], + [Blocks::MANGROVE_LOG(), "mangrove_log"], + [Blocks::OAK_LOG(), "oak_log"], + [Blocks::PALE_OAK_LOG(), "pale_oak_log"], + [Blocks::SPRUCE_LOG(), "spruce_log"], + [Blocks::CRIMSON_STEM(), "crimson_stem"], + [Blocks::WARPED_STEM(), "warped_stem"], + + //all-sided logs + [Blocks::ACACIA_WOOD(), "acacia_wood"], + [Blocks::BIRCH_WOOD(), "birch_wood"], + [Blocks::CHERRY_WOOD(), "cherry_wood"], + [Blocks::DARK_OAK_WOOD(), "dark_oak_wood"], + [Blocks::JUNGLE_WOOD(), "jungle_wood"], + [Blocks::MANGROVE_WOOD(), "mangrove_wood"], + [Blocks::OAK_WOOD(), "oak_wood"], + [Blocks::PALE_OAK_WOOD(), "pale_oak_wood"], + [Blocks::SPRUCE_WOOD(), "spruce_wood"], + [Blocks::CRIMSON_HYPHAE(), "crimson_hyphae"], + [Blocks::WARPED_HYPHAE(), "warped_hyphae"] + ] as [$block, $idSuffix]){ + $reg->mapFlattenedId(FlattenedIdModel::create($block) + ->idComponents([...$commonProperties->woodIdPrefixes, $idSuffix]) + ->properties([$commonProperties->pillarAxis]) + ); + } + + //planks + foreach([ + [Blocks::ACACIA_PLANKS(), Ids::ACACIA_PLANKS], + [Blocks::BIRCH_PLANKS(), Ids::BIRCH_PLANKS], + [Blocks::CHERRY_PLANKS(), Ids::CHERRY_PLANKS], + [Blocks::DARK_OAK_PLANKS(), Ids::DARK_OAK_PLANKS], + [Blocks::JUNGLE_PLANKS(), Ids::JUNGLE_PLANKS], + [Blocks::MANGROVE_PLANKS(), Ids::MANGROVE_PLANKS], + [Blocks::OAK_PLANKS(), Ids::OAK_PLANKS], + [Blocks::PALE_OAK_PLANKS(), Ids::PALE_OAK_PLANKS], + [Blocks::SPRUCE_PLANKS(), Ids::SPRUCE_PLANKS], + [Blocks::CRIMSON_PLANKS(), Ids::CRIMSON_PLANKS], + [Blocks::WARPED_PLANKS(), Ids::WARPED_PLANKS] + ] as [$block, $id]){ + $reg->mapSimple($block, $id); + } + + //pressure plates + foreach([ + [Blocks::ACACIA_PRESSURE_PLATE(), Ids::ACACIA_PRESSURE_PLATE], + [Blocks::BIRCH_PRESSURE_PLATE(), Ids::BIRCH_PRESSURE_PLATE], + [Blocks::CHERRY_PRESSURE_PLATE(), Ids::CHERRY_PRESSURE_PLATE], + [Blocks::DARK_OAK_PRESSURE_PLATE(), Ids::DARK_OAK_PRESSURE_PLATE], + [Blocks::JUNGLE_PRESSURE_PLATE(), Ids::JUNGLE_PRESSURE_PLATE], + [Blocks::MANGROVE_PRESSURE_PLATE(), Ids::MANGROVE_PRESSURE_PLATE], + [Blocks::OAK_PRESSURE_PLATE(), Ids::WOODEN_PRESSURE_PLATE], + [Blocks::PALE_OAK_PRESSURE_PLATE(), Ids::PALE_OAK_PRESSURE_PLATE], + [Blocks::SPRUCE_PRESSURE_PLATE(), Ids::SPRUCE_PRESSURE_PLATE], + [Blocks::CRIMSON_PRESSURE_PLATE(), Ids::CRIMSON_PRESSURE_PLATE], + [Blocks::WARPED_PRESSURE_PLATE(), Ids::WARPED_PRESSURE_PLATE] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties($commonProperties->simplePressurePlateProperties)); + } + + //slabs + foreach([ + [Blocks::ACACIA_SLAB(), "acacia"], + [Blocks::BIRCH_SLAB(), "birch"], + [Blocks::CHERRY_SLAB(), "cherry"], + [Blocks::DARK_OAK_SLAB(), "dark_oak"], + [Blocks::JUNGLE_SLAB(), "jungle"], + [Blocks::MANGROVE_SLAB(), "mangrove"], + [Blocks::OAK_SLAB(), "oak"], + [Blocks::PALE_OAK_SLAB(), "pale_oak"], + [Blocks::SPRUCE_SLAB(), "spruce"], + [Blocks::CRIMSON_SLAB(), "crimson"], + [Blocks::WARPED_SLAB(), "warped"] + ] as [$block, $type]){ + $reg->mapSlab($block, $type); + } + + //stairs + foreach([ + [Blocks::ACACIA_STAIRS(), Ids::ACACIA_STAIRS], + [Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS], + [Blocks::CHERRY_STAIRS(), Ids::CHERRY_STAIRS], + [Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS], + [Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS], + [Blocks::MANGROVE_STAIRS(), Ids::MANGROVE_STAIRS], + [Blocks::OAK_STAIRS(), Ids::OAK_STAIRS], + [Blocks::PALE_OAK_STAIRS(), Ids::PALE_OAK_STAIRS], + [Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS], + [Blocks::CRIMSON_STAIRS(), Ids::CRIMSON_STAIRS], + [Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS] + ] as [$block, $id]){ + $reg->mapStairs($block, $id); + } + + //trapdoors + foreach([ + [Blocks::ACACIA_TRAPDOOR(), Ids::ACACIA_TRAPDOOR], + [Blocks::BIRCH_TRAPDOOR(), Ids::BIRCH_TRAPDOOR], + [Blocks::CHERRY_TRAPDOOR(), Ids::CHERRY_TRAPDOOR], + [Blocks::DARK_OAK_TRAPDOOR(), Ids::DARK_OAK_TRAPDOOR], + [Blocks::JUNGLE_TRAPDOOR(), Ids::JUNGLE_TRAPDOOR], + [Blocks::MANGROVE_TRAPDOOR(), Ids::MANGROVE_TRAPDOOR], + [Blocks::OAK_TRAPDOOR(), Ids::TRAPDOOR], + [Blocks::PALE_OAK_TRAPDOOR(), Ids::PALE_OAK_TRAPDOOR], + [Blocks::SPRUCE_TRAPDOOR(), Ids::SPRUCE_TRAPDOOR], + [Blocks::CRIMSON_TRAPDOOR(), Ids::CRIMSON_TRAPDOOR], + [Blocks::WARPED_TRAPDOOR(), Ids::WARPED_TRAPDOOR] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties($commonProperties->trapdoorProperties)); + } + + //wall signs + foreach([ + [Blocks::ACACIA_WALL_SIGN(), Ids::ACACIA_WALL_SIGN], + [Blocks::BIRCH_WALL_SIGN(), Ids::BIRCH_WALL_SIGN], + [Blocks::CHERRY_WALL_SIGN(), Ids::CHERRY_WALL_SIGN], + [Blocks::DARK_OAK_WALL_SIGN(), Ids::DARKOAK_WALL_SIGN], + [Blocks::JUNGLE_WALL_SIGN(), Ids::JUNGLE_WALL_SIGN], + [Blocks::MANGROVE_WALL_SIGN(), Ids::MANGROVE_WALL_SIGN], + [Blocks::OAK_WALL_SIGN(), Ids::WALL_SIGN], + [Blocks::PALE_OAK_WALL_SIGN(), Ids::PALE_OAK_WALL_SIGN], + [Blocks::SPRUCE_WALL_SIGN(), Ids::SPRUCE_WALL_SIGN], + [Blocks::CRIMSON_WALL_SIGN(), Ids::CRIMSON_WALL_SIGN], + [Blocks::WARPED_WALL_SIGN(), Ids::WARPED_WALL_SIGN] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties([$commonProperties->horizontalFacingClassic])); + } + } + + private static function registerTorchMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + foreach([ + [Blocks::BLUE_TORCH(), Ids::COLORED_TORCH_BLUE], + [Blocks::GREEN_TORCH(), Ids::COLORED_TORCH_GREEN], + [Blocks::PURPLE_TORCH(), Ids::COLORED_TORCH_PURPLE], + [Blocks::RED_TORCH(), Ids::COLORED_TORCH_RED], + [Blocks::SOUL_TORCH(), Ids::SOUL_TORCH], + [Blocks::TORCH(), Ids::TORCH], + [Blocks::UNDERWATER_TORCH(), Ids::UNDERWATER_TORCH] + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties([$commonProperties->torchFacing])); + } + } + + private static function registerChemistryMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + foreach([ + [Blocks::COMPOUND_CREATOR(), Ids::COMPOUND_CREATOR], + [Blocks::ELEMENT_CONSTRUCTOR(), Ids::ELEMENT_CONSTRUCTOR], + [Blocks::LAB_TABLE(), Ids::LAB_TABLE], + [Blocks::MATERIAL_REDUCER(), Ids::MATERIAL_REDUCER], + ] as [$block, $id]){ + $reg->mapModel(Model::create($block, $id)->properties([$commonProperties->horizontalFacingSWNEInverted])); + } + } + + private static function register1to1CustomMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ + //TODO: some of these have repeated accessor refs, we might be able to deduplicate them + //A + $reg->mapModel(Model::create(Blocks::ACTIVATOR_RAIL(), Ids::ACTIVATOR_RAIL)->properties([ + new BoolProperty(StateNames::RAIL_DATA_BIT, fn(ActivatorRail $b) => $b->isPowered(), fn(ActivatorRail $b, bool $v) => $b->setPowered($v)), + new IntProperty(StateNames::RAIL_DIRECTION, 0, 5, fn(ActivatorRail $b) => $b->getShape(), fn(ActivatorRail $b, int $v) => $b->setShape($v)) + ])); + + //B + $reg->mapModel(Model::create(Blocks::BAMBOO(), Ids::BAMBOO)->properties([ + new ValueFromStringProperty(StateNames::BAMBOO_LEAF_SIZE, ValueMappings::getInstance()->bambooLeafSize, fn(Bamboo $b) => $b->getLeafSize(), fn(Bamboo $b, int $v) => $b->setLeafSize($v)), + new BoolProperty(StateNames::AGE_BIT, fn(Bamboo $b) => $b->isReady(), fn(Bamboo $b, bool $v) => $b->setReady($v)), + new BoolFromStringProperty(StateNames::BAMBOO_STALK_THICKNESS, StringValues::BAMBOO_STALK_THICKNESS_THIN, StringValues::BAMBOO_STALK_THICKNESS_THICK, fn(Bamboo $b) => $b->isThick(), fn(Bamboo $b, bool $v) => $b->setThick($v)) + ])); + $reg->mapModel(Model::create(Blocks::BAMBOO_SAPLING(), Ids::BAMBOO_SAPLING)->properties([ + new BoolProperty(StateNames::AGE_BIT, fn(BambooSapling $b) => $b->isReady(), fn(BambooSapling $b, bool $v) => $b->setReady($v)) + ])); + $reg->mapModel(Model::create(Blocks::BANNER(), Ids::STANDING_BANNER)->properties([$commonProperties->floorSignLikeRotation])); + $reg->mapModel(Model::create(Blocks::BARREL(), Ids::BARREL)->properties([ + $commonProperties->anyFacingClassic, + new BoolProperty(StateNames::OPEN_BIT, fn(Barrel $b) => $b->isOpen(), fn(Barrel $b, bool $v) => $b->setOpen($v)) + ])); + $reg->mapModel(Model::create(Blocks::BASALT(), Ids::BASALT)->properties([$commonProperties->pillarAxis])); + $reg->mapModel(Model::create(Blocks::BED(), Ids::BED)->properties([ + new BoolProperty(StateNames::HEAD_PIECE_BIT, fn(Bed $b) => $b->isHeadPart(), fn(Bed $b, bool $v) => $b->setHead($v)), + new BoolProperty(StateNames::OCCUPIED_BIT, fn(Bed $b) => $b->isOccupied(), fn(Bed $b, bool $v) => $b->setOccupied($v)), + $commonProperties->horizontalFacingSWNE + ])); + $reg->mapModel(Model::create(Blocks::BEDROCK(), Ids::BEDROCK)->properties([ + new BoolProperty(StateNames::INFINIBURN_BIT, fn(Bedrock $b) => $b->burnsForever(), fn(Bedrock $b, bool $v) => $b->setBurnsForever($v)) + ])); + $reg->mapModel(Model::create(Blocks::BELL(), Ids::BELL)->properties([ + BoolProperty::unused(StateNames::TOGGLE_BIT, false), + new ValueFromStringProperty(StateNames::ATTACHMENT, ValueMappings::getInstance()->bellAttachmentType, fn(Bell $b) => $b->getAttachmentType(), fn(Bell $b, BellAttachmentType $v) => $b->setAttachmentType($v)), + $commonProperties->horizontalFacingSWNE + ])); + $reg->mapModel(Model::create(Blocks::BONE_BLOCK(), Ids::BONE_BLOCK)->properties([ + IntProperty::unused(StateNames::DEPRECATED, 0), + $commonProperties->pillarAxis + ])); + + $reg->mapModel(Model::create(Blocks::BREWING_STAND(), Ids::BREWING_STAND)->properties(array_map(fn(BrewingStandSlot $slot) => new BoolProperty(match ($slot) { + BrewingStandSlot::EAST => StateNames::BREWING_STAND_SLOT_A_BIT, + BrewingStandSlot::SOUTHWEST => StateNames::BREWING_STAND_SLOT_B_BIT, + BrewingStandSlot::NORTHWEST => StateNames::BREWING_STAND_SLOT_C_BIT + }, fn(BrewingStand $b) => $b->hasSlot($slot), fn(BrewingStand $b, bool $v) => $b->setSlot($slot, $v)), BrewingStandSlot::cases()))); + + //C + $reg->mapModel(Model::create(Blocks::CACTUS(), Ids::CACTUS)->properties([ + new IntProperty(StateNames::AGE, 0, 15, fn(Cactus $b) => $b->getAge(), fn(Cactus $b, int $v) => $b->setAge($v)) + ])); + $reg->mapModel(Model::create(Blocks::CAKE(), Ids::CAKE)->properties([ + new IntProperty(StateNames::BITE_COUNTER, 0, 6, fn(Cake $b) => $b->getBites(), fn(Cake $b, int $v) => $b->setBites($v)) + ])); + $reg->mapModel(Model::create(Blocks::CAMPFIRE(), Ids::CAMPFIRE)->properties($commonProperties->campfireProperties)); + $reg->mapModel(Model::create(Blocks::CARVED_PUMPKIN(), Ids::CARVED_PUMPKIN)->properties([ + $commonProperties->horizontalFacingCardinal + ])); + $reg->mapModel(Model::create(Blocks::CHAIN(), Ids::CHAIN)->properties([$commonProperties->pillarAxis])); + $reg->mapModel(Model::create(Blocks::CHISELED_BOOKSHELF(), Ids::CHISELED_BOOKSHELF)->properties([ + $commonProperties->horizontalFacingSWNE, + new OptionSetFromIntProperty( + StateNames::BOOKS_STORED, + EnumFromRawStateMap::int(ChiseledBookshelfSlot::class, fn(ChiseledBookshelfSlot $case) => match($case){ + //these are (currently) the same as the internal values, but it's best not to rely on those in case Mojang mess with the flags + ChiseledBookshelfSlot::TOP_LEFT => 1 << 0, + ChiseledBookshelfSlot::TOP_MIDDLE => 1 << 1, + ChiseledBookshelfSlot::TOP_RIGHT => 1 << 2, + ChiseledBookshelfSlot::BOTTOM_LEFT => 1 << 3, + ChiseledBookshelfSlot::BOTTOM_MIDDLE => 1 << 4, + ChiseledBookshelfSlot::BOTTOM_RIGHT => 1 << 5 + }), + fn(ChiseledBookshelf $b) => $b->getSlots(), + fn(ChiseledBookshelf $b, array $v) => $b->setSlots($v) + ) + ])); + $reg->mapModel(Model::create(Blocks::CHISELED_QUARTZ(), Ids::CHISELED_QUARTZ_BLOCK)->properties([$commonProperties->pillarAxis])); + $reg->mapModel(Model::create(Blocks::CHEST(), Ids::CHEST)->properties([$commonProperties->horizontalFacingCardinal])); + $reg->mapModel(Model::create(Blocks::CHORUS_FLOWER(), Ids::CHORUS_FLOWER)->properties([ + new IntProperty(StateNames::AGE, ChorusFlower::MIN_AGE, ChorusFlower::MAX_AGE, fn(ChorusFlower $b) => $b->getAge(), fn(ChorusFlower $b, int $v) => $b->setAge($v)) + ])); + $reg->mapModel(Model::create(Blocks::COCOA_POD(), Ids::COCOA)->properties([ + new IntProperty(StateNames::AGE, 0, 2, fn(CocoaBlock $b) => $b->getAge(), fn(CocoaBlock $b, int $v) => $b->setAge($v)), + $commonProperties->horizontalFacingSWNEInverted + ])); + + //D + $reg->mapModel(Model::create(Blocks::DEEPSLATE(), Ids::DEEPSLATE)->properties([$commonProperties->pillarAxis])); + $reg->mapModel(Model::create(Blocks::DETECTOR_RAIL(), Ids::DETECTOR_RAIL)->properties([ + new BoolProperty(StateNames::RAIL_DATA_BIT, fn(DetectorRail $b) => $b->isActivated(), fn(DetectorRail $b, bool $v) => $b->setActivated($v)), + new IntProperty(StateNames::RAIL_DIRECTION, 0, 5, fn(StraightOnlyRail $b) => $b->getShape(), fn(StraightOnlyRail $b, int $v) => $b->setShape($v)) //TODO: shared with ActivatorRail + ])); + + //E + $reg->mapModel(Model::create(Blocks::ENDER_CHEST(), Ids::ENDER_CHEST)->properties([$commonProperties->horizontalFacingCardinal])); + $reg->mapModel(Model::create(Blocks::END_PORTAL_FRAME(), Ids::END_PORTAL_FRAME)->properties([ + new BoolProperty(StateNames::END_PORTAL_EYE_BIT, fn(EndPortalFrame $b) => $b->hasEye(), fn(EndPortalFrame $b, bool $v) => $b->setEye($v)), + $commonProperties->horizontalFacingCardinal + ])); + $reg->mapModel(Model::create(Blocks::END_ROD(), Ids::END_ROD)->properties([ + new ValueFromIntProperty(StateNames::FACING_DIRECTION, ValueMappings::getInstance()->facingEndRod, fn(EndRod $b) => $b->getFacing(), fn(EndRod $b, int $v) => $b->setFacing($v)), + ])); + + //F + $reg->mapModel(Model::create(Blocks::FARMLAND(), Ids::FARMLAND)->properties([ + new IntProperty(StateNames::MOISTURIZED_AMOUNT, 0, 7, fn(Farmland $b) => $b->getWetness(), fn(Farmland $b, int $v) => $b->setWetness($v)) + ])); + $reg->mapModel(Model::create(Blocks::FIRE(), Ids::FIRE)->properties([ + new IntProperty(StateNames::AGE, 0, 15, fn(Fire $b) => $b->getAge(), fn(Fire $b, int $v) => $b->setAge($v)) + ])); + $reg->mapModel(Model::create(Blocks::FLOWER_POT(), Ids::FLOWER_POT)->properties([ + BoolProperty::unused(StateNames::UPDATE_BIT, false) + ])); + $reg->mapModel(Model::create(Blocks::FROSTED_ICE(), Ids::FROSTED_ICE)->properties([ + new IntProperty(StateNames::AGE, 0, 3, fn(FrostedIce $b) => $b->getAge(), fn(FrostedIce $b, int $v) => $b->setAge($v)) + ])); + + //G + $reg->mapModel(Model::create(Blocks::GLOWING_ITEM_FRAME(), Ids::GLOW_FRAME)->properties($commonProperties->itemFrameProperties)); + + //H + $reg->mapModel(Model::create(Blocks::HAY_BALE(), Ids::HAY_BLOCK)->properties([ + IntProperty::unused(StateNames::DEPRECATED, 0), + $commonProperties->pillarAxis + ])); + $reg->mapModel(Model::create(Blocks::HOPPER(), Ids::HOPPER)->properties([ + //kinda weird this doesn't use powered_bit? + new BoolProperty(StateNames::TOGGLE_BIT, fn(PoweredByRedstone $b) => $b->isPowered(), fn(PoweredByRedstone $b, bool $v) => $b->setPowered($v)), + new ValueFromIntProperty(StateNames::FACING_DIRECTION, ValueMappings::getInstance()->facingExceptUp, fn(Hopper $b) => $b->getFacing(), fn(Hopper $b, int $v) => $b->setFacing($v)), + ])); + + //I + $reg->mapModel(Model::create(Blocks::IRON_DOOR(), Ids::IRON_DOOR)->properties($commonProperties->doorProperties)); + $reg->mapModel(Model::create(Blocks::IRON_TRAPDOOR(), Ids::IRON_TRAPDOOR)->properties($commonProperties->trapdoorProperties)); + $reg->mapModel(Model::create(Blocks::ITEM_FRAME(), Ids::FRAME)->properties($commonProperties->itemFrameProperties)); + + //L + $reg->mapModel(Model::create(Blocks::LADDER(), Ids::LADDER)->properties([$commonProperties->horizontalFacingClassic])); + $reg->mapModel(Model::create(Blocks::LANTERN(), Ids::LANTERN)->properties([ + new BoolProperty(StateNames::HANGING, fn(Lantern $b) => $b->isHanging(), fn(Lantern $b, bool $v) => $b->setHanging($v)) + ])); + $reg->mapModel(Model::create(Blocks::LECTERN(), Ids::LECTERN)->properties([ + new BoolProperty(StateNames::POWERED_BIT, fn(Lectern $b) => $b->isProducingSignal(), fn(Lectern $b, bool $v) => $b->setProducingSignal($v)), + $commonProperties->horizontalFacingCardinal, + ])); + $reg->mapModel(Model::create(Blocks::LEVER(), Ids::LEVER)->properties([ + new ValueFromStringProperty(StateNames::LEVER_DIRECTION, ValueMappings::getInstance()->leverFacing, fn(Lever $b) => $b->getFacing(), fn(Lever $b, LeverFacing $v) => $b->setFacing($v)), + new BoolProperty(StateNames::OPEN_BIT, fn(Lever $b) => $b->isActivated(), fn(Lever $b, bool $v) => $b->setActivated($v)), + ])); + $reg->mapModel(Model::create(Blocks::LIGHTNING_ROD(), Ids::LIGHTNING_ROD)->properties([$commonProperties->anyFacingClassic])); + $reg->mapModel(Model::create(Blocks::LIT_PUMPKIN(), Ids::LIT_PUMPKIN)->properties([$commonProperties->horizontalFacingCardinal])); + $reg->mapModel(Model::create(Blocks::LOOM(), Ids::LOOM)->properties([$commonProperties->horizontalFacingSWNE])); + + //M + $reg->mapModel(Model::create(Blocks::MUDDY_MANGROVE_ROOTS(), Ids::MUDDY_MANGROVE_ROOTS)->properties([$commonProperties->pillarAxis])); + $reg->mapModel(Model::create(Blocks::NETHER_WART(), Ids::NETHER_WART)->properties([ + new IntProperty(StateNames::AGE, 0, 3, fn(NetherWartPlant $b) => $b->getAge(), fn(NetherWartPlant $b, int $v) => $b->setAge($v)) + ])); + $reg->mapModel(Model::create(Blocks::NETHER_PORTAL(), Ids::PORTAL)->properties([ + new ValueFromStringProperty(StateNames::PORTAL_AXIS, ValueMappings::getInstance()->portalAxis, fn(NetherPortal $b) => $b->getAxis(), fn(NetherPortal $b, int $v) => $b->setAxis($v)) + ])); + + //P + $reg->mapModel(Model::create(Blocks::PINK_PETALS(), Ids::PINK_PETALS)->properties([ + //Pink petals only uses 0-3, but GROWTH state can go up to 7 + new IntProperty(StateNames::GROWTH, 0, 7, fn(PinkPetals $b) => $b->getCount(), fn(PinkPetals $b, int $v) => $b->setCount(min($v, PinkPetals::MAX_COUNT)), offset: 1), + $commonProperties->horizontalFacingCardinal + ])); + $reg->mapModel(Model::create(Blocks::POWERED_RAIL(), Ids::GOLDEN_RAIL)->properties([ + new BoolProperty(StateNames::RAIL_DATA_BIT, fn(PoweredRail $b) => $b->isPowered(), fn(PoweredRail $b, bool $v) => $b->setPowered($v)), //TODO: shared with ActivatorRail + new IntProperty(StateNames::RAIL_DIRECTION, 0, 5, fn(StraightOnlyRail $b) => $b->getShape(), fn(StraightOnlyRail $b, int $v) => $b->setShape($v)) //TODO: shared with ActivatorRail + ])); + $reg->mapModel(Model::create(Blocks::PITCHER_PLANT(), Ids::PITCHER_PLANT)->properties([ + new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(DoublePlant $b) => $b->isTop(), fn(DoublePlant $b, bool $v) => $b->setTop($v)), //TODO: don't we have helpers for this? + ])); + $reg->mapModel(Model::create(Blocks::POLISHED_BASALT(), Ids::POLISHED_BASALT)->properties([$commonProperties->pillarAxis])); + $reg->mapModel(Model::create(Blocks::POLISHED_BLACKSTONE_BUTTON(), Ids::POLISHED_BLACKSTONE_BUTTON)->properties($commonProperties->buttonProperties)); + $reg->mapModel(Model::create(Blocks::POLISHED_BLACKSTONE_PRESSURE_PLATE(), Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE)->properties($commonProperties->simplePressurePlateProperties)); + $reg->mapModel(Model::create(Blocks::PUMPKIN(), Ids::PUMPKIN)->properties([ + //not used, has no visible effect + $commonProperties->dummyCardinalDirection + ])); + $reg->mapModel(Model::create(Blocks::PURPUR(), Ids::PURPUR_BLOCK)->properties([ + $commonProperties->dummyPillarAxis + ])); + $reg->mapModel(Model::create(Blocks::PURPUR_PILLAR(), Ids::PURPUR_PILLAR)->properties([$commonProperties->pillarAxis])); + + //Q + $reg->mapModel(Model::create(Blocks::QUARTZ(), Ids::QUARTZ_BLOCK)->properties([ + $commonProperties->dummyPillarAxis + ])); + $reg->mapModel(Model::create(Blocks::QUARTZ_PILLAR(), Ids::QUARTZ_PILLAR)->properties([$commonProperties->pillarAxis])); + + //R + $reg->mapModel(Model::create(Blocks::RAIL(), Ids::RAIL)->properties([ + new IntProperty(StateNames::RAIL_DIRECTION, 0, 9, fn(Rail $b) => $b->getShape(), fn(Rail $b, int $v) => $b->setShape($v)) + ])); + $reg->mapModel(Model::create(Blocks::REDSTONE_WIRE(), Ids::REDSTONE_WIRE)->properties([$commonProperties->analogRedstoneSignal])); + $reg->mapModel(Model::create(Blocks::RESPAWN_ANCHOR(), Ids::RESPAWN_ANCHOR)->properties([ + new IntProperty(StateNames::RESPAWN_ANCHOR_CHARGE, 0, 4, fn(RespawnAnchor $b) => $b->getCharges(), fn(RespawnAnchor $b, int $v) => $b->setCharges($v)) + ])); + + //S + $reg->mapModel(Model::create(Blocks::SEA_PICKLE(), Ids::SEA_PICKLE)->properties([ + new IntProperty(StateNames::CLUSTER_COUNT, 0, 3, fn(SeaPickle $b) => $b->getCount(), fn(SeaPickle $b, int $v) => $b->setCount($v), offset: 1), + new BoolProperty(StateNames::DEAD_BIT, fn(SeaPickle $b) => $b->isUnderwater(), fn(SeaPickle $b, bool $v) => $b->setUnderwater($v), inverted: true) + ])); + $reg->mapModel(Model::create(Blocks::SMALL_DRIPLEAF(), Ids::SMALL_DRIPLEAF_BLOCK)->properties([ + new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(SmallDripleaf $b) => $b->isTop(), fn(SmallDripleaf $b, bool $v) => $b->setTop($v)), + $commonProperties->horizontalFacingCardinal + ])); + $reg->mapModel(Model::create(Blocks::SMOOTH_QUARTZ(), Ids::SMOOTH_QUARTZ)->properties([ + $commonProperties->dummyPillarAxis + ])); + $reg->mapModel(Model::create(Blocks::SNOW_LAYER(), Ids::SNOW_LAYER)->properties([ + new DummyProperty(StateNames::COVERED_BIT, false), + new IntProperty(StateNames::HEIGHT, 0, 7, fn(SnowLayer $b) => $b->getLayers(), fn(SnowLayer $b, int $v) => $b->setLayers($v), offset: 1) + ])); + $reg->mapModel(Model::create(Blocks::SOUL_CAMPFIRE(), Ids::SOUL_CAMPFIRE)->properties($commonProperties->campfireProperties)); + $reg->mapModel(Model::create(Blocks::SOUL_FIRE(), Ids::SOUL_FIRE)->properties([ + new DummyProperty(StateNames::AGE, 0) //this is useless for soul fire, since it doesn't have the logic associated + ])); + $reg->mapModel(Model::create(Blocks::SOUL_LANTERN(), Ids::SOUL_LANTERN)->properties([ + new BoolProperty(StateNames::HANGING, fn(Lantern $b) => $b->isHanging(), fn(Lantern $b, bool $v) => $b->setHanging($v)) //TODO: repeated + ])); + $reg->mapModel(Model::create(Blocks::STONE_BUTTON(), Ids::STONE_BUTTON)->properties($commonProperties->buttonProperties)); + $reg->mapModel(Model::create(Blocks::STONE_PRESSURE_PLATE(), Ids::STONE_PRESSURE_PLATE)->properties($commonProperties->simplePressurePlateProperties)); + $reg->mapModel(Model::create(Blocks::STONECUTTER(), Ids::STONECUTTER_BLOCK)->properties([ + $commonProperties->horizontalFacingCardinal + ])); + $reg->mapModel(Model::create(Blocks::SUGARCANE(), Ids::REEDS)->properties([ + new IntProperty(StateNames::AGE, 0, 15, fn(Sugarcane $b) => $b->getAge(), fn(Sugarcane $b, int $v) => $b->setAge($v)) + ])); + + //T + $reg->mapModel(Model::create(Blocks::TRAPPED_CHEST(), Ids::TRAPPED_CHEST)->properties([ + $commonProperties->horizontalFacingCardinal + ])); + $reg->mapModel(Model::create(Blocks::TRIPWIRE(), Ids::TRIP_WIRE)->properties([ + new BoolProperty(StateNames::ATTACHED_BIT, fn(Tripwire $b) => $b->isConnected(), fn(Tripwire $b, bool $v) => $b->setConnected($v)), + new BoolProperty(StateNames::DISARMED_BIT, fn(Tripwire $b) => $b->isDisarmed(), fn(Tripwire $b, bool $v) => $b->setDisarmed($v)), + new BoolProperty(StateNames::SUSPENDED_BIT, fn(Tripwire $b) => $b->isSuspended(), fn(Tripwire $b, bool $v) => $b->setSuspended($v)), + new BoolProperty(StateNames::POWERED_BIT, fn(Tripwire $b) => $b->isTriggered(), fn(Tripwire $b, bool $v) => $b->setTriggered($v)), + ])); + $reg->mapModel(Model::create(Blocks::TRIPWIRE_HOOK(), Ids::TRIPWIRE_HOOK)->properties([ + new BoolProperty(StateNames::ATTACHED_BIT, fn(TripwireHook $b) => $b->isConnected(), fn(TripwireHook $b, bool $v) => $b->setConnected($v)), + new BoolProperty(StateNames::POWERED_BIT, fn(TripwireHook $b) => $b->isPowered(), fn(TripwireHook $b, bool $v) => $b->setPowered($v)), + $commonProperties->horizontalFacingSWNE + ])); + + $reg->mapModel(Model::create(Blocks::TWISTING_VINES(), Ids::TWISTING_VINES)->properties([ + new IntProperty(StateNames::TWISTING_VINES_AGE, 0, 25, fn(NetherVines $b) => $b->getAge(), fn(NetherVines $b, int $v) => $b->setAge($v)) + ])); + + //W + $reg->mapModel(Model::create(Blocks::WALL_BANNER(), Ids::WALL_BANNER)->properties([$commonProperties->horizontalFacingClassic])); + $reg->mapModel(Model::create(Blocks::WEEPING_VINES(), Ids::WEEPING_VINES)->properties([ + new IntProperty(StateNames::WEEPING_VINES_AGE, 0, 25, fn(NetherVines $b) => $b->getAge(), fn(NetherVines $b, int $v) => $b->setAge($v)) + ])); + $reg->mapModel(Model::create(Blocks::WEIGHTED_PRESSURE_PLATE_HEAVY(), Ids::HEAVY_WEIGHTED_PRESSURE_PLATE)->properties([$commonProperties->analogRedstoneSignal])); + $reg->mapModel(Model::create(Blocks::WEIGHTED_PRESSURE_PLATE_LIGHT(), Ids::LIGHT_WEIGHTED_PRESSURE_PLATE)->properties([$commonProperties->analogRedstoneSignal])); + } + + /** + * All mappings that still use the split form of serializer/deserializer registration + * This is typically only used by blocks with one ID but multiple PM types (split by property) + * These currently can't be registered in a unified way, and due to their small number it may not be worth the + * effort to implement a unified way to deal with them + */ + private static function registerSplitMappings(BlockSerializerDeserializerRegistrar $reg) : void{ + //big dripleaf - split into head / stem variants, as stems don't have tilt or leaf state + $reg->serializer->map(Blocks::BIG_DRIPLEAF_HEAD(), function(BigDripleafHead $block) : Writer{ + return Writer::create(Ids::BIG_DRIPLEAF) + ->writeCardinalHorizontalFacing($block->getFacing()) + ->writeUnitEnum(StateNames::BIG_DRIPLEAF_TILT, ValueMappings::getInstance()->dripleafState, $block->getLeafState()) + ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, true); + }); + $reg->serializer->map(Blocks::BIG_DRIPLEAF_STEM(), function(BigDripleafStem $block) : Writer{ + return Writer::create(Ids::BIG_DRIPLEAF) + ->writeCardinalHorizontalFacing($block->getFacing()) + ->writeString(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE) + ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, false); + }); + $reg->deserializer->map(Ids::BIG_DRIPLEAF, function(Reader $in) : Block{ + if($in->readBool(StateNames::BIG_DRIPLEAF_HEAD)){ + return Blocks::BIG_DRIPLEAF_HEAD() + ->setFacing($in->readCardinalHorizontalFacing()) + ->setLeafState($in->readUnitEnum(StateNames::BIG_DRIPLEAF_TILT, ValueMappings::getInstance()->dripleafState)); + }else{ + $in->ignored(StateNames::BIG_DRIPLEAF_TILT); + return Blocks::BIG_DRIPLEAF_STEM()->setFacing($in->readCardinalHorizontalFacing()); + } + }); + + //cauldrons - split into liquid variants, as each have different behaviour + $reg->serializer->map(Blocks::CAULDRON(), Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0)); + $reg->serializer->map(Blocks::LAVA_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_LAVA, $b->getFillLevel())); + //potion cauldrons store their real information in the block actor data + $reg->serializer->map(Blocks::POTION_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); + $reg->serializer->map(Blocks::WATER_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); + $reg->deserializer->map(Ids::CAULDRON, function(Reader $in) : Block{ + $level = $in->readBoundedInt(StateNames::FILL_LEVEL, 0, 6); + if($level === 0){ + $in->ignored(StateNames::CAULDRON_LIQUID); + return Blocks::CAULDRON(); + } + + return (match ($liquid = $in->readString(StateNames::CAULDRON_LIQUID)) { + StringValues::CAULDRON_LIQUID_WATER => Blocks::WATER_CAULDRON(), + StringValues::CAULDRON_LIQUID_LAVA => Blocks::LAVA_CAULDRON(), + StringValues::CAULDRON_LIQUID_POWDER_SNOW => throw new UnsupportedBlockStateException("Powder snow is not supported yet"), + default => throw $in->badValueException(StateNames::CAULDRON_LIQUID, $liquid) + })->setFillLevel($level); + }); + + //mushroom stems, split for consistency with all-sided logs vs normal logs + $reg->serializer->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) + ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM)); + $reg->serializer->map(Blocks::MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) + ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_STEM)); + $reg->deserializer->map(Ids::MUSHROOM_STEM, fn(Reader $in) => match ($in->readBoundedInt(StateNames::HUGE_MUSHROOM_BITS, 0, 15)) { + BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM => Blocks::ALL_SIDED_MUSHROOM_STEM(), + BlockLegacyMetadata::MUSHROOM_BLOCK_STEM => Blocks::MUSHROOM_STEM(), + default => throw new BlockStateDeserializeException("This state does not exist"), + }); + + //pitcher crop, split into single and double variants as double has different properties and behaviour + //this will probably be the most annoying to unify + $reg->serializer->map(Blocks::PITCHER_CROP(), function(PitcherCrop $block) : Writer{ + return Writer::create(Ids::PITCHER_CROP) + ->writeInt(StateNames::GROWTH, $block->getAge()) + ->writeBool(StateNames::UPPER_BLOCK_BIT, false); + }); + $reg->serializer->map(Blocks::DOUBLE_PITCHER_CROP(), function(DoublePitcherCrop $block) : Writer{ + return Writer::create(Ids::PITCHER_CROP) + ->writeInt(StateNames::GROWTH, $block->getAge() + 1 + PitcherCrop::MAX_AGE) + ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); + }); + $reg->deserializer->map(Ids::PITCHER_CROP, function(Reader $in) : Block{ + $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7); + $top = $in->readBool(StateNames::UPPER_BLOCK_BIT); + if($growth <= PitcherCrop::MAX_AGE){ + //top pitcher crop with age 0-2 is an invalid state + //only the bottom half should exist in this case + return $top ? Blocks::AIR() : Blocks::PITCHER_CROP()->setAge($growth); + } + return Blocks::DOUBLE_PITCHER_CROP() + ->setAge(min($growth - PitcherCrop::MAX_AGE - 1, DoublePitcherCrop::MAX_AGE)) + ->setTop($top); + }); + } +} diff --git a/src/data/bedrock/block/convert/property/BoolFromStringProperty.php b/src/data/bedrock/block/convert/property/BoolFromStringProperty.php new file mode 100644 index 000000000..89c64188d --- /dev/null +++ b/src/data/bedrock/block/convert/property/BoolFromStringProperty.php @@ -0,0 +1,78 @@ + + */ +final class BoolFromStringProperty implements StringProperty{ + + /** + * @param \Closure(TBlock) : bool $getter + * @param \Closure(TBlock, bool) : mixed $setter + */ + public function __construct( + private string $name, + private string $falseValue, + private string $trueValue, + private \Closure $getter, + private \Closure $setter + ){} + + public function getName() : string{ + return $this->name; + } + + public function getPossibleValues() : array{ + return [$this->falseValue, $this->trueValue]; + } + + public function deserialize(object $block, BlockStateReader $in) : void{ + $this->deserializePlain($block, $in->readString($this->name)); + } + + public function deserializePlain(object $block, string $raw) : void{ + $value = match($raw){ + $this->falseValue => false, + $this->trueValue => true, + default => throw new BlockStateSerializeException("Invalid value for {$this->name}: $raw"), + }; + + ($this->setter)($block, $value); + } + + public function serialize(object $block, BlockStateWriter $out) : void{ + $out->writeString($this->name, $this->serializePlain($block)); + } + + public function serializePlain(object $block) : string{ + $value = ($this->getter)($block); + return $value ? $this->trueValue : $this->falseValue; + } +} diff --git a/src/data/bedrock/block/convert/property/BoolProperty.php b/src/data/bedrock/block/convert/property/BoolProperty.php new file mode 100644 index 000000000..299ec4076 --- /dev/null +++ b/src/data/bedrock/block/convert/property/BoolProperty.php @@ -0,0 +1,71 @@ + + */ +final class BoolProperty implements Property{ + /** + * @phpstan-param \Closure(TBlock) : bool $getter + * @phpstan-param \Closure(TBlock, bool) : mixed $setter + */ + public function __construct( + private string $name, + private \Closure $getter, + private \Closure $setter, + private bool $inverted = false //we don't *need* this, but it avoids accidentally forgetting a ! in the getter/setter closures (and makes it analysable) + ){} + + /** + * @phpstan-return self + */ + public static function unused(string $name, bool $serializedValue) : self{ + return new self($name, fn() => $serializedValue, fn() => null); + } + + public function getName() : string{ return $this->name; } + + /** + * @phpstan-param TBlock $block + */ + public function deserialize(object $block, BlockStateReader $in) : void{ + $raw = $in->readBool($this->name); + $value = $raw !== $this->inverted; + ($this->setter)($block, $value); + } + + /** + * @phpstan-param TBlock $block + */ + public function serialize(object $block, BlockStateWriter $out) : void{ + $value = ($this->getter)($block); + $raw = $value !== $this->inverted; + $out->writeBool($this->name, $raw); + } +} diff --git a/src/data/bedrock/block/convert/property/CommonProperties.php b/src/data/bedrock/block/convert/property/CommonProperties.php new file mode 100644 index 000000000..71b87139c --- /dev/null +++ b/src/data/bedrock/block/convert/property/CommonProperties.php @@ -0,0 +1,429 @@ + */ + public readonly ValueFromStringProperty $blockFace; + /** @phpstan-var ValueFromStringProperty */ + public readonly ValueFromStringProperty $pillarAxis; + /** @phpstan-var ValueFromStringProperty */ + public readonly ValueFromStringProperty $torchFacing; + + /** @phpstan-var ValueFromStringProperty */ + public readonly ValueFromStringProperty $horizontalFacingCardinal; + /** @phpstan-var ValueFromIntProperty */ + public readonly ValueFromIntProperty $horizontalFacingSWNE; + /** @phpstan-var ValueFromIntProperty */ + public readonly ValueFromIntProperty $horizontalFacingSWNEInverted; + /** @phpstan-var ValueFromIntProperty */ + public readonly ValueFromIntProperty $horizontalFacingClassic; + + /** @phpstan-var ValueFromIntProperty */ + public readonly ValueFromIntProperty $anyFacingClassic; + + /** @phpstan-var OptionSetFromIntProperty */ + public readonly OptionSetFromIntProperty $multiFacingFlags; + + /** @phpstan-var IntProperty */ + public readonly IntProperty $floorSignLikeRotation; + + /** @phpstan-var IntProperty */ + public readonly IntProperty $analogRedstoneSignal; + + /** @phpstan-var IntProperty */ + public readonly IntProperty $cropAgeMax7; + /** @phpstan-var BoolProperty */ + public readonly BoolProperty $doublePlantHalf; + + /** @phpstan-var IntProperty */ + public readonly IntProperty $liquidData; + + /** @phpstan-var BoolProperty */ + public readonly BoolProperty $lit; + + public readonly DummyProperty $dummyCardinalDirection; + public readonly DummyProperty $dummyPillarAxis; + + /** @phpstan-var ValueFromStringProperty */ + public readonly ValueFromStringProperty $dyeColorIdInfix; + + /** @phpstan-var BoolFromStringProperty */ + public readonly BoolFromStringProperty $litIdInfix; + + /** @phpstan-var BoolFromStringProperty */ + public readonly BoolFromStringProperty $slabIdInfix; + /** @phpstan-var BoolFromStringProperty */ + public readonly BoolFromStringProperty $slabPositionProperty; + + /** + * @var StringProperty[] + * @phpstan-var non-empty-list> + */ + public readonly array $coralIdPrefixes; + /** + * @var StringProperty[] + * @phpstan-var non-empty-list> + */ + public readonly array $copperIdPrefixes; + + /** + * @var StringProperty[] + * @phpstan-var non-empty-list> + */ + public readonly array $furnaceIdPrefixes; + + /** + * @var StringProperty[]|string[] + * @phpstan-var non-empty-list> + */ + public readonly array $liquidIdPrefixes; + + /** + * @var StringProperty[] + * @phpstan-var non-empty-list> + */ + public readonly array $woodIdPrefixes; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $buttonProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $campfireProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $doorProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $fenceGateProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $itemFrameProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $simplePressurePlateProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $stairProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $stemProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $trapdoorProperties; + + /** + * @var Property[] + * @phpstan-var non-empty-list> + */ + public readonly array $wallProperties; + + private function __construct(){ + $vm = ValueMappings::getInstance(); + + $hfGet = fn(HorizontalFacing $v) => $v->getFacing(); + $hfSet = fn(HorizontalFacing $v, int $facing) => $v->setFacing($facing); + $this->horizontalFacingCardinal = new ValueFromStringProperty(StateNames::MC_CARDINAL_DIRECTION, $vm->cardinalDirection, $hfGet, $hfSet); + + $this->blockFace = new ValueFromStringProperty( + StateNames::MC_BLOCK_FACE, + $vm->blockFace, + fn(AnyFacing $b) => $b->getFacing(), + fn(AnyFacing $b, int $v) => $b->setFacing($v) + ); + + $this->pillarAxis = new ValueFromStringProperty( + StateNames::PILLAR_AXIS, + $vm->pillarAxis, + fn(PillarRotation $b) => $b->getAxis(), + fn(PillarRotation $b, int $v) => $b->setAxis($v) + ); + + $this->torchFacing = new ValueFromStringProperty( + StateNames::TORCH_FACING_DIRECTION, + $vm->torchFacing, + fn(Torch $b) => $b->getFacing(), + fn(Torch $b, int $v) => $b->setFacing($v) + ); + + $this->horizontalFacingSWNE = new ValueFromIntProperty(StateNames::DIRECTION, $vm->horizontalFacingSWNE, $hfGet, $hfSet); + $this->horizontalFacingSWNEInverted = new ValueFromIntProperty(StateNames::DIRECTION, $vm->horizontalFacingSWNEInverted, $hfGet, $hfSet); + $this->horizontalFacingClassic = new ValueFromIntProperty(StateNames::FACING_DIRECTION, $vm->horizontalFacingClassic, $hfGet, $hfSet); + + $this->anyFacingClassic = new ValueFromIntProperty( + StateNames::FACING_DIRECTION, + $vm->facing, + fn(AnyFacing $b) => $b->getFacing(), + fn(AnyFacing $b, int $v) => $b->setFacing($v) + ); + + $this->multiFacingFlags = new OptionSetFromIntProperty( + StateNames::MULTI_FACE_DIRECTION_BITS, + IntFromRawStateMap::int([ + Facing::DOWN => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN, + Facing::UP => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_UP, + Facing::NORTH => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_NORTH, + Facing::SOUTH => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_SOUTH, + Facing::WEST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST, + Facing::EAST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST + ]), + fn(MultiFacing $b) => $b->getFaces(), + fn(MultiFacing $b, array $v) => $b->setFaces($v) + ); + + $this->floorSignLikeRotation = new IntProperty(StateNames::GROUND_SIGN_DIRECTION, 0, 15, fn(SignLikeRotation $b) => $b->getRotation(), fn(SignLikeRotation $b, int $v) => $b->setRotation($v)); + + $this->analogRedstoneSignal = new IntProperty(StateNames::REDSTONE_SIGNAL, 0, 15, fn(AnalogRedstoneSignalEmitter $b) => $b->getOutputSignalStrength(), fn(AnalogRedstoneSignalEmitter $b, int $v) => $b->setOutputSignalStrength($v)); + + $this->cropAgeMax7 = new IntProperty(StateNames::GROWTH, 0, 7, fn(Ageable $b) => $b->getAge(), fn(Ageable $b, int $v) => $b->setAge($v)); + $this->doublePlantHalf = new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(DoublePlant $b) => $b->isTop(), fn(DoublePlant $b, bool $v) => $b->setTop($v)); + + $fallingFlag = BlockLegacyMetadata::LIQUID_FALLING_FLAG; + $this->liquidData = new IntProperty( + StateNames::LIQUID_DEPTH, + 0, + 15, + fn(Liquid $b) => $b->getDecay() | ($b->isFalling() ? $fallingFlag : 0), + fn(Liquid $b, int $v) => $b->setDecay($v & ~$fallingFlag)->setFalling(($v & $fallingFlag) !== 0) + ); + + $this->lit = new BoolProperty(StateNames::LIT, fn(Lightable $b) => $b->isLit(), fn(Lightable $b, bool $v) => $b->setLit($v)); + + $this->dummyCardinalDirection = new DummyProperty(StateNames::MC_CARDINAL_DIRECTION, BlockStateStringValues::MC_CARDINAL_DIRECTION_SOUTH); + $this->dummyPillarAxis = new DummyProperty(StateNames::PILLAR_AXIS, BlockStateStringValues::PILLAR_AXIS_Y); + + $this->dyeColorIdInfix = new ValueFromStringProperty("color", $vm->dyeColor, fn(Colored $b) => $b->getColor(), fn(Colored $b, DyeColor $v) => $b->setColor($v)); + $this->litIdInfix = new BoolFromStringProperty("lit", "", "lit_", fn(Lightable $b) => $b->isLit(), fn(Lightable $b, bool $v) => $b->setLit($v)); + + $this->slabIdInfix = new BoolFromStringProperty( + "double", + "", + "double_", + fn(Slab $b) => $b->getSlabType() === SlabType::DOUBLE, + + //we don't know this is actually a bottom slab yet but we don't have enough information to set the + //correct type in this handler + //BOTTOM serves as a signal value for the state deserializer to decide whether to ignore the + //upper_block_bit property + fn(Slab $b, bool $v) => $b->setSlabType($v ? SlabType::DOUBLE : SlabType::BOTTOM) + ); + $this->slabPositionProperty = new BoolFromStringProperty( + StateNames::MC_VERTICAL_HALF, + BlockStateStringValues::MC_VERTICAL_HALF_BOTTOM, + BlockStateStringValues::MC_VERTICAL_HALF_TOP, + fn(Slab $b) => $b->getSlabType() === SlabType::TOP, + + //Ignore the value for double slabs (should be set by ID component before this is reached) + fn(Slab $b, bool $v) => $b->getSlabType() !== SlabType::DOUBLE ? $b->setSlabType($v ? SlabType::TOP : SlabType::BOTTOM) : null + ); + + $this->coralIdPrefixes = [ + "minecraft:", + new BoolFromStringProperty("dead", "", "dead_", fn(CoralMaterial $b) => $b->isDead(), fn(CoralMaterial $b, bool $v) => $b->setDead($v)), + new ValueFromStringProperty("type", EnumFromRawStateMap::string(CoralType::class, fn(CoralType $case) => match ($case) { + CoralType::BRAIN => "brain", + CoralType::BUBBLE => "bubble", + CoralType::FIRE => "fire", + CoralType::HORN => "horn", + CoralType::TUBE => "tube" + }), fn(CoralMaterial $b) => $b->getCoralType(), fn(CoralMaterial $b, CoralType $v) => $b->setCoralType($v)), + ]; + $this->copperIdPrefixes = [ + "minecraft:", + new BoolFromStringProperty("waxed", "", "waxed_", fn(CopperMaterial $b) => $b->isWaxed(), fn(CopperMaterial $b, bool $v) => $b->setWaxed($v)), + new ValueFromStringProperty("oxidation", EnumFromRawStateMap::string(CopperOxidation::class, fn(CopperOxidation $case) => match ($case) { + CopperOxidation::NONE => "", + CopperOxidation::EXPOSED => "exposed_", + CopperOxidation::WEATHERED => "weathered_", + CopperOxidation::OXIDIZED => "oxidized_", + }), fn(CopperMaterial $b) => $b->getOxidation(), fn(CopperMaterial $b, CopperOxidation $v) => $b->setOxidation($v)) + ]; + + $this->furnaceIdPrefixes = ["minecraft:", $this->litIdInfix]; + + $this->liquidIdPrefixes = [ + "minecraft:", + new BoolFromStringProperty("still", "flowing_", "", fn(Liquid $b) => $b->isStill(), fn(Liquid $b, bool $v) => $b->setStill($v)) + ]; + + $this->woodIdPrefixes = [ + "minecraft:", + new BoolFromStringProperty("stripped", "", "stripped_", fn(Wood $b) => $b->isStripped(), fn(Wood $b, bool $v) => $b->setStripped($v)), + ]; + + $this->buttonProperties = [ + $this->anyFacingClassic, + new BoolProperty(StateNames::BUTTON_PRESSED_BIT, fn(Button $b) => $b->isPressed(), fn(Button $b, bool $v) => $b->setPressed($v)), + ]; + + $this->campfireProperties = [ + $this->horizontalFacingCardinal, + new BoolProperty(StateNames::EXTINGUISHED, fn(Lightable $b) => $b->isLit(), fn(Lightable $b, bool $v) => $b->setLit($v), inverted: true), + ]; + + //TODO: check if these need any special treatment to get the appropriate data to both halves of the door + $this->doorProperties = [ + new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(Door $b) => $b->isTop(), fn(Door $b, bool $v) => $b->setTop($v)), + new BoolProperty(StateNames::DOOR_HINGE_BIT, fn(Door $b) => $b->isHingeRight(), fn(Door $b, bool $v) => $b->setHingeRight($v)), + new BoolProperty(StateNames::OPEN_BIT, fn(Door $b) => $b->isOpen(), fn(Door $b, bool $v) => $b->setOpen($v)), + new ValueFromStringProperty( + StateNames::MC_CARDINAL_DIRECTION, + IntFromRawStateMap::string([ + //a door facing "east" is actually facing north - thanks mojang + Facing::NORTH => BlockStateStringValues::MC_CARDINAL_DIRECTION_EAST, + Facing::EAST => BlockStateStringValues::MC_CARDINAL_DIRECTION_SOUTH, + Facing::SOUTH => BlockStateStringValues::MC_CARDINAL_DIRECTION_WEST, + Facing::WEST => BlockStateStringValues::MC_CARDINAL_DIRECTION_NORTH + ]), + fn(HorizontalFacing $b) => $b->getFacing(), + fn(HorizontalFacing $b, int $v) => $b->setFacing($v) + ) + ]; + + $this->fenceGateProperties = [ + new BoolProperty(StateNames::IN_WALL_BIT, fn(FenceGate $b) => $b->isInWall(), fn(FenceGate $b, bool $v) => $b->setInWall($v)), + new BoolProperty(StateNames::OPEN_BIT, fn(FenceGate $b) => $b->isOpen(), fn(FenceGate $b, bool $v) => $b->setOpen($v)), + $this->horizontalFacingCardinal, + ]; + + $this->itemFrameProperties = [ + new DummyProperty(StateNames::ITEM_FRAME_PHOTO_BIT, false), //TODO: not sure what the point of this is + new BoolProperty(StateNames::ITEM_FRAME_MAP_BIT, fn(ItemFrame $b) => $b->hasMap(), fn(ItemFrame $b, bool $v) => $b->setHasMap($v)), + $this->anyFacingClassic + ]; + + $this->simplePressurePlateProperties = [ + //TODO: not sure what the deal is here ... seems like a mojang bug / artifact of bad implementation? + //best to keep this separate from weighted plates anyway... + new IntProperty( + StateNames::REDSTONE_SIGNAL, + 0, + 15, + fn(SimplePressurePlate $b) => $b->isPressed() ? 15 : 0, + fn(SimplePressurePlate $b, int $v) => $b->setPressed($v !== 0) + ) + ]; + + $this->stairProperties = [ + new BoolProperty(StateNames::UPSIDE_DOWN_BIT, fn(Stair $b) => $b->isUpsideDown(), fn(Stair $b, bool $v) => $b->setUpsideDown($v)), + new ValueFromIntProperty(StateNames::WEIRDO_DIRECTION, $vm->horizontalFacing5Minus, $hfGet, $hfSet), + ]; + + $this->stemProperties = [ + new ValueFromIntProperty(StateNames::FACING_DIRECTION, $vm->facingStem, fn(Stem $b) => $b->getFacing(), fn(Stem $b, int $v) => $b->setFacing($v)), + $this->cropAgeMax7 + ]; + + $this->trapdoorProperties = [ + //this uses the same values as stairs, but the state is named differently + new ValueFromIntProperty(StateNames::DIRECTION, $vm->horizontalFacing5Minus, $hfGet, $hfSet), + + new BoolProperty(StateNames::UPSIDE_DOWN_BIT, fn(Trapdoor $b) => $b->isTop(), fn(Trapdoor $b, bool $v) => $b->setTop($v)), + new BoolProperty(StateNames::OPEN_BIT, fn(Trapdoor $b) => $b->isOpen(), fn(Trapdoor $b, bool $v) => $b->setOpen($v)), + ]; + + $wallProperties = [ + new BoolProperty(StateNames::WALL_POST_BIT, fn(Wall $b) => $b->isPost(), fn(Wall $b, bool $v) => $b->setPost($v)), + ]; + foreach([ + Facing::NORTH => StateNames::WALL_CONNECTION_TYPE_NORTH, + Facing::SOUTH => StateNames::WALL_CONNECTION_TYPE_SOUTH, + Facing::WEST => StateNames::WALL_CONNECTION_TYPE_WEST, + Facing::EAST => StateNames::WALL_CONNECTION_TYPE_EAST + ] as $facing => $stateName){ + $wallProperties[] = new ValueFromStringProperty( + $stateName, + EnumFromRawStateMap::string(WallConnectionTypeShim::class, fn(WallConnectionTypeShim $case) => $case->getValue()), + fn(Wall $b) => WallConnectionTypeShim::serialize($b->getConnection($facing)), + fn(Wall $b, WallConnectionTypeShim $v) => $b->setConnection($facing, $v->deserialize()) + ); + } + $this->wallProperties = $wallProperties; + } +} diff --git a/src/data/bedrock/block/convert/property/DummyProperty.php b/src/data/bedrock/block/convert/property/DummyProperty.php new file mode 100644 index 000000000..a9d32b417 --- /dev/null +++ b/src/data/bedrock/block/convert/property/DummyProperty.php @@ -0,0 +1,61 @@ + + */ +final class DummyProperty implements Property{ + public function __construct( + private string $name, + private bool|int|string $value + ){} + + public function getName() : string{ + return $this->name; + } + + public function deserialize(object $block, BlockStateReader $in) : void{ + $in->ignored($this->name); + } + + public function serialize(object $block, BlockStateWriter $out) : void{ + if(is_bool($this->value)){ + $out->writeBool($this->name, $this->value); + }elseif(is_int($this->value)){ + $out->writeInt($this->name, $this->value); + }elseif(is_string($this->value)){ + $out->writeString($this->name, $this->value); + }else{ + throw new AssumptionFailedError(); + } + } +} diff --git a/src/data/bedrock/block/convert/property/EnumFromRawStateMap.php b/src/data/bedrock/block/convert/property/EnumFromRawStateMap.php new file mode 100644 index 000000000..c6f4d0516 --- /dev/null +++ b/src/data/bedrock/block/convert/property/EnumFromRawStateMap.php @@ -0,0 +1,109 @@ + + */ +class EnumFromRawStateMap implements StateMap{ + /** + * @var int[] + * @phpstan-var array + */ + private array $enumToValue = []; + + /** + * @var \UnitEnum[] + * @phpstan-var array + */ + private array $valueToEnum = []; + + /** + * @phpstan-param class-string $class + * @phpstan-param \Closure(TEnum) : TRaw $mapper + * @phpstan-param ?\Closure(TEnum) : list $aliasMapper + */ + public function __construct( + string $class, + \Closure $mapper, + ?\Closure $aliasMapper = null + ){ + foreach($class::cases() as $case){ + $int = $mapper($case); + $this->valueToEnum[$int] = $case; + $this->enumToValue[spl_object_id($case)] = $int; + + if($aliasMapper !== null){ + $aliases = $aliasMapper($case); + foreach($aliases as $alias){ + $this->valueToEnum[$alias] = $case; + } + } + } + } + + /** + * Workaround PHPStan too-specific literal type inference - if it ever gets fixed we can get rid of these functions + * + * @phpstan-template TEnum_ of \UnitEnum + * @phpstan-param class-string $class + * @param \Closure(TEnum_) : string $mapper + * @param ?\Closure(TEnum_) : list $aliasMapper + * + * @phpstan-return EnumFromRawStateMap + */ + public static function string(string $class, \Closure $mapper, ?\Closure $aliasMapper = null) : self{ return new self($class, $mapper, $aliasMapper); } + + /** + * Workaround PHPStan too-specific literal type inference - if it ever gets fixed we can get rid of these functions + * + * @phpstan-template TEnum_ of \UnitEnum + * @phpstan-param class-string $class + * @param \Closure(TEnum_) : int $mapper + * @param ?\Closure(TEnum_) : list $aliasMapper + * + * @phpstan-return EnumFromRawStateMap + */ + public static function int(string $class, \Closure $mapper, ?\Closure $aliasMapper = null) : self{ return new self($class, $mapper, $aliasMapper); } + + public function getRawToValueMap() : array{ + return $this->valueToEnum; + } + + public function valueToRaw(mixed $value) : int|string{ + return $this->enumToValue[spl_object_id($value)]; + } + + public function rawToValue(int|string $raw) : ?\UnitEnum{ + return $this->valueToEnum[$raw] ?? null; + } + + public function printableValue(mixed $value) : string{ + return $value::class . "::" . $value->name; + } +} diff --git a/src/data/bedrock/block/convert/property/FlattenedCaveVinesVariant.php b/src/data/bedrock/block/convert/property/FlattenedCaveVinesVariant.php new file mode 100644 index 000000000..979fc4751 --- /dev/null +++ b/src/data/bedrock/block/convert/property/FlattenedCaveVinesVariant.php @@ -0,0 +1,35 @@ + + */ +class IntFromRawStateMap implements StateMap{ + + /** + * @var int[] + * @phpstan-var array + */ + private array $deserializeMap; + + /** + * Constructs a bidirectional mapping, given a mapping of internal values -> serialized values, and an optional set + * of aliases per internal value (used for deserializing invalid serialized values). + * + * @param (int|string)[] $serializeMap + * @param (int|int[])|(string|string[]) $deserializeAliases + * + * @phpstan-param array $serializeMap + * @phpstan-param array> $deserializeAliases + */ + public function __construct( + private array $serializeMap, + array $deserializeAliases = [] + ){ + $this->deserializeMap = array_flip($this->serializeMap); + foreach($deserializeAliases as $pmValue => $mcValues){ + if(!is_array($mcValues)){ + $this->deserializeMap[$mcValues] = $pmValue; + }else{ + foreach($mcValues as $mcValue){ + $this->deserializeMap[$mcValue] = $pmValue; + } + } + } + } + + /** + * @param int[] $serializeMap + * @param (int|int[]) $deserializeAliases + * + * @phpstan-param array $serializeMap + * @phpstan-param array> $deserializeAliases + * + * @phpstan-return self + */ + public static function int(array $serializeMap, array $deserializeAliases = []) : self{ return new self($serializeMap, $deserializeAliases); } + + /** + * @param string[] $serializeMap + * @param (string|string[]) $deserializeAliases + * + * @phpstan-param array $serializeMap + * @phpstan-param array> $deserializeAliases + * + * @phpstan-return self + */ + public static function string(array $serializeMap, array $deserializeAliases = []) : self{ return new self($serializeMap, $deserializeAliases); } + + public function getRawToValueMap() : array{ + return $this->deserializeMap; + } + + public function valueToRaw(mixed $value) : int|string{ + return $this->serializeMap[$value]; + } + + public function rawToValue(int|string $raw) : mixed{ + return $this->deserializeMap[$raw] ?? null; + } + + public function printableValue(mixed $value) : string{ + return "$value"; + } +} diff --git a/src/data/bedrock/block/convert/property/IntProperty.php b/src/data/bedrock/block/convert/property/IntProperty.php new file mode 100644 index 000000000..bab865522 --- /dev/null +++ b/src/data/bedrock/block/convert/property/IntProperty.php @@ -0,0 +1,70 @@ + + */ +final class IntProperty implements Property{ + /** + * @phpstan-param \Closure(TBlock) : int $getter + * @phpstan-param \Closure(TBlock, int) : mixed $setter + */ + public function __construct( + private string $name, + private int $min, + private int $max, + private \Closure $getter, + private \Closure $setter, + private int $offset = 0 + ){ + if($min > $max){ + throw new \InvalidArgumentException("Min value cannot be greater than max value"); + } + } + + public function getName() : string{ return $this->name; } + + /** + * @phpstan-return self + */ + public static function unused(string $name, int $serializedValue) : self{ + return new self($name, Limits::INT32_MIN, Limits::INT32_MAX, fn() => $serializedValue, fn() => null); + } + + public function deserialize(object $block, BlockStateReader $in) : void{ + $value = $in->readBoundedInt($this->name, $this->min, $this->max); + ($this->setter)($block, $value + $this->offset); + } + + public function serialize(object $block, BlockStateWriter $out) : void{ + $value = ($this->getter)($block); + $out->writeInt($this->name, $value - $this->offset); + } +} diff --git a/src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php b/src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php new file mode 100644 index 000000000..a91c681b8 --- /dev/null +++ b/src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php @@ -0,0 +1,93 @@ + + */ +class OptionSetFromIntProperty implements Property{ + + private int $maxValue = 0; + + /** + * @phpstan-param StateMap $map + * @phpstan-param \Closure(TBlock) : array $getter + * @phpstan-param \Closure(TBlock, array) : mixed $setter + */ + public function __construct( + private string $name, + private StateMap $map, + private \Closure $getter, + private \Closure $setter + ){ + $flagsToCases = $this->map->getRawToValueMap(); + foreach($flagsToCases as $possibleFlag => $option){ + if(($this->maxValue & $possibleFlag) !== 0){ + foreach($flagsToCases as $otherFlag => $otherOption){ + if(($possibleFlag & $otherFlag) === $otherFlag && $otherOption !== $option){ + $printableOption = $this->map->printableValue($option); + $printableOtherOption = $this->map->printableValue($otherOption); + throw new \InvalidArgumentException("Flag for option $printableOption overlaps with flag for option $printableOtherOption in property $this->name"); + } + } + + throw new AssumptionFailedError("Unreachable"); + } + + $this->maxValue |= $possibleFlag; + } + } + + public function getName() : string{ return $this->name; } + + public function deserialize(object $block, BlockStateReader $in) : void{ + $flags = $in->readBoundedInt($this->name, 0, $this->maxValue); + + $value = []; + foreach($this->map->getRawToValueMap() as $possibleFlag => $option){ + if(($flags & $possibleFlag) === $possibleFlag){ + $value[] = $option; + } + } + + ($this->setter)($block, $value); + } + + public function serialize(object $block, BlockStateWriter $out) : void{ + $flags = 0; + + $value = ($this->getter)($block); + foreach($value as $option){ + $flags |= $this->map->valueToRaw($option); + } + + $out->writeInt($this->name, $flags); + } +} diff --git a/src/data/bedrock/block/convert/property/Property.php b/src/data/bedrock/block/convert/property/Property.php new file mode 100644 index 000000000..30868dcd1 --- /dev/null +++ b/src/data/bedrock/block/convert/property/Property.php @@ -0,0 +1,44 @@ + + */ + public function getRawToValueMap() : array; + + /** + * @phpstan-param TValue $value + * @phpstan-return TRaw + */ + public function valueToRaw(mixed $value) : int|string; + + /** + * @phpstan-param TRaw $raw + * @phpstan-return TValue|null + */ + public function rawToValue(int|string $raw) : mixed; + + /** + * @phpstan-param TValue $value + */ + public function printableValue(mixed $value) : string; +} diff --git a/src/data/bedrock/block/convert/property/StringProperty.php b/src/data/bedrock/block/convert/property/StringProperty.php new file mode 100644 index 000000000..14ef1beff --- /dev/null +++ b/src/data/bedrock/block/convert/property/StringProperty.php @@ -0,0 +1,50 @@ + + */ +interface StringProperty extends Property{ + /** + * @return string[] + * @phpstan-return list + */ + public function getPossibleValues() : array; + + /** + * TODO: These are only used for flattened IDs for now, we should expand their use to all properties + * in the future and remove the dependencies on BlockStateReader and BlockStateWriter + * @phpstan-param TBlock $block + */ + public function deserializePlain(object $block, string $raw) : void; + + /** + * TODO: These are only used for flattened IDs for now, we should expand their use to all properties + * in the future and remove the dependencies on BlockStateReader and BlockStateWriter + * @phpstan-param TBlock $block + */ + public function serializePlain(object $block) : string; +} diff --git a/src/data/bedrock/block/convert/property/ValueFromIntProperty.php b/src/data/bedrock/block/convert/property/ValueFromIntProperty.php new file mode 100644 index 000000000..46b721255 --- /dev/null +++ b/src/data/bedrock/block/convert/property/ValueFromIntProperty.php @@ -0,0 +1,75 @@ + + */ +final class ValueFromIntProperty implements Property{ + + /** + * @phpstan-param StateMap $map + * @phpstan-param \Closure(TBlock) : TValue $getter + * @phpstan-param \Closure(TBlock, TValue) : mixed $setter + */ + public function __construct( + private string $name, + private StateMap $map, + private \Closure $getter, + private \Closure $setter + ){} + + public function getName() : string{ return $this->name; } + + /** + * @return int[] + * @phpstan-return list + */ + public function getPossibleValues() : array{ + return array_keys($this->map->getRawToValueMap()); + } + + public function deserialize(object $block, BlockStateReader $in) : void{ + $raw = $in->readInt($this->name); + $value = $this->map->rawToValue($raw); + + if($value === null){ + throw $in->badValueException($this->name, (string) $raw); + } + ($this->setter)($block, $value); + } + + public function serialize(object $block, BlockStateWriter $out) : void{ + $value = ($this->getter)($block); + $raw = $this->map->valueToRaw($value); + + $out->writeInt($this->name, $raw); + } +} diff --git a/src/data/bedrock/block/convert/property/ValueFromStringProperty.php b/src/data/bedrock/block/convert/property/ValueFromStringProperty.php new file mode 100644 index 000000000..b33634ae9 --- /dev/null +++ b/src/data/bedrock/block/convert/property/ValueFromStringProperty.php @@ -0,0 +1,81 @@ + + */ +final class ValueFromStringProperty implements StringProperty{ + + /** + * @phpstan-param StateMap $map + * @phpstan-param \Closure(TBlock) : TValue $getter + * @phpstan-param \Closure(TBlock, TValue) : mixed $setter + */ + public function __construct( + private string $name, + private StateMap $map, + private \Closure $getter, + private \Closure $setter + ){} + + public function getName() : string{ return $this->name; } + + /** + * @return string[] + * @phpstan-return list + */ + public function getPossibleValues() : array{ + //PHP sucks + return array_map(strval(...), array_keys($this->map->getRawToValueMap())); + } + + public function deserialize(object $block, BlockStateReader $in) : void{ + $this->deserializePlain($block, $in->readString($this->name)); + } + + public function deserializePlain(object $block, string $raw) : void{ + //TODO: duplicated code from BlockStateReader :( + $value = $this->map->rawToValue($raw) ?? throw new BlockStateDeserializeException("Property \"$this->name\" has invalid value \"$raw\""); + ($this->setter)($block, $value); + } + + public function serialize(object $block, BlockStateWriter $out) : void{ + $out->writeString($this->name, $this->serializePlain($block)); + } + + public function serializePlain(object $block) : string{ + $value = ($this->getter)($block); + return $this->map->valueToRaw($value); + } +} diff --git a/src/data/bedrock/block/convert/property/ValueMappings.php b/src/data/bedrock/block/convert/property/ValueMappings.php new file mode 100644 index 000000000..22e9803a5 --- /dev/null +++ b/src/data/bedrock/block/convert/property/ValueMappings.php @@ -0,0 +1,305 @@ + */ + public readonly EnumFromRawStateMap $dyeColor; + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $dyeColorWithSilver; + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $mobHeadType; + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $froglightType; + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $dirtType; + + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $dripleafState; + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $bellAttachmentType; + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $leverFacing; + + /** @phpstan-var EnumFromRawStateMap */ + public readonly EnumFromRawStateMap $mushroomBlockType; + + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $cardinalDirection; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $blockFace; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $pillarAxis; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $torchFacing; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $portalAxis; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $bambooLeafSize; + + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $horizontalFacing5Minus; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $horizontalFacingSWNE; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $horizontalFacingSWNEInverted; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $horizontalFacingCoral; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $horizontalFacingClassic; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $facing; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $facingEndRod; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $coralAxis; + + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $facingExceptDown; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $facingExceptUp; + /** @phpstan-var IntFromRawStateMap */ + public readonly IntFromRawStateMap $facingStem; + + public function __construct(){ + //flattened ID components - we can't generate constants for these + $this->dyeColor = EnumFromRawStateMap::string(DyeColor::class, fn(DyeColor $case) => match ($case) { + DyeColor::BLACK => "black", + DyeColor::BLUE => "blue", + DyeColor::BROWN => "brown", + DyeColor::CYAN => "cyan", + DyeColor::GRAY => "gray", + DyeColor::GREEN => "green", + DyeColor::LIGHT_BLUE => "light_blue", + DyeColor::LIGHT_GRAY => "light_gray", + DyeColor::LIME => "lime", + DyeColor::MAGENTA => "magenta", + DyeColor::ORANGE => "orange", + DyeColor::PINK => "pink", + DyeColor::PURPLE => "purple", + DyeColor::RED => "red", + DyeColor::WHITE => "white", + DyeColor::YELLOW => "yellow" + }); + $this->dyeColorWithSilver = EnumFromRawStateMap::string(DyeColor::class, fn(DyeColor $case) => match ($case) { + DyeColor::LIGHT_GRAY => "silver", + default => $this->dyeColor->valueToRaw($case) + }); + + $this->mobHeadType = EnumFromRawStateMap::string(MobHeadType::class, fn(MobHeadType $case) => match ($case) { + MobHeadType::CREEPER => Ids::CREEPER_HEAD, + MobHeadType::DRAGON => Ids::DRAGON_HEAD, + MobHeadType::PIGLIN => Ids::PIGLIN_HEAD, + MobHeadType::PLAYER => Ids::PLAYER_HEAD, + MobHeadType::SKELETON => Ids::SKELETON_SKULL, + MobHeadType::WITHER_SKELETON => Ids::WITHER_SKELETON_SKULL, + MobHeadType::ZOMBIE => Ids::ZOMBIE_HEAD + }); + $this->froglightType = EnumFromRawStateMap::string(FroglightType::class, fn(FroglightType $case) => match ($case) { + FroglightType::OCHRE => Ids::OCHRE_FROGLIGHT, + FroglightType::PEARLESCENT => Ids::PEARLESCENT_FROGLIGHT, + FroglightType::VERDANT => Ids::VERDANT_FROGLIGHT, + }); + $this->dirtType = EnumFromRawStateMap::string(DirtType::class, fn(DirtType $case) => match ($case) { + DirtType::NORMAL => Ids::DIRT, + DirtType::COARSE => Ids::COARSE_DIRT, + DirtType::ROOTED => Ids::DIRT_WITH_ROOTS, + }); + + //state value mappings + $this->dripleafState = EnumFromRawStateMap::string(DripleafState::class, fn(DripleafState $case) => match ($case) { + DripleafState::STABLE => StringValues::BIG_DRIPLEAF_TILT_NONE, + DripleafState::UNSTABLE => StringValues::BIG_DRIPLEAF_TILT_UNSTABLE, + DripleafState::PARTIAL_TILT => StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT, + DripleafState::FULL_TILT => StringValues::BIG_DRIPLEAF_TILT_FULL_TILT + }); + $this->bellAttachmentType = EnumFromRawStateMap::string(BellAttachmentType::class, fn(BellAttachmentType $case) => match ($case) { + BellAttachmentType::FLOOR => StringValues::ATTACHMENT_STANDING, + BellAttachmentType::CEILING => StringValues::ATTACHMENT_HANGING, + BellAttachmentType::ONE_WALL => StringValues::ATTACHMENT_SIDE, + BellAttachmentType::TWO_WALLS => StringValues::ATTACHMENT_MULTIPLE, + }); + $this->leverFacing = EnumFromRawStateMap::string(LeverFacing::class, fn(LeverFacing $case) => match ($case) { + LeverFacing::DOWN_AXIS_Z => StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH, + LeverFacing::DOWN_AXIS_X => StringValues::LEVER_DIRECTION_DOWN_EAST_WEST, + LeverFacing::UP_AXIS_Z => StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH, + LeverFacing::UP_AXIS_X => StringValues::LEVER_DIRECTION_UP_EAST_WEST, + LeverFacing::NORTH => StringValues::LEVER_DIRECTION_NORTH, + LeverFacing::SOUTH => StringValues::LEVER_DIRECTION_SOUTH, + LeverFacing::WEST => StringValues::LEVER_DIRECTION_WEST, + LeverFacing::EAST => StringValues::LEVER_DIRECTION_EAST + }); + + $this->mushroomBlockType = EnumFromRawStateMap::int( + MushroomBlockType::class, + fn(MushroomBlockType $case) => match ($case) { + MushroomBlockType::PORES => LegacyMeta::MUSHROOM_BLOCK_ALL_PORES, + MushroomBlockType::CAP_NORTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER, + MushroomBlockType::CAP_NORTH => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE, + MushroomBlockType::CAP_NORTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER, + MushroomBlockType::CAP_WEST => LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE, + MushroomBlockType::CAP_MIDDLE => LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY, + MushroomBlockType::CAP_EAST => LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE, + MushroomBlockType::CAP_SOUTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER, + MushroomBlockType::CAP_SOUTH => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE, + MushroomBlockType::CAP_SOUTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER, + MushroomBlockType::ALL_CAP => LegacyMeta::MUSHROOM_BLOCK_ALL_CAP, + }, + fn(MushroomBlockType $case) => match ($case) { + MushroomBlockType::ALL_CAP => [11, 12, 13], + default => [] + } + ); + + $this->cardinalDirection = IntFromRawStateMap::string([ + Facing::NORTH => StringValues::MC_CARDINAL_DIRECTION_NORTH, + Facing::SOUTH => StringValues::MC_CARDINAL_DIRECTION_SOUTH, + Facing::WEST => StringValues::MC_CARDINAL_DIRECTION_WEST, + Facing::EAST => StringValues::MC_CARDINAL_DIRECTION_EAST, + ]); + $this->blockFace = IntFromRawStateMap::string([ + Facing::DOWN => StringValues::MC_BLOCK_FACE_DOWN, + Facing::UP => StringValues::MC_BLOCK_FACE_UP, + Facing::NORTH => StringValues::MC_BLOCK_FACE_NORTH, + Facing::SOUTH => StringValues::MC_BLOCK_FACE_SOUTH, + Facing::WEST => StringValues::MC_BLOCK_FACE_WEST, + Facing::EAST => StringValues::MC_BLOCK_FACE_EAST, + ]); + $this->pillarAxis = IntFromRawStateMap::string([ + Axis::X => StringValues::PILLAR_AXIS_X, + Axis::Y => StringValues::PILLAR_AXIS_Y, + Axis::Z => StringValues::PILLAR_AXIS_Z + ]); + $this->torchFacing = IntFromRawStateMap::string([ + //TODO: horizontal directions are flipped (MCPE bug: https://bugs.mojang.com/browse/MCPE-152036) + Facing::WEST => StringValues::TORCH_FACING_DIRECTION_EAST, + Facing::SOUTH => StringValues::TORCH_FACING_DIRECTION_NORTH, + Facing::NORTH => StringValues::TORCH_FACING_DIRECTION_SOUTH, + Facing::UP => StringValues::TORCH_FACING_DIRECTION_TOP, + Facing::EAST => StringValues::TORCH_FACING_DIRECTION_WEST, + ], deserializeAliases: [ + Facing::UP => StringValues::TORCH_FACING_DIRECTION_UNKNOWN //should be illegal, but still supported + ]); + $this->portalAxis = IntFromRawStateMap::string([ + Axis::X => StringValues::PORTAL_AXIS_X, + Axis::Z => StringValues::PORTAL_AXIS_Z, + ], deserializeAliases: [ + Axis::X => StringValues::PORTAL_AXIS_UNKNOWN, + ]); + $this->bambooLeafSize = IntFromRawStateMap::string([ + Bamboo::NO_LEAVES => StringValues::BAMBOO_LEAF_SIZE_NO_LEAVES, + Bamboo::SMALL_LEAVES => StringValues::BAMBOO_LEAF_SIZE_SMALL_LEAVES, + Bamboo::LARGE_LEAVES => StringValues::BAMBOO_LEAF_SIZE_LARGE_LEAVES, + ]); + + $this->horizontalFacing5Minus = IntFromRawStateMap::int([ + Facing::EAST => 0, + Facing::WEST => 1, + Facing::SOUTH => 2, + Facing::NORTH => 3 + ]); + $this->horizontalFacingSWNE = IntFromRawStateMap::int([ + Facing::SOUTH => 0, + Facing::WEST => 1, + Facing::NORTH => 2, + Facing::EAST => 3 + ]); + $this->horizontalFacingSWNEInverted = IntFromRawStateMap::int([ + Facing::NORTH => 0, + Facing::EAST => 1, + Facing::SOUTH => 2, + Facing::WEST => 3, + ]); + $this->horizontalFacingCoral = IntFromRawStateMap::int([ + Facing::WEST => 0, + Facing::EAST => 1, + Facing::NORTH => 2, + Facing::SOUTH => 3 + ]); + $horizontalFacingClassicTable = [ + Facing::NORTH => 2, + Facing::SOUTH => 3, + Facing::WEST => 4, + Facing::EAST => 5 + ]; + $this->horizontalFacingClassic = IntFromRawStateMap::int($horizontalFacingClassicTable, deserializeAliases: [ + Facing::NORTH => [0, 1] //should be illegal but still technically possible + ]); + + $this->facing = IntFromRawStateMap::int([ + Facing::DOWN => 0, + Facing::UP => 1 + ] + $horizontalFacingClassicTable); + + //end rods have all the horizontal facing values opposite to classic facing + $this->facingEndRod = IntFromRawStateMap::int([ + Facing::DOWN => 0, + Facing::UP => 1, + Facing::SOUTH => 2, + Facing::NORTH => 3, + Facing::EAST => 4, + Facing::WEST => 5, + ]); + + $this->coralAxis = IntFromRawStateMap::int([ + Axis::X => 0, + Axis::Z => 1, + ]); + + //TODO: shitty copy pasta job, we can do this better but this is good enough for now + $this->facingExceptDown = IntFromRawStateMap::int( + [Facing::UP => 1] + $horizontalFacingClassicTable, + deserializeAliases: [Facing::UP => 0]); + $this->facingExceptUp = IntFromRawStateMap::int( + [Facing::DOWN => 0] + $horizontalFacingClassicTable, + deserializeAliases: [Facing::DOWN => 1] + ); + + //In PM, we use Facing::UP to indicate that the stem is not attached to a pumpkin/melon, since this makes the + //most intuitive sense (the stem is pointing at the sky). However, Bedrock uses the DOWN state for this, which + //is absurd, and I refuse to make our API similarly absurd. + $this->facingStem = IntFromRawStateMap::int( + [Facing::UP => 0] + $horizontalFacingClassicTable, + deserializeAliases: [Facing::UP => 1] + ); + } +} diff --git a/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php b/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php new file mode 100644 index 000000000..bdd878b52 --- /dev/null +++ b/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php @@ -0,0 +1,66 @@ + BlockStateStringValues::WALL_CONNECTION_TYPE_EAST_NONE, + self::SHORT => BlockStateStringValues::WALL_CONNECTION_TYPE_EAST_SHORT, + self::TALL => BlockStateStringValues::WALL_CONNECTION_TYPE_EAST_TALL, + }; + } + + public function deserialize() : ?WallConnectionType{ + return match($this){ + self::NONE => null, + self::SHORT => WallConnectionType::SHORT, + self::TALL => WallConnectionType::TALL, + }; + } + + public static function serialize(?WallConnectionType $value) : self{ + return match($value){ + null => self::NONE, + WallConnectionType::SHORT => self::SHORT, + WallConnectionType::TALL => self::TALL, + }; + } +} diff --git a/src/world/format/io/GlobalBlockStateHandlers.php b/src/world/format/io/GlobalBlockStateHandlers.php index c1d3934cf..731b15f74 100644 --- a/src/world/format/io/GlobalBlockStateHandlers.php +++ b/src/world/format/io/GlobalBlockStateHandlers.php @@ -26,7 +26,9 @@ namespace pocketmine\world\format\io; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\BlockTypeNames; use pocketmine\data\bedrock\block\convert\BlockObjectToStateSerializer; +use pocketmine\data\bedrock\block\convert\BlockSerializerDeserializerRegistrar; use pocketmine\data\bedrock\block\convert\BlockStateToObjectDeserializer; +use pocketmine\data\bedrock\block\convert\VanillaBlockMappings; use pocketmine\data\bedrock\block\upgrade\BlockDataUpgrader; use pocketmine\data\bedrock\block\upgrade\BlockIdMetaUpgrader; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader; @@ -44,20 +46,28 @@ use const pocketmine\BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH; * benefits for now. */ final class GlobalBlockStateHandlers{ - private static ?BlockObjectToStateSerializer $blockStateSerializer = null; - - private static ?BlockStateToObjectDeserializer $blockStateDeserializer = null; - private static ?BlockDataUpgrader $blockDataUpgrader = null; private static ?BlockStateData $unknownBlockStateData = null; + private static ?BlockSerializerDeserializerRegistrar $registrar = null; + + public static function getRegistrar() : BlockSerializerDeserializerRegistrar{ + if(self::$registrar === null){ + $deserializer = new BlockStateToObjectDeserializer(); + $serializer = new BlockObjectToStateSerializer(); + self::$registrar = new BlockSerializerDeserializerRegistrar($deserializer, $serializer); + VanillaBlockMappings::init(self::$registrar); + } + return self::$registrar; + } + public static function getDeserializer() : BlockStateToObjectDeserializer{ - return self::$blockStateDeserializer ??= new BlockStateToObjectDeserializer(); + return self::getRegistrar()->deserializer; } public static function getSerializer() : BlockObjectToStateSerializer{ - return self::$blockStateSerializer ??= new BlockObjectToStateSerializer(); + return self::getRegistrar()->serializer; } public static function getUpgrader() : BlockDataUpgrader{ diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 2d609ae2c..06abe7fee 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -600,6 +600,12 @@ parameters: count: 1 path: ../../../src/crash/CrashDumpRenderer.php + - + message: '#^Parameter \#1 \$faces of method pocketmine\\block\\Vine\:\:setFaces\(\) expects list\<2\|3\|4\|5\>, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/data/bedrock/block/convert/VanillaBlockMappings.php + - message: '#^Parameter \#1 \$blockToItemId of class pocketmine\\data\\bedrock\\item\\BlockItemIdMap constructor expects array\, array\ given\.$#' identifier: argument.type diff --git a/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php b/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php index a47a9b155..674ae8152 100644 --- a/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php +++ b/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php @@ -42,6 +42,8 @@ final class BlockSerializerDeserializerTest extends TestCase{ public function setUp() : void{ $this->deserializer = new BlockStateToObjectDeserializer(); $this->serializer = new BlockObjectToStateSerializer(); + $registrar = new BlockSerializerDeserializerRegistrar($this->deserializer, $this->serializer); + VanillaBlockMappings::init($registrar); } public function testAllKnownBlockStatesSerializableAndDeserializable() : void{ @@ -49,12 +51,12 @@ final class BlockSerializerDeserializerTest extends TestCase{ try{ $blockStateData = $this->serializer->serializeBlock($block); }catch(BlockStateSerializeException $e){ - self::fail($e->getMessage()); + self::fail("Failed to serialize " . $block->getName() . ": " . $e->getMessage()); } try{ $newBlock = $this->deserializer->deserializeBlock($blockStateData); }catch(BlockStateDeserializeException $e){ - self::fail($e->getMessage()); + self::fail("Failed to deserialize " . $blockStateData->getName() . ": " . $e->getMessage() . " with data " . $blockStateData->toNbt()); } if($block->getTypeId() === BlockTypeIds::POTION_CAULDRON){ From 8f9478e82fec72a8b9f75a90882a278af0ab3a08 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 15:31:10 +0100 Subject: [PATCH 271/334] Illager banners finally working closes #2951 --- src/block/BaseBanner.php | 6 ++ src/block/BaseOminousBanner.php | 85 +++++++++++++++++++ src/block/BlockTypeIds.php | 4 +- src/block/FloorBanner.php | 4 + src/block/OminousFloorBanner.php | 53 ++++++++++++ src/block/OminousWallBanner.php | 49 +++++++++++ src/block/VanillaBlocks.php | 4 + src/block/WallBanner.php | 4 + src/block/tile/Banner.php | 14 +++ .../block/convert/VanillaBlockMappings.php | 19 ++++- .../ItemSerializerDeserializerRegistrar.php | 25 ++++-- src/item/ItemTypeIds.php | 3 +- src/item/VanillaItems.php | 2 + 13 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 src/block/BaseOminousBanner.php create mode 100644 src/block/OminousFloorBanner.php create mode 100644 src/block/OminousWallBanner.php diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index 376f1f9dc..71a892c20 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -50,6 +50,10 @@ abstract class BaseBanner extends Transparent implements Colored{ parent::readStateFromWorld(); $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileBanner){ + if($tile->getType() === TileBanner::TYPE_OMINOUS){ + //illager banner is implemented as a separate block, as it doesn't support base color or custom patterns + return $this->getOminousVersion(); + } $this->color = $tile->getBaseColor(); $this->setPatterns($tile->getPatterns()); } @@ -57,6 +61,8 @@ abstract class BaseBanner extends Transparent implements Colored{ return $this; } + abstract protected function getOminousVersion() : Block; + public function writeStateToWorld() : void{ parent::writeStateToWorld(); $tile = $this->position->getWorld()->getTile($this->position); diff --git a/src/block/BaseOminousBanner.php b/src/block/BaseOminousBanner.php new file mode 100644 index 000000000..ef2ef9c9a --- /dev/null +++ b/src/block/BaseOminousBanner.php @@ -0,0 +1,85 @@ +position->getWorld()->getTile($this->position); + assert($tile instanceof TileBanner); + $tile->setBaseColor(DyeColor::WHITE); + $tile->setPatterns([]); + $tile->setType(TileBanner::TYPE_OMINOUS); + } + + public function isSolid() : bool{ + return false; + } + + public function getMaxStackSize() : int{ + return 16; + } + + public function getFuelTime() : int{ + return 300; + } + + protected function recalculateCollisionBoxes() : array{ + return []; + } + + public function getSupportType(int $facing) : SupportType{ + 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; + } + + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } + + abstract protected function getSupportingFace() : int; + + public function onNearbyBlockChange() : void{ + if(!$this->canBeSupportedBy($this->getSide($this->getSupportingFace()))){ + $this->position->getWorld()->useBreakOn($this->position); + } + } +} diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 4af1894bd..52b141bcf 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -787,8 +787,10 @@ final class BlockTypeIds{ public const RESIN_CLUMP = 10757; public const CHISELED_RESIN_BRICKS = 10758; public const RESPAWN_ANCHOR = 10759; + public const OMINOUS_BANNER = 10760; + public const OMINOUS_WALL_BANNER = 10761; - public const FIRST_UNUSED_BLOCK_ID = 10760; + public const FIRST_UNUSED_BLOCK_ID = 10762; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/FloorBanner.php b/src/block/FloorBanner.php index ba089b6c0..ff57b5973 100644 --- a/src/block/FloorBanner.php +++ b/src/block/FloorBanner.php @@ -34,6 +34,10 @@ use pocketmine\world\BlockTransaction; final class FloorBanner extends BaseBanner implements SignLikeRotation{ use SignLikeRotationTrait; + protected function getOminousVersion() : Block{ + return VanillaBlocks::OMINOUS_BANNER()->setRotation($this->rotation); + } + protected function getSupportingFace() : int{ return Facing::DOWN; } diff --git a/src/block/OminousFloorBanner.php b/src/block/OminousFloorBanner.php new file mode 100644 index 000000000..240aeccfc --- /dev/null +++ b/src/block/OminousFloorBanner.php @@ -0,0 +1,53 @@ +rotation = self::getRotationFromYaw($player->getLocation()->getYaw()); + } + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } +} diff --git a/src/block/OminousWallBanner.php b/src/block/OminousWallBanner.php new file mode 100644 index 000000000..1eb5ba753 --- /dev/null +++ b/src/block/OminousWallBanner.php @@ -0,0 +1,49 @@ +facing); + } + + public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + if(Facing::axis($face) === Axis::Y){ + return false; + } + $this->facing = $face; + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } +} diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 54ec27fc2..3255c6f6f 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -587,6 +587,8 @@ use function strtolower; * @method static WallSign OAK_WALL_SIGN() * @method static Wood OAK_WOOD() * @method static Opaque OBSIDIAN() + * @method static OminousFloorBanner OMINOUS_BANNER() + * @method static OminousWallBanner OMINOUS_WALL_BANNER() * @method static Flower ORANGE_TULIP() * @method static Flower OXEYE_DAISY() * @method static PackedIce PACKED_ICE() @@ -873,6 +875,8 @@ final class VanillaBlocks{ $bannerBreakInfo = new Info(BreakInfo::axe(1.0)); self::register("banner", fn(BID $id) => new FloorBanner($id, "Banner", $bannerBreakInfo), TileBanner::class); self::register("wall_banner", fn(BID $id) => new WallBanner($id, "Wall Banner", $bannerBreakInfo), TileBanner::class); + self::register("ominous_banner", fn(BID $id) => new OminousFloorBanner($id, "Ominous Banner", $bannerBreakInfo), TileBanner::class); + self::register("ominous_wall_banner", fn(BID $id) => new OminousWallBanner($id, "Ominous Wall Banner", $bannerBreakInfo), TileBanner::class); self::register("barrel", fn(BID $id) => new Barrel($id, "Barrel", new Info(BreakInfo::axe(2.5))), TileBarrel::class); self::register("barrier", fn(BID $id) => new Transparent($id, "Barrier", new Info(BreakInfo::indestructible()))); self::register("beacon", fn(BID $id) => new Beacon($id, "Beacon", new Info(new BreakInfo(3.0))), TileBeacon::class); diff --git a/src/block/WallBanner.php b/src/block/WallBanner.php index ddb157cda..b631e0c81 100644 --- a/src/block/WallBanner.php +++ b/src/block/WallBanner.php @@ -35,6 +35,10 @@ use pocketmine\world\BlockTransaction; final class WallBanner extends BaseBanner implements HorizontalFacing{ use HorizontalFacingTrait; + protected function getOminousVersion() : Block{ + return VanillaBlocks::OMINOUS_WALL_BANNER()->setFacing($this->facing); + } + protected function getSupportingFace() : int{ return Facing::opposite($this->facing); } diff --git a/src/block/tile/Banner.php b/src/block/tile/Banner.php index 08a560707..97ffe630d 100644 --- a/src/block/tile/Banner.php +++ b/src/block/tile/Banner.php @@ -41,6 +41,10 @@ class Banner extends Spawnable{ public const TAG_PATTERNS = "Patterns"; public const TAG_PATTERN_COLOR = "Color"; public const TAG_PATTERN_NAME = "Pattern"; + public const TAG_TYPE = "Type"; + + public const TYPE_NORMAL = 0; + public const TYPE_OMINOUS = 1; private DyeColor $baseColor = DyeColor::BLACK; @@ -50,6 +54,8 @@ class Banner extends Spawnable{ */ private array $patterns = []; + private int $type = self::TYPE_NORMAL; + public function readSaveData(CompoundTag $nbt) : void{ $colorIdMap = DyeColorIdMap::getInstance(); if( @@ -75,6 +81,8 @@ class Banner extends Spawnable{ $this->patterns[] = new BannerPatternLayer($patternType, $patternColor); } } + + $this->type = $nbt->getInt(self::TAG_TYPE); } protected function writeSaveData(CompoundTag $nbt) : void{ @@ -89,6 +97,7 @@ class Banner extends Spawnable{ ); } $nbt->setTag(self::TAG_PATTERNS, $patterns); + $nbt->setInt(self::TAG_TYPE, $this->type); } protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ @@ -103,6 +112,7 @@ class Banner extends Spawnable{ ); } $nbt->setTag(self::TAG_PATTERNS, $patterns); + $nbt->setInt(self::TAG_TYPE, $this->type); } /** @@ -136,6 +146,10 @@ class Banner extends Spawnable{ $this->patterns = $patterns; } + public function getType() : int{ return $this->type; } + + public function setType(int $type) : void{ $this->type = $type; } + public function getDefaultName() : string{ return "Banner"; } diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php index 16ae1e244..3dfa81644 100644 --- a/src/data/bedrock/block/convert/VanillaBlockMappings.php +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -55,6 +55,7 @@ use pocketmine\block\Farmland; use pocketmine\block\FillableCauldron; use pocketmine\block\Fire; use pocketmine\block\FloorCoralFan; +use pocketmine\block\OminousFloorBanner; use pocketmine\block\Froglight; use pocketmine\block\FrostedIce; use pocketmine\block\GlazedTerracotta; @@ -103,6 +104,7 @@ use pocketmine\block\utils\MushroomBlockType; use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\VanillaBlocks as Blocks; use pocketmine\block\Vine; +use pocketmine\block\OminousWallBanner; use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\data\bedrock\block\BlockStateNames as StateNames; @@ -154,7 +156,7 @@ final class VanillaBlockMappings{ self::registerChemistryMappings($reg, $commonProperties); self::register1to1CustomMappings($reg, $commonProperties); - self::registerSplitMappings($reg); + self::registerSplitMappings($reg, $commonProperties); } private static function registerSimpleIdOnlyMappings(BlockSerializerDeserializerRegistrar $reg) : void{ @@ -1476,7 +1478,7 @@ final class VanillaBlockMappings{ * These currently can't be registered in a unified way, and due to their small number it may not be worth the * effort to implement a unified way to deal with them */ - private static function registerSplitMappings(BlockSerializerDeserializerRegistrar $reg) : void{ + private static function registerSplitMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ //big dripleaf - split into head / stem variants, as stems don't have tilt or leaf state $reg->serializer->map(Blocks::BIG_DRIPLEAF_HEAD(), function(BigDripleafHead $block) : Writer{ return Writer::create(Ids::BIG_DRIPLEAF) @@ -1557,5 +1559,18 @@ final class VanillaBlockMappings{ ->setAge(min($growth - PitcherCrop::MAX_AGE - 1, DoublePitcherCrop::MAX_AGE)) ->setTop($top); }); + + //these only exist within PM (mapped from tile properties) as they don't support the same properties as a + //normal banner + $reg->serializer->map(Blocks::OMINOUS_BANNER(), function(OminousFloorBanner $block) use ($commonProperties) : Writer{ + $writer = Writer::create(Ids::STANDING_BANNER); + $commonProperties->floorSignLikeRotation->serialize($block, $writer); + return $writer; + }); + $reg->serializer->map(Blocks::OMINOUS_WALL_BANNER(), function(OminousWallBanner $block) use ($commonProperties) : Writer{ + $writer = Writer::create(Ids::WALL_BANNER); + $commonProperties->horizontalFacingClassic->serialize($block, $writer); + return $writer; + }); } } diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 771154d46..f176351b7 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -26,6 +26,7 @@ namespace pocketmine\data\bedrock\item; use pocketmine\block\Bed; use pocketmine\block\Block; use pocketmine\block\CopperDoor; +use pocketmine\block\tile\Banner as TileBanner; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\DyeColor; use pocketmine\block\VanillaBlocks as Blocks; @@ -46,6 +47,7 @@ use pocketmine\item\Potion; use pocketmine\item\SplashPotion; use pocketmine\item\SuspiciousStew; use pocketmine\item\VanillaItems as Items; +use pocketmine\nbt\tag\CompoundTag; final class ItemSerializerDeserializerRegistrar{ @@ -487,14 +489,6 @@ final class ItemSerializerDeserializerRegistrar{ * in a unified manner. */ private function register1to1ItemWithMetaMappings() : void{ - $this->map1to1ItemWithMeta( - Ids::BANNER, - Items::BANNER(), - function(Banner $item, int $meta) : void{ - $item->setColor(DyeColorIdMap::getInstance()->fromInvertedId($meta) ?? throw new ItemTypeDeserializeException("Unknown banner meta $meta")); - }, - fn(Banner $item) => DyeColorIdMap::getInstance()->toInvertedId($item->getColor()) - ); $this->map1to1ItemWithMeta( Ids::GOAT_HORN, Items::GOAT_HORN(), @@ -550,6 +544,21 @@ final class ItemSerializerDeserializerRegistrar{ $this->deserializer?->map($id, fn() => Items::DYE()->setColor($color)); } $this->serializer?->map(Items::DYE(), fn(Dye $item) => new Data(DyeColorIdMap::getInstance()->toItemId($item->getColor()))); + + $this->deserializer?->map(Ids::BANNER, function(Data $data) : Item{ + $type = $data->getTag()?->getInt(TileBanner::TAG_TYPE) ?? TileBanner::TYPE_NORMAL; + if($type === TileBanner::TYPE_OMINOUS){ + return Items::OMINOUS_BANNER(); + } + $color = DyeColorIdMap::getInstance()->fromInvertedId($data->getMeta()) ?? throw new ItemTypeDeserializeException("Unknown banner meta " . $data->getMeta()); + return Items::BANNER()->setColor($color); + }); + $this->serializer?->map(Items::OMINOUS_BANNER(), fn() => new Data(Ids::BANNER, tag: CompoundTag::create() + ->setInt(TileBanner::TAG_TYPE, TileBanner::TYPE_OMINOUS)) + ); + $this->serializer?->map(Items::BANNER(), function(Banner $item) : Data{ + return new Data(Ids::BANNER, DyeColorIdMap::getInstance()->toInvertedId($item->getColor())); + }); } /** diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index c63046c6b..37be3ab9e 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -334,8 +334,9 @@ final class ItemTypeIds{ public const RECORD_CREATOR = 20295; public const RECORD_CREATOR_MUSIC_BOX = 20296; public const RECORD_PRECIPICE = 20297; + public const OMINOUS_BANNER = 20298; - public const FIRST_UNUSED_ITEM_ID = 20298; + public const FIRST_UNUSED_ITEM_ID = 20299; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index f76cf369f..7103d8878 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -242,6 +242,7 @@ use function strtolower; * @method static Item NETHER_STAR() * @method static Boat OAK_BOAT() * @method static ItemBlockWallOrFloor OAK_SIGN() + * @method static ItemBlockWallOrFloor OMINOUS_BANNER() * @method static PaintingItem PAINTING() * @method static ItemBlockWallOrFloor PALE_OAK_SIGN() * @method static Item PAPER() @@ -540,6 +541,7 @@ final class VanillaItems{ public function isFireProof() : bool{ return true; } }); self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN())); + self::register("ominous_banner", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OMINOUS_BANNER(), Blocks::OMINOUS_WALL_BANNER())); self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting")); self::register("pale_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::PALE_OAK_SIGN(), Blocks::PALE_OAK_WALL_SIGN())); self::register("paper", fn(IID $id) => new Item($id, "Paper")); From ef53676a596dd581efa1792e7252bdc43214ffb3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 15:38:07 +0100 Subject: [PATCH 272/334] Fix unit tests --- src/block/BaseOminousBanner.php | 5 +++++ .../bedrock/item/ItemSerializerDeserializerRegistrar.php | 2 +- tests/phpunit/block/block_factory_consistency_check.json | 4 ++++ .../block/convert/BlockSerializerDeserializerTest.php | 9 +++++++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/block/BaseOminousBanner.php b/src/block/BaseOminousBanner.php index ef2ef9c9a..192e6fac2 100644 --- a/src/block/BaseOminousBanner.php +++ b/src/block/BaseOminousBanner.php @@ -27,6 +27,7 @@ use pocketmine\block\tile\Banner as TileBanner; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; +use pocketmine\item\VanillaItems; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; @@ -82,4 +83,8 @@ abstract class BaseOminousBanner extends Transparent{ $this->position->getWorld()->useBreakOn($this->position); } } + + public function asItem() : Item{ + return VanillaItems::OMINOUS_BANNER(); + } } diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index f176351b7..fa6f88483 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -546,7 +546,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->serializer?->map(Items::DYE(), fn(Dye $item) => new Data(DyeColorIdMap::getInstance()->toItemId($item->getColor()))); $this->deserializer?->map(Ids::BANNER, function(Data $data) : Item{ - $type = $data->getTag()?->getInt(TileBanner::TAG_TYPE) ?? TileBanner::TYPE_NORMAL; + $type = $data->getTag()?->getInt(TileBanner::TAG_TYPE, TileBanner::TYPE_NORMAL) ?? TileBanner::TYPE_NORMAL; if($type === TileBanner::TYPE_OMINOUS){ return Items::OMINOUS_BANNER(); } diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index c7629656d..86a44113f 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -508,6 +508,8 @@ "OAK_WALL_SIGN": 4, "OAK_WOOD": 6, "OBSIDIAN": 1, + "OMINOUS_BANNER": 16, + "OMINOUS_WALL_BANNER": 4, "ORANGE_TULIP": 1, "OXEYE_DAISY": 1, "PACKED_ICE": 1, @@ -776,6 +778,8 @@ "NOTE_BLOCK": "pocketmine\\block\\tile\\Note", "OAK_SIGN": "pocketmine\\block\\tile\\Sign", "OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "OMINOUS_BANNER": "pocketmine\\block\\tile\\Banner", + "OMINOUS_WALL_BANNER": "pocketmine\\block\\tile\\Banner", "PALE_OAK_SIGN": "pocketmine\\block\\tile\\Sign", "PALE_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron", diff --git a/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php b/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php index 674ae8152..c0f850027 100644 --- a/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php +++ b/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php @@ -59,8 +59,13 @@ final class BlockSerializerDeserializerTest extends TestCase{ self::fail("Failed to deserialize " . $blockStateData->getName() . ": " . $e->getMessage() . " with data " . $blockStateData->toNbt()); } - if($block->getTypeId() === BlockTypeIds::POTION_CAULDRON){ - //this pretends to be a water cauldron in the blockstate, and stores its actual data in the blockentity + if(match ($block->getTypeId()) { + BlockTypeIds::POTION_CAULDRON, + BlockTypeIds::OMINOUS_BANNER, + BlockTypeIds::OMINOUS_WALL_BANNER => true, + default => false + }){ + //these pretend to be something else in the blockstate, and the variant switching is done via block entity data continue; } From 5bf0cbec875f8cbc7f6f2ef37433837041e7f3c4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 15:39:23 +0100 Subject: [PATCH 273/334] ... --- src/data/bedrock/block/convert/VanillaBlockMappings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php index 3dfa81644..4438b01bf 100644 --- a/src/data/bedrock/block/convert/VanillaBlockMappings.php +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -55,7 +55,6 @@ use pocketmine\block\Farmland; use pocketmine\block\FillableCauldron; use pocketmine\block\Fire; use pocketmine\block\FloorCoralFan; -use pocketmine\block\OminousFloorBanner; use pocketmine\block\Froglight; use pocketmine\block\FrostedIce; use pocketmine\block\GlazedTerracotta; @@ -69,6 +68,8 @@ use pocketmine\block\MobHead; use pocketmine\block\NetherPortal; use pocketmine\block\NetherVines; use pocketmine\block\NetherWartPlant; +use pocketmine\block\OminousFloorBanner; +use pocketmine\block\OminousWallBanner; use pocketmine\block\PinkPetals; use pocketmine\block\PitcherCrop; use pocketmine\block\PoweredRail; @@ -104,7 +105,6 @@ use pocketmine\block\utils\MushroomBlockType; use pocketmine\block\utils\PoweredByRedstone; use pocketmine\block\VanillaBlocks as Blocks; use pocketmine\block\Vine; -use pocketmine\block\OminousWallBanner; use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\data\bedrock\block\BlockStateNames as StateNames; From 4cdf064344a6aba4d0136865508ffadb3c9bce03 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 16:37:42 +0100 Subject: [PATCH 274/334] VanillaBlockMappings: Use some model mappings this way there are some minor symmetry benefits, and the only asymmetric parts are the code that selects which model to use. it also has the added benefit of removing some duplicated code paths (e.g. now it's possible to get rid of readUnitEnum() and such). --- .../block/convert/VanillaBlockMappings.php | 185 ++++++++++-------- 1 file changed, 106 insertions(+), 79 deletions(-) diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php index 4438b01bf..f70de9f4c 100644 --- a/src/data/bedrock/block/convert/VanillaBlockMappings.php +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -96,6 +96,7 @@ use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\ChiseledBookshelfSlot; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\DirtType; +use pocketmine\block\utils\DripleafState; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FroglightType; use pocketmine\block\utils\HorizontalFacing; @@ -1472,6 +1473,35 @@ final class VanillaBlockMappings{ $reg->mapModel(Model::create(Blocks::WEIGHTED_PRESSURE_PLATE_LIGHT(), Ids::LIGHT_WEIGHTED_PRESSURE_PLATE)->properties([$commonProperties->analogRedstoneSignal])); } + /** + * @phpstan-template TBlock of Block + * @phpstan-param Model $model + */ + private static function mapAsymmetricSerializer(BlockSerializerDeserializerRegistrar $reg, Model $model) : void{ + $id = $model->getId(); + $properties = $model->getProperties(); + $reg->serializer->map($model->getBlock(), function(Block $block) use ($id, $properties) : Writer{ + $writer = new Writer($id); + foreach($properties as $property){ + $property->serialize($block, $writer); + } + return $writer; + }); + } + + /** + * @phpstan-template TBlock of Block + * @phpstan-param Model $model + * @phpstan-return TBlock + */ + private static function deserializeAsymmetric(Model $model, Reader $in) : Block{ + $block = clone $model->getBlock(); + foreach($model->getProperties() as $property){ + $property->deserialize($block, $in); + } + return $block; + } + /** * All mappings that still use the split form of serializer/deserializer registration * This is typically only used by blocks with one ID but multiple PM types (split by property) @@ -1480,97 +1510,94 @@ final class VanillaBlockMappings{ */ private static function registerSplitMappings(BlockSerializerDeserializerRegistrar $reg, CommonProperties $commonProperties) : void{ //big dripleaf - split into head / stem variants, as stems don't have tilt or leaf state - $reg->serializer->map(Blocks::BIG_DRIPLEAF_HEAD(), function(BigDripleafHead $block) : Writer{ - return Writer::create(Ids::BIG_DRIPLEAF) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeUnitEnum(StateNames::BIG_DRIPLEAF_TILT, ValueMappings::getInstance()->dripleafState, $block->getLeafState()) - ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, true); - }); - $reg->serializer->map(Blocks::BIG_DRIPLEAF_STEM(), function(BigDripleafStem $block) : Writer{ - return Writer::create(Ids::BIG_DRIPLEAF) - ->writeCardinalHorizontalFacing($block->getFacing()) - ->writeString(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE) - ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, false); - }); - $reg->deserializer->map(Ids::BIG_DRIPLEAF, function(Reader $in) : Block{ - if($in->readBool(StateNames::BIG_DRIPLEAF_HEAD)){ - return Blocks::BIG_DRIPLEAF_HEAD() - ->setFacing($in->readCardinalHorizontalFacing()) - ->setLeafState($in->readUnitEnum(StateNames::BIG_DRIPLEAF_TILT, ValueMappings::getInstance()->dripleafState)); - }else{ - $in->ignored(StateNames::BIG_DRIPLEAF_TILT); - return Blocks::BIG_DRIPLEAF_STEM()->setFacing($in->readCardinalHorizontalFacing()); - } - }); + $bigDripleafHeadModel = Model::create(Blocks::BIG_DRIPLEAF_HEAD(), Ids::BIG_DRIPLEAF)->properties([ + $commonProperties->horizontalFacingCardinal, + new ValueFromStringProperty(StateNames::BIG_DRIPLEAF_TILT, ValueMappings::getInstance()->dripleafState, fn(BigDripleafHead $b) => $b->getLeafState(), fn(BigDripleafHead $b, DripleafState $v) => $b->setLeafState($v)), + new DummyProperty(StateNames::BIG_DRIPLEAF_HEAD, true) + ]); + $bigDripleafStemModel = Model::create(Blocks::BIG_DRIPLEAF_STEM(), Ids::BIG_DRIPLEAF)->properties([ + $commonProperties->horizontalFacingCardinal, + new DummyProperty(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE), + new DummyProperty(StateNames::BIG_DRIPLEAF_HEAD, false) + ]); + self::mapAsymmetricSerializer($reg, $bigDripleafHeadModel); + self::mapAsymmetricSerializer($reg, $bigDripleafStemModel); + $reg->deserializer->map(Ids::BIG_DRIPLEAF, fn(Reader $in) => $in->readBool(StateNames::BIG_DRIPLEAF_HEAD) ? + self::deserializeAsymmetric($bigDripleafHeadModel, $in) : + self::deserializeAsymmetric($bigDripleafStemModel, $in) + ); - //cauldrons - split into liquid variants, as each have different behaviour - $reg->serializer->map(Blocks::CAULDRON(), Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0)); - $reg->serializer->map(Blocks::LAVA_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_LAVA, $b->getFillLevel())); - //potion cauldrons store their real information in the block actor data - $reg->serializer->map(Blocks::POTION_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); - $reg->serializer->map(Blocks::WATER_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel())); - $reg->deserializer->map(Ids::CAULDRON, function(Reader $in) : Block{ - $level = $in->readBoundedInt(StateNames::FILL_LEVEL, 0, 6); - if($level === 0){ - $in->ignored(StateNames::CAULDRON_LIQUID); - return Blocks::CAULDRON(); - } + $fillLevelProperty = new IntProperty(StateNames::FILL_LEVEL, 1, 6, fn(FillableCauldron $b) => $b->getFillLevel(), fn(FillableCauldron $b, int $v) => $b->setFillLevel($v)); - return (match ($liquid = $in->readString(StateNames::CAULDRON_LIQUID)) { - StringValues::CAULDRON_LIQUID_WATER => Blocks::WATER_CAULDRON(), - StringValues::CAULDRON_LIQUID_LAVA => Blocks::LAVA_CAULDRON(), + //this pretends to be a water cauldron on disk and stores its real information in the block actor data, therefore only a serializer is needed + self::mapAsymmetricSerializer($reg, Model::create(Blocks::POTION_CAULDRON(), Ids::CAULDRON)->properties([$fillLevelProperty, new DummyProperty(StateNames::CAULDRON_LIQUID, StringValues::CAULDRON_LIQUID_WATER)])); + + $lavaCauldronModel = Model::create(Blocks::LAVA_CAULDRON(), Ids::CAULDRON)->properties([ + $fillLevelProperty, + new DummyProperty(StateNames::CAULDRON_LIQUID, StringValues::CAULDRON_LIQUID_LAVA) + ]); + $waterCauldronModel = Model::create(Blocks::WATER_CAULDRON(), Ids::CAULDRON)->properties([ + $fillLevelProperty, + new DummyProperty(StateNames::CAULDRON_LIQUID, StringValues::CAULDRON_LIQUID_WATER) + ]); + $emptyCauldronModel = Model::create(Blocks::CAULDRON(), Ids::CAULDRON)->properties([ + new DummyProperty(StateNames::FILL_LEVEL, 0), + new DummyProperty(StateNames::CAULDRON_LIQUID, StringValues::CAULDRON_LIQUID_WATER) + ]); + self::mapAsymmetricSerializer($reg, $lavaCauldronModel); + self::mapAsymmetricSerializer($reg, $waterCauldronModel); + self::mapAsymmetricSerializer($reg, $emptyCauldronModel); + $reg->deserializer->map(Ids::CAULDRON, fn(Reader $in) => $in->readInt(StateNames::FILL_LEVEL) === 0 ? + self::deserializeAsymmetric($emptyCauldronModel, $in) : + match ($liquid = $in->readString(StateNames::CAULDRON_LIQUID)) { + StringValues::CAULDRON_LIQUID_WATER => self::deserializeAsymmetric($waterCauldronModel, $in), + StringValues::CAULDRON_LIQUID_LAVA => self::deserializeAsymmetric($lavaCauldronModel, $in), StringValues::CAULDRON_LIQUID_POWDER_SNOW => throw new UnsupportedBlockStateException("Powder snow is not supported yet"), default => throw $in->badValueException(StateNames::CAULDRON_LIQUID, $liquid) - })->setFillLevel($level); - }); + } + ); //mushroom stems, split for consistency with all-sided logs vs normal logs - $reg->serializer->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) - ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM)); - $reg->serializer->map(Blocks::MUSHROOM_STEM(), Writer::create(Ids::MUSHROOM_STEM) - ->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_STEM)); - $reg->deserializer->map(Ids::MUSHROOM_STEM, fn(Reader $in) => match ($in->readBoundedInt(StateNames::HUGE_MUSHROOM_BITS, 0, 15)) { - BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM => Blocks::ALL_SIDED_MUSHROOM_STEM(), - BlockLegacyMetadata::MUSHROOM_BLOCK_STEM => Blocks::MUSHROOM_STEM(), + $allSidedMushroomStemModel = Model::create(Blocks::ALL_SIDED_MUSHROOM_STEM(), Ids::MUSHROOM_STEM)->properties([new DummyProperty(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM)]); + $mushroomStemModel = Model::create(Blocks::MUSHROOM_STEM(), Ids::MUSHROOM_STEM)->properties([new DummyProperty(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_STEM)]); + self::mapAsymmetricSerializer($reg, $allSidedMushroomStemModel); + self::mapAsymmetricSerializer($reg, $mushroomStemModel); + $reg->deserializer->map(Ids::MUSHROOM_STEM, fn(Reader $in) : Block => match ($in->readInt(StateNames::HUGE_MUSHROOM_BITS)) { + BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM => self::deserializeAsymmetric($allSidedMushroomStemModel, $in), + BlockLegacyMetadata::MUSHROOM_BLOCK_STEM => self::deserializeAsymmetric($mushroomStemModel, $in), default => throw new BlockStateDeserializeException("This state does not exist"), }); //pitcher crop, split into single and double variants as double has different properties and behaviour //this will probably be the most annoying to unify - $reg->serializer->map(Blocks::PITCHER_CROP(), function(PitcherCrop $block) : Writer{ - return Writer::create(Ids::PITCHER_CROP) - ->writeInt(StateNames::GROWTH, $block->getAge()) - ->writeBool(StateNames::UPPER_BLOCK_BIT, false); - }); - $reg->serializer->map(Blocks::DOUBLE_PITCHER_CROP(), function(DoublePitcherCrop $block) : Writer{ - return Writer::create(Ids::PITCHER_CROP) - ->writeInt(StateNames::GROWTH, $block->getAge() + 1 + PitcherCrop::MAX_AGE) - ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop()); - }); - $reg->deserializer->map(Ids::PITCHER_CROP, function(Reader $in) : Block{ - $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7); - $top = $in->readBool(StateNames::UPPER_BLOCK_BIT); - if($growth <= PitcherCrop::MAX_AGE){ - //top pitcher crop with age 0-2 is an invalid state - //only the bottom half should exist in this case - return $top ? Blocks::AIR() : Blocks::PITCHER_CROP()->setAge($growth); - } - return Blocks::DOUBLE_PITCHER_CROP() - ->setAge(min($growth - PitcherCrop::MAX_AGE - 1, DoublePitcherCrop::MAX_AGE)) - ->setTop($top); - }); + $pitcherCropModel = Model::create(Blocks::PITCHER_CROP(), Ids::PITCHER_CROP)->properties([ + new IntProperty(StateNames::GROWTH, 0, PitcherCrop::MAX_AGE, fn(PitcherCrop $b) => $b->getAge(), fn(PitcherCrop $b, int $v) => $b->setAge($v)), + new DummyProperty(StateNames::UPPER_BLOCK_BIT, false) + ]); + $doublePitcherCropAgeOffset = PitcherCrop::MAX_AGE + 1; + $doublePitcherCropModel = Model::create(Blocks::DOUBLE_PITCHER_CROP(), Ids::PITCHER_CROP)->properties([ + new IntProperty( + StateNames::GROWTH, + $doublePitcherCropAgeOffset, //TODO: it would be a bit less awkward if the bounds applied _after_ applying the offset, instead of before + 7, + fn(DoublePitcherCrop $b) => $b->getAge(), + fn(DoublePitcherCrop $b, int $v) => $b->setAge(min($v, DoublePitcherCrop::MAX_AGE)), //state may give up to 7, but only up to 4 is valid + offset: -$doublePitcherCropAgeOffset + ), + new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(DoublePitcherCrop $b) => $b->isTop(), fn(DoublePitcherCrop $b, bool $v) => $b->setTop($v)) + ]); + self::mapAsymmetricSerializer($reg, $pitcherCropModel); + self::mapAsymmetricSerializer($reg, $doublePitcherCropModel); + $reg->deserializer->map(Ids::PITCHER_CROP, fn(Reader $in) => $in->readInt(StateNames::GROWTH) <= PitcherCrop::MAX_AGE ? + ($in->readBool(StateNames::UPPER_BLOCK_BIT) ? + Blocks::AIR() : + self::deserializeAsymmetric($pitcherCropModel, $in) + ) : self::deserializeAsymmetric($doublePitcherCropModel, $in) + ); //these only exist within PM (mapped from tile properties) as they don't support the same properties as a - //normal banner - $reg->serializer->map(Blocks::OMINOUS_BANNER(), function(OminousFloorBanner $block) use ($commonProperties) : Writer{ - $writer = Writer::create(Ids::STANDING_BANNER); - $commonProperties->floorSignLikeRotation->serialize($block, $writer); - return $writer; - }); - $reg->serializer->map(Blocks::OMINOUS_WALL_BANNER(), function(OminousWallBanner $block) use ($commonProperties) : Writer{ - $writer = Writer::create(Ids::WALL_BANNER); - $commonProperties->horizontalFacingClassic->serialize($block, $writer); - return $writer; - }); + //normal banner, therefore no deserializer is needed + self::mapAsymmetricSerializer($reg, Model::create(Blocks::OMINOUS_BANNER(), Ids::STANDING_BANNER)->properties([$commonProperties->floorSignLikeRotation])); + self::mapAsymmetricSerializer($reg, Model::create(Blocks::OMINOUS_WALL_BANNER(), Ids::WALL_BANNER)->properties([$commonProperties->horizontalFacingClassic])); } } From 93e33dad8e2b607efba3ef8a84caeb1b75420451 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 16:42:05 +0100 Subject: [PATCH 275/334] tidy CS --- src/data/bedrock/block/convert/VanillaBlockMappings.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php index f70de9f4c..e276ed6e7 100644 --- a/src/data/bedrock/block/convert/VanillaBlockMappings.php +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -33,7 +33,6 @@ use pocketmine\block\Bed; use pocketmine\block\Bedrock; use pocketmine\block\Bell; use pocketmine\block\BigDripleafHead; -use pocketmine\block\BigDripleafStem; use pocketmine\block\Block; use pocketmine\block\BrewingStand; use pocketmine\block\Cactus; @@ -68,8 +67,6 @@ use pocketmine\block\MobHead; use pocketmine\block\NetherPortal; use pocketmine\block\NetherVines; use pocketmine\block\NetherWartPlant; -use pocketmine\block\OminousFloorBanner; -use pocketmine\block\OminousWallBanner; use pocketmine\block\PinkPetals; use pocketmine\block\PitcherCrop; use pocketmine\block\PoweredRail; @@ -112,7 +109,6 @@ use pocketmine\data\bedrock\block\BlockStateNames as StateNames; use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues; use pocketmine\data\bedrock\block\BlockTypeNames as Ids; use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader; -use pocketmine\data\bedrock\block\convert\BlockStateSerializerHelper as Helper; use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer; use pocketmine\data\bedrock\block\convert\property\BoolFromStringProperty; use pocketmine\data\bedrock\block\convert\property\BoolProperty; @@ -1590,6 +1586,7 @@ final class VanillaBlockMappings{ self::mapAsymmetricSerializer($reg, $doublePitcherCropModel); $reg->deserializer->map(Ids::PITCHER_CROP, fn(Reader $in) => $in->readInt(StateNames::GROWTH) <= PitcherCrop::MAX_AGE ? ($in->readBool(StateNames::UPPER_BLOCK_BIT) ? + //top pitcher crop with age 0-2 is an invalid state, only the bottom half should exist in this case Blocks::AIR() : self::deserializeAsymmetric($pitcherCropModel, $in) ) : self::deserializeAsymmetric($doublePitcherCropModel, $in) From 17ecf11a1bae60600fbf56637e50aea714255e5b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 16:49:49 +0100 Subject: [PATCH 276/334] Remove stupid thing PhpStorm keeps doing --- src/data/bedrock/block/convert/property/CommonProperties.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/bedrock/block/convert/property/CommonProperties.php b/src/data/bedrock/block/convert/property/CommonProperties.php index 71b87139c..1a3224270 100644 --- a/src/data/bedrock/block/convert/property/CommonProperties.php +++ b/src/data/bedrock/block/convert/property/CommonProperties.php @@ -55,7 +55,6 @@ use pocketmine\block\Wood; use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateNames as StateNames; use pocketmine\data\bedrock\block\BlockStateStringValues; -use pocketmine\data\bedrock\block\convert\non; use pocketmine\math\Facing; use pocketmine\utils\SingletonTrait; From be90c6c399b4fa40ae86bcdd9fd8c3623a0ef78c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 17:01:59 +0100 Subject: [PATCH 277/334] World: trigger readStateFromWorld on tile blocks immediately on load this ensures that the state IDs reflect the actual PM block type, which would probably be important for a bunch of different async things. --- src/world/World.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/world/World.php b/src/world/World.php index 8602c5803..bd790c299 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2978,7 +2978,7 @@ class World implements ChunkManager{ unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); - $this->initChunk($x, $z, $chunkData); + $this->initChunk($x, $z, $chunkData, $chunk); if(ChunkLoadEvent::hasHandlers()){ (new ChunkLoadEvent($this, $x, $z, $this->chunks[$chunkHash], false))->call(); @@ -2998,7 +2998,7 @@ class World implements ChunkManager{ return $this->chunks[$chunkHash]; } - private function initChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void{ + private function initChunk(int $chunkX, int $chunkZ, ChunkData $chunkData, Chunk $chunk) : void{ $logger = new \PrefixedLogger($this->logger, "Loading chunk $chunkX $chunkZ"); if(count($chunkData->getEntityNBT()) !== 0){ @@ -3063,6 +3063,16 @@ class World implements ChunkManager{ }else{ $this->addTile($tile); } + $expectedStateId = $chunk->getBlockStateId($tilePosition->getFloorX() & Chunk::COORD_MASK, $tilePosition->getFloorY(), $tilePosition->getFloorZ() & Chunk::COORD_MASK); + $actualStateId = $this->getBlock($tilePosition)->getStateId(); + if($expectedStateId !== $actualStateId){ + //state ID was updated by readStateFromWorld - typically because the block pulled some data from the tile + //make sure this is synced to the chunk + //TODO: in the future we should pull tile reading logic out of readStateFromWorld() and do it only + //when the tile is loaded - this would be cleaner and faster + $chunk->setBlockStateId($tilePosition->getFloorX() & Chunk::COORD_MASK, $tilePosition->getFloorY(), $tilePosition->getFloorZ() & Chunk::COORD_MASK, $actualStateId); + $this->logger->debug("Tile " . $tile::class . " at x=$tilePosition->x,y=$tilePosition->y,z=$tilePosition->z updated block state ID from $expectedStateId to $actualStateId"); + } } foreach(Utils::promoteKeys($deletedTiles) as $saveId => $count){ From 00d617146355815549b0f86fd9a355166fbd5fe7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 20:07:59 +0100 Subject: [PATCH 278/334] Implement hanging signs --- src/block/BaseSign.php | 19 +++++- src/block/BlockTypeIds.php | 35 +++++++++- src/block/CeilingCenterHangingSign.php | 52 +++++++++++++++ src/block/CeilingEdgesHangingSign.php | 51 ++++++++++++++ src/block/VanillaBlocks.php | 51 ++++++++++++++ src/block/WallHangingSign.php | 63 ++++++++++++++++++ src/block/tile/HangingSign.php | 33 ++++++++++ src/block/tile/TileFactory.php | 2 + .../block/convert/VanillaBlockMappings.php | 46 +++++++++++++ .../ItemSerializerDeserializerRegistrar.php | 11 ++++ src/item/HangingSign.php | 66 +++++++++++++++++++ src/item/Item.php | 4 ++ src/item/ItemTypeIds.php | 13 +++- src/item/StringToItemParser.php | 11 ++++ src/item/VanillaItems.php | 22 +++++++ src/world/World.php | 2 +- .../block_factory_consistency_check.json | 66 +++++++++++++++++++ 17 files changed, 543 insertions(+), 4 deletions(-) create mode 100644 src/block/CeilingCenterHangingSign.php create mode 100644 src/block/CeilingEdgesHangingSign.php create mode 100644 src/block/WallHangingSign.php create mode 100644 src/block/tile/HangingSign.php create mode 100644 src/item/HangingSign.php diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 0efaa603c..b01157343 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -103,10 +103,27 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return SupportType::NONE; } + /** + * @deprecated + */ abstract protected function getSupportingFace() : int; + /** + * @return int[] + */ + protected function getSupportingFaceOptions() : array{ + return [$this->getSupportingFace()]; + } + public function onNearbyBlockChange() : void{ - if($this->getSide($this->getSupportingFace())->getTypeId() === BlockTypeIds::AIR){ + $foundSupport = false; + foreach($this->getSupportingFaceOptions() as $face){ + if($this->getSide($face)->getTypeId() !== BlockTypeIds::AIR){ + $foundSupport = true; + break; + } + } + if(!$foundSupport){ $this->position->getWorld()->useBreakOn($this->position); } } diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 52b141bcf..e4d49746f 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -789,8 +789,41 @@ final class BlockTypeIds{ public const RESPAWN_ANCHOR = 10759; public const OMINOUS_BANNER = 10760; public const OMINOUS_WALL_BANNER = 10761; + public const ACACIA_CEILING_CENTER_HANGING_SIGN = 10762; + public const ACACIA_CEILING_EDGES_HANGING_SIGN = 10763; + public const ACACIA_WALL_HANGING_SIGN = 10764; + public const BIRCH_CEILING_CENTER_HANGING_SIGN = 10765; + public const BIRCH_CEILING_EDGES_HANGING_SIGN = 10766; + public const BIRCH_WALL_HANGING_SIGN = 10767; + public const CHERRY_CEILING_CENTER_HANGING_SIGN = 10768; + public const CHERRY_CEILING_EDGES_HANGING_SIGN = 10769; + public const CHERRY_WALL_HANGING_SIGN = 10770; + public const CRIMSON_CEILING_CENTER_HANGING_SIGN = 10771; + public const CRIMSON_CEILING_EDGES_HANGING_SIGN = 10772; + public const CRIMSON_WALL_HANGING_SIGN = 10773; + public const DARK_OAK_CEILING_CENTER_HANGING_SIGN = 10774; + public const DARK_OAK_CEILING_EDGES_HANGING_SIGN = 10775; + public const DARK_OAK_WALL_HANGING_SIGN = 10776; + public const JUNGLE_CEILING_CENTER_HANGING_SIGN = 10777; + public const JUNGLE_CEILING_EDGES_HANGING_SIGN = 10778; + public const JUNGLE_WALL_HANGING_SIGN = 10779; + public const MANGROVE_CEILING_CENTER_HANGING_SIGN = 10780; + public const MANGROVE_CEILING_EDGES_HANGING_SIGN = 10781; + public const MANGROVE_WALL_HANGING_SIGN = 10782; + public const OAK_CEILING_CENTER_HANGING_SIGN = 10783; + public const OAK_CEILING_EDGES_HANGING_SIGN = 10784; + public const OAK_WALL_HANGING_SIGN = 10785; + public const PALE_OAK_CEILING_CENTER_HANGING_SIGN = 10786; + public const PALE_OAK_CEILING_EDGES_HANGING_SIGN = 10787; + public const PALE_OAK_WALL_HANGING_SIGN = 10788; + public const SPRUCE_CEILING_CENTER_HANGING_SIGN = 10789; + public const SPRUCE_CEILING_EDGES_HANGING_SIGN = 10790; + public const SPRUCE_WALL_HANGING_SIGN = 10791; + public const WARPED_CEILING_CENTER_HANGING_SIGN = 10792; + public const WARPED_CEILING_EDGES_HANGING_SIGN = 10793; + public const WARPED_WALL_HANGING_SIGN = 10794; - public const FIRST_UNUSED_BLOCK_ID = 10762; + public const FIRST_UNUSED_BLOCK_ID = 10795; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/CeilingCenterHangingSign.php b/src/block/CeilingCenterHangingSign.php new file mode 100644 index 000000000..7078f38d8 --- /dev/null +++ b/src/block/CeilingCenterHangingSign.php @@ -0,0 +1,52 @@ +rotation = self::getRotationFromYaw($player->getLocation()->getYaw()); + } + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } +} diff --git a/src/block/CeilingEdgesHangingSign.php b/src/block/CeilingEdgesHangingSign.php new file mode 100644 index 000000000..5dafe6932 --- /dev/null +++ b/src/block/CeilingEdgesHangingSign.php @@ -0,0 +1,51 @@ +facing = Facing::opposite($player->getHorizontalFacing()); + } + + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } +} diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 3255c6f6f..bf9f7e5f5 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -45,6 +45,7 @@ use pocketmine\block\tile\EnchantTable as TileEnchantingTable; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\tile\FlowerPot as TileFlowerPot; use pocketmine\block\tile\GlowingItemFrame as TileGlowingItemFrame; +use pocketmine\block\tile\HangingSign as TileHangingSign; use pocketmine\block\tile\Hopper as TileHopper; use pocketmine\block\tile\ItemFrame as TileItemFrame; use pocketmine\block\tile\Jukebox as TileJukebox; @@ -80,6 +81,8 @@ use function strtolower; * @generate-registry-docblock * * @method static WoodenButton ACACIA_BUTTON() + * @method static CeilingCenterHangingSign ACACIA_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign ACACIA_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor ACACIA_DOOR() * @method static WoodenFence ACACIA_FENCE() * @method static FenceGate ACACIA_FENCE_GATE() @@ -92,6 +95,7 @@ use function strtolower; * @method static WoodenSlab ACACIA_SLAB() * @method static WoodenStairs ACACIA_STAIRS() * @method static WoodenTrapdoor ACACIA_TRAPDOOR() + * @method static WallHangingSign ACACIA_WALL_HANGING_SIGN() * @method static WallSign ACACIA_WALL_SIGN() * @method static Wood ACACIA_WOOD() * @method static ActivatorRail ACTIVATOR_RAIL() @@ -122,6 +126,8 @@ use function strtolower; * @method static BigDripleafHead BIG_DRIPLEAF_HEAD() * @method static BigDripleafStem BIG_DRIPLEAF_STEM() * @method static WoodenButton BIRCH_BUTTON() + * @method static CeilingCenterHangingSign BIRCH_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign BIRCH_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor BIRCH_DOOR() * @method static WoodenFence BIRCH_FENCE() * @method static FenceGate BIRCH_FENCE_GATE() @@ -134,6 +140,7 @@ use function strtolower; * @method static WoodenSlab BIRCH_SLAB() * @method static WoodenStairs BIRCH_STAIRS() * @method static WoodenTrapdoor BIRCH_TRAPDOOR() + * @method static WallHangingSign BIRCH_WALL_HANGING_SIGN() * @method static WallSign BIRCH_WALL_SIGN() * @method static Wood BIRCH_WOOD() * @method static Opaque BLACKSTONE() @@ -170,6 +177,8 @@ use function strtolower; * @method static Chain CHAIN() * @method static ChemicalHeat CHEMICAL_HEAT() * @method static WoodenButton CHERRY_BUTTON() + * @method static CeilingCenterHangingSign CHERRY_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign CHERRY_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor CHERRY_DOOR() * @method static WoodenFence CHERRY_FENCE() * @method static FenceGate CHERRY_FENCE_GATE() @@ -181,6 +190,7 @@ use function strtolower; * @method static WoodenSlab CHERRY_SLAB() * @method static WoodenStairs CHERRY_STAIRS() * @method static WoodenTrapdoor CHERRY_TRAPDOOR() + * @method static WallHangingSign CHERRY_WALL_HANGING_SIGN() * @method static WallSign CHERRY_WALL_SIGN() * @method static Wood CHERRY_WOOD() * @method static Chest CHEST() @@ -231,6 +241,8 @@ use function strtolower; * @method static Opaque CRACKED_STONE_BRICKS() * @method static CraftingTable CRAFTING_TABLE() * @method static WoodenButton CRIMSON_BUTTON() + * @method static CeilingCenterHangingSign CRIMSON_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign CRIMSON_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor CRIMSON_DOOR() * @method static WoodenFence CRIMSON_FENCE() * @method static FenceGate CRIMSON_FENCE_GATE() @@ -243,6 +255,7 @@ use function strtolower; * @method static WoodenStairs CRIMSON_STAIRS() * @method static Wood CRIMSON_STEM() * @method static WoodenTrapdoor CRIMSON_TRAPDOOR() + * @method static WallHangingSign CRIMSON_WALL_HANGING_SIGN() * @method static WallSign CRIMSON_WALL_SIGN() * @method static Opaque CRYING_OBSIDIAN() * @method static Copper CUT_COPPER() @@ -254,6 +267,8 @@ use function strtolower; * @method static Slab CUT_SANDSTONE_SLAB() * @method static Flower DANDELION() * @method static WoodenButton DARK_OAK_BUTTON() + * @method static CeilingCenterHangingSign DARK_OAK_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign DARK_OAK_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor DARK_OAK_DOOR() * @method static WoodenFence DARK_OAK_FENCE() * @method static FenceGate DARK_OAK_FENCE_GATE() @@ -266,6 +281,7 @@ use function strtolower; * @method static WoodenSlab DARK_OAK_SLAB() * @method static WoodenStairs DARK_OAK_STAIRS() * @method static WoodenTrapdoor DARK_OAK_TRAPDOOR() + * @method static WallHangingSign DARK_OAK_WALL_HANGING_SIGN() * @method static WallSign DARK_OAK_WALL_SIGN() * @method static Wood DARK_OAK_WOOD() * @method static Opaque DARK_PRISMARINE() @@ -488,6 +504,8 @@ use function strtolower; * @method static ItemFrame ITEM_FRAME() * @method static Jukebox JUKEBOX() * @method static WoodenButton JUNGLE_BUTTON() + * @method static CeilingCenterHangingSign JUNGLE_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign JUNGLE_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor JUNGLE_DOOR() * @method static WoodenFence JUNGLE_FENCE() * @method static FenceGate JUNGLE_FENCE_GATE() @@ -500,6 +518,7 @@ use function strtolower; * @method static WoodenSlab JUNGLE_SLAB() * @method static WoodenStairs JUNGLE_STAIRS() * @method static WoodenTrapdoor JUNGLE_TRAPDOOR() + * @method static WallHangingSign JUNGLE_WALL_HANGING_SIGN() * @method static WallSign JUNGLE_WALL_SIGN() * @method static Wood JUNGLE_WOOD() * @method static ChemistryTable LAB_TABLE() @@ -522,6 +541,8 @@ use function strtolower; * @method static Loom LOOM() * @method static Magma MAGMA() * @method static WoodenButton MANGROVE_BUTTON() + * @method static CeilingCenterHangingSign MANGROVE_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign MANGROVE_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor MANGROVE_DOOR() * @method static WoodenFence MANGROVE_FENCE() * @method static FenceGate MANGROVE_FENCE_GATE() @@ -534,6 +555,7 @@ use function strtolower; * @method static WoodenSlab MANGROVE_SLAB() * @method static WoodenStairs MANGROVE_STAIRS() * @method static WoodenTrapdoor MANGROVE_TRAPDOOR() + * @method static WallHangingSign MANGROVE_WALL_HANGING_SIGN() * @method static WallSign MANGROVE_WALL_SIGN() * @method static Wood MANGROVE_WOOD() * @method static ChemistryTable MATERIAL_REDUCER() @@ -572,6 +594,8 @@ use function strtolower; * @method static Opaque NETHER_WART_BLOCK() * @method static Note NOTE_BLOCK() * @method static WoodenButton OAK_BUTTON() + * @method static CeilingCenterHangingSign OAK_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign OAK_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor OAK_DOOR() * @method static WoodenFence OAK_FENCE() * @method static FenceGate OAK_FENCE_GATE() @@ -584,6 +608,7 @@ use function strtolower; * @method static WoodenSlab OAK_SLAB() * @method static WoodenStairs OAK_STAIRS() * @method static WoodenTrapdoor OAK_TRAPDOOR() + * @method static WallHangingSign OAK_WALL_HANGING_SIGN() * @method static WallSign OAK_WALL_SIGN() * @method static Wood OAK_WOOD() * @method static Opaque OBSIDIAN() @@ -594,6 +619,8 @@ use function strtolower; * @method static PackedIce PACKED_ICE() * @method static Opaque PACKED_MUD() * @method static WoodenButton PALE_OAK_BUTTON() + * @method static CeilingCenterHangingSign PALE_OAK_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign PALE_OAK_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor PALE_OAK_DOOR() * @method static WoodenFence PALE_OAK_FENCE() * @method static FenceGate PALE_OAK_FENCE_GATE() @@ -605,6 +632,7 @@ use function strtolower; * @method static WoodenSlab PALE_OAK_SLAB() * @method static WoodenStairs PALE_OAK_STAIRS() * @method static WoodenTrapdoor PALE_OAK_TRAPDOOR() + * @method static WallHangingSign PALE_OAK_WALL_HANGING_SIGN() * @method static WallSign PALE_OAK_WALL_SIGN() * @method static Wood PALE_OAK_WOOD() * @method static DoublePlant PEONY() @@ -735,6 +763,8 @@ use function strtolower; * @method static Sponge SPONGE() * @method static SporeBlossom SPORE_BLOSSOM() * @method static WoodenButton SPRUCE_BUTTON() + * @method static CeilingCenterHangingSign SPRUCE_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign SPRUCE_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor SPRUCE_DOOR() * @method static WoodenFence SPRUCE_FENCE() * @method static FenceGate SPRUCE_FENCE_GATE() @@ -747,6 +777,7 @@ use function strtolower; * @method static WoodenSlab SPRUCE_SLAB() * @method static WoodenStairs SPRUCE_STAIRS() * @method static WoodenTrapdoor SPRUCE_TRAPDOOR() + * @method static WallHangingSign SPRUCE_WALL_HANGING_SIGN() * @method static WallSign SPRUCE_WALL_SIGN() * @method static Wood SPRUCE_WOOD() * @method static StainedHardenedClay STAINED_CLAY() @@ -790,6 +821,8 @@ use function strtolower; * @method static WallBanner WALL_BANNER() * @method static WallCoralFan WALL_CORAL_FAN() * @method static WoodenButton WARPED_BUTTON() + * @method static CeilingCenterHangingSign WARPED_CEILING_CENTER_HANGING_SIGN() + * @method static CeilingEdgesHangingSign WARPED_CEILING_EDGES_HANGING_SIGN() * @method static WoodenDoor WARPED_DOOR() * @method static WoodenFence WARPED_FENCE() * @method static FenceGate WARPED_FENCE_GATE() @@ -802,6 +835,7 @@ use function strtolower; * @method static WoodenStairs WARPED_STAIRS() * @method static Wood WARPED_STEM() * @method static WoodenTrapdoor WARPED_TRAPDOOR() + * @method static WallHangingSign WARPED_WALL_HANGING_SIGN() * @method static WallSign WARPED_WALL_SIGN() * @method static Opaque WARPED_WART_BLOCK() * @method static Water WATER() @@ -1396,6 +1430,23 @@ final class VanillaBlocks{ }; self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); + + $hangingSignAsItem = match($woodType){ + WoodType::OAK => VanillaItems::OAK_HANGING_SIGN(...), + WoodType::SPRUCE => VanillaItems::SPRUCE_HANGING_SIGN(...), + WoodType::BIRCH => VanillaItems::BIRCH_HANGING_SIGN(...), + WoodType::JUNGLE => VanillaItems::JUNGLE_HANGING_SIGN(...), + WoodType::ACACIA => VanillaItems::ACACIA_HANGING_SIGN(...), + WoodType::DARK_OAK => VanillaItems::DARK_OAK_HANGING_SIGN(...), + WoodType::MANGROVE => VanillaItems::MANGROVE_HANGING_SIGN(...), + WoodType::CRIMSON => VanillaItems::CRIMSON_HANGING_SIGN(...), + WoodType::WARPED => VanillaItems::WARPED_HANGING_SIGN(...), + WoodType::CHERRY => VanillaItems::CHERRY_HANGING_SIGN(...), + WoodType::PALE_OAK => VanillaItems::PALE_OAK_HANGING_SIGN(...), + }; + self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . "Center Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . "Edges Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("wall_hanging_sign"), fn(BID $id) => new WallHangingSign($id, $name . " Wall Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); } } diff --git a/src/block/WallHangingSign.php b/src/block/WallHangingSign.php new file mode 100644 index 000000000..c167036b1 --- /dev/null +++ b/src/block/WallHangingSign.php @@ -0,0 +1,63 @@ +facing, clockwise: true); + } + + protected function getSupportingFaceOptions() : array{ + //wall hanging signs can be supported from either end of the post + return [ + Facing::rotateY($this->facing, clockwise: true), + Facing::rotateY($this->facing, clockwise: false) + ]; + } + + public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + if(Facing::axis($face) === Axis::Y){ + return false; + } + + $this->facing = Facing::rotateY($face, clockwise: true); + //the front should always face the player if possible + if($player !== null && $this->facing === $player->getHorizontalFacing()){ + $this->facing = Facing::opposite($this->facing); + } + + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } +} diff --git a/src/block/tile/HangingSign.php b/src/block/tile/HangingSign.php new file mode 100644 index 000000000..9bf088f47 --- /dev/null +++ b/src/block/tile/HangingSign.php @@ -0,0 +1,33 @@ +register(SporeBlossom::class, ["SporeBlossom", "minecraft:spore_blossom"]); $this->register(MobHead::class, ["Skull", "minecraft:skull"]); $this->register(GlowingItemFrame::class, ["GlowItemFrame"]); + $this->register(HangingSign::class, ["HangingSign", "minecraft:hanging_sign"]); //TODO: ChalkboardBlock //TODO: ChemistryTable diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php index e276ed6e7..fc1c6acb6 100644 --- a/src/data/bedrock/block/convert/VanillaBlockMappings.php +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -1596,5 +1596,51 @@ final class VanillaBlockMappings{ //normal banner, therefore no deserializer is needed self::mapAsymmetricSerializer($reg, Model::create(Blocks::OMINOUS_BANNER(), Ids::STANDING_BANNER)->properties([$commonProperties->floorSignLikeRotation])); self::mapAsymmetricSerializer($reg, Model::create(Blocks::OMINOUS_WALL_BANNER(), Ids::WALL_BANNER)->properties([$commonProperties->horizontalFacingClassic])); + + foreach([ + Ids::ACACIA_HANGING_SIGN => [Blocks::ACACIA_CEILING_CENTER_HANGING_SIGN(), Blocks::ACACIA_CEILING_EDGES_HANGING_SIGN(), Blocks::ACACIA_WALL_HANGING_SIGN()], + Ids::BIRCH_HANGING_SIGN => [Blocks::BIRCH_CEILING_CENTER_HANGING_SIGN(), Blocks::BIRCH_CEILING_EDGES_HANGING_SIGN(), Blocks::BIRCH_WALL_HANGING_SIGN()], + Ids::CHERRY_HANGING_SIGN => [Blocks::CHERRY_CEILING_CENTER_HANGING_SIGN(), Blocks::CHERRY_CEILING_EDGES_HANGING_SIGN(), Blocks::CHERRY_WALL_HANGING_SIGN()], + Ids::CRIMSON_HANGING_SIGN => [Blocks::CRIMSON_CEILING_CENTER_HANGING_SIGN(), Blocks::CRIMSON_CEILING_EDGES_HANGING_SIGN(), Blocks::CRIMSON_WALL_HANGING_SIGN()], + Ids::DARK_OAK_HANGING_SIGN => [Blocks::DARK_OAK_CEILING_CENTER_HANGING_SIGN(), Blocks::DARK_OAK_CEILING_EDGES_HANGING_SIGN(), Blocks::DARK_OAK_WALL_HANGING_SIGN()], + Ids::JUNGLE_HANGING_SIGN => [Blocks::JUNGLE_CEILING_CENTER_HANGING_SIGN(), Blocks::JUNGLE_CEILING_EDGES_HANGING_SIGN(), Blocks::JUNGLE_WALL_HANGING_SIGN()], + Ids::MANGROVE_HANGING_SIGN => [Blocks::MANGROVE_CEILING_CENTER_HANGING_SIGN(), Blocks::MANGROVE_CEILING_EDGES_HANGING_SIGN(), Blocks::MANGROVE_WALL_HANGING_SIGN()], + Ids::OAK_HANGING_SIGN => [Blocks::OAK_CEILING_CENTER_HANGING_SIGN(), Blocks::OAK_CEILING_EDGES_HANGING_SIGN(), Blocks::OAK_WALL_HANGING_SIGN()], + Ids::PALE_OAK_HANGING_SIGN => [Blocks::PALE_OAK_CEILING_CENTER_HANGING_SIGN(), Blocks::PALE_OAK_CEILING_EDGES_HANGING_SIGN(), Blocks::PALE_OAK_WALL_HANGING_SIGN()], + Ids::SPRUCE_HANGING_SIGN => [Blocks::SPRUCE_CEILING_CENTER_HANGING_SIGN(), Blocks::SPRUCE_CEILING_EDGES_HANGING_SIGN(), Blocks::SPRUCE_WALL_HANGING_SIGN()], + Ids::WARPED_HANGING_SIGN => [Blocks::WARPED_CEILING_CENTER_HANGING_SIGN(), Blocks::WARPED_CEILING_EDGES_HANGING_SIGN(), Blocks::WARPED_WALL_HANGING_SIGN()], + ] as $id => [$center, $edges, $wall]){ + //attached_bit - true for ceiling center signs, false for ceiling edges signs and wall signs + //hanging - true for all ceiling signs, false for wall signs + //facing_direction - used for ceiling edges signs and wall signs + //ground_sign_direction - used by ceiling center signs only + $centerModel = Model::create($center, $id)->properties([ + $commonProperties->floorSignLikeRotation, + new DummyProperty(StateNames::ATTACHED_BIT, true), + new DummyProperty(StateNames::HANGING, true), + new DummyProperty(StateNames::FACING_DIRECTION, 2) + ]); + $edgesModel = Model::create($edges, $id)->properties([ + new DummyProperty(StateNames::GROUND_SIGN_DIRECTION, 0), + new DummyProperty(StateNames::ATTACHED_BIT, false), + new DummyProperty(StateNames::HANGING, true), + $commonProperties->horizontalFacingClassic, + ]); + $wallModel = Model::create($wall, $id)->properties([ + new DummyProperty(StateNames::GROUND_SIGN_DIRECTION, 0), + new DummyProperty(StateNames::ATTACHED_BIT, false), + new DummyProperty(StateNames::HANGING, false), + $commonProperties->horizontalFacingClassic + ]); + self::mapAsymmetricSerializer($reg, $centerModel); + self::mapAsymmetricSerializer($reg, $edgesModel); + self::mapAsymmetricSerializer($reg, $wallModel); + $reg->deserializer->map($id, fn(Reader $in) => $in->readBool(StateNames::HANGING) ? + ($in->readBool(StateNames::ATTACHED_BIT) ? + self::deserializeAsymmetric($centerModel, $in) : + self::deserializeAsymmetric($edgesModel, $in) + ) : + self::deserializeAsymmetric($wallModel, $in)); + } } } diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index fa6f88483..f5c03dbeb 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -167,6 +167,7 @@ final class ItemSerializerDeserializerRegistrar{ */ private function register1to1ItemMappings() : void{ $this->map1to1Item(Ids::ACACIA_BOAT, Items::ACACIA_BOAT()); + $this->map1to1Item(Ids::ACACIA_HANGING_SIGN, Items::ACACIA_HANGING_SIGN()); $this->map1to1Item(Ids::ACACIA_SIGN, Items::ACACIA_SIGN()); $this->map1to1Item(Ids::AMETHYST_SHARD, Items::AMETHYST_SHARD()); $this->map1to1Item(Ids::APPLE, Items::APPLE()); @@ -176,6 +177,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::BEETROOT_SEEDS, Items::BEETROOT_SEEDS()); $this->map1to1Item(Ids::BEETROOT_SOUP, Items::BEETROOT_SOUP()); $this->map1to1Item(Ids::BIRCH_BOAT, Items::BIRCH_BOAT()); + $this->map1to1Item(Ids::BIRCH_HANGING_SIGN, Items::BIRCH_HANGING_SIGN()); $this->map1to1Item(Ids::BIRCH_SIGN, Items::BIRCH_SIGN()); $this->map1to1Item(Ids::BLAZE_POWDER, Items::BLAZE_POWDER()); $this->map1to1Item(Ids::BLAZE_ROD, Items::BLAZE_ROD()); @@ -194,6 +196,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::CHAINMAIL_HELMET, Items::CHAINMAIL_HELMET()); $this->map1to1Item(Ids::CHAINMAIL_LEGGINGS, Items::CHAINMAIL_LEGGINGS()); $this->map1to1Item(Ids::CHARCOAL, Items::CHARCOAL()); + $this->map1to1Item(Ids::CHERRY_HANGING_SIGN, Items::CHERRY_HANGING_SIGN()); $this->map1to1Item(Ids::CHERRY_SIGN, Items::CHERRY_SIGN()); $this->map1to1Item(Ids::CHICKEN, Items::RAW_CHICKEN()); $this->map1to1Item(Ids::CHORUS_FRUIT, Items::CHORUS_FRUIT()); @@ -213,8 +216,10 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::COOKED_SALMON, Items::COOKED_SALMON()); $this->map1to1Item(Ids::COOKIE, Items::COOKIE()); $this->map1to1Item(Ids::COPPER_INGOT, Items::COPPER_INGOT()); + $this->map1to1Item(Ids::CRIMSON_HANGING_SIGN, Items::CRIMSON_HANGING_SIGN()); $this->map1to1Item(Ids::CRIMSON_SIGN, Items::CRIMSON_SIGN()); $this->map1to1Item(Ids::DARK_OAK_BOAT, Items::DARK_OAK_BOAT()); + $this->map1to1Item(Ids::DARK_OAK_HANGING_SIGN, Items::DARK_OAK_HANGING_SIGN()); $this->map1to1Item(Ids::DARK_OAK_SIGN, Items::DARK_OAK_SIGN()); $this->map1to1Item(Ids::DIAMOND, Items::DIAMOND()); $this->map1to1Item(Ids::DIAMOND_AXE, Items::DIAMOND_AXE()); @@ -283,6 +288,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::IRON_SHOVEL, Items::IRON_SHOVEL()); $this->map1to1Item(Ids::IRON_SWORD, Items::IRON_SWORD()); $this->map1to1Item(Ids::JUNGLE_BOAT, Items::JUNGLE_BOAT()); + $this->map1to1Item(Ids::JUNGLE_HANGING_SIGN, Items::JUNGLE_HANGING_SIGN()); $this->map1to1Item(Ids::JUNGLE_SIGN, Items::JUNGLE_SIGN()); $this->map1to1Item(Ids::LAPIS_LAZULI, Items::LAPIS_LAZULI()); $this->map1to1Item(Ids::LAVA_BUCKET, Items::LAVA_BUCKET()); @@ -293,6 +299,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::LEATHER_LEGGINGS, Items::LEATHER_PANTS()); $this->map1to1Item(Ids::MAGMA_CREAM, Items::MAGMA_CREAM()); $this->map1to1Item(Ids::MANGROVE_BOAT, Items::MANGROVE_BOAT()); + $this->map1to1Item(Ids::MANGROVE_HANGING_SIGN, Items::MANGROVE_HANGING_SIGN()); $this->map1to1Item(Ids::MANGROVE_SIGN, Items::MANGROVE_SIGN()); $this->map1to1Item(Ids::MELON_SEEDS, Items::MELON_SEEDS()); $this->map1to1Item(Ids::MELON_SLICE, Items::MELON()); @@ -336,8 +343,10 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::NETHERITE_SWORD, Items::NETHERITE_SWORD()); $this->map1to1Item(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::OAK_BOAT, Items::OAK_BOAT()); + $this->map1to1Item(Ids::OAK_HANGING_SIGN, Items::OAK_HANGING_SIGN()); $this->map1to1Item(Ids::OAK_SIGN, Items::OAK_SIGN()); $this->map1to1Item(Ids::PAINTING, Items::PAINTING()); + $this->map1to1Item(Ids::PALE_OAK_HANGING_SIGN, Items::PALE_OAK_HANGING_SIGN()); $this->map1to1Item(Ids::PALE_OAK_SIGN, Items::PALE_OAK_SIGN()); $this->map1to1Item(Ids::PAPER, Items::PAPER()); $this->map1to1Item(Ids::PHANTOM_MEMBRANE, Items::PHANTOM_MEMBRANE()); @@ -378,6 +387,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::SPIDER_EYE, Items::SPIDER_EYE()); $this->map1to1Item(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::SPRUCE_BOAT, Items::SPRUCE_BOAT()); + $this->map1to1Item(Ids::SPRUCE_HANGING_SIGN, Items::SPRUCE_HANGING_SIGN()); $this->map1to1Item(Ids::SPRUCE_SIGN, Items::SPRUCE_SIGN()); $this->map1to1Item(Ids::SPYGLASS, Items::SPYGLASS()); $this->map1to1Item(Ids::SQUID_SPAWN_EGG, Items::SQUID_SPAWN_EGG()); @@ -398,6 +408,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE, Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::VILLAGER_SPAWN_EGG, Items::VILLAGER_SPAWN_EGG()); $this->map1to1Item(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE()); + $this->map1to1Item(Ids::WARPED_HANGING_SIGN, Items::WARPED_HANGING_SIGN()); $this->map1to1Item(Ids::WARPED_SIGN, Items::WARPED_SIGN()); $this->map1to1Item(Ids::WATER_BUCKET, Items::WATER_BUCKET()); $this->map1to1Item(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE()); diff --git a/src/item/HangingSign.php b/src/item/HangingSign.php new file mode 100644 index 000000000..e3e95d68a --- /dev/null +++ b/src/item/HangingSign.php @@ -0,0 +1,66 @@ +getSide(Facing::UP)->getSupportType(Facing::DOWN) === SupportType::CENTER ? + $this->centerPointCeilingVariant : + $this->edgePointCeilingVariant + : $this->wallVariant; + return clone $result; + } + + public function getBlock(?int $clickedFace = null) : Block{ + //we don't have enough information here to decide which ceiling type to use + return $clickedFace === Facing::DOWN ? clone $this->centerPointCeilingVariant : clone $this->wallVariant; + } + + public function getMaxStackSize() : int{ + return 16; + } + + public function getFuelTime() : int{ + return 200; + } +} diff --git a/src/item/Item.php b/src/item/Item.php index 205f15e13..6786238b0 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -485,6 +485,10 @@ class Item implements \JsonSerializable{ return $this->getBlock()->canBePlaced(); } + public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ + return $this->getBlock($face); + } + /** * Returns the block corresponding to this Item. */ diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index 37be3ab9e..af32cbcc2 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -335,8 +335,19 @@ final class ItemTypeIds{ public const RECORD_CREATOR_MUSIC_BOX = 20296; public const RECORD_PRECIPICE = 20297; public const OMINOUS_BANNER = 20298; + public const ACACIA_HANGING_SIGN = 20299; + public const BIRCH_HANGING_SIGN = 20300; + public const CHERRY_HANGING_SIGN = 20301; + public const CRIMSON_HANGING_SIGN = 20302; + public const DARK_OAK_HANGING_SIGN = 20303; + public const JUNGLE_HANGING_SIGN = 20304; + public const MANGROVE_HANGING_SIGN = 20305; + public const OAK_HANGING_SIGN = 20306; + public const PALE_OAK_HANGING_SIGN = 20307; + public const SPRUCE_HANGING_SIGN = 20308; + public const WARPED_HANGING_SIGN = 20309; - public const FIRST_UNUSED_ITEM_ID = 20299; + public const FIRST_UNUSED_ITEM_ID = 20310; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 7a90babed..2f316f66b 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1232,6 +1232,7 @@ final class StringToItemParser extends StringToTParser{ private static function registerItems(self $result) : void{ $result->register("acacia_boat", fn() => Items::ACACIA_BOAT()); + $result->register("acacia_hanging_sign", fn() => Items::ACACIA_HANGING_SIGN()); $result->register("amethyst_shard", fn() => Items::AMETHYST_SHARD()); $result->register("antidote", fn() => Items::MEDICINE()->setType(MedicineType::ANTIDOTE)); $result->register("apple", fn() => Items::APPLE()); @@ -1246,6 +1247,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("beetroot_seeds", fn() => Items::BEETROOT_SEEDS()); $result->register("beetroot_soup", fn() => Items::BEETROOT_SOUP()); $result->register("birch_boat", fn() => Items::BIRCH_BOAT()); + $result->register("birch_hanging_sign", fn() => Items::BIRCH_HANGING_SIGN()); $result->register("blaze_powder", fn() => Items::BLAZE_POWDER()); $result->register("blaze_rod", fn() => Items::BLAZE_ROD()); $result->register("bleach", fn() => Items::BLEACH()); @@ -1307,6 +1309,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("chemical_sulphate", fn() => Items::CHEMICAL_SULPHATE()); $result->register("chemical_tungsten_chloride", fn() => Items::CHEMICAL_TUNGSTEN_CHLORIDE()); $result->register("chemical_water", fn() => Items::CHEMICAL_WATER()); + $result->register("cherry_hanging_sign", fn() => Items::CHERRY_HANGING_SIGN()); $result->register("chicken", fn() => Items::RAW_CHICKEN()); $result->register("chorus_fruit", fn() => Items::CHORUS_FRUIT()); $result->register("chorus_fruit_popped", fn() => Items::POPPED_CHORUS_FRUIT()); @@ -1331,7 +1334,9 @@ final class StringToItemParser extends StringToTParser{ $result->register("cooked_salmon", fn() => Items::COOKED_SALMON()); $result->register("cookie", fn() => Items::COOKIE()); $result->register("copper_ingot", fn() => Items::COPPER_INGOT()); + $result->register("crimson_hanging_sign", fn() => Items::CRIMSON_HANGING_SIGN()); $result->register("dark_oak_boat", fn() => Items::DARK_OAK_BOAT()); + $result->register("dark_oak_hanging_sign", fn() => Items::DARK_OAK_HANGING_SIGN()); $result->register("diamond", fn() => Items::DIAMOND()); $result->register("diamond_axe", fn() => Items::DIAMOND_AXE()); $result->register("diamond_boots", fn() => Items::DIAMOND_BOOTS()); @@ -1416,6 +1421,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("iron_shovel", fn() => Items::IRON_SHOVEL()); $result->register("iron_sword", fn() => Items::IRON_SWORD()); $result->register("jungle_boat", fn() => Items::JUNGLE_BOAT()); + $result->register("jungle_hanging_sign", fn() => Items::JUNGLE_HANGING_SIGN()); $result->register("lapis_lazuli", fn() => Items::LAPIS_LAZULI()); $result->register("lava_bucket", fn() => Items::LAVA_BUCKET()); $result->register("leather", fn() => Items::LEATHER()); @@ -1427,6 +1433,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("leather_pants", fn() => Items::LEATHER_PANTS()); $result->register("leather_tunic", fn() => Items::LEATHER_TUNIC()); $result->register("magma_cream", fn() => Items::MAGMA_CREAM()); + $result->register("mangrove_hanging_sign", fn() => Items::MANGROVE_HANGING_SIGN()); $result->register("melon", fn() => Items::MELON()); $result->register("melon_seeds", fn() => Items::MELON_SEEDS()); $result->register("melon_slice", fn() => Items::MELON()); @@ -1458,7 +1465,9 @@ final class StringToItemParser extends StringToTParser{ $result->register("netherstar", fn() => Items::NETHER_STAR()); $result->register("netherite_upgrade_smithing_template", fn() => Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE()); $result->register("oak_boat", fn() => Items::OAK_BOAT()); + $result->register("oak_hanging_sign", fn() => Items::OAK_HANGING_SIGN()); $result->register("painting", fn() => Items::PAINTING()); + $result->register("pale_oak_hanging_sign", fn() => Items::PALE_OAK_HANGING_SIGN()); $result->register("paper", fn() => Items::PAPER()); $result->register("phantom_membrane", fn() => Items::PHANTOM_MEMBRANE()); $result->register("pitcher_pod", fn() => Items::PITCHER_POD()); @@ -1532,6 +1541,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("spire_armor_trim_smithing_template", fn() => Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("splash_potion", fn() => Items::SPLASH_POTION()); $result->register("spruce_boat", fn() => Items::SPRUCE_BOAT()); + $result->register("spruce_hanging_sign", fn() => Items::SPRUCE_HANGING_SIGN()); $result->register("spyglass", fn() => Items::SPYGLASS()); $result->register("squid_spawn_egg", fn() => Items::SQUID_SPAWN_EGG()); $result->register("steak", fn() => Items::STEAK()); @@ -1555,6 +1565,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("turtle_shell_piece", fn() => Items::SCUTE()); $result->register("villager_spawn_egg", fn() => Items::VILLAGER_SPAWN_EGG()); $result->register("ward_armor_trim_smithing_template", fn() => Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE()); + $result->register("warped_hanging_sign", fn() => Items::WARPED_HANGING_SIGN()); $result->register("water_bucket", fn() => Items::WATER_BUCKET()); $result->register("wayfinder_armor_trim_smithing_template", fn() => Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("wheat", fn() => Items::WHEAT()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 7103d8878..e4eeffc1d 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -48,6 +48,7 @@ use function strtolower; * @generate-registry-docblock * * @method static Boat ACACIA_BOAT() + * @method static HangingSign ACACIA_HANGING_SIGN() * @method static ItemBlockWallOrFloor ACACIA_SIGN() * @method static ItemBlock AIR() * @method static Item AMETHYST_SHARD() @@ -60,6 +61,7 @@ use function strtolower; * @method static BeetrootSeeds BEETROOT_SEEDS() * @method static BeetrootSoup BEETROOT_SOUP() * @method static Boat BIRCH_BOAT() + * @method static HangingSign BIRCH_HANGING_SIGN() * @method static ItemBlockWallOrFloor BIRCH_SIGN() * @method static Item BLAZE_POWDER() * @method static BlazeRod BLAZE_ROD() @@ -116,6 +118,7 @@ use function strtolower; * @method static Item CHEMICAL_SULPHATE() * @method static Item CHEMICAL_TUNGSTEN_CHLORIDE() * @method static Item CHEMICAL_WATER() + * @method static HangingSign CHERRY_HANGING_SIGN() * @method static ItemBlockWallOrFloor CHERRY_SIGN() * @method static ChorusFruit CHORUS_FRUIT() * @method static Item CLAY() @@ -134,8 +137,10 @@ use function strtolower; * @method static Cookie COOKIE() * @method static Item COPPER_INGOT() * @method static CoralFan CORAL_FAN() + * @method static HangingSign CRIMSON_HANGING_SIGN() * @method static ItemBlockWallOrFloor CRIMSON_SIGN() * @method static Boat DARK_OAK_BOAT() + * @method static HangingSign DARK_OAK_HANGING_SIGN() * @method static ItemBlockWallOrFloor DARK_OAK_SIGN() * @method static Item DIAMOND() * @method static Axe DIAMOND_AXE() @@ -206,6 +211,7 @@ use function strtolower; * @method static Shovel IRON_SHOVEL() * @method static Sword IRON_SWORD() * @method static Boat JUNGLE_BOAT() + * @method static HangingSign JUNGLE_HANGING_SIGN() * @method static ItemBlockWallOrFloor JUNGLE_SIGN() * @method static Item LAPIS_LAZULI() * @method static LiquidBucket LAVA_BUCKET() @@ -216,6 +222,7 @@ use function strtolower; * @method static Armor LEATHER_TUNIC() * @method static Item MAGMA_CREAM() * @method static Boat MANGROVE_BOAT() + * @method static HangingSign MANGROVE_HANGING_SIGN() * @method static ItemBlockWallOrFloor MANGROVE_SIGN() * @method static Medicine MEDICINE() * @method static Melon MELON() @@ -241,9 +248,11 @@ use function strtolower; * @method static Item NETHER_QUARTZ() * @method static Item NETHER_STAR() * @method static Boat OAK_BOAT() + * @method static HangingSign OAK_HANGING_SIGN() * @method static ItemBlockWallOrFloor OAK_SIGN() * @method static ItemBlockWallOrFloor OMINOUS_BANNER() * @method static PaintingItem PAINTING() + * @method static HangingSign PALE_OAK_HANGING_SIGN() * @method static ItemBlockWallOrFloor PALE_OAK_SIGN() * @method static Item PAPER() * @method static Item PHANTOM_MEMBRANE() @@ -308,6 +317,7 @@ use function strtolower; * @method static Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static SplashPotion SPLASH_POTION() * @method static Boat SPRUCE_BOAT() + * @method static HangingSign SPRUCE_HANGING_SIGN() * @method static ItemBlockWallOrFloor SPRUCE_SIGN() * @method static Spyglass SPYGLASS() * @method static SpawnEgg SQUID_SPAWN_EGG() @@ -329,6 +339,7 @@ use function strtolower; * @method static Item VEX_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static SpawnEgg VILLAGER_SPAWN_EGG() * @method static Item WARD_ARMOR_TRIM_SMITHING_TEMPLATE() + * @method static HangingSign WARPED_HANGING_SIGN() * @method static ItemBlockWallOrFloor WARPED_SIGN() * @method static LiquidBucket WATER_BUCKET() * @method static Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE() @@ -398,6 +409,7 @@ final class VanillaItems{ self::_registryRegister("air", Blocks::AIR()->asItem()->setCount(0)); self::register("acacia_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN())); + self::register("acacia_hanging_sign", fn(IID $id) => new HangingSign($id, "Acacia Hanging Sign", Blocks::ACACIA_CEILING_CENTER_HANGING_SIGN(), Blocks::ACACIA_CEILING_EDGES_HANGING_SIGN(), Blocks::ACACIA_WALL_HANGING_SIGN())); self::register("amethyst_shard", fn(IID $id) => new Item($id, "Amethyst Shard")); self::register("apple", fn(IID $id) => new Apple($id, "Apple")); self::register("arrow", fn(IID $id) => new Arrow($id, "Arrow")); @@ -408,6 +420,7 @@ final class VanillaItems{ self::register("beetroot_seeds", fn(IID $id) => new BeetrootSeeds($id, "Beetroot Seeds")); self::register("beetroot_soup", fn(IID $id) => new BeetrootSoup($id, "Beetroot Soup")); self::register("birch_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN())); + self::register("birch_hanging_sign", fn(IID $id) => new HangingSign($id, "Birch Hanging Sign", Blocks::BIRCH_CEILING_CENTER_HANGING_SIGN(), Blocks::BIRCH_CEILING_EDGES_HANGING_SIGN(), Blocks::BIRCH_WALL_HANGING_SIGN())); self::register("blaze_powder", fn(IID $id) => new Item($id, "Blaze Powder")); self::register("blaze_rod", fn(IID $id) => new BlazeRod($id, "Blaze Rod")); self::register("bleach", fn(IID $id) => new Item($id, "Bleach")); @@ -422,6 +435,7 @@ final class VanillaItems{ self::register("carrot", fn(IID $id) => new Carrot($id, "Carrot")); self::register("charcoal", fn(IID $id) => new Coal($id, "Charcoal")); self::register("cherry_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN())); + self::register("cherry_hanging_sign", fn(IID $id) => new HangingSign($id, "Cherry Hanging Sign", Blocks::CHERRY_CEILING_CENTER_HANGING_SIGN(), Blocks::CHERRY_CEILING_EDGES_HANGING_SIGN(), Blocks::CHERRY_WALL_HANGING_SIGN())); self::register("chemical_aluminium_oxide", fn(IID $id) => new Item($id, "Aluminium Oxide")); self::register("chemical_ammonia", fn(IID $id) => new Item($id, "Ammonia")); self::register("chemical_barium_sulphate", fn(IID $id) => new Item($id, "Barium Sulphate")); @@ -477,7 +491,9 @@ final class VanillaItems{ self::register("copper_ingot", fn(IID $id) => new Item($id, "Copper Ingot")); self::register("coral_fan", fn(IID $id) => new CoralFan($id)); self::register("crimson_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN())); + self::register("crimson_hanging_sign", fn(IID $id) => new HangingSign($id, "Crimson Hanging Sign", Blocks::CRIMSON_CEILING_CENTER_HANGING_SIGN(), Blocks::CRIMSON_CEILING_EDGES_HANGING_SIGN(), Blocks::CRIMSON_WALL_HANGING_SIGN())); self::register("dark_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN())); + self::register("dark_oak_hanging_sign", fn(IID $id) => new HangingSign($id, "Dark Oak Hanging Sign", Blocks::DARK_OAK_CEILING_CENTER_HANGING_SIGN(), Blocks::DARK_OAK_CEILING_EDGES_HANGING_SIGN(), Blocks::DARK_OAK_WALL_HANGING_SIGN())); self::register("diamond", fn(IID $id) => new Item($id, "Diamond")); self::register("disc_fragment_5", fn(IID $id) => new Item($id, "Disc Fragment (5)")); self::register("dragon_breath", fn(IID $id) => new Item($id, "Dragon's Breath")); @@ -518,11 +534,13 @@ final class VanillaItems{ self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot")); self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget")); self::register("jungle_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN())); + self::register("jungle_hanging_sign", fn(IID $id) => new HangingSign($id, "Jungle Hanging Sign", Blocks::JUNGLE_CEILING_CENTER_HANGING_SIGN(), Blocks::JUNGLE_CEILING_EDGES_HANGING_SIGN(), Blocks::JUNGLE_WALL_HANGING_SIGN())); self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli")); self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA())); self::register("leather", fn(IID $id) => new Item($id, "Leather")); self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream")); self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN())); + self::register("mangrove_hanging_sign", fn(IID $id) => new HangingSign($id, "Mangrove Hanging Sign", Blocks::MANGROVE_CEILING_CENTER_HANGING_SIGN(), Blocks::MANGROVE_CEILING_EDGES_HANGING_SIGN(), Blocks::MANGROVE_WALL_HANGING_SIGN())); self::register("medicine", fn(IID $id) => new Medicine($id, "Medicine")); self::register("melon", fn(IID $id) => new Melon($id, "Melon")); self::register("melon_seeds", fn(IID $id) => new MelonSeeds($id, "Melon Seeds")); @@ -541,9 +559,11 @@ final class VanillaItems{ public function isFireProof() : bool{ return true; } }); self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN())); + self::register("oak_hanging_sign", fn(IID $id) => new HangingSign($id, "Oak Hanging Sign", Blocks::OAK_CEILING_CENTER_HANGING_SIGN(), Blocks::OAK_CEILING_EDGES_HANGING_SIGN(), Blocks::OAK_WALL_HANGING_SIGN())); self::register("ominous_banner", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OMINOUS_BANNER(), Blocks::OMINOUS_WALL_BANNER())); self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting")); self::register("pale_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::PALE_OAK_SIGN(), Blocks::PALE_OAK_WALL_SIGN())); + self::register("pale_oak_hanging_sign", fn(IID $id) => new HangingSign($id, "Pale Oak Hanging Sign", Blocks::PALE_OAK_CEILING_CENTER_HANGING_SIGN(), Blocks::PALE_OAK_CEILING_EDGES_HANGING_SIGN(), Blocks::PALE_OAK_WALL_HANGING_SIGN())); self::register("paper", fn(IID $id) => new Item($id, "Paper")); self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane")); self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod")); @@ -600,6 +620,7 @@ final class VanillaItems{ self::register("spider_eye", fn(IID $id) => new SpiderEye($id, "Spider Eye")); self::register("splash_potion", fn(IID $id) => new SplashPotion($id, "Splash Potion")); self::register("spruce_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN())); + self::register("spruce_hanging_sign", fn(IID $id) => new HangingSign($id, "Spruce Hanging Sign", Blocks::SPRUCE_CEILING_CENTER_HANGING_SIGN(), Blocks::SPRUCE_CEILING_EDGES_HANGING_SIGN(), Blocks::SPRUCE_WALL_HANGING_SIGN())); self::register("spyglass", fn(IID $id) => new Spyglass($id, "Spyglass")); self::register("steak", fn(IID $id) => new Steak($id, "Steak")); self::register("stick", fn(IID $id) => new Stick($id, "Stick")); @@ -610,6 +631,7 @@ final class VanillaItems{ self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds")); self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying")); self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN())); + self::register("warped_hanging_sign", fn(IID $id) => new HangingSign($id, "Warped Hanging Sign", Blocks::WARPED_CEILING_CENTER_HANGING_SIGN(), Blocks::WARPED_CEILING_EDGES_HANGING_SIGN(), Blocks::WARPED_WALL_HANGING_SIGN())); self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER())); self::register("wheat", fn(IID $id) => new Item($id, "Wheat")); self::register("wheat_seeds", fn(IID $id) => new WheatSeeds($id, "Wheat Seeds")); diff --git a/src/world/World.php b/src/world/World.php index bd790c299..4f2e222ca 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2298,7 +2298,7 @@ class World implements ChunkManager{ if($item->isNull() || !$item->canBePlaced()){ return false; } - $hand = $item->getBlock($face); + $hand = $item->getPlacementBlock($blockReplace, $blockClicked, $face, $clickVector); $hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z); if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){ diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index 86a44113f..b96607c0b 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -1,6 +1,8 @@ { "stateCounts": { "ACACIA_BUTTON": 12, + "ACACIA_CEILING_CENTER_HANGING_SIGN": 16, + "ACACIA_CEILING_EDGES_HANGING_SIGN": 4, "ACACIA_DOOR": 32, "ACACIA_FENCE": 1, "ACACIA_FENCE_GATE": 16, @@ -13,6 +15,7 @@ "ACACIA_SLAB": 3, "ACACIA_STAIRS": 8, "ACACIA_TRAPDOOR": 16, + "ACACIA_WALL_HANGING_SIGN": 4, "ACACIA_WALL_SIGN": 4, "ACACIA_WOOD": 6, "ACTIVATOR_RAIL": 12, @@ -43,6 +46,8 @@ "BIG_DRIPLEAF_HEAD": 16, "BIG_DRIPLEAF_STEM": 4, "BIRCH_BUTTON": 12, + "BIRCH_CEILING_CENTER_HANGING_SIGN": 16, + "BIRCH_CEILING_EDGES_HANGING_SIGN": 4, "BIRCH_DOOR": 32, "BIRCH_FENCE": 1, "BIRCH_FENCE_GATE": 16, @@ -55,6 +60,7 @@ "BIRCH_SLAB": 3, "BIRCH_STAIRS": 8, "BIRCH_TRAPDOOR": 16, + "BIRCH_WALL_HANGING_SIGN": 4, "BIRCH_WALL_SIGN": 4, "BIRCH_WOOD": 6, "BLACKSTONE": 1, @@ -91,6 +97,8 @@ "CHAIN": 3, "CHEMICAL_HEAT": 1, "CHERRY_BUTTON": 12, + "CHERRY_CEILING_CENTER_HANGING_SIGN": 16, + "CHERRY_CEILING_EDGES_HANGING_SIGN": 4, "CHERRY_DOOR": 32, "CHERRY_FENCE": 1, "CHERRY_FENCE_GATE": 16, @@ -102,6 +110,7 @@ "CHERRY_SLAB": 3, "CHERRY_STAIRS": 8, "CHERRY_TRAPDOOR": 16, + "CHERRY_WALL_HANGING_SIGN": 4, "CHERRY_WALL_SIGN": 4, "CHERRY_WOOD": 6, "CHEST": 4, @@ -152,6 +161,8 @@ "CRACKED_STONE_BRICKS": 1, "CRAFTING_TABLE": 1, "CRIMSON_BUTTON": 12, + "CRIMSON_CEILING_CENTER_HANGING_SIGN": 16, + "CRIMSON_CEILING_EDGES_HANGING_SIGN": 4, "CRIMSON_DOOR": 32, "CRIMSON_FENCE": 1, "CRIMSON_FENCE_GATE": 16, @@ -164,6 +175,7 @@ "CRIMSON_STAIRS": 8, "CRIMSON_STEM": 6, "CRIMSON_TRAPDOOR": 16, + "CRIMSON_WALL_HANGING_SIGN": 4, "CRIMSON_WALL_SIGN": 4, "CRYING_OBSIDIAN": 1, "CUT_COPPER": 8, @@ -175,6 +187,8 @@ "CUT_SANDSTONE_SLAB": 3, "DANDELION": 1, "DARK_OAK_BUTTON": 12, + "DARK_OAK_CEILING_CENTER_HANGING_SIGN": 16, + "DARK_OAK_CEILING_EDGES_HANGING_SIGN": 4, "DARK_OAK_DOOR": 32, "DARK_OAK_FENCE": 1, "DARK_OAK_FENCE_GATE": 16, @@ -187,6 +201,7 @@ "DARK_OAK_SLAB": 3, "DARK_OAK_STAIRS": 8, "DARK_OAK_TRAPDOOR": 16, + "DARK_OAK_WALL_HANGING_SIGN": 4, "DARK_OAK_WALL_SIGN": 4, "DARK_OAK_WOOD": 6, "DARK_PRISMARINE": 1, @@ -409,6 +424,8 @@ "ITEM_FRAME": 12, "JUKEBOX": 1, "JUNGLE_BUTTON": 12, + "JUNGLE_CEILING_CENTER_HANGING_SIGN": 16, + "JUNGLE_CEILING_EDGES_HANGING_SIGN": 4, "JUNGLE_DOOR": 32, "JUNGLE_FENCE": 1, "JUNGLE_FENCE_GATE": 16, @@ -421,6 +438,7 @@ "JUNGLE_SLAB": 3, "JUNGLE_STAIRS": 8, "JUNGLE_TRAPDOOR": 16, + "JUNGLE_WALL_HANGING_SIGN": 4, "JUNGLE_WALL_SIGN": 4, "JUNGLE_WOOD": 6, "LAB_TABLE": 4, @@ -443,6 +461,8 @@ "LOOM": 4, "MAGMA": 1, "MANGROVE_BUTTON": 12, + "MANGROVE_CEILING_CENTER_HANGING_SIGN": 16, + "MANGROVE_CEILING_EDGES_HANGING_SIGN": 4, "MANGROVE_DOOR": 32, "MANGROVE_FENCE": 1, "MANGROVE_FENCE_GATE": 16, @@ -455,6 +475,7 @@ "MANGROVE_SLAB": 3, "MANGROVE_STAIRS": 8, "MANGROVE_TRAPDOOR": 16, + "MANGROVE_WALL_HANGING_SIGN": 4, "MANGROVE_WALL_SIGN": 4, "MANGROVE_WOOD": 6, "MATERIAL_REDUCER": 4, @@ -493,6 +514,8 @@ "NETHER_WART_BLOCK": 1, "NOTE_BLOCK": 1, "OAK_BUTTON": 12, + "OAK_CEILING_CENTER_HANGING_SIGN": 16, + "OAK_CEILING_EDGES_HANGING_SIGN": 4, "OAK_DOOR": 32, "OAK_FENCE": 1, "OAK_FENCE_GATE": 16, @@ -505,6 +528,7 @@ "OAK_SLAB": 3, "OAK_STAIRS": 8, "OAK_TRAPDOOR": 16, + "OAK_WALL_HANGING_SIGN": 4, "OAK_WALL_SIGN": 4, "OAK_WOOD": 6, "OBSIDIAN": 1, @@ -515,6 +539,8 @@ "PACKED_ICE": 1, "PACKED_MUD": 1, "PALE_OAK_BUTTON": 12, + "PALE_OAK_CEILING_CENTER_HANGING_SIGN": 16, + "PALE_OAK_CEILING_EDGES_HANGING_SIGN": 4, "PALE_OAK_DOOR": 32, "PALE_OAK_FENCE": 1, "PALE_OAK_FENCE_GATE": 16, @@ -526,6 +552,7 @@ "PALE_OAK_SLAB": 3, "PALE_OAK_STAIRS": 8, "PALE_OAK_TRAPDOOR": 16, + "PALE_OAK_WALL_HANGING_SIGN": 4, "PALE_OAK_WALL_SIGN": 4, "PALE_OAK_WOOD": 6, "PEONY": 2, @@ -656,6 +683,8 @@ "SPONGE": 2, "SPORE_BLOSSOM": 1, "SPRUCE_BUTTON": 12, + "SPRUCE_CEILING_CENTER_HANGING_SIGN": 16, + "SPRUCE_CEILING_EDGES_HANGING_SIGN": 4, "SPRUCE_DOOR": 32, "SPRUCE_FENCE": 1, "SPRUCE_FENCE_GATE": 16, @@ -668,6 +697,7 @@ "SPRUCE_SLAB": 3, "SPRUCE_STAIRS": 8, "SPRUCE_TRAPDOOR": 16, + "SPRUCE_WALL_HANGING_SIGN": 4, "SPRUCE_WALL_SIGN": 4, "SPRUCE_WOOD": 6, "STAINED_CLAY": 16, @@ -711,6 +741,8 @@ "WALL_BANNER": 64, "WALL_CORAL_FAN": 40, "WARPED_BUTTON": 12, + "WARPED_CEILING_CENTER_HANGING_SIGN": 16, + "WARPED_CEILING_EDGES_HANGING_SIGN": 4, "WARPED_DOOR": 32, "WARPED_FENCE": 1, "WARPED_FENCE_GATE": 16, @@ -723,6 +755,7 @@ "WARPED_STAIRS": 8, "WARPED_STEM": 6, "WARPED_TRAPDOOR": 16, + "WARPED_WALL_HANGING_SIGN": 4, "WARPED_WALL_SIGN": 4, "WARPED_WART_BLOCK": 1, "WATER": 32, @@ -736,26 +769,41 @@ "WOOL": 16 }, "tiles": { + "ACACIA_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "ACACIA_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "ACACIA_SIGN": "pocketmine\\block\\tile\\Sign", + "ACACIA_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "ACACIA_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "BANNER": "pocketmine\\block\\tile\\Banner", "BARREL": "pocketmine\\block\\tile\\Barrel", "BEACON": "pocketmine\\block\\tile\\Beacon", "BED": "pocketmine\\block\\tile\\Bed", "BELL": "pocketmine\\block\\tile\\Bell", + "BIRCH_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "BIRCH_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "BIRCH_SIGN": "pocketmine\\block\\tile\\Sign", + "BIRCH_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "BIRCH_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "BLAST_FURNACE": "pocketmine\\block\\tile\\BlastFurnace", "BREWING_STAND": "pocketmine\\block\\tile\\BrewingStand", "CAMPFIRE": "pocketmine\\block\\tile\\Campfire", "CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "CHERRY_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "CHERRY_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "CHERRY_SIGN": "pocketmine\\block\\tile\\Sign", + "CHERRY_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "CHERRY_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "CHEST": "pocketmine\\block\\tile\\Chest", "CHISELED_BOOKSHELF": "pocketmine\\block\\tile\\ChiseledBookshelf", + "CRIMSON_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "CRIMSON_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "CRIMSON_SIGN": "pocketmine\\block\\tile\\Sign", + "CRIMSON_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "CRIMSON_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "DARK_OAK_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "DARK_OAK_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "DARK_OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "DARK_OAK_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "DARK_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "DAYLIGHT_SENSOR": "pocketmine\\block\\tile\\DaylightSensor", "DYED_SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", @@ -767,31 +815,49 @@ "HOPPER": "pocketmine\\block\\tile\\Hopper", "ITEM_FRAME": "pocketmine\\block\\tile\\ItemFrame", "JUKEBOX": "pocketmine\\block\\tile\\Jukebox", + "JUNGLE_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "JUNGLE_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "JUNGLE_SIGN": "pocketmine\\block\\tile\\Sign", + "JUNGLE_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "JUNGLE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "LAVA_CAULDRON": "pocketmine\\block\\tile\\Cauldron", "LECTERN": "pocketmine\\block\\tile\\Lectern", + "MANGROVE_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "MANGROVE_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "MANGROVE_SIGN": "pocketmine\\block\\tile\\Sign", + "MANGROVE_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "MANGROVE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "MOB_HEAD": "pocketmine\\block\\tile\\MobHead", "MONSTER_SPAWNER": "pocketmine\\block\\tile\\MonsterSpawner", "NOTE_BLOCK": "pocketmine\\block\\tile\\Note", + "OAK_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "OAK_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "OAK_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "OMINOUS_BANNER": "pocketmine\\block\\tile\\Banner", "OMINOUS_WALL_BANNER": "pocketmine\\block\\tile\\Banner", + "PALE_OAK_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "PALE_OAK_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "PALE_OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "PALE_OAK_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "PALE_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron", "REDSTONE_COMPARATOR": "pocketmine\\block\\tile\\Comparator", "SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", "SMOKER": "pocketmine\\block\\tile\\Smoker", "SOUL_CAMPFIRE": "pocketmine\\block\\tile\\Campfire", + "SPRUCE_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "SPRUCE_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "SPRUCE_SIGN": "pocketmine\\block\\tile\\Sign", + "SPRUCE_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "SPRUCE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "TRAPPED_CHEST": "pocketmine\\block\\tile\\Chest", "WALL_BANNER": "pocketmine\\block\\tile\\Banner", + "WARPED_CEILING_CENTER_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", + "WARPED_CEILING_EDGES_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "WARPED_SIGN": "pocketmine\\block\\tile\\Sign", + "WARPED_WALL_HANGING_SIGN": "pocketmine\\block\\tile\\HangingSign", "WARPED_WALL_SIGN": "pocketmine\\block\\tile\\Sign", "WATER_CAULDRON": "pocketmine\\block\\tile\\Cauldron" } From 0e498720bddc34407dc2b48526ad9c6783624c13 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 20:10:34 +0100 Subject: [PATCH 279/334] Regenerate phpstan-bugs baseline --- tests/phpstan/configs/phpstan-bugs.neon | 78 +++++++++++++++++++++---- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index cb92bf968..e67f5768e 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -18,78 +18,138 @@ parameters: count: 1 path: ../../../src/Server.php - - - message: '#^Method pocketmine\\block\\Block\:\:readStateFromWorld\(\) is marked as impure but does not have any side effects\.$#' - identifier: impureMethod.pure - count: 1 - path: ../../../src/block/Block.php - - message: '#^Method pocketmine\\block\\DoubleTallGrass\:\:traitGetDropsForIncompatibleTool\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue count: 1 path: ../../../src/block/DoubleTallGrass.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:ACACIA_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:ACACIA_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:BIRCH_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:BIRCH_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CHERRY_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CHERRY_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CRIMSON_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CRIMSON_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:DARK_OAK_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:DARK_OAK_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:JUNGLE_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:JUNGLE_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:MANGROVE_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:MANGROVE_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:OAK_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:OAK_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:PALE_OAK_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:PALE_OAK_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:SPRUCE_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:SPRUCE_SIGN\(\)\.$#' identifier: callable.nonNativeMethod count: 1 path: ../../../src/block/VanillaBlocks.php + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:WARPED_HANGING_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + - message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:WARPED_SIGN\(\)\.$#' identifier: callable.nonNativeMethod @@ -252,9 +312,3 @@ parameters: count: 2 path: ../../phpunit/promise/PromiseTest.php - - - message: '#^Strict comparison using \=\=\= between 0 and 0 will always evaluate to true\.$#' - identifier: identical.alwaysTrue - count: 1 - path: ../rules/UnsafeForeachArrayWithStringKeysRule.php - From 31f6f5d2522f088b78c57188de8ecb967df2cdb6 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Aug 2025 20:13:15 +0100 Subject: [PATCH 280/334] CS again --- src/block/tile/HangingSign.php | 2 -- src/block/tile/TileFactory.php | 1 - src/item/HangingSign.php | 1 - 3 files changed, 4 deletions(-) diff --git a/src/block/tile/HangingSign.php b/src/block/tile/HangingSign.php index 9bf088f47..a5be9ba5c 100644 --- a/src/block/tile/HangingSign.php +++ b/src/block/tile/HangingSign.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\nbt\tag\CompoundTag; - /** * @deprecated */ diff --git a/src/block/tile/TileFactory.php b/src/block/tile/TileFactory.php index 6cab78b2c..108483894 100644 --- a/src/block/tile/TileFactory.php +++ b/src/block/tile/TileFactory.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\block\HangingRoots; use pocketmine\data\SavedDataLoadingException; use pocketmine\math\Vector3; use pocketmine\nbt\NbtException; diff --git a/src/item/HangingSign.php b/src/item/HangingSign.php index e3e95d68a..7143637ba 100644 --- a/src/item/HangingSign.php +++ b/src/item/HangingSign.php @@ -27,7 +27,6 @@ use pocketmine\block\Block; use pocketmine\block\utils\SupportType; use pocketmine\math\Facing; use pocketmine\math\Vector3; -use pocketmine\player\Player; final class HangingSign extends Item{ From 5c0a109f1849a148731c8adf0e53d5b2a98d16f3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Aug 2025 01:48:29 +0100 Subject: [PATCH 281/334] Sign: Strip trailing newlines from text blobs fixes sign editor always putting the cursor on the last line when right-clicking to edit --- src/block/tile/Sign.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index 0bb21a6d3..aef83e3cc 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -37,6 +37,7 @@ use function array_slice; use function explode; use function implode; use function mb_scrub; +use function rtrim; use function sprintf; /** @@ -117,7 +118,7 @@ class Sign extends Spawnable{ protected function writeSaveData(CompoundTag $nbt) : void{ $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())) + ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) ->setByte(self::TAG_PERSIST_FORMATTING, 1) @@ -162,7 +163,7 @@ class Sign extends Spawnable{ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())) + ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) ->setByte(self::TAG_PERSIST_FORMATTING, 1) //TODO: not sure what this is used for From 4a2c7dc684f110cab3c37a75f1f79af4246c9ee7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Aug 2025 02:15:24 +0100 Subject: [PATCH 282/334] Apparently hanging signs are self supporting --- src/block/BaseSign.php | 19 +------------------ src/block/WallHangingSign.php | 14 ++++++++------ 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index b01157343..5a7df4663 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -103,27 +103,10 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return SupportType::NONE; } - /** - * @deprecated - */ abstract protected function getSupportingFace() : int; - /** - * @return int[] - */ - protected function getSupportingFaceOptions() : array{ - return [$this->getSupportingFace()]; - } - public function onNearbyBlockChange() : void{ - $foundSupport = false; - foreach($this->getSupportingFaceOptions() as $face){ - if($this->getSide($face)->getTypeId() !== BlockTypeIds::AIR){ - $foundSupport = true; - break; - } - } - if(!$foundSupport){ + if($this->getSide($this->getSupportingFace())->getTypeId() !== BlockTypeIds::AIR){ $this->position->getWorld()->useBreakOn($this->position); } } diff --git a/src/block/WallHangingSign.php b/src/block/WallHangingSign.php index c167036b1..2332f8e4f 100644 --- a/src/block/WallHangingSign.php +++ b/src/block/WallHangingSign.php @@ -27,6 +27,7 @@ use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Axis; +use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -39,12 +40,13 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{ return Facing::rotateY($this->facing, clockwise: true); } - protected function getSupportingFaceOptions() : array{ - //wall hanging signs can be supported from either end of the post - return [ - Facing::rotateY($this->facing, clockwise: true), - Facing::rotateY($this->facing, clockwise: false) - ]; + public function onNearbyBlockChange() : void{ + //NOOP - disable default self-destruct behaviour + } + + protected function recalculateCollisionBoxes() : array{ + //only the cross bar is collidable + return [AxisAlignedBB::one()->trim(Facing::DOWN, 7 / 8)->squash(Facing::axis($this->facing), 3 / 4)]; } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ From c54892311612b2786e964c9bc7f416fbdf4c2d64 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Aug 2025 02:16:38 +0100 Subject: [PATCH 283/334] ... --- src/block/BaseSign.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 5a7df4663..0efaa603c 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -106,7 +106,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ abstract protected function getSupportingFace() : int; public function onNearbyBlockChange() : void{ - if($this->getSide($this->getSupportingFace())->getTypeId() !== BlockTypeIds::AIR){ + if($this->getSide($this->getSupportingFace())->getTypeId() === BlockTypeIds::AIR){ $this->position->getWorld()->useBreakOn($this->position); } } From ec56d65bcc3b2696f759867d30c5e2f6d3d99082 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Aug 2025 02:17:45 +0100 Subject: [PATCH 284/334] Fix BC break in BaseBanner --- src/block/BaseBanner.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index 71a892c20..e8bf187ee 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -61,7 +61,12 @@ abstract class BaseBanner extends Transparent implements Colored{ return $this; } - abstract protected function getOminousVersion() : Block; + /** + * TODO: make this abstract in PM6 (BC break) + */ + protected function getOminousVersion() : Block{ + return VanillaBlocks::AIR(); + } public function writeStateToWorld() : void{ parent::writeStateToWorld(); From ac2c07c3fe7e90585cce4fc2b215aa0e20b687d0 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Mon, 25 Aug 2025 19:00:41 +0300 Subject: [PATCH 285/334] Added a space after hanging sign wood type (#6776) --- src/block/VanillaBlocks.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index bf9f7e5f5..e3b7a5f06 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -1444,8 +1444,8 @@ final class VanillaBlocks{ WoodType::CHERRY => VanillaItems::CHERRY_HANGING_SIGN(...), WoodType::PALE_OAK => VanillaItems::PALE_OAK_HANGING_SIGN(...), }; - self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . "Center Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); - self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . "Edges Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . " Center Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . " Edges Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); self::register($idName("wall_hanging_sign"), fn(BID $id) => new WallHangingSign($id, $name . " Wall Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); } } From db54c481aa5248a01ae59435cf3a7d954f3e28f8 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Tue, 26 Aug 2025 01:27:17 +0300 Subject: [PATCH 286/334] Fixed hanging signs placement criteria (#6775) --- src/block/BlockTypeTags.php | 1 + src/block/CeilingCenterHangingSign.php | 9 ++++++++ src/block/CeilingEdgesHangingSign.php | 17 +++++++++++++++ src/block/VanillaBlocks.php | 7 ++++--- src/block/WallHangingSign.php | 22 ++++++++++++++++--- src/item/HangingSign.php | 29 ++++++++++++++++++++------ src/item/Item.php | 2 +- src/world/World.php | 2 +- 8 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/block/BlockTypeTags.php b/src/block/BlockTypeTags.php index 19a4825d9..531f3bcb3 100644 --- a/src/block/BlockTypeTags.php +++ b/src/block/BlockTypeTags.php @@ -31,4 +31,5 @@ final class BlockTypeTags{ public const SAND = self::PREFIX . "sand"; public const POTTABLE_PLANTS = self::PREFIX . "pottable"; public const FIRE = self::PREFIX . "fire"; + public const HANGING_SIGN = self::PREFIX . "hanging_sign"; } diff --git a/src/block/CeilingCenterHangingSign.php b/src/block/CeilingCenterHangingSign.php index 7078f38d8..1125de553 100644 --- a/src/block/CeilingCenterHangingSign.php +++ b/src/block/CeilingCenterHangingSign.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\SignLikeRotation; use pocketmine\block\utils\SignLikeRotationTrait; +use pocketmine\block\utils\StaticSupportTrait; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -33,6 +34,7 @@ use pocketmine\world\BlockTransaction; final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotation{ use SignLikeRotationTrait; + use StaticSupportTrait; protected function getSupportingFace() : int{ return Facing::UP; @@ -49,4 +51,11 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + private function canBeSupportedAt(Block $block) : bool{ + $supportBlock = $block->getSide(Facing::UP); + return + $supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() || + $supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN); + } } diff --git a/src/block/CeilingEdgesHangingSign.php b/src/block/CeilingEdgesHangingSign.php index 5dafe6932..3f7b6489b 100644 --- a/src/block/CeilingEdgesHangingSign.php +++ b/src/block/CeilingEdgesHangingSign.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; +use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -45,7 +46,23 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing if($player !== null){ $this->facing = Facing::opposite($player->getHorizontalFacing()); } + if(!$this->canBeSupportedAt($blockReplace)){ + return false; + } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + public function onNearbyBlockChange() : void{ + if(!$this->canBeSupportedAt($this)){ + $this->position->getWorld()->useBreakOn($this->position); + } + } + + private function canBeSupportedAt(Block $block) : bool{ + $supportBlock = $block->getSide(Facing::UP); + return + $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || + (($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing)); + } } diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index e3b7a5f06..36074c606 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -1391,6 +1391,7 @@ final class VanillaBlocks{ private static function registerWoodenBlocks() : void{ $planksBreakInfo = new Info(BreakInfo::axe(2.0, null, 15.0)); $signBreakInfo = new Info(BreakInfo::axe(1.0)); + $hangingSignBreakInfo = new Info(BreakInfo::axe(1.0), [Tags::HANGING_SIGN]); $logBreakInfo = new Info(BreakInfo::axe(2.0)); $woodenDoorBreakInfo = new Info(BreakInfo::axe(3.0, null, 15.0)); $woodenButtonBreakInfo = new Info(BreakInfo::axe(0.5)); @@ -1444,9 +1445,9 @@ final class VanillaBlocks{ WoodType::CHERRY => VanillaItems::CHERRY_HANGING_SIGN(...), WoodType::PALE_OAK => VanillaItems::PALE_OAK_HANGING_SIGN(...), }; - self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . " Center Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); - self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . " Edges Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); - self::register($idName("wall_hanging_sign"), fn(BID $id) => new WallHangingSign($id, $name . " Wall Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . " Center Hanging Sign", $hangingSignBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . " Edges Hanging Sign", $hangingSignBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); + self::register($idName("wall_hanging_sign"), fn(BID $id) => new WallHangingSign($id, $name . " Wall Hanging Sign", $hangingSignBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class); } } diff --git a/src/block/WallHangingSign.php b/src/block/WallHangingSign.php index 2332f8e4f..df959c720 100644 --- a/src/block/WallHangingSign.php +++ b/src/block/WallHangingSign.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacingTrait; +use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\Axis; use pocketmine\math\AxisAlignedBB; @@ -50,16 +51,31 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(Facing::axis($face) === Axis::Y){ + if($player === null){ + return false; + } + $attachFace = Facing::axis($face) === Axis::Y ? Facing::rotateY($player->getHorizontalFacing(), clockwise: true) : $face; + + if($this->canBeSupportedAt($blockReplace->getSide($attachFace), $attachFace)){ + $direction = $attachFace; + }elseif($this->canBeSupportedAt($blockReplace->getSide($opposite = Facing::opposite($attachFace)), $opposite)){ + $direction = $opposite; + }else{ return false; } - $this->facing = Facing::rotateY($face, clockwise: true); + $this->facing = Facing::rotateY(Facing::opposite($direction), clockwise: true); //the front should always face the player if possible - if($player !== null && $this->facing === $player->getHorizontalFacing()){ + if($this->facing === $player->getHorizontalFacing()){ $this->facing = Facing::opposite($this->facing); } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + private function canBeSupportedAt(Block $block, int $face) : bool{ + return + ($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) || + $block->getSupportType(Facing::opposite($face)) === SupportType::FULL; + } } diff --git a/src/item/HangingSign.php b/src/item/HangingSign.php index 7143637ba..5e3bd068a 100644 --- a/src/item/HangingSign.php +++ b/src/item/HangingSign.php @@ -24,9 +24,13 @@ declare(strict_types=1); namespace pocketmine\item; use pocketmine\block\Block; +use pocketmine\block\CeilingCenterHangingSign; +use pocketmine\block\CeilingEdgesHangingSign; use pocketmine\block\utils\SupportType; +use pocketmine\block\WallHangingSign; use pocketmine\math\Facing; use pocketmine\math\Vector3; +use pocketmine\player\Player; final class HangingSign extends Item{ @@ -40,13 +44,26 @@ final class HangingSign extends Item{ parent::__construct($identifier, $name); } - public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ + public function getPlacementBlock(?Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ //we don't verify valid placement conditions here, only decide which block to return - $result = $face === Facing::DOWN ? - $blockReplace->getSide(Facing::UP)->getSupportType(Facing::DOWN) === SupportType::CENTER ? - $this->centerPointCeilingVariant : - $this->edgePointCeilingVariant - : $this->wallVariant; + if($face === Facing::DOWN){ + if($player !== null && $player->isSneaking()){ + return clone $this->centerPointCeilingVariant; + } + + //we select the center variant when support is edge/wall sign with perpendicular player facing, + //support is a center sign itself, or support provides center support. + //otherwise use the edge variant. + $support = $blockReplace->getSide(Facing::UP); + $result = + (($support instanceof CeilingEdgesHangingSign || $support instanceof WallHangingSign) && ($player === null || Facing::axis($player->getHorizontalFacing()) !== Facing::axis($support->getFacing()))) || + $support instanceof CeilingCenterHangingSign || + $support->getSupportType(Facing::DOWN) === SupportType::CENTER ? + $this->centerPointCeilingVariant : + $this->edgePointCeilingVariant; + }else{ + $result = $this->wallVariant; + } return clone $result; } diff --git a/src/item/Item.php b/src/item/Item.php index 6786238b0..c286a2bff 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -485,7 +485,7 @@ class Item implements \JsonSerializable{ return $this->getBlock()->canBePlaced(); } - public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ + public function getPlacementBlock(?Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ return $this->getBlock($face); } diff --git a/src/world/World.php b/src/world/World.php index 4f2e222ca..bd79ae083 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2298,7 +2298,7 @@ class World implements ChunkManager{ if($item->isNull() || !$item->canBePlaced()){ return false; } - $hand = $item->getPlacementBlock($blockReplace, $blockClicked, $face, $clickVector); + $hand = $item->getPlacementBlock($player, $blockReplace, $blockClicked, $face, $clickVector); $hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z); if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){ From de234d1f382ca4b4c8bf489e016ae59013693910 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 26 Aug 2025 00:10:50 +0100 Subject: [PATCH 287/334] Improved placement logic --- src/item/HangingSign.php | 33 +++++++++------------------------ src/item/Item.php | 15 +++++++++++++-- src/world/World.php | 35 +++++++++++++++-------------------- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/src/item/HangingSign.php b/src/item/HangingSign.php index 5e3bd068a..a6752087a 100644 --- a/src/item/HangingSign.php +++ b/src/item/HangingSign.php @@ -24,13 +24,10 @@ declare(strict_types=1); namespace pocketmine\item; use pocketmine\block\Block; -use pocketmine\block\CeilingCenterHangingSign; -use pocketmine\block\CeilingEdgesHangingSign; -use pocketmine\block\utils\SupportType; -use pocketmine\block\WallHangingSign; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\world\BlockTransaction; final class HangingSign extends Item{ @@ -44,27 +41,15 @@ final class HangingSign extends Item{ parent::__construct($identifier, $name); } - public function getPlacementBlock(?Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ - //we don't verify valid placement conditions here, only decide which block to return - if($face === Facing::DOWN){ - if($player !== null && $player->isSneaking()){ - return clone $this->centerPointCeilingVariant; - } - - //we select the center variant when support is edge/wall sign with perpendicular player facing, - //support is a center sign itself, or support provides center support. - //otherwise use the edge variant. - $support = $blockReplace->getSide(Facing::UP); - $result = - (($support instanceof CeilingEdgesHangingSign || $support instanceof WallHangingSign) && ($player === null || Facing::axis($player->getHorizontalFacing()) !== Facing::axis($support->getFacing()))) || - $support instanceof CeilingCenterHangingSign || - $support->getSupportType(Facing::DOWN) === SupportType::CENTER ? - $this->centerPointCeilingVariant : - $this->edgePointCeilingVariant; - }else{ - $result = $this->wallVariant; + public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{ + if($face !== Facing::DOWN){ + return $this->tryPlacementTransaction(clone $this->wallVariant, $blockReplace, $blockClicked, $face, $clickVector, $player); } - return clone $result; + //ceiling edges sign has stricter placement conditions than ceiling center sign, so try that first + $ceilingEdgeTx = $player === null || !$player->isSneaking() ? + $this->tryPlacementTransaction(clone $this->edgePointCeilingVariant, $blockReplace, $blockClicked, $face, $clickVector, $player) : + null; + return $ceilingEdgeTx ?? $this->tryPlacementTransaction(clone $this->centerPointCeilingVariant, $blockReplace, $blockClicked, $face, $clickVector, $player); } public function getBlock(?int $clickedFace = null) : Block{ diff --git a/src/item/Item.php b/src/item/Item.php index c286a2bff..af7cab433 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -48,6 +48,7 @@ use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\TreeRoot; use pocketmine\player\Player; use pocketmine\utils\Utils; +use pocketmine\world\BlockTransaction; use pocketmine\world\format\io\GlobalItemDataHandlers; use function base64_decode; use function base64_encode; @@ -485,8 +486,18 @@ class Item implements \JsonSerializable{ return $this->getBlock()->canBePlaced(); } - public function getPlacementBlock(?Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ - return $this->getBlock($face); + protected final function tryPlacementTransaction(Block $blockPlace, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player) : ?BlockTransaction{ + $position = $blockReplace->getPosition(); + $blockPlace->position($position->getWorld(), $position->getFloorX(), $position->getFloorY(), $position->getFloorZ()); + if(!$blockPlace->canBePlacedAt($blockReplace, $clickVector, $face, $blockReplace->getPosition()->equals($blockClicked->getPosition()))){ + return null; + } + $transaction = new BlockTransaction($position->getWorld()); + return $blockPlace->place($transaction, $this, $blockReplace, $blockClicked, $face, $clickVector, $player) ? $transaction : null; + } + + public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{ + return $this->tryPlacementTransaction($this->getBlock($face), $blockReplace, $blockClicked, $face, $clickVector, $player); } /** diff --git a/src/world/World.php b/src/world/World.php index bd79ae083..236fd6e56 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2298,22 +2298,15 @@ class World implements ChunkManager{ if($item->isNull() || !$item->canBePlaced()){ return false; } - $hand = $item->getPlacementBlock($player, $blockReplace, $blockClicked, $face, $clickVector); - $hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z); - 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; - } - - $tx = new BlockTransaction($this); - if(!$hand->place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player)){ + //TODO: while passing Facing::UP 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. + $tx = + $item->getPlacementTransaction($blockClicked, $blockClicked, Facing::UP, $clickVector, $player) ?? + $item->getPlacementTransaction($blockReplace, $blockClicked, $face, $clickVector, $player); + if($tx === null){ + //no placement options available return false; } @@ -2357,6 +2350,7 @@ class World implements ChunkManager{ if(!$tx->apply()){ return false; } + $first = true; foreach($tx->getBlocks() as [$x, $y, $z, $_]){ $tile = $this->getTileAt($x, $y, $z); if($tile !== null){ @@ -2364,11 +2358,12 @@ class World implements ChunkManager{ $tile->copyDataFromItem($item); } - $this->getBlockAt($x, $y, $z)->onPostPlace(); - } - - if($playSound){ - $this->addSound($hand->getPosition(), new BlockPlaceSound($hand)); + $placed = $this->getBlockAt($x, $y, $z); + $placed->onPostPlace(); + if($first && $playSound){ + $this->addSound($placed->getPosition(), new BlockPlaceSound($placed)); + } + $first = false; } $item->pop(); From dd9030f1f5e81cc7e72b567db392f06323744635 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 28 Aug 2025 21:15:09 +0100 Subject: [PATCH 288/334] tools/generate-bedrock-data-from-packets: generate less noise for items if we have only a name (the majority case), we can just return the name directly instead of an object. this massively reduces the amount of noise in the files as seen in pmmp/BedrockData@f814036229a3a8a71450159ed2aaea17832f5abf --- .../CraftingManagerFromDataHelper.php | 2 +- src/crafting/json/ItemStackData.php | 15 ++++++++++++++- tests/phpstan/configs/phpstan-bugs.neon | 6 ++++++ tools/generate-bedrock-data-from-packets.php | 19 +++++++++++++------ 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 3df6bfb62..7c9cdd58b 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -162,7 +162,7 @@ final class CraftingManagerFromDataHelper{ } $mapper = new \JsonMapper(); - $mapper->bStrictObjectTypeChecking = true; + $mapper->bStrictObjectTypeChecking = false; //to allow hydrating ItemStackData - since this is only used for offline data it's safe $mapper->bExceptionOnUndefinedProperty = true; $mapper->bExceptionOnMissingData = true; diff --git a/src/crafting/json/ItemStackData.php b/src/crafting/json/ItemStackData.php index 032c7da7d..bf079e920 100644 --- a/src/crafting/json/ItemStackData.php +++ b/src/crafting/json/ItemStackData.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\crafting\json; -final class ItemStackData{ +use function count; + +final class ItemStackData implements \JsonSerializable{ /** @required */ public string $name; @@ -40,4 +42,15 @@ final class ItemStackData{ public function __construct(string $name){ $this->name = $name; } + + /** + * @return mixed[]|string + */ + public function jsonSerialize() : array|string{ + $result = (array) $this; + if(count($result) === 1 && isset($result["name"])){ + return $this->name; + } + return $result; + } } diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index e67f5768e..a80050020 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -168,6 +168,12 @@ parameters: count: 1 path: ../../../src/crafting/ShapedRecipe.php + - + message: '#^Offset ''name'' on \*NEVER\* in isset\(\) always exists and is not nullable\.$#' + identifier: isset.offset + count: 1 + path: ../../../src/crafting/json/ItemStackData.php + - message: '#^Property pocketmine\\crash\\CrashDumpData\:\:\$parameters \(list\\) does not accept array\.$#' identifier: assign.propertyType diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index b0aae57df..f40029365 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -209,11 +209,18 @@ class ParserPacketHandler extends PacketHandler{ return $data; } - /** - * @return mixed[] - */ - private static function objectToOrderedArray(object $object) : array{ - $result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object); + private static function objectToOrderedArray(object $object) : mixed{ + if($object instanceof \JsonSerializable){ + $result = $object->jsonSerialize(); + if(is_object($result)){ + $result = (array) $result; + }elseif(!is_array($result)){ + return $result; + } + }else{ + $result = (array) $object; + } + ksort($result, SORT_STRING); foreach(Utils::promoteKeys($result) as $property => $value){ @@ -280,7 +287,7 @@ class ParserPacketHandler extends PacketHandler{ file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); echo "updating item registry\n"; - $items = array_map(function(ItemTypeEntry $entry) : array{ + $items = array_map(function(ItemTypeEntry $entry) : mixed{ return self::objectToOrderedArray($entry); }, $packet->getEntries()); file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); From 2404d63b1fcc9a7b65a839b752dd52e69cb503ee Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 12:24:24 +0100 Subject: [PATCH 289/334] Ageable: added getMaxAge() we'll probably need this... --- src/block/utils/Ageable.php | 3 +++ src/block/utils/AgeableTrait.php | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/block/utils/Ageable.php b/src/block/utils/Ageable.php index 31fe3f459..180392ef3 100644 --- a/src/block/utils/Ageable.php +++ b/src/block/utils/Ageable.php @@ -27,7 +27,10 @@ interface Ageable{ public function getAge() : int; + public function getMaxAge() : int; + /** + * Must be in range 0 - getMaxAge() * @return $this */ public function setAge(int $age) : self; diff --git a/src/block/utils/AgeableTrait.php b/src/block/utils/AgeableTrait.php index dc1369c87..7a83ad66e 100644 --- a/src/block/utils/AgeableTrait.php +++ b/src/block/utils/AgeableTrait.php @@ -38,6 +38,8 @@ trait AgeableTrait{ public function getAge() : int{ return $this->age; } + public function getMaxAge() : int{ return self::MAX_AGE; } + /** * @return $this */ From 0be15a7403fb88f0fdc7887abd949057a87ddc1a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 12:33:04 +0100 Subject: [PATCH 290/334] Rename MultiFacing -> MultiAnyFacing to match the trait name --- src/block/GlowLichen.php | 4 ++-- src/block/ResinClump.php | 4 ++-- src/block/utils/{MultiFacing.php => MultiAnyFacing.php} | 2 +- .../bedrock/block/convert/property/CommonProperties.php | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/block/utils/{MultiFacing.php => MultiAnyFacing.php} (97%) diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index 6ad8d3748..c9dfad7f4 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -25,7 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\MultiAnySupportTrait; -use pocketmine\block\utils\MultiFacing; +use pocketmine\block\utils\MultiAnyFacing; use pocketmine\block\utils\SupportType; use pocketmine\item\Fertilizer; use pocketmine\item\Item; @@ -36,7 +36,7 @@ use pocketmine\world\World; use function count; use function shuffle; -class GlowLichen extends Transparent implements MultiFacing{ +class GlowLichen extends Transparent implements MultiAnyFacing{ use MultiAnySupportTrait; public function getLightLevel() : int{ diff --git a/src/block/ResinClump.php b/src/block/ResinClump.php index 2855a7cc3..56b42fa4a 100644 --- a/src/block/ResinClump.php +++ b/src/block/ResinClump.php @@ -24,10 +24,10 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\MultiAnySupportTrait; -use pocketmine\block\utils\MultiFacing; +use pocketmine\block\utils\MultiAnyFacing; use pocketmine\block\utils\SupportType; -final class ResinClump extends Transparent implements MultiFacing{ +final class ResinClump extends Transparent implements MultiAnyFacing{ use MultiAnySupportTrait; public function isSolid() : bool{ diff --git a/src/block/utils/MultiFacing.php b/src/block/utils/MultiAnyFacing.php similarity index 97% rename from src/block/utils/MultiFacing.php rename to src/block/utils/MultiAnyFacing.php index 9bdb3640d..dafe041e4 100644 --- a/src/block/utils/MultiFacing.php +++ b/src/block/utils/MultiAnyFacing.php @@ -25,7 +25,7 @@ namespace pocketmine\block\utils; use pocketmine\math\Facing; -interface MultiFacing{ +interface MultiAnyFacing{ /** * @return int[] diff --git a/src/data/bedrock/block/convert/property/CommonProperties.php b/src/data/bedrock/block/convert/property/CommonProperties.php index 1a3224270..35d7e21b6 100644 --- a/src/data/bedrock/block/convert/property/CommonProperties.php +++ b/src/data/bedrock/block/convert/property/CommonProperties.php @@ -46,7 +46,7 @@ use pocketmine\block\utils\CoralType; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\Lightable; -use pocketmine\block\utils\MultiFacing; +use pocketmine\block\utils\MultiAnyFacing; use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\SignLikeRotation; use pocketmine\block\utils\SlabType; @@ -80,7 +80,7 @@ final class CommonProperties{ /** @phpstan-var ValueFromIntProperty */ public readonly ValueFromIntProperty $anyFacingClassic; - /** @phpstan-var OptionSetFromIntProperty */ + /** @phpstan-var OptionSetFromIntProperty */ public readonly OptionSetFromIntProperty $multiFacingFlags; /** @phpstan-var IntProperty */ @@ -252,8 +252,8 @@ final class CommonProperties{ Facing::WEST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST, Facing::EAST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST ]), - fn(MultiFacing $b) => $b->getFaces(), - fn(MultiFacing $b, array $v) => $b->setFaces($v) + fn(MultiAnyFacing $b) => $b->getFaces(), + fn(MultiAnyFacing $b, array $v) => $b->setFaces($v) ); $this->floorSignLikeRotation = new IntProperty(StateNames::GROUND_SIGN_DIRECTION, 0, 15, fn(SignLikeRotation $b) => $b->getRotation(), fn(SignLikeRotation $b, int $v) => $b->setRotation($v)); From 48ba3342187a5ff0602fbc44b55c2958d25d8877 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 12:33:50 +0100 Subject: [PATCH 291/334] CS again :< --- src/block/GlowLichen.php | 2 +- src/block/ResinClump.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index c9dfad7f4..4588d647d 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; -use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\MultiAnyFacing; +use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Fertilizer; use pocketmine\item\Item; diff --git a/src/block/ResinClump.php b/src/block/ResinClump.php index 56b42fa4a..a56a386d4 100644 --- a/src/block/ResinClump.php +++ b/src/block/ResinClump.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\MultiAnyFacing; +use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\SupportType; final class ResinClump extends Transparent implements MultiAnyFacing{ From beaedc3627458804bb822f64e44ae37975e07f4f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 13:07:09 +0100 Subject: [PATCH 292/334] Tidy up in block properties aisle --- src/data/bedrock/block/convert/VanillaBlockMappings.php | 6 +++--- .../bedrock/block/convert/property/CommonProperties.php | 6 +++--- ...onSetFromIntProperty.php => ValueSetFromIntProperty.php} | 2 +- .../block/convert/property/WallConnectionTypeShim.php | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) rename src/data/bedrock/block/convert/property/{OptionSetFromIntProperty.php => ValueSetFromIntProperty.php} (98%) diff --git a/src/data/bedrock/block/convert/VanillaBlockMappings.php b/src/data/bedrock/block/convert/VanillaBlockMappings.php index fc1c6acb6..f339ce3ef 100644 --- a/src/data/bedrock/block/convert/VanillaBlockMappings.php +++ b/src/data/bedrock/block/convert/VanillaBlockMappings.php @@ -118,10 +118,10 @@ use pocketmine\data\bedrock\block\convert\property\EnumFromRawStateMap; use pocketmine\data\bedrock\block\convert\property\FlattenedCaveVinesVariant; use pocketmine\data\bedrock\block\convert\property\IntFromRawStateMap; use pocketmine\data\bedrock\block\convert\property\IntProperty; -use pocketmine\data\bedrock\block\convert\property\OptionSetFromIntProperty; use pocketmine\data\bedrock\block\convert\property\ValueFromIntProperty; use pocketmine\data\bedrock\block\convert\property\ValueFromStringProperty; use pocketmine\data\bedrock\block\convert\property\ValueMappings; +use pocketmine\data\bedrock\block\convert\property\ValueSetFromIntProperty; use pocketmine\math\Facing; use function array_map; use function min; @@ -581,7 +581,7 @@ final class VanillaBlockMappings{ $reg->mapModel(Model::create(Blocks::RESIN_CLUMP(), Ids::RESIN_CLUMP)->properties([$commonProperties->multiFacingFlags])); $reg->mapModel(Model::create(Blocks::VINES(), Ids::VINE)->properties([ - new OptionSetFromIntProperty( + new ValueSetFromIntProperty( StateNames::VINE_DIRECTION_BITS, IntFromRawStateMap::int([ Facing::NORTH => BlockLegacyMetadata::VINE_FLAG_NORTH, @@ -1267,7 +1267,7 @@ final class VanillaBlockMappings{ $reg->mapModel(Model::create(Blocks::CHAIN(), Ids::CHAIN)->properties([$commonProperties->pillarAxis])); $reg->mapModel(Model::create(Blocks::CHISELED_BOOKSHELF(), Ids::CHISELED_BOOKSHELF)->properties([ $commonProperties->horizontalFacingSWNE, - new OptionSetFromIntProperty( + new ValueSetFromIntProperty( StateNames::BOOKS_STORED, EnumFromRawStateMap::int(ChiseledBookshelfSlot::class, fn(ChiseledBookshelfSlot $case) => match($case){ //these are (currently) the same as the internal values, but it's best not to rely on those in case Mojang mess with the flags diff --git a/src/data/bedrock/block/convert/property/CommonProperties.php b/src/data/bedrock/block/convert/property/CommonProperties.php index 35d7e21b6..666637027 100644 --- a/src/data/bedrock/block/convert/property/CommonProperties.php +++ b/src/data/bedrock/block/convert/property/CommonProperties.php @@ -80,8 +80,8 @@ final class CommonProperties{ /** @phpstan-var ValueFromIntProperty */ public readonly ValueFromIntProperty $anyFacingClassic; - /** @phpstan-var OptionSetFromIntProperty */ - public readonly OptionSetFromIntProperty $multiFacingFlags; + /** @phpstan-var ValueSetFromIntProperty */ + public readonly ValueSetFromIntProperty $multiFacingFlags; /** @phpstan-var IntProperty */ public readonly IntProperty $floorSignLikeRotation; @@ -242,7 +242,7 @@ final class CommonProperties{ fn(AnyFacing $b, int $v) => $b->setFacing($v) ); - $this->multiFacingFlags = new OptionSetFromIntProperty( + $this->multiFacingFlags = new ValueSetFromIntProperty( StateNames::MULTI_FACE_DIRECTION_BITS, IntFromRawStateMap::int([ Facing::DOWN => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN, diff --git a/src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php b/src/data/bedrock/block/convert/property/ValueSetFromIntProperty.php similarity index 98% rename from src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php rename to src/data/bedrock/block/convert/property/ValueSetFromIntProperty.php index a91c681b8..89913d78b 100644 --- a/src/data/bedrock/block/convert/property/OptionSetFromIntProperty.php +++ b/src/data/bedrock/block/convert/property/ValueSetFromIntProperty.php @@ -32,7 +32,7 @@ use pocketmine\utils\AssumptionFailedError; * @phpstan-template TOption of int|\UnitEnum * @phpstan-implements Property */ -class OptionSetFromIntProperty implements Property{ +class ValueSetFromIntProperty implements Property{ private int $maxValue = 0; diff --git a/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php b/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php index bdd878b52..c7d4913cf 100644 --- a/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php +++ b/src/data/bedrock/block/convert/property/WallConnectionTypeShim.php @@ -30,6 +30,7 @@ use pocketmine\data\bedrock\block\BlockStateStringValues; * Internally we use null for no connections, but accepting this in the mapping code would require a fair amount of * extra complexity for this one case. This shim allows us to use the regular systems for handling walls. * TODO: get rid of this in PM6 and make the internal enum have a NONE case + * @internal */ enum WallConnectionTypeShim{ case NONE; From 8f7e16a9adc697b4dd9dea6a97b6f570961c831c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 14:11:50 +0100 Subject: [PATCH 293/334] Prepare 5.33.0 release --- changelogs/5.33.md | 124 ++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +- 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.33.md diff --git a/changelogs/5.33.md b/changelogs/5.33.md new file mode 100644 index 000000000..dc174cd76 --- /dev/null +++ b/changelogs/5.33.md @@ -0,0 +1,124 @@ +# 5.33.0 +Released 29th August 2025. + +This is a minor feature release containing internals improvements, API improvements and new gameplay features. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## Performance +- Worlds now remember when a chunk isn't generated. This reduces world I/O during world generation. +- `BlockObjectToStateSerializer` now creates fewer objects in certain cases. + +## Gameplay +- The following blocks have been added and/or are now properly supported: + - Hanging signs + - Illager banners + +## Tools +- `generate-bedrock-data-from-packets.php` now represents items as strings directly when only an ID is present. This significantly improves readability in `BedrockData` and reduces file sizes. + +## API +### `pocketmine\block` +- Added (and implemented) interfaces for many common block properties, to allow `instanceof` to be used: + - `Ageable`: for blocks with age, such as crops + - `AnyFacing`: for blocks which can face up, down, and horizontal directions (not the same as `HorizontalFacing`!) + - `Colored`: for blocks with 16 `DyeColor` variants + - `CoralMaterial`: for coral blocks, provides access to coral type and dead/alive + - `HorizontalFacing`: for blocks which can **only** face horizontal directions (not the same as `AnyFacing`!) + - `Lightable`: for light-source blocks which can be turned on and off, e.g. redstone lamp + - `MultiAnyFacing`: for blocks which can appear in multiple faces of the same block (including up, down, and horizontal faces), e.g. glow lichen + - `PillarRotation`: for blocks which can be oriented on an axis, e.g. logs + - `PoweredByRedstone`: for blocks which receive power from a redstone component, e.g. redstone lamp + - `SignLikeRotation`: for blocks which can be rotated 16 ways, e.g. signs, banners + - `WoodMaterial`: for blocks made from wood + - These interfaces have been implemented on many blocks. For the sake of brevity, they are not listed here, but you can expect to see them wherever the corresponding traits were used. +- The following classes have been added: + - `BaseOminousBanner` + - `CeilingCenterHangingSign` - both chains connected to the same point on the block above, can face 16 directions + - `CeilingEdgesHangingSign` - each chain connected to separate edges of the block above, can face 4 directions + - `OminousFloorBanner` - floor version of illager banner, can face 16 directions + - `OminousWallBanner` - wall version of illager banner, can face 4 directions + - `WallHangingSign` - hangs from a horizontal beam, can face 4 directions +- The following API methods have been added: + - `public ChiseledBookshelf->setSlots(list $slots) : $this` + - `public static VanillaBlocks` methods: + - `ACACIA_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `ACACIA_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `ACACIA_WALL_HANGING_SIGN() : WallHangingSign` + - `BIRCH_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `BIRCH_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `BIRCH_WALL_HANGING_SIGN() : WallHangingSign` + - `CHERRY_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `CHERRY_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `CHERRY_WALL_HANGING_SIGN() : WallHangingSign` + - `CRIMSON_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `CRIMSON_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `CRIMSON_WALL_HANGING_SIGN() : WallHangingSign` + - `DARK_OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `DARK_OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `DARK_OAK_WALL_HANGING_SIGN() : WallHangingSign` + - `JUNGLE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `JUNGLE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `JUNGLE_WALL_HANGING_SIGN() : WallHangingSign` + - `MANGROVE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `MANGROVE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `MANGROVE_WALL_HANGING_SIGN() : WallHangingSign` + - `OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `OAK_WALL_HANGING_SIGN() : WallHangingSign` + - `OMINOUS_FLOOR_BANNER() : OminousFloorBanner` + - `OMINOUS_WALL_BANNER() : OminousWallBanner` + - `PALE_OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `PALE_OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `PALE_OAK_WALL_HANGING_SIGN() : WallHangingSign` + - `SPRUCE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `SPRUCE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `SPRUCE_WALL_HANGING_SIGN() : WallHangingSign` + - `WARPED_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` + - `WARPED_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` + - `WARPED_WALL_HANGING_SIGN() : WallHangingSign` + +### `pocketmine\data\bedrock\block\convert` +- A new system for symmetric block serializers and deserializers has been introduced. + - This allows registering both a serializer and a deserializer with the same code, meaning way less code + - It also eliminates information duplication and potential inconsistencies, improving maintainability. + - A proper way to deal with flattened IDs (e.g. color blocks) has been introduced which _doesn't_ require hardcoding a giant mess of IDs + - This symmetric system covers 99% of blocks which have a 1:1 association between PM and vanilla blocks, or 1:N where IDs are flattened + - However, there are still some special cases which require registering separate serializers and deserializers (usually in cases where the PM implementation deviates from Mojang where Mojang's implementation sucks, such as hanging signs or big dripleaf). + - No backwards compatibility breaks are expected as a result of this change. However, it's recommended to migrate old code to this new system for maintainability. + - The following new classes have been added: + - `BlockSerializerDeserializerRegistrar` - handles unified registration of block serializers and deserializers, based on a provided block model + - `FlattenedIdModel` - represents a block with some properties baked into its Minecraft ID, e.g. coral or color blocks + - `Model` - represents a regular block with all properties in its `states` NBT + - `property\BoolFromStringProperty` - property mapping a bool value from a string NBT state + - `property\BoolProperty` + - `property\CommonProperties` - singleton containing commonly-used block property definitions and groups, e.g. facing, stair properties + - `property\EnumFromRawStateMap` - maps a raw NBT value to a PHP `enum` and vice versa + - `property\IntFromRawStateMap` - maps a raw NBT value to PM integer constants and vice versa + - `property\IntProperty` - an integer range property with a min, max, and optional offset + - `property\Property` - interface implemented by all property definitions accepted by a `Model` or `FlattenedIdModel` + - `property\StateMap` - interface implemented by classes accepted by mapping properties, e.g. `BoolFromStringProperty` + - `property\StringProperty` - interface implemented by properties whose raw outputs are strings - these can be used as ID components in `FlattenedIdModel` + - `property\ValueFromIntProperty` - property mapping a generic PM value from an int NBT state + - `property\ValueFromStringProperty` - same as above, but for a string NBT state + - `property\ValueSetFromIntProperty` - a property mapping an `int[]` or `enum[]` from a set of flags in NBT states + - `property\ValueMappings` - singleton containing commonly-needed `StateMap`s + - The following classes have been deprecated: + - `BlockStateDeserializerHelper` + - `BlockStateSerializerHelper` + - The following methods have been deprecated: + - All methods for decoding mapped property types in `BlockStateReader`, e.g. `readFacingDirection()` + - All methods for encoding mapped property types in `BlockStateWriter`, e.g. `writeFacingDirection()` + - All specific blocktype mapping functions in `BlockStateToObjectDeserializer`, e.g. `mapStairs()` + - All specific blocktype mapping functions in `BlockObjectToStateSerializer`, e.g. `mapStairs()` + +### `pocketmine\world` +- `World->setChunk()` now verifies that blockstate IDs in the provided chunk are all registered in `RuntimeBlockStateRegistry`. This should provide earlier detection for custom block registration errors by plugins. + +## Internals +- `BlockStateUpgrader` is now almost entirely independent from `BlockStateData`. It's anticipated that the upgrader library will be separable from the core in the future. +- `Block->readStateFromWorld()` is now triggered on chunk load for any position containing a tile. This should allow more effective updating of blocks with properties in their tiles. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index b3f37677b..dd8bed4b6 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.32.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.33.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 23d612f1af73e84f3dc26844d64df9565551fe54 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 18:49:08 +0100 Subject: [PATCH 294/334] Suggested additions --- changelogs/5.33.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelogs/5.33.md b/changelogs/5.33.md index dc174cd76..8c61e90bc 100644 --- a/changelogs/5.33.md +++ b/changelogs/5.33.md @@ -81,6 +81,7 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - `WARPED_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign` - `WARPED_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign` - `WARPED_WALL_HANGING_SIGN() : WallHangingSign` + - `public AgeableTrait->getMaxAge() : int` (included by all growable plant-like blocks, e.g. crops) ### `pocketmine\data\bedrock\block\convert` - A new system for symmetric block serializers and deserializers has been introduced. @@ -116,6 +117,10 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - All specific blocktype mapping functions in `BlockStateToObjectDeserializer`, e.g. `mapStairs()` - All specific blocktype mapping functions in `BlockObjectToStateSerializer`, e.g. `mapStairs()` +### `pocketmine\item` +- The following hooks have been added: + - `public Item->getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction` - allows more complex logic for itemblocks to place blocks, without duplicating their placement conditions (used for hanging signs) + ### `pocketmine\world` - `World->setChunk()` now verifies that blockstate IDs in the provided chunk are all registered in `RuntimeBlockStateRegistry`. This should provide earlier detection for custom block registration errors by plugins. From f1b1e1977edd1c1ae7560ef12d7f4ce9e4df19bd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 29 Aug 2025 20:37:29 +0100 Subject: [PATCH 295/334] Harden validation for server auth block breaking --- src/network/mcpe/handler/InGamePacketHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index b5990d38f..6aae60745 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -719,6 +719,7 @@ class InGamePacketHandler extends PacketHandler{ case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK: //TODO: do we need to handle this? case PlayerAction::PREDICT_DESTROY_BLOCK: + self::validateFacing($face); if(!$this->player->breakBlock($pos)){ $this->syncBlocksNearby($pos, $face); } From 95679b5a297fe55291d8a3cfbda81879b40e0b23 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 30 Aug 2025 18:36:35 +0100 Subject: [PATCH 296/334] Update BedrockData and some transient deps --- composer.json | 2 +- composer.lock | 99 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/composer.json b/composer.json index e66269b40..17271955e 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", - "pocketmine/bedrock-data": "~5.3.0+bedrock-1.21.100", + "pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100", "pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100", "pocketmine/bedrock-protocol": "~40.0.0+bedrock-1.21.100", "pocketmine/binaryutils": "^0.2.1", diff --git a/composer.lock b/composer.lock index 9a69e6e14..330f002d7 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": "402ad5667b1e636a8ec6acf2f1b5f055", + "content-hash": "27fee330bdcb6ea2373c57cdfb3bc22f", "packages": [ { "name": "adhocore/json-comment", @@ -204,16 +204,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "5.3.0+bedrock-1.21.100", + "version": "6.0.0+bedrock-1.21.100", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "5279e76261df158d5af187cfaafc1618c1da9e3f" + "reference": "edc0d829175e5e1e57c87001acfd03526c63fd34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/5279e76261df158d5af187cfaafc1618c1da9e3f", - "reference": "5279e76261df158d5af187cfaafc1618c1da9e3f", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/edc0d829175e5e1e57c87001acfd03526c63fd34", + "reference": "edc0d829175e5e1e57c87001acfd03526c63fd34", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/5.3.0+bedrock-1.21.100" + "source": "https://github.com/pmmp/BedrockData/tree/6.0.0+bedrock-1.21.100" }, - "time": "2025-07-30T22:07:56+00:00" + "time": "2025-08-30T17:25:42+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -896,16 +896,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.13", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" + "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8", + "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8", "shasum": "" }, "require": { @@ -942,7 +942,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.13" + "source": "https://github.com/symfony/filesystem/tree/v6.4.24" }, "funding": [ { @@ -953,27 +953,31 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2025-07-10T08:14:14+00:00" } ], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -1012,7 +1016,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -1020,20 +1024,20 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -1052,7 +1056,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -1076,9 +1080,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -1680,16 +1684,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.47", + "version": "10.5.53", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3" + "reference": "32768472ebfb6969e6c7399f1c7b09009723f653" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", - "reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32768472ebfb6969e6c7399f1c7b09009723f653", + "reference": "32768472ebfb6969e6c7399f1c7b09009723f653", "shasum": "" }, "require": { @@ -1699,7 +1703,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -1716,7 +1720,7 @@ "sebastian/exporter": "^5.1.2", "sebastian/global-state": "^6.0.2", "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", "sebastian/type": "^4.0.0", "sebastian/version": "^4.0.1" }, @@ -1761,7 +1765,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.5.47" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.53" }, "funding": [ { @@ -1785,7 +1789,7 @@ "type": "tidelift" } ], - "time": "2025-06-20T11:29:11+00:00" + "time": "2025-08-20T14:40:06+00:00" }, { "name": "sebastian/cli-parser", @@ -2533,23 +2537,23 @@ }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -2584,15 +2588,28 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { "name": "sebastian/type", From 5c363965f0950516b4967b7fc00cb1f6edf4fbe6 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 30 Aug 2025 18:43:18 +0100 Subject: [PATCH 297/334] Fix build date we really need a better way to deal with this --- changelogs/5.33.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/5.33.md b/changelogs/5.33.md index 8c61e90bc..45f03f521 100644 --- a/changelogs/5.33.md +++ b/changelogs/5.33.md @@ -1,5 +1,5 @@ # 5.33.0 -Released 29th August 2025. +Released 30th August 2025. This is a minor feature release containing internals improvements, API improvements and new gameplay features. From f673159471b21097f15c6abb45e4022912496c7c Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:46:07 +0000 Subject: [PATCH 298/334] 5.33.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/17346780638 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index dd8bed4b6..1d4179f7d 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.33.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.33.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 9eee1a9a6ec2cf1ac33f57b88bbac1ec04c36bfa Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 31 Aug 2025 03:22:58 +0100 Subject: [PATCH 299/334] Banner: don't bail on missing type tags we didn't set these prior to 5.33.0, so these won't be present on older worlds. --- src/block/tile/Banner.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/tile/Banner.php b/src/block/tile/Banner.php index 97ffe630d..b6a143fe7 100644 --- a/src/block/tile/Banner.php +++ b/src/block/tile/Banner.php @@ -82,7 +82,7 @@ class Banner extends Spawnable{ } } - $this->type = $nbt->getInt(self::TAG_TYPE); + $this->type = $nbt->getInt(self::TAG_TYPE, self::TYPE_NORMAL); } protected function writeSaveData(CompoundTag $nbt) : void{ From a540de1e3cd2edbbebf5e7d71057f060dea9d54e Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 31 Aug 2025 03:27:21 +0100 Subject: [PATCH 300/334] Prepare 5.33.1 patch release (#6784) --- changelogs/5.33.md | 6 ++++++ src/VersionInfo.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelogs/5.33.md b/changelogs/5.33.md index 45f03f521..fe2e15a07 100644 --- a/changelogs/5.33.md +++ b/changelogs/5.33.md @@ -127,3 +127,9 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if ## Internals - `BlockStateUpgrader` is now almost entirely independent from `BlockStateData`. It's anticipated that the upgrader library will be separable from the core in the future. - `Block->readStateFromWorld()` is now triggered on chunk load for any position containing a tile. This should allow more effective updating of blocks with properties in their tiles. + +# 5.33.1 +Released 31st August 2025. + +## Fixes +- Fixed banners placed in prior versions getting their tiles deleted (due to missing `Type` tags). diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 1d4179f7d..4592b3d14 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.33.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 09cc76ae2b49f1fe3ab0253e6e987fb82bd0a08f Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 02:28:17 +0000 Subject: [PATCH 301/334] 5.33.2 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/17351431906 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 4592b3d14..855e78aaa 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.33.1"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.33.2"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From c931437a303de526be3dc28858f7ed5dbefc7503 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 31 Aug 2025 21:45:55 +0100 Subject: [PATCH 302/334] InventoryTransaction: Remove action shuffling we used to have this to prevent dependence on client ordering, and make ordering consistently not work. However, since the introduction of the ItemStackRequest protocol, we don't expect to see client actions in the wrong order anymore, so this shouldn't be needed anymore. closes #6701 --- .../transaction/InventoryTransaction.php | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 6e010c7b8..99fa6a097 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -95,10 +95,13 @@ class InventoryTransaction{ } /** - * Returns an **unordered** set of actions involved in this transaction. + * Returns a set of actions involved in this transaction. * - * WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO - * significance and should not be relied on. + * Note: This system is designed to care only about item balances. While you can usually assume that the actions + * are provided in the correct order, it will still successfully complete transactions whose actions are provided in + * the "wrong" order, as long as the transaction balances. + * For example, you may see that an action setting a slot to a particular item may appear before the action that + * removes that item from its original slot. While unintuitive, this is still valid. * * @return InventoryAction[] * @phpstan-return array @@ -119,19 +122,6 @@ class InventoryTransaction{ } } - /** - * Shuffles actions in the transaction to prevent external things relying on any implicit ordering. - */ - private function shuffleActions() : void{ - $keys = array_keys($this->actions); - shuffle($keys); - $actions = []; - foreach($keys as $key){ - $actions[$key] = $this->actions[$key]; - } - $this->actions = $actions; - } - /** * @param Item[] $needItems * @param Item[] $haveItems @@ -308,8 +298,6 @@ class InventoryTransaction{ throw new TransactionValidationException("Transaction has already been executed"); } - $this->shuffleActions(); - $this->validate(); if(!$this->callExecuteEvent()){ From e569cc3275a88a454ce5b01412df50a03dd6c7bd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 31 Aug 2025 21:47:49 +0100 Subject: [PATCH 303/334] stfu --- src/inventory/transaction/InventoryTransaction.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 99fa6a097..f8624030c 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -30,13 +30,11 @@ use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\item\Item; use pocketmine\player\Player; use pocketmine\utils\Utils; -use function array_keys; use function array_values; use function assert; use function count; use function get_class; use function min; -use function shuffle; use function spl_object_hash; use function spl_object_id; From b2d0be5b75050082988d5b106af799256673c784 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 1 Sep 2025 17:15:29 +0100 Subject: [PATCH 304/334] Support editing the back side of signs (#6774) * Deprecate BaseSign get/set/updateText(), add get/set/updateFaceText() which accepts true/false for front/back * add isFrontFace() to SignChangeEvent * add optional frontFace to Player::openSignEditor() * add BaseSign::getFacingDegrees() and getHitboxCenter() which need to be implemented by subclasses --- src/block/BaseSign.php | 92 ++++++++++++++++--- src/block/CeilingCenterHangingSign.php | 4 + src/block/CeilingEdgesHangingSign.php | 11 +++ src/block/FloorSign.php | 4 + src/block/WallHangingSign.php | 11 +++ src/block/WallSign.php | 22 +++++ src/block/tile/Sign.php | 53 +++++------ src/event/block/SignChangeEvent.php | 10 +- .../mcpe/handler/InGamePacketHandler.php | 63 ++++++++----- src/player/Player.php | 5 +- 10 files changed, 203 insertions(+), 72 deletions(-) diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 0efaa603c..8c324918b 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -41,14 +41,19 @@ use pocketmine\utils\TextFormat; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\DyeUseSound; use pocketmine\world\sound\InkSacUseSound; +use function abs; use function array_map; use function assert; +use function atan2; +use function fmod; +use function rad2deg; use function strlen; abstract class BaseSign extends Transparent implements WoodMaterial{ use WoodTypeTrait; - protected SignText $text; + protected SignText $text; //TODO: rename this (BC break) + protected SignText $backText; private bool $waxed = false; protected ?int $editorEntityRuntimeId = null; @@ -63,6 +68,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ $this->woodType = $woodType; parent::__construct($idInfo, $name, $typeInfo); $this->text = new SignText(); + $this->backText = new SignText(); $this->asItemCallback = $asItemCallback; } @@ -71,6 +77,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileSign){ $this->text = $tile->getText(); + $this->backText = $tile->getBackText(); $this->waxed = $tile->isWaxed(); $this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId(); } @@ -83,6 +90,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ $tile = $this->position->getWorld()->getTile($this->position); assert($tile instanceof TileSign); $tile->setText($this->text); + $tile->setBackText($this->backText); $tile->setWaxed($this->waxed); $tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId); } @@ -127,11 +135,11 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ } } - private function doSignChange(SignText $newText, Player $player, Item $item) : bool{ - $ev = new SignChangeEvent($this, $player, $newText); + private function doSignChange(SignText $newText, Player $player, Item $item, bool $frontFace) : bool{ + $ev = new SignChangeEvent($this, $player, $newText, $frontFace); $ev->call(); if(!$ev->isCancelled()){ - $this->text = $ev->getNewText(); + $this->setFaceText($frontFace, $ev->getNewText()); $this->position->getWorld()->setBlock($this->position, $this); $item->pop(); return true; @@ -140,8 +148,9 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return false; } - private function changeSignGlowingState(bool $glowing, Player $player, Item $item) : bool{ - if($this->text->isGlowing() !== $glowing && $this->doSignChange(new SignText($this->text->getLines(), $this->text->getBaseColor(), $glowing), $player, $item)){ + private function changeSignGlowingState(bool $glowing, Player $player, Item $item, bool $frontFace) : bool{ + $text = $this->getFaceText($frontFace); + if($text->isGlowing() !== $glowing && $this->doSignChange(new SignText($text->getLines(), $text->getBaseColor(), $glowing), $player, $item, $frontFace)){ $this->position->getWorld()->addSound($this->position, new InkSacUseSound()); return true; } @@ -168,6 +177,8 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return true; } + $frontFace = $this->interactsFront($this->getHitboxCenter(), $player->getPosition(), $this->getFacingDegrees()); + $dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){ ItemTypeIds::BONE_MEAL => DyeColor::WHITE, ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE, @@ -176,40 +187,82 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ }; if($dyeColor !== null){ $color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue(); + $text = $this->getFaceText($frontFace); if( - $color->toARGB() !== $this->text->getBaseColor()->toARGB() && - $this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item) + $color->toARGB() !== $text->getBaseColor()->toARGB() && + $this->doSignChange(new SignText($text->getLines(), $color, $text->isGlowing()), $player, $item, $frontFace) ){ $this->position->getWorld()->addSound($this->position, new DyeUseSound()); return true; } }elseif(match($item->getTypeId()){ - ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item), - ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item), + ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item, $frontFace), + ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item, $frontFace), ItemTypeIds::HONEYCOMB => $this->wax($player, $item), default => false }){ return true; } - $player->openSignEditor($this->position); + $player->openSignEditor($this->position, $frontFace); return true; } + private function interactsFront(Vector3 $hitboxCenter, Vector3 $playerPosition, float $signFacingDegrees) : bool{ + $playerCenterDiffX = $playerPosition->x - $hitboxCenter->x; + $playerCenterDiffZ = $playerPosition->z - $hitboxCenter->z; + + $f1 = rad2deg(atan2($playerCenterDiffZ, $playerCenterDiffX)) - 90.0; + + $rotationDiff = $signFacingDegrees - $f1; + $rotation = fmod($rotationDiff + 180.0, 360.0) - 180.0; // Normalize to [-180, 180] + return abs($rotation) <= 90.0; + } + + /** + * Returns the center of the sign's hitbox. Used to decide which face of the sign to open when a player interacts. + */ + protected function getHitboxCenter() : Vector3{ + return $this->position->add(0.5, 0.5, 0.5); + } + + /** + * TODO: make this abstract (BC break) + */ + protected function getFacingDegrees() : float{ + return 0; + } + /** * Returns an object containing information about the sign text. + * @deprecated + * @see self::getFaceText() */ public function getText() : SignText{ return $this->text; } - /** @return $this */ + /** + * @deprecated + * @see self::setFaceText() + * @return $this + */ public function setText(SignText $text) : self{ $this->text = $text; return $this; } + public function getFaceText(bool $frontFace) : SignText{ + return $frontFace ? $this->text : $this->backText; + } + + /** @return $this */ + public function setFaceText(bool $frontFace, SignText $text) : self{ + $frontFace ? $this->text = $text : $this->backText = $text; + return $this; + } + /** * Returns whether the sign has been waxed using a honeycomb. If true, the sign cannot be edited by a player. */ @@ -234,13 +287,21 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return $this; } + /** + * @deprecated + * @see self::updateFaceText() + */ + public function updateText(Player $author, SignText $text) : bool{ + return $this->updateFaceText($author, true, $text); + } + /** * Called by the player controller (network session) to update the sign text, firing events as appropriate. * * @return bool if the sign update was successful. * @throws \UnexpectedValueException if the text payload is too large */ - public function updateText(Player $author, SignText $text) : bool{ + public function updateFaceText(Player $author, bool $frontFace, SignText $text) : bool{ $size = 0; foreach($text->getLines() as $line){ $size += strlen($line); @@ -248,15 +309,16 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ if($size > 1000){ throw new \UnexpectedValueException($author->getName() . " tried to write $size bytes of text onto a sign (bigger than max 1000)"); } + $oldText = $this->getFaceText($frontFace); $ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{ return TextFormat::clean($line, false); - }, $text->getLines()), $this->text->getBaseColor(), $this->text->isGlowing())); + }, $text->getLines()), $oldText->getBaseColor(), $oldText->isGlowing()), $frontFace); if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){ $ev->cancel(); } $ev->call(); if(!$ev->isCancelled()){ - $this->setText($ev->getNewText()); + $this->setFaceText($frontFace, $ev->getNewText()); $this->setEditorEntityRuntimeId(null); $this->position->getWorld()->setBlock($this->position, $this); return true; diff --git a/src/block/CeilingCenterHangingSign.php b/src/block/CeilingCenterHangingSign.php index 1125de553..df6b4e229 100644 --- a/src/block/CeilingCenterHangingSign.php +++ b/src/block/CeilingCenterHangingSign.php @@ -58,4 +58,8 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio $supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() || $supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN); } + + protected function getFacingDegrees() : float{ + return $this->rotation * 22.5; + } } diff --git a/src/block/CeilingEdgesHangingSign.php b/src/block/CeilingEdgesHangingSign.php index 3f7b6489b..503915fa0 100644 --- a/src/block/CeilingEdgesHangingSign.php +++ b/src/block/CeilingEdgesHangingSign.php @@ -30,6 +30,7 @@ use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{ @@ -65,4 +66,14 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || (($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing)); } + + protected function getFacingDegrees() : float{ + return match($this->facing){ + Facing::SOUTH => 0, + Facing::WEST => 90, + Facing::NORTH => 180, + Facing::EAST => 270, + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + } } diff --git a/src/block/FloorSign.php b/src/block/FloorSign.php index 94e51ffe8..08bc480d8 100644 --- a/src/block/FloorSign.php +++ b/src/block/FloorSign.php @@ -48,4 +48,8 @@ final class FloorSign extends BaseSign implements SignLikeRotation{ } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + protected function getFacingDegrees() : float{ + return $this->rotation * 22.5; + } } diff --git a/src/block/WallHangingSign.php b/src/block/WallHangingSign.php index df959c720..6d4cfb95e 100644 --- a/src/block/WallHangingSign.php +++ b/src/block/WallHangingSign.php @@ -32,6 +32,7 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; final class WallHangingSign extends BaseSign implements HorizontalFacing{ @@ -78,4 +79,14 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{ ($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) || $block->getSupportType(Facing::opposite($face)) === SupportType::FULL; } + + protected function getFacingDegrees() : float{ + return match($this->facing){ + Facing::SOUTH => 0, + Facing::WEST => 90, + Facing::NORTH => 180, + Facing::EAST => 270, + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + } } diff --git a/src/block/WallSign.php b/src/block/WallSign.php index c6b42608d..40e1ba458 100644 --- a/src/block/WallSign.php +++ b/src/block/WallSign.php @@ -30,6 +30,7 @@ use pocketmine\math\Axis; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; final class WallSign extends BaseSign implements HorizontalFacing{ @@ -46,4 +47,25 @@ final class WallSign extends BaseSign implements HorizontalFacing{ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + protected function getHitboxCenter() : Vector3{ + [$xOffset, $zOffset] = match($this->facing){ + Facing::NORTH => [0, 15 / 16], + Facing::SOUTH => [0, 1 / 16], + Facing::WEST => [15 / 16, 0], + Facing::EAST => [1 / 16, 0], + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + return $this->position->add($xOffset, 0.5, $zOffset); + } + + protected function getFacingDegrees() : float{ + return match($this->facing){ + Facing::SOUTH => 0, + Facing::WEST => 90, + Facing::NORTH => 180, + Facing::EAST => 270, + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + } } diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index aef83e3cc..3040e74c0 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -70,16 +70,18 @@ class Sign extends Spawnable{ } protected SignText $text; + protected SignText $backText; private bool $waxed = false; protected ?int $editorEntityRuntimeId = null; public function __construct(World $world, Vector3 $pos){ $this->text = new SignText(); + $this->backText = new SignText(); parent::__construct($world, $pos); } - private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : void{ + private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : SignText{ $baseColor = new Color(0, 0, 0); $glowingText = false; if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){ @@ -90,19 +92,27 @@ class Sign extends Spawnable{ //see https://bugs.mojang.com/browse/MCPE-117835 $glowingText = $glowingTextTag->getValue() !== 0; } - $this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText); + return SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText); + } + + private function writeTextTag(SignText $text) : CompoundTag{ + return CompoundTag::create() + ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $text->getLines()), "\n")) + ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($text->getBaseColor()->toARGB())) + ->setByte(self::TAG_GLOWING_TEXT, $text->isGlowing() ? 1 : 0) + ->setByte(self::TAG_PERSIST_FORMATTING, 1); } public function readSaveData(CompoundTag $nbt) : void{ $frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT); if($frontTextTag instanceof CompoundTag){ - $this->readTextTag($frontTextTag, true); + $this->text = $this->readTextTag($frontTextTag, true); }elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format $lightingBugResolved = false; if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){ $lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0; } - $this->readTextTag($nbt, $lightingBugResolved); + $this->text = $this->readTextTag($nbt, $lightingBugResolved); }else{ $text = []; for($i = 0; $i < SignText::LINE_COUNT; ++$i){ @@ -113,22 +123,14 @@ class Sign extends Spawnable{ } $this->text = new SignText($text); } + $backTextTag = $nbt->getTag(self::TAG_BACK_TEXT); + $this->backText = $backTextTag instanceof CompoundTag ? $this->readTextTag($backTextTag, true) : new SignText(); $this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0; } protected function writeSaveData(CompoundTag $nbt) : void{ - $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) - ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) - ); - $nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, "") - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00)) - ->setByte(self::TAG_GLOWING_TEXT, 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) - ); + $nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text)); + $nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText)); $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0); } @@ -141,6 +143,10 @@ class Sign extends Spawnable{ $this->text = $text; } + public function getBackText() : SignText{ return $this->backText; } + + public function setBackText(SignText $backText) : void{ $this->backText = $backText; } + public function isWaxed() : bool{ return $this->waxed; } public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; } @@ -162,19 +168,8 @@ class Sign extends Spawnable{ } protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ - $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) - ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) //TODO: not sure what this is used for - ); - //TODO: this is not yet used by the server, but needed to rollback any client-side changes to the back text - $nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, "") - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00)) - ->setByte(self::TAG_GLOWING_TEXT, 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) - ); + $nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text)); + $nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText)); $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0); $nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1); } diff --git a/src/event/block/SignChangeEvent.php b/src/event/block/SignChangeEvent.php index aed59a462..e337ebc36 100644 --- a/src/event/block/SignChangeEvent.php +++ b/src/event/block/SignChangeEvent.php @@ -35,11 +35,15 @@ use pocketmine\player\Player; class SignChangeEvent extends BlockEvent implements Cancellable{ use CancellableTrait; + private SignText $oldText; + public function __construct( private BaseSign $sign, private Player $player, - private SignText $text + private SignText $text, + private bool $frontFace = true ){ + $this->oldText = $this->sign->getFaceText($this->frontFace); parent::__construct($sign); } @@ -55,7 +59,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{ * Returns the text currently on the sign. */ public function getOldText() : SignText{ - return $this->sign->getText(); + return $this->oldText; } /** @@ -71,4 +75,6 @@ class SignChangeEvent extends BlockEvent implements Cancellable{ public function setNewText(SignText $text) : void{ $this->text = $text; } + + public function isFrontFace() : bool{ return $this->frontFace; } } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 6aae60745..dc8c3a0ba 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -756,6 +756,43 @@ class InGamePacketHandler extends PacketHandler{ return true; //this packet is useless } + /** + * @throws PacketHandlingException + */ + private function updateSignText(CompoundTag $nbt, string $tagName, bool $frontFace, BaseSign $block, Vector3 $pos) : bool{ + $textTag = $nbt->getTag($tagName); + if(!$textTag instanceof CompoundTag){ + throw new PacketHandlingException("Invalid tag type " . get_debug_type($textTag) . " for tag \"$tagName\" in sign update data"); + } + $textBlobTag = $textTag->getTag(Sign::TAG_TEXT_BLOB); + if(!$textBlobTag instanceof StringTag){ + throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data"); + } + + try{ + $text = SignText::fromBlob($textBlobTag->getValue()); + }catch(\InvalidArgumentException $e){ + throw PacketHandlingException::wrap($e, "Invalid sign text update"); + } + + $oldText = $block->getFaceText($frontFace); + if($text->getLines() === $oldText->getLines()){ + return false; + } + + try{ + if(!$block->updateFaceText($this->player, $frontFace, $text)){ + foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){ + $this->session->sendDataPacket($updatePacket); + } + return false; + } + return true; + }catch(\UnexpectedValueException $e){ + throw PacketHandlingException::wrap($e); + } + } + public function handleBlockActorData(BlockActorDataPacket $packet) : bool{ $pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()); if($pos->distanceSquared($this->player->getLocation()) > 10000){ @@ -767,29 +804,9 @@ class InGamePacketHandler extends PacketHandler{ if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit if($block instanceof BaseSign){ - $frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT); - if(!$frontTextTag instanceof CompoundTag){ - throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data"); - } - $textBlobTag = $frontTextTag->getTag(Sign::TAG_TEXT_BLOB); - if(!$textBlobTag instanceof StringTag){ - throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data"); - } - - try{ - $text = SignText::fromBlob($textBlobTag->getValue()); - }catch(\InvalidArgumentException $e){ - throw PacketHandlingException::wrap($e, "Invalid sign text update"); - } - - try{ - if(!$block->updateText($this->player, $text)){ - foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){ - $this->session->sendDataPacket($updatePacket); - } - } - }catch(\UnexpectedValueException $e){ - throw PacketHandlingException::wrap($e); + if(!$this->updateSignText($nbt, Sign::TAG_FRONT_TEXT, true, $block, $pos)){ + //only one side can be updated at a time + $this->updateSignText($nbt, Sign::TAG_BACK_TEXT, false, $block, $pos); } return true; diff --git a/src/player/Player.php b/src/player/Player.php index aa2d2af88..4468d929e 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -2838,13 +2838,12 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ /** * Opens the player's sign editor GUI for the sign at the given position. - * TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations) */ - public function openSignEditor(Vector3 $position) : void{ + public function openSignEditor(Vector3 $position, bool $frontFace = true) : void{ $block = $this->getWorld()->getBlock($position); if($block instanceof BaseSign){ $this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId())); - $this->getNetworkSession()->onOpenSignEditor($position, true); + $this->getNetworkSession()->onOpenSignEditor($position, $frontFace); }else{ throw new \InvalidArgumentException("Block at this position is not a sign"); } From 9a0a8a55b1c750d1443bd247811ead52fd3f63f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:17:41 +0000 Subject: [PATCH 305/334] Bump shivammathur/setup-php in the github-actions group (#6787) --- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release-pr-check.yml | 2 +- .github/workflows/draft-release.yml | 2 +- .github/workflows/main.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index e15134fdb..76780d633 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index cf6036968..9905d843d 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup PHP - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: 8.2 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 052635234..6edf05dfb 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -87,7 +87,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: ${{ env.PHP_VERSION }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9d5f282bb..4a4efc2e3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: 8.3 tools: php-cs-fixer:3.75 From fa5cc3301c8bf46f4d5b1bcb2d8944d517a3b880 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 2 Sep 2025 18:36:00 +0100 Subject: [PATCH 306/334] Strip unnecessary NBT from network items (#6790) TypeConverter: Strip unnecessary NBT from clientbound items Item containers like shulker boxes, or chests with block entity data obtained with ctrl+middle click, will often have extremely large NBT payloads. This problem gets exponentially worse in cases where it's possible to nest inventories, as in #4665. We can't easily address this at the core level, because tiles are not able to exist within items (due to position requirement) so we don't have a good way to avoid this useless NBT in the first place. However, we can strip it before the item is sent to the client, which dramatically reduces the network costs of such items, as well as removing any reason the client could have to send such enormous items to the server. A fully loaded shulker box with written books now takes only 5-6 KB on the wire instead of ~1 MB, which is a substantial improvement. Dealing with this in a less hacky way is currently blocked on #6147. --- src/network/mcpe/convert/TypeConverter.php | 90 +++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index e886b2b8b..a6520ce12 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pocketmine\block\tile\Container; use pocketmine\block\VanillaBlocks; use pocketmine\crafting\ExactRecipeIngredient; use pocketmine\crafting\MetaWildcardRecipeIngredient; @@ -31,10 +32,16 @@ use pocketmine\crafting\TagWildcardRecipeIngredient; use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeNames; +use pocketmine\data\SavedDataLoadingException; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\NBT; use pocketmine\nbt\NbtException; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\nbt\tag\ListTag; +use pocketmine\nbt\tag\Tag; +use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode; @@ -52,11 +59,13 @@ use pocketmine\utils\SingletonTrait; use pocketmine\world\format\io\GlobalBlockStateHandlers; use pocketmine\world\format\io\GlobalItemDataHandlers; use function get_class; +use function hash; class TypeConverter{ use SingletonTrait; private const PM_ID_TAG = "___Id___"; + private const PM_FULL_NBT_HASH_TAG = "___FullNbtHash___"; private const RECIPE_INPUT_WILDCARD_META = 0x7fff; @@ -197,6 +206,85 @@ class TypeConverter{ return new ExactRecipeIngredient($result); } + /** + * Strips unnecessary block actor NBT from items that have it. + * This tag can potentially be extremely large, and is not read by the client anyway. + */ + protected function stripBlockEntityNBT(CompoundTag $tag) : bool{ + if(($tag->getTag(Item::TAG_BLOCK_ENTITY_TAG)) !== null){ + //client doesn't use this tag, so it's fine to delete completely + $tag->removeTag(Item::TAG_BLOCK_ENTITY_TAG); + return true; + } + return false; + } + + /** + * Strips non-viewable data from shulker boxes and similar blocks + * The lore for shulker boxes only requires knowing the type & count of items and possibly custom name + * We don't need to, and should not allow, sending nested inventories across the network. + */ + protected function stripContainedItemNonVisualNBT(CompoundTag $tag) : bool{ + if( + ($blockEntityInventoryTag = $tag->getTag(Container::TAG_ITEMS)) !== null && + $blockEntityInventoryTag instanceof ListTag && + $blockEntityInventoryTag->getTagType() === NBT::TAG_Compound && + $blockEntityInventoryTag->count() > 0 + ){ + $stripped = new ListTag(); + + /** @var CompoundTag $itemTag */ + foreach($blockEntityInventoryTag as $itemTag){ + try{ + $containedItem = Item::nbtDeserialize($itemTag); + $customName = $containedItem->getCustomName(); + $containedItem->clearNamedTag(); + $containedItem->setCustomName($customName); + $stripped->push($containedItem->nbtSerialize()); + }catch(SavedDataLoadingException){ + continue; + } + } + $tag->setTag(Container::TAG_ITEMS, $stripped); + return true; + } + return false; + } + + /** + * Computes a hash of an item's server-side NBT. + * This is baked into an item's network NBT to make sure the client doesn't try to stack items with the same network + * NBT but different server-side NBT. + */ + protected function hashNBT(Tag $tag) : string{ + $encoded = (new LittleEndianNbtSerializer())->write(new TreeRoot($tag)); + return hash('sha256', $encoded, binary: true); + } + + /** + * TODO: HACK! + * Creates a copy of an item's NBT with non-viewable data stripped. + * This is a pretty yucky hack that's mainly needed because of inventories inside blockitems containing blockentity + * data. There isn't really a good way to deal with this due to the way tiles currently require a position, + * otherwise we could just keep a copy of the tile context and ask it for persistent vs network NBT as needed. + * Unfortunately, making this nice will require significant BC breaks, so this will have to do for now. + */ + protected function cleanupUnnecessaryItemNBT(CompoundTag $original) : CompoundTag{ + $tag = clone $original; + $anythingStripped = false; + foreach([ + $this->stripContainedItemNonVisualNBT($tag), + $this->stripBlockEntityNBT($tag) + ] as $stripped){ + $anythingStripped = $anythingStripped || $stripped; + } + + if($anythingStripped){ + $tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original)); + } + return $tag; + } + public function coreItemStackToNet(Item $itemStack) : ItemStack{ if($itemStack->isNull()){ return ItemStack::null(); @@ -205,7 +293,7 @@ class TypeConverter{ if($nbt->count() === 0){ $nbt = null; }else{ - $nbt = clone $nbt; + $nbt = $this->cleanupUnnecessaryItemNBT($nbt); } $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack); From 3999a1f9f439c16a81685ae461f20b1cf501461f Mon Sep 17 00:00:00 2001 From: Darya Markova <122279000+Dasciam@users.noreply.github.com> Date: Wed, 3 Sep 2025 09:53:32 +0300 Subject: [PATCH 307/334] Fix sneaking hitbox height (#6771) --- src/entity/Human.php | 4 ++++ src/entity/Living.php | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/entity/Human.php b/src/entity/Human.php index c94b76097..d2637a3f9 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -247,6 +247,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ return $this->enderInventory; } + public function getSneakOffset() : float{ + return 0.31; + } + /** * For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT. */ diff --git a/src/entity/Living.php b/src/entity/Living.php index 6d62c85d2..5ae1fd4e6 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -242,6 +242,10 @@ abstract class Living extends Entity{ $this->absorptionAttr->setValue($absorption); } + public function getSneakOffset() : float{ + return 0.0; + } + public function isSneaking() : bool{ return $this->sneaking; } @@ -292,7 +296,7 @@ abstract class Living extends Entity{ $width = $size->getWidth(); $this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale())); }elseif($this->isSneaking()){ - $this->setSize((new EntitySizeInfo(3 / 4 * $size->getHeight(), $size->getWidth(), 3 / 4 * $size->getEyeHeight()))->scale($this->getScale())); + $this->setSize((new EntitySizeInfo($size->getHeight() - $this->getSneakOffset(), $size->getWidth(), $size->getEyeHeight() - $this->getSneakOffset()))->scale($this->getScale())); }else{ $this->setSize($size->scale($this->getScale())); } From 186853691645f346b50ba0d7385ce1c332a7c546 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 4 Sep 2025 21:58:12 +0100 Subject: [PATCH 308/334] Add PHP 8.4 to test matrix --- .github/workflows/main.yml | 2 +- phpstan.neon.dist | 2 + .../executor/AsyncGeneratorExecutor.php | 2 +- .../configs/property-hook-sadness.neon | 61 +++++++++++++++++++ .../configs/reflection-class-sadness.neon | 31 ++++++++++ .../phpunit/event/HandlerListManagerTest.php | 14 ++--- 6 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 tests/phpstan/configs/property-hook-sadness.neon create mode 100644 tests/phpstan/configs/reflection-class-sadness.neon diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a4efc2e3..a7d6eb0fc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - php: ["8.1", "8.2", "8.3"] + php: ["8.1", "8.2", "8.3", "8.4"] uses: ./.github/workflows/main-php-matrix.yml with: diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 12c739f2f..07ca545c4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,8 @@ includes: - tests/phpstan/configs/impossible-generics.neon - tests/phpstan/configs/php-bugs.neon - tests/phpstan/configs/phpstan-bugs.neon + - tests/phpstan/configs/property-hook-sadness.neon + - tests/phpstan/configs/reflection-class-sadness.neon - tests/phpstan/configs/spl-fixed-array-sucks.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon diff --git a/src/world/generator/executor/AsyncGeneratorExecutor.php b/src/world/generator/executor/AsyncGeneratorExecutor.php index d19b6e661..007ffa5b2 100644 --- a/src/world/generator/executor/AsyncGeneratorExecutor.php +++ b/src/world/generator/executor/AsyncGeneratorExecutor.php @@ -48,7 +48,7 @@ final class AsyncGeneratorExecutor implements GeneratorExecutor{ \Logger $logger, private readonly AsyncPool $workerPool, private readonly GeneratorExecutorSetupParameters $setupParameters, - int $asyncContextId = null + ?int $asyncContextId = null ){ $this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor"); diff --git a/tests/phpstan/configs/property-hook-sadness.neon b/tests/phpstan/configs/property-hook-sadness.neon new file mode 100644 index 000000000..b3901993a --- /dev/null +++ b/tests/phpstan/configs/property-hook-sadness.neon @@ -0,0 +1,61 @@ +parameters: + ignoreErrors: + - + message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$enderInventory because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Human.php + + - + message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$hungerManager because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Human.php + + - + message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$inventory because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Human.php + + - + message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$offHandInventory because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Human.php + + - + message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$xpManager because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Human.php + + - + message: '#^Cannot unset property pocketmine\\entity\\Living\:\:\$armorInventory because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Living.php + + - + message: '#^Cannot unset property pocketmine\\entity\\Living\:\:\$effectManager because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/entity/Living.php + + - + message: '#^Cannot unset property pocketmine\\player\\Player\:\:\$craftingGrid because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/player/Player.php + + - + message: '#^Cannot unset property pocketmine\\player\\Player\:\:\$cursorInventory because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/player/Player.php + + - + message: '#^Cannot unset property pocketmine\\world\\format\\io\\leveldb\\LevelDB\:\:\$db because it might have hooks in a subclass\.$#' + identifier: unset.possiblyHookedProperty + count: 1 + path: ../../../src/world/format/io/leveldb/LevelDB.php diff --git a/tests/phpstan/configs/reflection-class-sadness.neon b/tests/phpstan/configs/reflection-class-sadness.neon new file mode 100644 index 000000000..477a97d41 --- /dev/null +++ b/tests/phpstan/configs/reflection-class-sadness.neon @@ -0,0 +1,31 @@ +parameters: + ignoreErrors: + - + message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\ in PHPDoc tag @param for parameter \$class is redundant, template type T of object of class ReflectionClass has the same variance\.$#' + identifier: generics.callSiteVarianceRedundant + count: 2 + path: ../../phpunit/event/HandlerListManagerTest.php + + - + message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\ in PHPDoc tag @param for parameter \$expect is redundant, template type T of object of class ReflectionClass has the same variance\.$#' + identifier: generics.callSiteVarianceRedundant + count: 1 + path: ../../phpunit/event/HandlerListManagerTest.php + + - + message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\ in PHPDoc tag @return is redundant, template type T of object of class ReflectionClass has the same variance\.$#' + identifier: generics.callSiteVarianceRedundant + count: 3 + path: ../../phpunit/event/HandlerListManagerTest.php + + - + message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\ in PHPDoc tag @var for property pocketmine\\event\\HandlerListManagerTest\:\:\$isValidFunc is redundant, template type T of object of class ReflectionClass has the same variance\.$#' + identifier: generics.callSiteVarianceRedundant + count: 1 + path: ../../phpunit/event/HandlerListManagerTest.php + + - + message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\ in PHPDoc tag @var for property pocketmine\\event\\HandlerListManagerTest\:\:\$resolveParentFunc is redundant, template type T of object of class ReflectionClass has the same variance\.$#' + identifier: generics.callSiteVarianceRedundant + count: 2 + path: ../../phpunit/event/HandlerListManagerTest.php diff --git a/tests/phpunit/event/HandlerListManagerTest.php b/tests/phpunit/event/HandlerListManagerTest.php index c61043dab..5b4f6babc 100644 --- a/tests/phpunit/event/HandlerListManagerTest.php +++ b/tests/phpunit/event/HandlerListManagerTest.php @@ -35,12 +35,12 @@ class HandlerListManagerTest extends TestCase{ /** * @var \Closure - * @phpstan-var \Closure(\ReflectionClass) : bool + * @phpstan-var \Closure(\ReflectionClass) : bool */ private $isValidFunc; /** * @var \Closure - * @phpstan-var \Closure(\ReflectionClass) : ?\ReflectionClass + * @phpstan-var \Closure(\ReflectionClass) : ?\ReflectionClass */ private $resolveParentFunc; @@ -53,7 +53,7 @@ class HandlerListManagerTest extends TestCase{ /** * @return \Generator|mixed[][] - * @phpstan-return \Generator, bool, string}, void, void> + * @phpstan-return \Generator, bool, string}, void, void> */ public static function isValidClassProvider() : \Generator{ yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"]; @@ -65,7 +65,7 @@ class HandlerListManagerTest extends TestCase{ /** * @dataProvider isValidClassProvider * - * @phpstan-param \ReflectionClass $class + * @phpstan-param \ReflectionClass $class */ public function testIsValidClass(\ReflectionClass $class, bool $isValid, string $reason) : void{ self::assertSame($isValid, ($this->isValidFunc)($class), $reason); @@ -73,7 +73,7 @@ class HandlerListManagerTest extends TestCase{ /** * @return \Generator|\ReflectionClass[][] - * @phpstan-return \Generator, \ReflectionClass|null}, void, void> + * @phpstan-return \Generator, \ReflectionClass|null}, void, void> */ public static function resolveParentClassProvider() : \Generator{ yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)]; @@ -85,8 +85,8 @@ class HandlerListManagerTest extends TestCase{ /** * @dataProvider resolveParentClassProvider * - * @phpstan-param \ReflectionClass $class - * @phpstan-param \ReflectionClass|null $expect + * @phpstan-param \ReflectionClass $class + * @phpstan-param \ReflectionClass|null $expect */ public function testResolveParentClass(\ReflectionClass $class, ?\ReflectionClass $expect) : void{ if($expect === null){ From 3411103e110032b3ce4f058dfcb9dd7dc9e55268 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 4 Sep 2025 23:29:55 +0100 Subject: [PATCH 309/334] Remove dead PHPStan ignores --- tests/phpstan/configs/actual-problems.neon | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 06abe7fee..570303843 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -1158,12 +1158,6 @@ parameters: count: 2 path: ../../../src/world/World.php - - - message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' - identifier: argument.type - count: 2 - path: ../../../src/world/World.php - - message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' identifier: argument.type @@ -1188,12 +1182,6 @@ parameters: count: 2 path: ../../../src/world/World.php - - - message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' - identifier: argument.type - count: 2 - path: ../../../src/world/World.php - - message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' identifier: argument.type @@ -1218,12 +1206,6 @@ parameters: count: 2 path: ../../../src/world/World.php - - - message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' - identifier: argument.type - count: 2 - path: ../../../src/world/World.php - - message: '#^Method pocketmine\\world\\biome\\BiomeRegistry\:\:getBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' identifier: return.type From bddab47ee8605d9d5d118e79d23db760c3384860 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:32:17 +0000 Subject: [PATCH 310/334] Bump build/php from `ce1b095` to `b839e52` (#6795) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index ce1b095a9..b839e5227 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit ce1b095a9c6f47dadc7b5812da4e469d52f272bc +Subproject commit b839e5227b9af858b633996f19af1e077a191870 From ce90835c7b33acc11f6c86f767e35fc634ca94ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:21:54 +0000 Subject: [PATCH 311/334] Bump build/php from `b839e52` to `1d9cda6` (#6796) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index b839e5227..1d9cda668 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit b839e5227b9af858b633996f19af1e077a191870 +Subproject commit 1d9cda668856390a64d33621a3da151b3507bec8 From 54e8ad2a9c397492b97a618f6af726a157123160 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 10 Sep 2025 16:21:29 +0100 Subject: [PATCH 312/334] Update BedrockProtocol --- composer.json | 2 +- composer.lock | 14 ++++++------- src/network/mcpe/auth/ProcessLoginTask.php | 12 +++++------ .../mcpe/handler/LoginPacketHandler.php | 20 +++++++++---------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index 17271955e..b4ca8de6b 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", "pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100", "pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100", - "pocketmine/bedrock-protocol": "~40.0.0+bedrock-1.21.100", + "pocketmine/bedrock-protocol": "~41.0.0+bedrock-1.21.100", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index 330f002d7..9af788d2b 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": "27fee330bdcb6ea2373c57cdfb3bc22f", + "content-hash": "7bf7cd54642c2d65ecdfdcb28f3a64a8", "packages": [ { "name": "adhocore/json-comment", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "40.0.0+bedrock-1.21.100", + "version": "41.0.0+bedrock-1.21.100", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca" + "reference": "920ac291fe1b0143b2ebc90b3374ddab0b8531bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca", - "reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/920ac291fe1b0143b2ebc90b3374ddab0b8531bf", + "reference": "920ac291fe1b0143b2ebc90b3374ddab0b8531bf", "shasum": "" }, "require": { @@ -296,9 +296,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/40.0.0+bedrock-1.21.100" + "source": "https://github.com/pmmp/BedrockProtocol/tree/41.0.0+bedrock-1.21.100" }, - "time": "2025-08-06T15:13:45+00:00" + "time": "2025-09-09T20:52:18+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/src/network/mcpe/auth/ProcessLoginTask.php b/src/network/mcpe/auth/ProcessLoginTask.php index b4c9e6d9c..218edc7a5 100644 --- a/src/network/mcpe/auth/ProcessLoginTask.php +++ b/src/network/mcpe/auth/ProcessLoginTask.php @@ -27,8 +27,8 @@ use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; use pocketmine\network\mcpe\JwtException; use pocketmine\network\mcpe\JwtUtils; -use pocketmine\network\mcpe\protocol\types\login\JwtChainLinkBody; -use pocketmine\network\mcpe\protocol\types\login\JwtHeader; +use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthJwtBody; +use pocketmine\network\mcpe\protocol\types\login\SelfSignedJwtHeader; use pocketmine\scheduler\AsyncTask; use pocketmine\thread\NonThreadSafeValue; use function base64_decode; @@ -128,8 +128,8 @@ class ProcessLoginTask extends AsyncTask{ $mapper->bEnforceMapType = false; try{ - /** @var JwtHeader $headers */ - $headers = $mapper->map($headersArray, new JwtHeader()); + /** @var SelfSignedJwtHeader $headers */ + $headers = $mapper->map($headersArray, new SelfSignedJwtHeader()); }catch(\JsonMapper_Exception $e){ throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e); } @@ -172,8 +172,8 @@ class ProcessLoginTask extends AsyncTask{ $mapper->bEnforceMapType = false; $mapper->bRemoveUndefinedAttributes = true; try{ - /** @var JwtChainLinkBody $claims */ - $claims = $mapper->map($claimsArray, new JwtChainLinkBody()); + /** @var LegacyAuthJwtBody $claims */ + $claims = $mapper->map($claimsArray, new LegacyAuthJwtBody()); }catch(\JsonMapper_Exception $e){ throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e); } diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index 5c467f2d4..c664c4b9f 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -32,12 +32,12 @@ use pocketmine\network\mcpe\JwtException; use pocketmine\network\mcpe\JwtUtils; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\LoginPacket; -use pocketmine\network\mcpe\protocol\types\login\AuthenticationData; use pocketmine\network\mcpe\protocol\types\login\AuthenticationInfo; use pocketmine\network\mcpe\protocol\types\login\AuthenticationType; -use pocketmine\network\mcpe\protocol\types\login\ClientData; -use pocketmine\network\mcpe\protocol\types\login\ClientDataToSkinDataHelper; -use pocketmine\network\mcpe\protocol\types\login\JwtChain; +use pocketmine\network\mcpe\protocol\types\login\clientdata\ClientData; +use pocketmine\network\mcpe\protocol\types\login\clientdata\ClientDataToSkinDataHelper; +use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthChain; +use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthIdentityData; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; use pocketmine\player\PlayerInfo; @@ -180,7 +180,7 @@ class LoginPacketHandler extends PacketHandler{ /** * @throws PacketHandlingException */ - protected function parseJwtChain(string $chainDataJwt) : JwtChain{ + protected function parseJwtChain(string $chainDataJwt) : LegacyAuthChain{ try{ $jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR); }catch(\JsonException $e){ @@ -195,7 +195,7 @@ class LoginPacketHandler extends PacketHandler{ $mapper->bExceptionOnUndefinedProperty = true; $mapper->bStrictObjectTypeChecking = true; try{ - $clientData = $mapper->map($jwtChainJson, new JwtChain()); + $clientData = $mapper->map($jwtChainJson, new LegacyAuthChain()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e); } @@ -205,8 +205,8 @@ class LoginPacketHandler extends PacketHandler{ /** * @throws PacketHandlingException */ - protected function fetchAuthData(JwtChain $chain) : AuthenticationData{ - /** @var AuthenticationData|null $extraData */ + protected function fetchAuthData(LegacyAuthChain $chain) : LegacyAuthIdentityData{ + /** @var LegacyAuthIdentityData|null $extraData */ $extraData = null; foreach($chain->chain as $jwt){ //validate every chain element @@ -229,8 +229,8 @@ class LoginPacketHandler extends PacketHandler{ $mapper->bExceptionOnUndefinedProperty = true; $mapper->bStrictObjectTypeChecking = true; try{ - /** @var AuthenticationData $extraData */ - $extraData = $mapper->map($claims["extraData"], new AuthenticationData()); + /** @var LegacyAuthIdentityData $extraData */ + $extraData = $mapper->map($claims["extraData"], new LegacyAuthIdentityData()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e); } From 4b4fc52cd7dc4790a28a4e507664eb2e7f19d559 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 10 Sep 2025 19:10:56 +0100 Subject: [PATCH 313/334] Updated Bitcoin donation address --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98f569346..352b628b6 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ PocketMine-MP is free, but it requires a lot of time and effort from unpaid volu You can support development using the following methods: - [Patreon](https://www.patreon.com/pocketminemp) -- Bitcoin (BTC): `171u8K9e4FtU6j3e5sqNoxKUgEw9qWQdRV` +- Bitcoin (BTC): `bc1q2v5ngyf8ugyd55kqa9ep35g2rv342ueqm6ks33` - Stellar Lumens (XLM): `GAAC5WZ33HCTE3BFJFZJXONMEIBNHFLBXM2HJVAZHXXPYA3HP5XPPS7T` Thanks for your support! From c854f2c76567d43b9a104f643c0d88b1e23d0033 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 10 Sep 2025 20:06:35 +0100 Subject: [PATCH 314/334] ItemSerializer: avoid slow NBT check hasNamedTag() rebuilds the NBT from item properties to decide if there's any NBT. This is a waste of resources when we then fetch the tag to encode anyway. --- src/data/bedrock/item/ItemSerializer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/bedrock/item/ItemSerializer.php b/src/data/bedrock/item/ItemSerializer.php index 845d5bc5f..c6edcfb8f 100644 --- a/src/data/bedrock/item/ItemSerializer.php +++ b/src/data/bedrock/item/ItemSerializer.php @@ -117,8 +117,8 @@ final class ItemSerializer{ $data = $serializer($item); } - if($item->hasNamedTag()){ - $resultTag = $item->getNamedTag(); + $resultTag = $item->getNamedTag(); + if($resultTag->count() > 0){ $extraTag = $data->getTag(); if($extraTag !== null){ $resultTag = $resultTag->merge($extraTag); From 636b96a9a5c35292f076e2f2e9c5723611d28d7b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 12 Sep 2025 01:13:55 +0100 Subject: [PATCH 315/334] Updated composer dependencies --- composer.lock | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/composer.lock b/composer.lock index 330f002d7..31cad5d9e 100644 --- a/composer.lock +++ b/composer.lock @@ -818,20 +818,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -890,9 +890,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.0" + "source": "https://github.com/ramsey/uuid/tree/4.9.1" }, - "time": "2025-06-25T14:20:11+00:00" + "time": "2025-09-04T20:59:21+00:00" }, { "name": "symfony/filesystem", @@ -1684,16 +1684,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.53", + "version": "10.5.54", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "32768472ebfb6969e6c7399f1c7b09009723f653" + "reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32768472ebfb6969e6c7399f1c7b09009723f653", - "reference": "32768472ebfb6969e6c7399f1c7b09009723f653", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b1dbbaaf96106b76d500b9d3db51f9b01f6a3589", + "reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589", "shasum": "" }, "require": { @@ -1765,7 +1765,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.5.53" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.54" }, "funding": [ { @@ -1789,7 +1789,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T14:40:06+00:00" + "time": "2025-09-11T06:19:38+00:00" }, { "name": "sebastian/cli-parser", @@ -1961,16 +1961,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.3", + "version": "5.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", "shasum": "" }, "require": { @@ -2026,15 +2026,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2025-09-07T05:25:07+00:00" }, { "name": "sebastian/complexity", From e47c189cb68d3c9b4014e0ed05c773458313fa2d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 14 Sep 2025 18:01:33 +0100 Subject: [PATCH 316/334] Add support for private timings pasting --- src/command/defaults/TimingsCommand.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php index 08a8b82aa..ffbc08491 100644 --- a/src/command/defaults/TimingsCommand.php +++ b/src/command/defaults/TimingsCommand.php @@ -148,7 +148,8 @@ class TimingsCommand extends VanillaCommand{ private function uploadReport(array $lines, CommandSender $sender) : void{ $data = [ "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), - "data" => implode("\n", $lines) + "data" => implode("\n", $lines), + "private" => "true" ]; $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); @@ -181,8 +182,13 @@ class TimingsCommand extends VanillaCommand{ } $response = json_decode($result->getBody(), true); if(is_array($response) && isset($response["id"]) && (is_int($response["id"]) || is_string($response["id"]))){ - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead( - "https://" . $host . "/?id=" . $response["id"])); + $url = "https://" . $host . "/?id=" . $response["id"]; + if(isset($response["access_token"]) && is_string($response["access_token"])){ + $url .= "&access_token=" . $response["access_token"]; + }else{ + $sender->getServer()->getLogger()->warning("Your chosen timings host does not support private reports. Anyone will be able to see your report if they guess the ID."); + } + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead($url)); }else{ $sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody()); Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError()); From c6a28d8df0e67251182d21e75127370da29e2683 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 15 Sep 2025 17:34:35 +0100 Subject: [PATCH 317/334] InventoryManager: fixed window sending getting stuck on client rejecting window opening closes #6778 honestly, we could just stop checking the window ID entirely, considering the need for delaying and waiting for window close acks, it seems useless at this point... --- src/network/mcpe/InventoryManager.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 19bd94fce..01368ec0a 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -64,6 +64,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Binary; use pocketmine\utils\ObjectSet; use function array_fill_keys; use function array_keys; @@ -419,6 +420,15 @@ class InventoryManager{ } public function onClientRemoveWindow(int $id) : void{ + if(Binary::signByte($id) === ContainerIds::NONE){ //TODO: REMOVE signByte() once BedrockProtocol + ext-encoding are implemented + //TODO: HACK! Since 1.21.100 (and probably earlier), the client will send -1 to close windows that it can't + //view for some reason, e.g. if the chat window was already open. This is pretty awkward, since it means + //that we can only assume it refers to the most recently sent window, and if we don't handle it, + //InventoryManager will never get the green light to send subsequent windows, which breaks inventory UIs. + //Fortunately, we already wait for close acks anyway, so the window ID is technically useless...? + $this->session->getLogger()->debug("Client rejected opening of a window, assuming it was $this->lastInventoryNetworkId"); + $id = $this->lastInventoryNetworkId; + } if($id === $this->lastInventoryNetworkId){ if(isset($this->networkIdToInventoryMap[$id]) && $id !== $this->pendingCloseWindowId){ $this->remove($id); From e7ad3c25fa0200d201284ea23d3781d5cd266a87 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 15 Sep 2025 18:02:10 +0100 Subject: [PATCH 318/334] InGamePacketHandler: ignore CREATIVE_PLAYER_DESTROY_BLOCK fixes #6757 we get PREDICT_DESTROY_BLOCK anyway, so it's not needed --- src/network/mcpe/handler/InGamePacketHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 6aae60745..8b8e0e69f 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -717,7 +717,8 @@ class InGamePacketHandler extends PacketHandler{ case PlayerAction::INTERACT_BLOCK: //TODO: ignored (for now) break; case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK: - //TODO: do we need to handle this? + //in server auth block breaking, we get PREDICT_DESTROY_BLOCK anyway, so this action is redundant + break; case PlayerAction::PREDICT_DESTROY_BLOCK: self::validateFacing($face); if(!$this->player->breakBlock($pos)){ From b237cacfc9466a1197058803ed405447f71a9521 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 15 Sep 2025 19:36:29 +0100 Subject: [PATCH 319/334] InGamePacketHandler: don't resync blocks if the client predicted an interact fail if the prediction was a fail, we can assume that the client didn't do anything visual on its end, which avoids the need to resend blocks. This fixes block lag when towering as discussed in #6803. The more general issue in #6803 remains unresolved (that the server's block resyncing may overwrite additional client side predictions if it places a block before the server's resync for the initial placement arrives), but that's more complicated to fix and I'm not convinced on the correct method to resolve it yet. In any case, this change nets a decent improvement for everyone, regardless. --- src/network/mcpe/handler/InGamePacketHandler.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 8b8e0e69f..18b3f110f 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -89,6 +89,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; use pocketmine\network\mcpe\protocol\types\inventory\MismatchTransactionData; use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction; use pocketmine\network\mcpe\protocol\types\inventory\NormalTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\PredictedResult; use pocketmine\network\mcpe\protocol\types\inventory\ReleaseItemTransactionData; use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest; use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse; @@ -498,11 +499,13 @@ class InGamePacketHandler extends PacketHandler{ $blockPos = $data->getBlockPosition(); $vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ()); $this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos); - //always sync this in case plugins caused a different result than the client expected - //we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating - //more information up the stack. For now I think this is good enough. - //if only the client would tell us what blocks it thinks changed... - $this->syncBlocksNearby($vBlockPos, $data->getFace()); + if($data->getClientInteractPrediction() === PredictedResult::SUCCESS){ + //always sync this in case plugins caused a different result than the client expected + //we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating + //more information up the stack. For now I think this is good enough. + //if only the client would tell us what blocks it thinks changed... + $this->syncBlocksNearby($vBlockPos, $data->getFace()); + } return true; case UseItemTransactionData::ACTION_CLICK_AIR: if($this->player->isUsingItem()){ From a056af16173cd178d8c4240422a8371fd945759c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 15 Sep 2025 22:41:02 +0100 Subject: [PATCH 320/334] Update composer dev dependencies --- build/generate-block-serializer-consts.php | 6 +++ composer.json | 2 +- composer.lock | 40 +++++++++---------- .../block/upgrade/BlockStateUpgrader.php | 4 ++ tests/phpstan/configs/actual-problems.neon | 12 ++++++ tests/phpstan/configs/phpstan-bugs.neon | 12 ------ .../configs/spl-fixed-array-sucks.neon | 23 +++++++++++ .../utils/CloningRegistryTraitTest.php | 2 +- tools/blockstate-upgrade-schema-utils.php | 3 ++ 9 files changed, 70 insertions(+), 34 deletions(-) diff --git a/build/generate-block-serializer-consts.php b/build/generate-block-serializer-consts.php index 875729fcf..6dab2ceb2 100644 --- a/build/generate-block-serializer-consts.php +++ b/build/generate-block-serializer-consts.php @@ -29,6 +29,9 @@ use pocketmine\data\bedrock\block\BlockStateStringValues; use pocketmine\data\bedrock\block\BlockTypeNames; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\nbt\NbtException; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; @@ -78,6 +81,9 @@ function generateBlockPaletteReport(array $states) : BlockPaletteReport{ $name = $stateData->getName(); $result->seenTypes[$name] = $name; foreach(Utils::stringifyKeys($stateData->getStates()) as $k => $v){ + if(!$v instanceof ByteTag && !$v instanceof IntTag && !$v instanceof StringTag){ + throw new AssumptionFailedError("Assumed all state tags should be TAG_Byte, TAG_Int or TAG_String, but found $k ($v) on block $name"); + } $result->seenStateValues[$k][$v->getValue()] = $v->getValue(); asort($result->seenStateValues[$k]); } diff --git a/composer.json b/composer.json index 17271955e..bef8b1250 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.17", + "phpstan/phpstan": "2.1.25", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index 31cad5d9e..be22909f1 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": "27fee330bdcb6ea2373c57cdfb3bc22f", + "content-hash": "008c888b5812dda09a0ec6e425453153", "packages": [ { "name": "adhocore/json-comment", @@ -1204,16 +1204,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.17", + "version": "2.1.25", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" + "reference": "4087d28bd252895874e174d65e26b2c202ed893a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", - "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4087d28bd252895874e174d65e26b2c202ed893a", + "reference": "4087d28bd252895874e174d65e26b2c202ed893a", "shasum": "" }, "require": { @@ -1258,25 +1258,25 @@ "type": "github" } ], - "time": "2025-05-21T20:55:28+00:00" + "time": "2025-09-12T14:26:42+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" + "reference": "9a9b161baee88a5f5c58d816943cff354ff233dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", - "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/9a9b161baee88a5f5c58d816943cff354ff233dc", + "reference": "9a9b161baee88a5f5c58d816943cff354ff233dc", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.18" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1309,9 +1309,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.7" }, - "time": "2025-03-26T12:47:06+00:00" + "time": "2025-07-13T11:31:46+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1684,16 +1684,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.54", + "version": "10.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589" + "reference": "4b2d546b336876bd9562f24641b08a25335b06b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b1dbbaaf96106b76d500b9d3db51f9b01f6a3589", - "reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b2d546b336876bd9562f24641b08a25335b06b6", + "reference": "4b2d546b336876bd9562f24641b08a25335b06b6", "shasum": "" }, "require": { @@ -1714,7 +1714,7 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.4", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", @@ -1765,7 +1765,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.5.54" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.55" }, "funding": [ { @@ -1789,7 +1789,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T06:19:38+00:00" + "time": "2025-09-14T06:19:20+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index a3e72807e..acfd0c503 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -278,6 +278,10 @@ final class BlockStateUpgrader{ private function applyPropertyFlattened(BlockStateUpgradeSchemaFlattenInfo $flattenInfo, string $oldName, array $states) : array{ $flattenedValue = $states[$flattenInfo->flattenedProperty] ?? null; $expectedType = $flattenInfo->flattenedPropertyType; + if($expectedType === null){ + //TODO: we can't make this non-nullable in a patch release + throw new AssumptionFailedError("We never give this null"); + } if(!$flattenedValue instanceof $expectedType){ //flattened property is not of the expected type, so this transformation is not applicable return [$oldName, $states]; diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 570303843..76cebf283 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -948,6 +948,12 @@ parameters: count: 1 path: ../../../src/plugin/PluginDescription.php + - + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$srcNamespacePrefix \(string\) does not accept mixed\.$#' + identifier: assign.propertyType + count: 1 + path: ../../../src/plugin/PluginDescription.php + - message: '#^Cannot call method addChild\(\) on pocketmine\\permission\\Permission\|null\.$#' identifier: method.nonObject @@ -990,6 +996,12 @@ parameters: count: 1 path: ../../../src/scheduler/TaskScheduler.php + - + message: '#^Possibly invalid array key type mixed\.$#' + identifier: offsetAccess.invalidOffset + count: 1 + path: ../../../src/thread/ThreadManager.php + - message: '#^Cannot access offset string on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index a80050020..65064ab6c 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -192,18 +192,6 @@ parameters: count: 1 path: ../../../src/network/mcpe/cache/CraftingDataCache.php - - - message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 1 so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/network/mcpe/compression/ZlibCompressor.php - - - - message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 255 so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/network/mcpe/compression/ZlibCompressor.php - - message: '#^Parameter \#1 \$states of class pocketmine\\network\\mcpe\\convert\\BlockStateDictionary constructor expects list\, array\, pocketmine\\network\\mcpe\\convert\\BlockStateDictionaryEntry\> given\.$#' identifier: argument.type diff --git a/tests/phpstan/configs/spl-fixed-array-sucks.neon b/tests/phpstan/configs/spl-fixed-array-sucks.neon index 05524fb8c..9c5ab915f 100644 --- a/tests/phpstan/configs/spl-fixed-array-sucks.neon +++ b/tests/phpstan/configs/spl-fixed-array-sucks.neon @@ -30,3 +30,26 @@ parameters: count: 4 path: ../../../src/world/generator/noise/Noise.php + - + message: '#^Parameter \$q00 of static method pocketmine\\world\\generator\\noise\\Noise\:\:bilinearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/generator/noise/Noise.php + + - + message: '#^Parameter \$q01 of static method pocketmine\\world\\generator\\noise\\Noise\:\:bilinearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/generator/noise/Noise.php + + - + message: '#^Parameter \$q10 of static method pocketmine\\world\\generator\\noise\\Noise\:\:bilinearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/generator/noise/Noise.php + + - + message: '#^Parameter \$q11 of static method pocketmine\\world\\generator\\noise\\Noise\:\:bilinearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/world/generator/noise/Noise.php diff --git a/tests/phpunit/utils/CloningRegistryTraitTest.php b/tests/phpunit/utils/CloningRegistryTraitTest.php index e3b53ecb5..3a1e6a5e0 100644 --- a/tests/phpunit/utils/CloningRegistryTraitTest.php +++ b/tests/phpunit/utils/CloningRegistryTraitTest.php @@ -47,7 +47,7 @@ final class CloningRegistryTraitTest extends TestCase{ public function testGetAllClone() : void{ $list1 = TestCloningRegistry::getAll(); $list2 = TestCloningRegistry::getAll(); - foreach(Utils::promoteKeys($list1) as $k => $member){ + foreach(Utils::stringifyKeys($list1) as $k => $member){ self::assertNotSame($member, $list2[$k], "VanillaBlocks ought to clone its members"); } } diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index 7c34b7728..80a79bd89 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -354,6 +354,9 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra * @param string[] $strings */ function findCommonPrefix(array $strings) : string{ + if(count($strings) === 0){ + return ""; + } sort($strings, SORT_STRING); $first = $strings[array_key_first($strings)]; From 1e797b989725bf0b2211f3fd381023ff157f573b Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 15 Sep 2025 23:53:05 +0100 Subject: [PATCH 321/334] Prepare 5.33.2 release (#6804) --- changelogs/5.33.md | 12 ++++++++++++ src/VersionInfo.php | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/changelogs/5.33.md b/changelogs/5.33.md index fe2e15a07..65ff6620d 100644 --- a/changelogs/5.33.md +++ b/changelogs/5.33.md @@ -133,3 +133,15 @@ Released 31st August 2025. ## Fixes - Fixed banners placed in prior versions getting their tiles deleted (due to missing `Type` tags). + +# 5.33.2 +Released 16th September 2025. + +## General +- PHP 8.4 has now been added to the test matrix. + +## Fixes +- Fixed PHP 8.4 deprecation notice in `AsyncGeneratorExecutor`. +- Fixed inventory windows breaking if a window is sent while the player has the chat window open (e.g. quickly pressing E followed by / could trigger this). +- Fixed `BlockBreakEvent` being called twice in creative if cancelled. +- Reduced block lag when towering and other situations where the placed block might intersect with the player's AABB. Block lag may still appear on higher latency clients - this is still being worked on. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 855e78aaa..5f457cfc1 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.33.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 40a1a29f0dac93c1afb887177dd36bf757492c70 Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:54:12 +0000 Subject: [PATCH 322/334] 5.33.3 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/17748879758 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 5f457cfc1..944d6d84f 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.33.2"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.33.3"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From f46fc18c57023805b03f61d8078dcbf4bc47b1e7 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Wed, 17 Sep 2025 01:59:39 +0100 Subject: [PATCH 323/334] Trigger branch merge at 9pm instead of midnight --- .github/workflows/branch-sync-cron-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/branch-sync-cron-trigger.yml b/.github/workflows/branch-sync-cron-trigger.yml index 145fcd222..896141d83 100644 --- a/.github/workflows/branch-sync-cron-trigger.yml +++ b/.github/workflows/branch-sync-cron-trigger.yml @@ -6,7 +6,7 @@ name: Trigger branch sync on: schedule: - - cron: "0 0 * * *" #once per day so we don't spam merge commits on busy days + - cron: "0 21 * * *" #once per day so we don't spam merge commits on busy days workflow_dispatch: #for testing jobs: From dd5a7adec487aa4fd24ef5cf1b9eff1aa9a6315b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 02:15:15 +0000 Subject: [PATCH 324/334] Bump build/php from `1d9cda6` to `627f8d6` (#6802) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 1d9cda668..627f8d670 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 1d9cda668856390a64d33621a3da151b3507bec8 +Subproject commit 627f8d670cc12c6fb7ad988f0c85f65cd39d6f7e From 6d2d23a210fd39fe8855318bfb504a834464a318 Mon Sep 17 00:00:00 2001 From: IvanCraft623 <57236932+IvanCraft623@users.noreply.github.com> Date: Thu, 18 Sep 2025 12:00:55 -0600 Subject: [PATCH 325/334] Implement Trident (#4547) Co-authored-by: Dylan T. --- .../ItemSerializerDeserializerRegistrar.php | 1 + src/entity/EntityFactory.php | 19 ++ src/entity/projectile/Projectile.php | 16 +- src/entity/projectile/Trident.php | 183 ++++++++++++++++++ src/event/player/PlayerDeathEvent.php | 11 +- src/item/ItemTypeIds.php | 3 +- src/item/StringToItemParser.php | 1 + src/item/Trident.php | 93 +++++++++ src/item/VanillaItems.php | 2 + src/world/sound/TridentHitEntitySound.php | 35 ++++ src/world/sound/TridentHitGroundSound.php | 35 ++++ src/world/sound/TridentThrowSound.php | 35 ++++ 12 files changed, 429 insertions(+), 5 deletions(-) create mode 100644 src/entity/projectile/Trident.php create mode 100644 src/item/Trident.php create mode 100644 src/world/sound/TridentHitEntitySound.php create mode 100644 src/world/sound/TridentHitGroundSound.php create mode 100644 src/world/sound/TridentThrowSound.php diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index f5c03dbeb..c7e5d7020 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -403,6 +403,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::TORCHFLOWER_SEEDS, Items::TORCHFLOWER_SEEDS()); $this->map1to1Item(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::TOTEM_OF_UNDYING, Items::TOTEM()); + $this->map1to1Item(Ids::TRIDENT, Items::TRIDENT()); $this->map1to1Item(Ids::TROPICAL_FISH, Items::CLOWNFISH()); $this->map1to1Item(Ids::TURTLE_HELMET, Items::TURTLE_HELMET()); $this->map1to1Item(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE, Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE()); diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 970fd986f..94639cd3f 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -46,6 +46,7 @@ use pocketmine\entity\projectile\ExperienceBottle; use pocketmine\entity\projectile\IceBomb; use pocketmine\entity\projectile\Snowball; use pocketmine\entity\projectile\SplashPotion; +use pocketmine\entity\projectile\Trident; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -171,6 +172,24 @@ final class EntityFactory{ return new SplashPotion(Helper::parseLocation($nbt, $world), null, $potionType, $nbt); }, ['ThrownPotion', 'minecraft:potion', 'thrownpotion']); + $this->register(Trident::class, function(World $world, CompoundTag $nbt) : Trident{ + $itemTag = $nbt->getCompoundTag(Trident::TAG_ITEM); + if($itemTag === null){ + throw new SavedDataLoadingException("Expected \"" . Trident::TAG_ITEM . "\" NBT tag not found"); + } + + $item = Item::nbtDeserialize($itemTag); + if($item->isNull()){ + throw new SavedDataLoadingException("Trident item is invalid"); + } + return new Trident(Helper::parseLocation($nbt, $world), $item, null, $nbt); + }, [ + 'minecraft:trident', //java + 'minecraft:thrown_trident', //bedrock + 'Trident', //backwards compat for people who used #4547 before it was merged, since it was sitting around for 4 years... + 'ThrownTrident' //as above + ]); + $this->register(Squid::class, function(World $world, CompoundTag $nbt) : Squid{ return new Squid(Helper::parseLocation($nbt, $world), $nbt); }, ['Squid', 'minecraft:squid']); diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php index 68b6c4763..f8c8f45a5 100644 --- a/src/entity/projectile/Projectile.php +++ b/src/entity/projectile/Projectile.php @@ -227,12 +227,15 @@ abstract class Projectile extends Entity{ $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult); } + $motionBeforeOnHit = clone $this->motion; $ev->call(); $this->onHit($ev); $specificHitFunc(); $this->isCollided = $this->onGround = true; - $this->motion = Vector3::zero(); + if($motionBeforeOnHit->equals($this->motion)){ + $this->motion = Vector3::zero(); + } }else{ $this->isCollided = $this->onGround = false; $this->blockHit = null; @@ -295,7 +298,9 @@ abstract class Projectile extends Entity{ } } - $this->flagForDespawn(); + if($this->despawnsOnEntityHit()){ + $this->flagForDespawn(); + } } /** @@ -305,4 +310,11 @@ abstract class Projectile extends Entity{ $this->blockHit = $blockHit->getPosition()->asVector3(); $blockHit->onProjectileHit($this, $hitResult); } + + /** + * @deprecated This will be dropped in favor of deciding whether to despawn within `onHitEntity()` method. + */ + protected function despawnsOnEntityHit() : bool{ + return true; + } } diff --git a/src/entity/projectile/Trident.php b/src/entity/projectile/Trident.php new file mode 100644 index 000000000..73b3880ac --- /dev/null +++ b/src/entity/projectile/Trident.php @@ -0,0 +1,183 @@ +isNull()){ + throw new \InvalidArgumentException("Trident must have a count of at least 1"); + } + $this->item = clone $item; + parent::__construct($location, $shootingEntity, $nbt); + } + + protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(0.35, 0.25); } + + protected function getInitialDragMultiplier() : float{ return 0.01; } + + protected function getInitialGravity() : float{ return 0.1; } + + protected function initEntity(CompoundTag $nbt) : void{ + parent::initEntity($nbt); + + $this->spawnedInCreative = $nbt->getByte(self::TAG_SPAWNED_IN_CREATIVE, 0) === 1; + } + + public function saveNBT() : CompoundTag{ + $nbt = parent::saveNBT(); + $nbt->setTag(self::TAG_ITEM, $this->item->nbtSerialize()); + $nbt->setByte(self::TAG_SPAWNED_IN_CREATIVE, $this->spawnedInCreative ? 1 : 0); + return $nbt; + } + + protected function onFirstUpdate(int $currentTick) : void{ + $owner = $this->getOwningEntity(); + $this->spawnedInCreative = $owner instanceof Player && $owner->isCreative(); + + parent::onFirstUpdate($currentTick); + } + + protected function entityBaseTick(int $tickDiff = 1) : bool{ + if($this->closed){ + return false; + } + //TODO: Loyalty enchantment. + + return parent::entityBaseTick($tickDiff); + } + + protected function despawnsOnEntityHit() : bool{ + return false; + } + + protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{ + parent::onHitEntity($entityHit, $hitResult); + + $this->canCollide = false; + $this->broadcastSound(new TridentHitEntitySound()); + $this->setMotion(new Vector3($this->motion->x * -0.01, $this->motion->y * -0.1, $this->motion->z * -0.01)); + } + + protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{ + parent::onHitBlock($blockHit, $hitResult); + $this->canCollide = true; + $this->broadcastSound(new TridentHitGroundSound()); + } + + public function getItem() : Item{ + return clone $this->item; + } + + public function setItem(Item $item) : void{ + if($item->isNull()){ + throw new \InvalidArgumentException("Trident must have a count of at least 1"); + } + if($this->item->hasEnchantments() !== $item->hasEnchantments()){ + $this->networkPropertiesDirty = true; + } + $this->item = clone $item; + } + + public function canCollideWith(Entity $entity) : bool{ + return $this->canCollide && $entity->getId() !== $this->ownerId && parent::canCollideWith($entity); + } + + public function onCollideWithPlayer(Player $player) : void{ + if($this->blockHit !== null){ + $this->pickup($player); + } + } + + private function pickup(Player $player) : void{ + $shouldDespawn = false; + + $playerInventory = $player->getInventory(); + $ev = new EntityItemPickupEvent($player, $this, $this->getItem(), $playerInventory); + if($player->hasFiniteResources() && !$playerInventory->canAddItem($ev->getItem())){ + $ev->cancel(); + } + if($this->spawnedInCreative){ + $ev->cancel(); + $shouldDespawn = true; + } + + $ev->call(); + if(!$ev->isCancelled()){ + $ev->getInventory()?->addItem($ev->getItem()); + $shouldDespawn = true; + } + + if($shouldDespawn){ + //even if the item was not actually picked up, the animation must be displayed. + NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this) + ); + $this->flagForDespawn(); + } + } + + protected function syncNetworkData(EntityMetadataCollection $properties) : void{ + parent::syncNetworkData($properties); + + $properties->setGenericFlag(EntityMetadataFlags::ENCHANTED, $this->item->hasEnchantments()); + } +} diff --git a/src/event/player/PlayerDeathEvent.php b/src/event/player/PlayerDeathEvent.php index aacff3438..ca4b46564 100644 --- a/src/event/player/PlayerDeathEvent.php +++ b/src/event/player/PlayerDeathEvent.php @@ -26,7 +26,9 @@ namespace pocketmine\event\player; use pocketmine\block\BlockTypeIds; use pocketmine\entity\Living; use pocketmine\entity\object\FallingBlock; +use pocketmine\entity\projectile\Trident; use pocketmine\event\entity\EntityDamageByBlockEvent; +use pocketmine\event\entity\EntityDamageByChildEntityEvent; use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDeathEvent; @@ -113,10 +115,15 @@ class PlayerDeathEvent extends EntityDeathEvent{ } break; case EntityDamageEvent::CAUSE_PROJECTILE: - if($deathCause instanceof EntityDamageByEntityEvent){ + if($deathCause instanceof EntityDamageByChildEntityEvent){ $e = $deathCause->getDamager(); if($e instanceof Living){ - return KnownTranslationFactory::death_attack_arrow($name, $e->getDisplayName()); + $child = $deathCause->getChild(); + if($child instanceof Trident){ + return KnownTranslationFactory::death_attack_trident($name, $e->getDisplayName()); + }else{ + return KnownTranslationFactory::death_attack_arrow($name, $e->getDisplayName()); + } } } break; diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index af32cbcc2..36fc2c65f 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -346,8 +346,9 @@ final class ItemTypeIds{ public const PALE_OAK_HANGING_SIGN = 20307; public const SPRUCE_HANGING_SIGN = 20308; public const WARPED_HANGING_SIGN = 20309; + public const TRIDENT = 20310; - public const FIRST_UNUSED_ITEM_ID = 20310; + public const FIRST_UNUSED_ITEM_ID = 20311; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 2f316f66b..5e45ea25d 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1560,6 +1560,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("torchflower_seeds", fn() => Items::TORCHFLOWER_SEEDS()); $result->register("tide_armor_trim_smithing_template", fn() => Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("totem", fn() => Items::TOTEM()); + $result->register("trident", fn() => Items::TRIDENT()); $result->register("turtle_helmet", fn() => Items::TURTLE_HELMET()); $result->register("vex_armor_trim_smithing_template", fn() => Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("turtle_shell_piece", fn() => Items::SCUTE()); diff --git a/src/item/Trident.php b/src/item/Trident.php new file mode 100644 index 000000000..991f45b1d --- /dev/null +++ b/src/item/Trident.php @@ -0,0 +1,93 @@ +getLocation(); + + $diff = $player->getItemUseDuration(); + if($diff < 14){ + return ItemUseResult::FAIL; + } + + $item = $this->pop(); + if($player->hasFiniteResources()){ + $item->applyDamage(1); + } + $entity = new TridentEntity(Location::fromObject( + $player->getEyePos(), + $player->getWorld(), + ($location->yaw > 180 ? 360 : 0) - $location->yaw, + -$location->pitch + ), $item, $player); + $p = $diff / 20; + $baseForce = min((($p ** 2) + $p * 2) / 3, 1) * 2.4; + $entity->setMotion($player->getDirectionVector()->multiply($baseForce)); + + $ev = new ProjectileLaunchEvent($entity); + $ev->call(); + if($ev->isCancelled()){ + $ev->getEntity()->flagForDespawn(); + return ItemUseResult::FAIL; + } + $ev->getEntity()->spawnToAll(); + $location->getWorld()->addSound($location, new TridentThrowSound()); + + return ItemUseResult::SUCCESS; + } + + public function getAttackPoints() : int{ + return 9; + } + + public function canStartUsingItem(Player $player) : bool{ + return $this->damage < $this->getMaxDurability(); + } + + public function onAttackEntity(Entity $victim, array &$returnedItems) : bool{ + return $this->applyDamage(1); + } + + public function onDestroyBlock(Block $block, array &$returnedItems) : bool{ + if(!$block->getBreakInfo()->breaksInstantly()){ + return $this->applyDamage(2); + } + return false; + } +} diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index e4eeffc1d..48ae95c32 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -335,6 +335,7 @@ use function strtolower; * @method static Item TIDE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static TorchflowerSeeds TORCHFLOWER_SEEDS() * @method static Totem TOTEM() + * @method static Trident TRIDENT() * @method static TurtleHelmet TURTLE_HELMET() * @method static Item VEX_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static SpawnEgg VILLAGER_SPAWN_EGG() @@ -630,6 +631,7 @@ final class VanillaItems{ self::register("sweet_berries", fn(IID $id) => new SweetBerries($id, "Sweet Berries")); self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds")); self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying")); + self::register("trident", fn(IID $id) => new Trident($id, "Trident")); self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN())); self::register("warped_hanging_sign", fn(IID $id) => new HangingSign($id, "Warped Hanging Sign", Blocks::WARPED_CEILING_CENTER_HANGING_SIGN(), Blocks::WARPED_CEILING_EDGES_HANGING_SIGN(), Blocks::WARPED_WALL_HANGING_SIGN())); self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER())); diff --git a/src/world/sound/TridentHitEntitySound.php b/src/world/sound/TridentHitEntitySound.php new file mode 100644 index 000000000..ea77a7404 --- /dev/null +++ b/src/world/sound/TridentHitEntitySound.php @@ -0,0 +1,35 @@ + Date: Fri, 19 Sep 2025 23:40:04 +0100 Subject: [PATCH 326/334] Updated NBT library to get new ListTag handling features --- composer.json | 2 +- composer.lock | 17 +++++----- src/block/tile/Banner.php | 3 +- src/block/tile/ChiseledBookshelf.php | 10 ++++-- src/block/tile/ContainerTrait.php | 10 ++++-- .../bedrock/item/upgrade/ItemDataUpgrader.php | 32 +++---------------- src/entity/EntityDataHelper.php | 13 +++----- src/entity/Human.php | 6 ++-- src/entity/Living.php | 3 +- src/entity/projectile/Projectile.php | 6 ++-- src/item/Banner.php | 6 ++-- src/item/Item.php | 20 +++++------- src/item/WritableBookBase.php | 11 +++---- src/network/mcpe/convert/TypeConverter.php | 14 ++++---- .../io/region/LegacyAnvilChunkTrait.php | 8 ++--- .../format/io/region/RegionWorldProvider.php | 17 ++-------- tools/generate-bedrock-data-from-packets.php | 9 ++---- 17 files changed, 72 insertions(+), 115 deletions(-) diff --git a/composer.json b/composer.json index d77203389..0f2ffe95f 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "pocketmine/locale-data": "~2.25.0", "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", - "pocketmine/nbt": "~1.1.0", + "pocketmine/nbt": "~1.2.0", "pocketmine/raklib": "~1.2.0", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", diff --git a/composer.lock b/composer.lock index fec0d3a29..ece3c5070 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": "9a4fa406f66a2f57be87c9ac955191b2", + "content-hash": "1e7545f6cc226b31d54238602143ba78", "packages": [ { "name": "adhocore/json-comment", @@ -576,16 +576,16 @@ }, { "name": "pocketmine/nbt", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/pmmp/NBT.git", - "reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1" + "reference": "51b8d6a97065fb93e0b4f660b65164b6e1ed2fff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/NBT/zipball/c3c7b0a7295daeaf7873d90fed5c5d10381d12e1", - "reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1", + "url": "https://api.github.com/repos/pmmp/NBT/zipball/51b8d6a97065fb93e0b4f660b65164b6e1ed2fff", + "reference": "51b8d6a97065fb93e0b4f660b65164b6e1ed2fff", "shasum": "" }, "require": { @@ -595,7 +595,8 @@ }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "2.1.0", + "phpstan/phpstan": "2.1.27", + "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.5" }, @@ -612,9 +613,9 @@ "description": "PHP library for working with Named Binary Tags", "support": { "issues": "https://github.com/pmmp/NBT/issues", - "source": "https://github.com/pmmp/NBT/tree/1.1.1" + "source": "https://github.com/pmmp/NBT/tree/1.2.0" }, - "time": "2025-03-09T01:46:03+00:00" + "time": "2025-09-19T18:09:30+00:00" }, { "name": "pocketmine/raklib", diff --git a/src/block/tile/Banner.php b/src/block/tile/Banner.php index b6a143fe7..ac19d9835 100644 --- a/src/block/tile/Banner.php +++ b/src/block/tile/Banner.php @@ -69,9 +69,8 @@ class Banner extends Spawnable{ $patternTypeIdMap = BannerPatternTypeIdMap::getInstance(); - $patterns = $nbt->getListTag(self::TAG_PATTERNS); + $patterns = $nbt->getListTag(self::TAG_PATTERNS, CompoundTag::class); if($patterns !== null){ - /** @var CompoundTag $pattern */ foreach($patterns as $pattern){ $patternColor = $colorIdMap->fromInvertedId($pattern->getInt(self::TAG_PATTERN_COLOR)) ?? DyeColor::BLACK; //TODO: missing pattern colour should be an error $patternType = $patternTypeIdMap->fromId($pattern->getString(self::TAG_PATTERN_NAME)); diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index 06175e27f..90bf8f29b 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -34,6 +34,7 @@ use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\nbt\UnexpectedTagTypeException; use pocketmine\world\World; use function count; @@ -86,13 +87,18 @@ class ChiseledBookshelf extends Tile implements Container{ } protected function loadItems(CompoundTag $tag) : void{ - if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ + try{ + $inventoryTag = $tag->getListTag(Container::TAG_ITEMS, CompoundTag::class); + }catch(UnexpectedTagTypeException){ + //preserve the old behaviour of not throwing on wrong types + $inventoryTag = null; + } + if($inventoryTag !== null){ $inventory = $this->getRealInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization $newContents = []; - /** @var CompoundTag $itemNBT */ foreach($inventoryTag as $slot => $itemNBT){ try{ $count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT); diff --git a/src/block/tile/ContainerTrait.php b/src/block/tile/ContainerTrait.php index fdd050a41..6b9158d7a 100644 --- a/src/block/tile/ContainerTrait.php +++ b/src/block/tile/ContainerTrait.php @@ -31,6 +31,7 @@ use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\nbt\UnexpectedTagTypeException; use pocketmine\world\Position; /** @@ -43,13 +44,18 @@ trait ContainerTrait{ abstract public function getRealInventory() : Inventory; protected function loadItems(CompoundTag $tag) : void{ - if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ + try{ + $inventoryTag = $tag->getListTag(Container::TAG_ITEMS, CompoundTag::class); + }catch(UnexpectedTagTypeException){ + //preserve the old behaviour of not throwing on wrong types + $inventoryTag = null; + } + if($inventoryTag !== null){ $inventory = $this->getRealInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization $newContents = []; - /** @var CompoundTag $itemNBT */ foreach($inventoryTag as $itemNBT){ try{ $newContents[$itemNBT->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($itemNBT); diff --git a/src/data/bedrock/item/upgrade/ItemDataUpgrader.php b/src/data/bedrock/item/upgrade/ItemDataUpgrader.php index 7b34ffcb6..98af7ffb0 100644 --- a/src/data/bedrock/item/upgrade/ItemDataUpgrader.php +++ b/src/data/bedrock/item/upgrade/ItemDataUpgrader.php @@ -29,16 +29,14 @@ use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\SavedItemData; use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\SavedDataLoadingException; -use pocketmine\nbt\NBT; use pocketmine\nbt\NbtException; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; -use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\ShortTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\utils\Binary; -use function assert; +use function array_map; final class ItemDataUpgrader{ private const TAG_LEGACY_ID = "id"; //TAG_Short (or TAG_String for Java itemstacks) @@ -169,26 +167,6 @@ final class ItemDataUpgrader{ return new SavedItemData($newNameId, $newMeta, $blockStateData, $tag->getCompoundTag(SavedItemData::TAG_TAG)); } - /** - * @return string[] - * @throws SavedDataLoadingException - */ - private static function deserializeListOfStrings(?ListTag $list, string $tagName) : array{ - if($list === null){ - return []; - } - if($list->getTagType() !== NBT::TAG_String){ - throw new SavedDataLoadingException("Unexpected type of list for tag '$tagName', expected TAG_String"); - } - $result = []; - foreach($list as $item){ - assert($item instanceof StringTag); - $result[] = $item->getValue(); - } - - return $result; - } - /** * @throws SavedDataLoadingException */ @@ -205,8 +183,8 @@ final class ItemDataUpgrader{ //optional $slot = ($slotTag = $tag->getTag(SavedItemStackData::TAG_SLOT)) instanceof ByteTag ? Binary::unsignByte($slotTag->getValue()) : null; $wasPickedUp = ($wasPickedUpTag = $tag->getTag(SavedItemStackData::TAG_WAS_PICKED_UP)) instanceof ByteTag ? $wasPickedUpTag->getValue() : null; - $canPlaceOnList = $tag->getListTag(SavedItemStackData::TAG_CAN_PLACE_ON); - $canDestroyList = $tag->getListTag(SavedItemStackData::TAG_CAN_DESTROY); + $canPlaceOnList = $tag->getListTag(SavedItemStackData::TAG_CAN_PLACE_ON, StringTag::class); + $canDestroyList = $tag->getListTag(SavedItemStackData::TAG_CAN_DESTROY, StringTag::class); }catch(NbtException $e){ throw new SavedDataLoadingException($e->getMessage(), 0, $e); } @@ -216,8 +194,8 @@ final class ItemDataUpgrader{ $count, $slot, $wasPickedUp !== 0, - self::deserializeListOfStrings($canPlaceOnList, SavedItemStackData::TAG_CAN_PLACE_ON), - self::deserializeListOfStrings($canDestroyList, SavedItemStackData::TAG_CAN_DESTROY) + $canPlaceOnList === null ? [] : array_map(fn(StringTag $t) => $t->getValue(), $canPlaceOnList->getValue()), + $canDestroyList === null ? [] : array_map(fn(StringTag $t) => $t->getValue(), $canDestroyList->getValue()) ); } diff --git a/src/entity/EntityDataHelper.php b/src/entity/EntityDataHelper.php index 60e45e535..4cce47127 100644 --- a/src/entity/EntityDataHelper.php +++ b/src/entity/EntityDataHelper.php @@ -25,7 +25,6 @@ namespace pocketmine\entity; use pocketmine\data\SavedDataLoadingException; use pocketmine\math\Vector3; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\DoubleTag; use pocketmine\nbt\tag\FloatTag; @@ -59,11 +58,10 @@ final class EntityDataHelper{ public static function parseLocation(CompoundTag $nbt, World $world) : Location{ $pos = self::parseVec3($nbt, Entity::TAG_POS, false); - $yawPitch = $nbt->getTag(Entity::TAG_ROTATION); - if(!($yawPitch instanceof ListTag) || $yawPitch->getTagType() !== NBT::TAG_Float){ + $generic = $nbt->getTag(Entity::TAG_ROTATION); + if(!($generic instanceof ListTag) || ($yawPitch = $generic->cast(FloatTag::class)) === null){ throw new SavedDataLoadingException("'" . Entity::TAG_ROTATION . "' should be a List"); } - /** @var FloatTag[] $values */ $values = $yawPitch->getValue(); if(count($values) !== 2){ throw new SavedDataLoadingException("Expected exactly 2 entries for 'Rotation'"); @@ -78,14 +76,13 @@ final class EntityDataHelper{ * @throws SavedDataLoadingException */ public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{ - $pos = $nbt->getTag($tagName); - if($pos === null && $optional){ + $generic = $nbt->getTag($tagName); + if($generic === null && $optional){ return Vector3::zero(); } - if(!($pos instanceof ListTag) || ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){ + if(!($generic instanceof ListTag) || ($pos = $generic->cast(DoubleTag::class) ?? $generic->cast(FloatTag::class)) === null){ throw new SavedDataLoadingException("'$tagName' should be a List or List"); } - /** @var DoubleTag[]|FloatTag[] $values */ $values = $pos->getValue(); if(count($values) !== 3){ throw new SavedDataLoadingException("Expected exactly 3 entries in '$tagName' tag"); diff --git a/src/entity/Human.php b/src/entity/Human.php index d2637a3f9..97ebdefca 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -299,12 +299,11 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ $this->enderInventory = new PlayerEnderInventory($this); $this->initHumanData($nbt); - $inventoryTag = $nbt->getListTag(self::TAG_INVENTORY); + $inventoryTag = $nbt->getListTag(self::TAG_INVENTORY, CompoundTag::class); if($inventoryTag !== null){ $inventoryItems = []; $armorInventoryItems = []; - /** @var CompoundTag $item */ foreach($inventoryTag as $i => $item){ $slot = $item->getByte(SavedItemStackData::TAG_SLOT); if($slot >= 0 && $slot < 9){ //Hotbar @@ -328,11 +327,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobOffHandItemChange($recipients, $this) ))); - $enderChestInventoryTag = $nbt->getListTag(self::TAG_ENDER_CHEST_INVENTORY); + $enderChestInventoryTag = $nbt->getListTag(self::TAG_ENDER_CHEST_INVENTORY, CompoundTag::class); if($enderChestInventoryTag !== null){ $enderChestInventoryItems = []; - /** @var CompoundTag $item */ foreach($enderChestInventoryTag as $i => $item){ $enderChestInventoryItems[$item->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($item); } diff --git a/src/entity/Living.php b/src/entity/Living.php index 5ae1fd4e6..e24e07b00 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -181,8 +181,7 @@ abstract class Living extends Entity{ $this->setAirSupplyTicks($nbt->getShort(self::TAG_BREATH_TICKS, self::DEFAULT_BREATH_TICKS)); - /** @var CompoundTag[]|ListTag|null $activeEffectsTag */ - $activeEffectsTag = $nbt->getListTag(self::TAG_ACTIVE_EFFECTS); + $activeEffectsTag = $nbt->getListTag(self::TAG_ACTIVE_EFFECTS, CompoundTag::class); if($activeEffectsTag !== null){ foreach($activeEffectsTag as $e){ $effect = EffectIdMap::getInstance()->fromId($e->getByte(self::TAG_EFFECT_ID)); diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php index f8c8f45a5..09529b6b3 100644 --- a/src/entity/projectile/Projectile.php +++ b/src/entity/projectile/Projectile.php @@ -39,7 +39,6 @@ use pocketmine\event\entity\ProjectileHitEvent; use pocketmine\math\RayTraceResult; use pocketmine\math\Vector3; use pocketmine\math\VoxelRayTrace; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; @@ -81,12 +80,11 @@ abstract class Projectile extends Entity{ $this->setHealth(1); $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage); - if(($stuckOnBlockPosTag = $nbt->getListTag(self::TAG_STUCK_ON_BLOCK_POS)) !== null){ - if($stuckOnBlockPosTag->getTagType() !== NBT::TAG_Int || count($stuckOnBlockPosTag) !== 3){ + if(($stuckOnBlockPosTag = $nbt->getListTag(self::TAG_STUCK_ON_BLOCK_POS, IntTag::class)) !== null){ + if(count($stuckOnBlockPosTag) !== 3){ throw new SavedDataLoadingException(self::TAG_STUCK_ON_BLOCK_POS . " tag should be a list of 3 TAG_Int"); } - /** @var IntTag[] $values */ $values = $stuckOnBlockPosTag->getValue(); $this->blockHit = new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); diff --git a/src/item/Banner.php b/src/item/Banner.php index 2fc53f5ae..f955a8a12 100644 --- a/src/item/Banner.php +++ b/src/item/Banner.php @@ -29,7 +29,6 @@ use pocketmine\block\utils\DyeColor; use pocketmine\data\bedrock\BannerPatternTypeIdMap; use pocketmine\data\bedrock\DyeColorIdMap; use pocketmine\data\runtime\RuntimeDataDescriber; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use function count; @@ -92,9 +91,8 @@ class Banner extends ItemBlockWallOrFloor{ $colorIdMap = DyeColorIdMap::getInstance(); $patternIdMap = BannerPatternTypeIdMap::getInstance(); - $patterns = $tag->getListTag(self::TAG_PATTERNS); - if($patterns !== null && $patterns->getTagType() === NBT::TAG_Compound){ - /** @var CompoundTag $t */ + $patterns = $tag->getListTag(self::TAG_PATTERNS, CompoundTag::class); + if($patterns !== null){ foreach($patterns as $t){ $patternColor = $colorIdMap->fromInvertedId($t->getInt(self::TAG_PATTERN_COLOR)) ?? DyeColor::BLACK; //TODO: missing pattern colour should be an error $patternType = $patternIdMap->fromId($t->getString(self::TAG_PATTERN_NAME)); diff --git a/src/item/Item.php b/src/item/Item.php index af7cab433..e7c86e167 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -293,9 +293,8 @@ class Item implements \JsonSerializable{ $display = $tag->getCompoundTag(self::TAG_DISPLAY); if($display !== null){ $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName); - $lore = $display->getListTag(self::TAG_DISPLAY_LORE); - if($lore !== null && $lore->getTagType() === NBT::TAG_String){ - /** @var StringTag $t */ + $lore = $display->getListTag(self::TAG_DISPLAY_LORE, StringTag::class); + if($lore !== null){ foreach($lore as $t){ $this->lore[] = $t->getValue(); } @@ -303,9 +302,8 @@ class Item implements \JsonSerializable{ } $this->removeEnchantments(); - $enchantments = $tag->getListTag(self::TAG_ENCH); - if($enchantments !== null && $enchantments->getTagType() === NBT::TAG_Compound){ - /** @var CompoundTag $enchantment */ + $enchantments = $tag->getListTag(self::TAG_ENCH, CompoundTag::class); + if($enchantments !== null){ foreach($enchantments as $enchantment){ $magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1); $level = $enchantment->getShort(self::TAG_ENCH_LVL, 0); @@ -322,17 +320,15 @@ class Item implements \JsonSerializable{ $this->blockEntityTag = $tag->getCompoundTag(self::TAG_BLOCK_ENTITY_TAG); $this->canPlaceOn = []; - $canPlaceOn = $tag->getListTag(self::TAG_CAN_PLACE_ON); - if($canPlaceOn !== null && $canPlaceOn->getTagType() === NBT::TAG_String){ - /** @var StringTag $entry */ + $canPlaceOn = $tag->getListTag(self::TAG_CAN_PLACE_ON, StringTag::class); + if($canPlaceOn !== null){ foreach($canPlaceOn as $entry){ $this->canPlaceOn[$entry->getValue()] = $entry->getValue(); } } $this->canDestroy = []; - $canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY); - if($canDestroy !== null && $canDestroy->getTagType() === NBT::TAG_String){ - /** @var StringTag $entry */ + $canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY, StringTag::class); + if($canDestroy !== null){ foreach($canDestroy as $entry){ $this->canDestroy[$entry->getValue()] = $entry->getValue(); } diff --git a/src/item/WritableBookBase.php b/src/item/WritableBookBase.php index d3b9b7061..de6d5414c 100644 --- a/src/item/WritableBookBase.php +++ b/src/item/WritableBookBase.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\item; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; @@ -167,14 +166,12 @@ abstract class WritableBookBase extends Item{ $pages = $tag->getListTag(self::TAG_PAGES); if($pages !== null){ - if($pages->getTagType() === NBT::TAG_Compound){ //PE format - /** @var CompoundTag $page */ - foreach($pages as $page){ + if(($compoundPages = $pages->cast(CompoundTag::class)) !== null){ //PE format + foreach($compoundPages as $page){ $this->pages[] = new WritableBookPage(mb_scrub($page->getString(self::TAG_PAGE_TEXT), 'UTF-8'), $page->getString(self::TAG_PAGE_PHOTONAME, "")); } - }elseif($pages->getTagType() === NBT::TAG_String){ //PC format - /** @var StringTag $page */ - foreach($pages as $page){ + }elseif(($stringPages = $pages->cast(StringTag::class)) !== null){ //PC format + foreach($stringPages as $page){ $this->pages[] = new WritableBookPage(mb_scrub($page->getValue(), 'UTF-8')); } } diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index a6520ce12..2a3a4e8f3 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -42,6 +42,7 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; +use pocketmine\nbt\UnexpectedTagTypeException; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode; @@ -225,15 +226,14 @@ class TypeConverter{ * We don't need to, and should not allow, sending nested inventories across the network. */ protected function stripContainedItemNonVisualNBT(CompoundTag $tag) : bool{ - if( - ($blockEntityInventoryTag = $tag->getTag(Container::TAG_ITEMS)) !== null && - $blockEntityInventoryTag instanceof ListTag && - $blockEntityInventoryTag->getTagType() === NBT::TAG_Compound && - $blockEntityInventoryTag->count() > 0 - ){ + try{ + $blockEntityInventoryTag = $tag->getListTag(Container::TAG_ITEMS, CompoundTag::class); + }catch(UnexpectedTagTypeException){ + return false; + } + if($blockEntityInventoryTag !== null && $blockEntityInventoryTag->count() > 0){ $stripped = new ListTag(); - /** @var CompoundTag $itemTag */ foreach($blockEntityInventoryTag as $itemTag){ try{ $containedItem = Item::nbtDeserialize($itemTag); diff --git a/src/world/format/io/region/LegacyAnvilChunkTrait.php b/src/world/format/io/region/LegacyAnvilChunkTrait.php index 6e2f4c8f8..05c79ea63 100644 --- a/src/world/format/io/region/LegacyAnvilChunkTrait.php +++ b/src/world/format/io/region/LegacyAnvilChunkTrait.php @@ -87,12 +87,10 @@ trait LegacyAnvilChunkTrait{ } $subChunks = []; - $subChunksTag = $chunk->getListTag("Sections") ?? []; + $subChunksTag = $chunk->getListTag("Sections", CompoundTag::class) ?? []; foreach($subChunksTag as $subChunk){ - if($subChunk instanceof CompoundTag){ - $y = $subChunk->getByte("Y"); - $subChunks[$y] = $this->deserializeSubChunk($subChunk, clone $biomes3d, new \PrefixedLogger($logger, "Subchunk y=$y")); - } + $y = $subChunk->getByte("Y"); + $subChunks[$y] = $this->deserializeSubChunk($subChunk, clone $biomes3d, new \PrefixedLogger($logger, "Subchunk y=$y")); } for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ if(!isset($subChunks[$y])){ diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 8fe7928b8..9ce0a35de 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\world\format\io\region; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\ByteArrayTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; @@ -164,21 +163,11 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ * @throws CorruptedChunkException */ protected static function getCompoundList(string $context, ListTag $list) : array{ - if($list->count() === 0){ //empty lists might have wrong types, we don't care - return []; - } - if($list->getTagType() !== NBT::TAG_Compound){ + $compoundList = $list->cast(CompoundTag::class); + if($compoundList === null){ throw new CorruptedChunkException("Expected TAG_List for '$context'"); } - $result = []; - foreach($list as $tag){ - if(!($tag instanceof CompoundTag)){ - //this should never happen, but it's still possible due to lack of native type safety - throw new CorruptedChunkException("Expected TAG_List for '$context'"); - } - $result[] = $tag; - } - return $result; + return $compoundList->getValue(); } protected static function readFixedSizeByteArray(CompoundTag $chunk, string $tagName, int $length) : string{ diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index f40029365..11b73e86d 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -37,10 +37,10 @@ use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeNames; use pocketmine\inventory\json\CreativeGroupData; use pocketmine\nbt\LittleEndianNbtSerializer; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\TreeRoot; +use pocketmine\nbt\UnexpectedTagTypeException; use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\network\mcpe\convert\BlockTranslator; use pocketmine\network\mcpe\convert\ItemTranslator; @@ -554,8 +554,8 @@ class ParserPacketHandler extends PacketHandler{ if(!($tag instanceof CompoundTag)){ throw new AssumptionFailedError(); } - $idList = $tag->getTag("idlist"); - if(!($idList instanceof ListTag) || $idList->getTagType() !== NBT::TAG_Compound){ + $generic = $tag->getTag("idlist"); + if(!($generic instanceof ListTag) || ($idList = $generic->cast(CompoundTag::class)) === null){ echo $tag . "\n"; throw new \RuntimeException("expected TAG_List(\"idlist\") tag inside root TAG_Compound"); } @@ -565,9 +565,6 @@ class ParserPacketHandler extends PacketHandler{ } echo "updating legacy => string entity ID mapping table\n"; $map = []; - /** - * @var CompoundTag $thing - */ foreach($idList as $thing){ $map[$thing->getString("id")] = $thing->getInt("rid"); } From 1dea35026110f00248284997d21fd36ad8a40ff5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 19 Sep 2025 23:43:04 +0100 Subject: [PATCH 327/334] shut --- tools/generate-bedrock-data-from-packets.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 11b73e86d..01ff368ab 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -40,7 +40,6 @@ use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\TreeRoot; -use pocketmine\nbt\UnexpectedTagTypeException; use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\network\mcpe\convert\BlockTranslator; use pocketmine\network\mcpe\convert\ItemTranslator; From dd9cbb74f0bfeac9203e11f681db535cee55a0ef Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 20 Sep 2025 00:01:16 +0100 Subject: [PATCH 328/334] Added NeverSavedWithChunkEntity interface closes #6809 turns out we can actually use this for players too. we need this also for fireworks and probably lightning in the future. --- src/entity/Entity.php | 2 +- src/entity/NeverSavedWithChunkEntity.php | 36 ++++++++++++++++++++++++ src/player/Player.php | 3 +- src/world/World.php | 3 +- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/entity/NeverSavedWithChunkEntity.php diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 73a0b3a9c..89f66d6b1 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -492,7 +492,7 @@ abstract class Entity{ new FloatTag($this->location->pitch) ])); - if(!($this instanceof Player)){ + if(!($this instanceof NeverSavedWithChunkEntity)){ EntityFactory::getInstance()->injectSaveId(get_class($this), $nbt); if($this->getNameTag() !== ""){ diff --git a/src/entity/NeverSavedWithChunkEntity.php b/src/entity/NeverSavedWithChunkEntity.php new file mode 100644 index 000000000..2245abdd9 --- /dev/null +++ b/src/entity/NeverSavedWithChunkEntity.php @@ -0,0 +1,36 @@ +getId()); } } - if(!EntityFactory::getInstance()->isRegistered($entity::class) && !$entity instanceof Player){ + if(!EntityFactory::getInstance()->isRegistered($entity::class) && !$entity instanceof NeverSavedWithChunkEntity){ //canSaveWithChunk is mutable, so that means it could be toggled after adding the entity and cause a crash //later on. Better we just force all entities to have a save ID, even if it might not be needed. throw new \LogicException("Entity " . $entity::class . " is not registered for a save ID in EntityFactory"); From ebeee29a8842fdfdc5499e219a0ef395c0fa99ef Mon Sep 17 00:00:00 2001 From: IvanCraft623 <57236932+IvanCraft623@users.noreply.github.com> Date: Sat, 20 Sep 2025 11:04:05 -0600 Subject: [PATCH 329/334] Implement firework rocket & firework star (#5455) Co-authored-by: Dylan T Co-authored-by: ipad54 <63200545+ipad54@users.noreply.github.com> --- src/data/bedrock/FireworkRocketTypeIdMap.php | 45 ++++ src/data/bedrock/FireworkRocketTypeIds.php | 32 +++ .../ItemSerializerDeserializerRegistrar.php | 10 + .../animation/FireworkParticlesAnimation.php | 41 ++++ src/entity/object/FireworkRocket.php | 204 ++++++++++++++++++ src/event/player/PlayerDeathEvent.php | 5 +- src/item/FireworkRocket.php | 141 ++++++++++++ src/item/FireworkRocketExplosion.php | 190 ++++++++++++++++ src/item/FireworkRocketType.php | 46 ++++ src/item/FireworkStar.php | 112 ++++++++++ src/item/ItemTypeIds.php | 4 +- src/item/StringToItemParser.php | 3 + src/item/VanillaItems.php | 4 + src/world/sound/FireworkCrackleSound.php | 35 +++ src/world/sound/FireworkExplosionSound.php | 35 +++ .../sound/FireworkLargeExplosionSound.php | 35 +++ src/world/sound/FireworkLaunchSound.php | 35 +++ 17 files changed, 975 insertions(+), 2 deletions(-) create mode 100644 src/data/bedrock/FireworkRocketTypeIdMap.php create mode 100644 src/data/bedrock/FireworkRocketTypeIds.php create mode 100644 src/entity/animation/FireworkParticlesAnimation.php create mode 100644 src/entity/object/FireworkRocket.php create mode 100644 src/item/FireworkRocket.php create mode 100644 src/item/FireworkRocketExplosion.php create mode 100644 src/item/FireworkRocketType.php create mode 100644 src/item/FireworkStar.php create mode 100644 src/world/sound/FireworkCrackleSound.php create mode 100644 src/world/sound/FireworkExplosionSound.php create mode 100644 src/world/sound/FireworkLargeExplosionSound.php create mode 100644 src/world/sound/FireworkLaunchSound.php diff --git a/src/data/bedrock/FireworkRocketTypeIdMap.php b/src/data/bedrock/FireworkRocketTypeIdMap.php new file mode 100644 index 000000000..4358c2073 --- /dev/null +++ b/src/data/bedrock/FireworkRocketTypeIdMap.php @@ -0,0 +1,45 @@ + */ + use IntSaveIdMapTrait; + + private function __construct(){ + foreach(FireworkRocketType::cases() as $case){ + $this->register(match($case){ + FireworkRocketType::SMALL_BALL => FireworkRocketTypeIds::SMALL_BALL, + FireworkRocketType::LARGE_BALL => FireworkRocketTypeIds::LARGE_BALL, + FireworkRocketType::STAR => FireworkRocketTypeIds::STAR, + FireworkRocketType::CREEPER => FireworkRocketTypeIds::CREEPER, + FireworkRocketType::BURST => FireworkRocketTypeIds::BURST, + }, $case); + } + } +} diff --git a/src/data/bedrock/FireworkRocketTypeIds.php b/src/data/bedrock/FireworkRocketTypeIds.php new file mode 100644 index 000000000..7ddf0e6b9 --- /dev/null +++ b/src/data/bedrock/FireworkRocketTypeIds.php @@ -0,0 +1,32 @@ +map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::FEATHER, Items::FEATHER()); $this->map1to1Item(Ids::FERMENTED_SPIDER_EYE, Items::FERMENTED_SPIDER_EYE()); + $this->map1to1Item(Ids::FIREWORK_ROCKET, Items::FIREWORK_ROCKET()); $this->map1to1Item(Ids::FIRE_CHARGE, Items::FIRE_CHARGE()); $this->map1to1Item(Ids::FISHING_ROD, Items::FISHING_ROD()); $this->map1to1Item(Ids::FLINT, Items::FLINT()); @@ -501,6 +503,14 @@ final class ItemSerializerDeserializerRegistrar{ * in a unified manner. */ private function register1to1ItemWithMetaMappings() : void{ + $this->map1to1ItemWithMeta( + Ids::FIREWORK_STAR, + Items::FIREWORK_STAR(), + function(FireworkStar $item, int $meta) : void{ + // Colors will be defined by CompoundTag deserialization. + }, + fn(FireworkStar $item) => DyeColorIdMap::getInstance()->toInvertedId($item->getExplosion()->getFlashColor()) + ); $this->map1to1ItemWithMeta( Ids::GOAT_HORN, Items::GOAT_HORN(), diff --git a/src/entity/animation/FireworkParticlesAnimation.php b/src/entity/animation/FireworkParticlesAnimation.php new file mode 100644 index 000000000..cdeb44f03 --- /dev/null +++ b/src/entity/animation/FireworkParticlesAnimation.php @@ -0,0 +1,41 @@ +entity->getId(), ActorEvent::FIREWORK_PARTICLES, 0) + ]; + } +} diff --git a/src/entity/object/FireworkRocket.php b/src/entity/object/FireworkRocket.php new file mode 100644 index 000000000..1077dbc39 --- /dev/null +++ b/src/entity/object/FireworkRocket.php @@ -0,0 +1,204 @@ +maxFlightTimeTicks = $maxFlightTimeTicks; + $this->setExplosions($explosions); + + parent::__construct($location, $nbt); + } + + protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(0.25, 0.25); } + + protected function getInitialDragMultiplier() : float{ return 0.0; } + + protected function getInitialGravity() : float{ return 0.0; } + + /** + * Returns the total number of ticks the firework will fly for before it explodes. + */ + public function getMaxFlightTimeTicks() : int{ + return $this->maxFlightTimeTicks; + } + + /** + * Sets the total number of ticks the firework will fly for before it explodes. + * + * @return $this + */ + public function setMaxFlightTimeTicks(int $maxFlightTimeTicks) : self{ + if($maxFlightTimeTicks < 0){ + throw new \InvalidArgumentException("Max flight time ticks cannot be negative"); + } + $this->maxFlightTimeTicks = $maxFlightTimeTicks; + return $this; + } + + /** + * @return FireworkRocketExplosion[] + */ + public function getExplosions() : array{ + return $this->explosions; + } + + /** + * @param FireworkRocketExplosion[] $explosions + * + * @return $this + */ + public function setExplosions(array $explosions) : self{ + Utils::validateArrayValueType($explosions, function(FireworkRocketExplosion $_) : void{}); + $this->explosions = $explosions; + return $this; + } + + protected function onFirstUpdate(int $currentTick) : void{ + parent::onFirstUpdate($currentTick); + + $this->broadcastSound(new FireworkLaunchSound()); + } + + protected function entityBaseTick(int $tickDiff = 1) : bool{ + $hasUpdate = parent::entityBaseTick($tickDiff); + + if(!$this->isFlaggedForDespawn()){ + //Don't keep accelerating long-lived fireworks - this gets very rapidly out of control and makes the server + //die. Vanilla fireworks will only live for about 52 ticks maximum anyway, so this only makes sure plugin + //created fireworks don't murder the server + if($this->ticksLived < 60){ + $this->addMotion($this->motion->x * 0.15, 0.04, $this->motion->z * 0.15); + } + + if($this->ticksLived >= $this->maxFlightTimeTicks){ + $this->flagForDespawn(); + $this->explode(); + } + } + + return $hasUpdate; + } + + public function explode() : void{ + if(($explosionCount = count($this->explosions)) !== 0){ + $this->broadcastAnimation(new FireworkParticlesAnimation($this)); + foreach($this->explosions as $explosion){ + $this->broadcastSound($explosion->getType()->getExplosionSound()); + if($explosion->willTwinkle()){ + $this->broadcastSound(new FireworkCrackleSound()); + } + } + + $force = ($explosionCount * 2) + 5; + $world = $this->getWorld(); + foreach($world->getCollidingEntities($this->getBoundingBox()->expandedCopy(5, 5, 5), $this) as $entity){ + if(!$entity instanceof Living){ + continue; + } + + $position = $entity->getPosition(); + $distance = $position->distanceSquared($this->location); + if($distance > 25){ + continue; + } + + //cast two rays - one to the entity's feet and another to halfway up its body (according to Java, anyway) + //this seems like it'd miss some cases but who am I to argue with vanilla logic :> + $height = $entity->getBoundingBox()->getYLength(); + for($i = 0; $i < 2; $i++){ + $target = $position->add(0, 0.5 * $i * $height, 0); + foreach(VoxelRayTrace::betweenPoints($this->location, $target) as $blockPos){ + if($world->getBlock($blockPos)->calculateIntercept($this->location, $target) !== null){ + continue 2; //obstruction, try another path + } + } + + //no obstruction + $damage = $force * sqrt((5 - $position->distance($this->location)) / 5); + $ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_ENTITY_EXPLOSION, $damage); + $entity->attack($ev); + break; + } + } + } + } + + public function canBeCollidedWith() : bool{ + return false; + } + + protected function syncNetworkData(EntityMetadataCollection $properties) : void{ + parent::syncNetworkData($properties); + + $explosions = new ListTag(); + foreach($this->explosions as $explosion){ + $explosions->push($explosion->toCompoundTag()); + } + $fireworksData = CompoundTag::create() + ->setTag(FireworkItem::TAG_FIREWORK_DATA, CompoundTag::create() + ->setTag(FireworkItem::TAG_EXPLOSIONS, $explosions) + ); + + $properties->setCompoundTag(EntityMetadataProperties::FIREWORK_ITEM, new CacheableNbt($fireworksData)); + } +} diff --git a/src/event/player/PlayerDeathEvent.php b/src/event/player/PlayerDeathEvent.php index ca4b46564..4b02b4811 100644 --- a/src/event/player/PlayerDeathEvent.php +++ b/src/event/player/PlayerDeathEvent.php @@ -26,6 +26,7 @@ namespace pocketmine\event\player; use pocketmine\block\BlockTypeIds; use pocketmine\entity\Living; use pocketmine\entity\object\FallingBlock; +use pocketmine\entity\object\FireworkRocket; use pocketmine\entity\projectile\Trident; use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageByChildEntityEvent; @@ -164,7 +165,9 @@ class PlayerDeathEvent extends EntityDeathEvent{ case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION: if($deathCause instanceof EntityDamageByEntityEvent){ $e = $deathCause->getDamager(); - if($e instanceof Living){ + if($e instanceof FireworkRocket){ + return KnownTranslationFactory::death_attack_fireworks($name); + }elseif($e instanceof Living){ return KnownTranslationFactory::death_attack_explosion_player($name, $e->getDisplayName()); } } diff --git a/src/item/FireworkRocket.php b/src/item/FireworkRocket.php new file mode 100644 index 000000000..39fe6dae2 --- /dev/null +++ b/src/item/FireworkRocket.php @@ -0,0 +1,141 @@ +flightTimeMultiplier; + } + + /** + * Sets the value that will be used to calculate a randomized flight duration + * for the firework. + * + * The higher this value, the longer the flight duration. + * + * @return $this + */ + public function setFlightTimeMultiplier(int $multiplier) : self{ + if($multiplier < 1 || $multiplier > 127){ + throw new \InvalidArgumentException("Flight time multiplier must be in range 1-127"); + } + $this->flightTimeMultiplier = $multiplier; + + return $this; + } + + /** + * @return FireworkRocketExplosion[] + */ + public function getExplosions() : array{ + return $this->explosions; + } + + /** + * @param FireworkRocketExplosion[] $explosions + * + * @return $this + */ + public function setExplosions(array $explosions) : self{ + Utils::validateArrayValueType($explosions, function(FireworkRocketExplosion $_) : void{}); + $this->explosions = $explosions; + + return $this; + } + + public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{ + //TODO: this would be nicer if Vector3::getSide() accepted floats for distance + $position = $blockClicked->getPosition()->addVector($clickVector)->addVector(Vector3::zero()->getSide($face)->multiply(0.15)); + + $randomDuration = (($this->flightTimeMultiplier + 1) * 10) + mt_rand(0, 12); + + $entity = new FireworkEntity(Location::fromObject($position, $player->getWorld(), Utils::getRandomFloat() * 360, 90), $randomDuration, $this->explosions); + $entity->setOwningEntity($player); + $entity->setMotion(new Vector3( + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.0023, + 0.05, + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.0023 + )); + $entity->spawnToAll(); + + $this->pop(); + + return ItemUseResult::SUCCESS; + } + + protected function deserializeCompoundTag(CompoundTag $tag) : void{ + parent::deserializeCompoundTag($tag); + + $fireworkData = $tag->getCompoundTag(self::TAG_FIREWORK_DATA); + if($fireworkData === null){ + throw new SavedDataLoadingException("Missing firework data"); + } + + $this->setFlightTimeMultiplier($fireworkData->getByte(self::TAG_FLIGHT_TIME_MULTIPLIER, 1)); + + if(($explosions = $fireworkData->getListTag(self::TAG_EXPLOSIONS, CompoundTag::class)) !== null){ + foreach($explosions as $explosion){ + $this->explosions[] = FireworkRocketExplosion::fromCompoundTag($explosion); + } + } + } + + protected function serializeCompoundTag(CompoundTag $tag) : void{ + parent::serializeCompoundTag($tag); + + $fireworkData = CompoundTag::create(); + $fireworkData->setByte(self::TAG_FLIGHT_TIME_MULTIPLIER, $this->flightTimeMultiplier); + $fireworkData->setTag(self::TAG_EXPLOSIONS, new ListTag(array_map(fn(FireworkRocketExplosion $e) => $e->toCompoundTag(), $this->explosions))); + + $tag->setTag(self::TAG_FIREWORK_DATA, $fireworkData); + } +} diff --git a/src/item/FireworkRocketExplosion.php b/src/item/FireworkRocketExplosion.php new file mode 100644 index 000000000..e113a6ded --- /dev/null +++ b/src/item/FireworkRocketExplosion.php @@ -0,0 +1,190 @@ +getByteArray(self::TAG_COLORS)); + if(count($colors) === 0){ + throw new SavedDataLoadingException("Colors list cannot be empty"); + } + + return new self( + FireworkRocketTypeIdMap::getInstance()->fromId($tag->getByte(self::TAG_TYPE)) ?? throw new SavedDataLoadingException("Invalid firework type"), + $colors, + self::decodeColors($tag->getByteArray(self::TAG_FADE_COLORS)), + $tag->getByte(self::TAG_TWINKLE, 0) !== 0, + $tag->getByte(self::TAG_TRAIL, 0) !== 0 + ); + } + + /** + * @return DyeColor[] + * @phpstan-return list + * @throws SavedDataLoadingException + */ + protected static function decodeColors(string $colorsBytes) : array{ + $colors = []; + + $dyeColorIdMap = DyeColorIdMap::getInstance(); + for($i = 0, $len = strlen($colorsBytes); $i < $len; $i++){ + $colorByte = ord($colorsBytes[$i]); + $color = $dyeColorIdMap->fromInvertedId($colorByte); + if($color !== null){ + $colors[] = $color; + }else{ + throw new SavedDataLoadingException("Unknown color $colorByte"); + } + } + + return $colors; + } + + /** + * @param DyeColor[] $colors + */ + protected static function encodeColors(array $colors) : string{ + $colorsBytes = ""; + + $dyeColorIdMap = DyeColorIdMap::getInstance(); + foreach($colors as $color){ + $colorsBytes .= chr($dyeColorIdMap->toInvertedId($color)); + } + + return $colorsBytes; + } + + /** + * @param DyeColor[] $colors + * @param DyeColor[] $fadeColors + * @phpstan-param non-empty-list $colors + * @phpstan-param list $fadeColors + */ + public function __construct( + protected FireworkRocketType $type, + protected array $colors, + protected array $fadeColors = [], + protected bool $twinkle = false, + protected bool $trail = false + ){ + if(count($colors) === 0){ + throw new \InvalidArgumentException("Colors list cannot be empty"); + } + + $colorsValidator = function(DyeColor $_) : void{}; + + Utils::validateArrayValueType($colors, $colorsValidator); + Utils::validateArrayValueType($fadeColors, $colorsValidator); + } + + public function getType() : FireworkRocketType{ + return $this->type; + } + + /** + * Returns the colors of the particles. + * + * @return DyeColor[] + * @phpstan-return non-empty-list + */ + public function getColors() : array{ + return $this->colors; + } + + /** + * Returns the flash color of the explosion. + */ + public function getFlashColor() : DyeColor{ + return $this->colors[array_key_first($this->colors)]; + } + + /** + * Returns the mixure of colors from {@link FireworkRocketExplosion::getColors()}) + */ + public function getColorMix() : Color{ + /** @var Color[] $colors */ + $colors = []; + foreach($this->colors as $dyeColor){ + $colors[] = $dyeColor->getRgbValue(); + } + return Color::mix(...$colors); + } + + /** + * Returns the colors to which the particles will change their color after a few seconds. + * If it is empty, there will be no color change in the particles. + * + * @return DyeColor[] + * @phpstan-return list + */ + public function getFadeColors() : array{ + return $this->fadeColors; + } + + /** + * Returns whether the explosion has a flickering effect. + */ + public function willTwinkle() : bool{ + return $this->twinkle; + } + + /** + * Returns whether the particles have a trail effect. + */ + public function getTrail() : bool{ + return $this->trail; + } + + public function toCompoundTag() : CompoundTag{ + return CompoundTag::create() + ->setByte(self::TAG_TYPE, FireworkRocketTypeIdMap::getInstance()->toId($this->type)) + ->setByteArray(self::TAG_COLORS, self::encodeColors($this->colors)) + ->setByteArray(self::TAG_FADE_COLORS, self::encodeColors($this->fadeColors)) + ->setByte(self::TAG_TWINKLE, $this->twinkle ? 1 : 0) + ->setByte(self::TAG_TRAIL, $this->trail ? 1 : 0); + } +} diff --git a/src/item/FireworkRocketType.php b/src/item/FireworkRocketType.php new file mode 100644 index 000000000..6aa20e6d9 --- /dev/null +++ b/src/item/FireworkRocketType.php @@ -0,0 +1,46 @@ + new FireworkExplosionSound(), + self::LARGE_BALL => new FireworkLargeExplosionSound(), + }; + } +} diff --git a/src/item/FireworkStar.php b/src/item/FireworkStar.php new file mode 100644 index 000000000..ed2c3e525 --- /dev/null +++ b/src/item/FireworkStar.php @@ -0,0 +1,112 @@ +explosion = new FireworkRocketExplosion( + FireworkRocketType::SMALL_BALL, + colors: [DyeColor::BLACK], + fadeColors: [], + twinkle: false, + trail: false + ); + } + + public function getExplosion() : FireworkRocketExplosion{ + return $this->explosion; + } + + /** @return $this */ + public function setExplosion(FireworkRocketExplosion $explosion) : self{ + $this->explosion = $explosion; + return $this; + } + + /** + * Returns the displayed color of the item. + * The mixture of explosion colors, or the custom color if it is set. + */ + public function getColor() : Color{ + return $this->customColor ?? $this->explosion->getColorMix(); + } + + /** + * Returns the displayed custom color of the item that overrides + * the mixture of explosion colors, or null is it is not set. + */ + public function getCustomColor() : ?Color{ + return $this->customColor; + } + + /** + * Sets the displayed custom color of the item that overrides + * the mixture of explosion colors, or removes if $color is null. + * + * @return $this + */ + public function setCustomColor(?Color $color) : self{ + $this->customColor = $color; + return $this; + } + + protected function deserializeCompoundTag(CompoundTag $tag) : void{ + parent::deserializeCompoundTag($tag); + + $explosionTag = $tag->getTag(self::TAG_EXPLOSION); + if(!$explosionTag instanceof CompoundTag){ + throw new SavedDataLoadingException("Missing explosion data"); + } + $this->explosion = FireworkRocketExplosion::fromCompoundTag($explosionTag); + + $customColor = Color::fromARGB(Binary::unsignInt($tag->getInt(self::TAG_CUSTOM_COLOR))); + $color = $this->explosion->getColorMix(); + if(!$customColor->equals($color)){ //check that $customColor is actually custom. + $this->customColor = $customColor; + } + } + + protected function serializeCompoundTag(CompoundTag $tag) : void{ + parent::serializeCompoundTag($tag); + + $tag->setTag(self::TAG_EXPLOSION, $this->explosion->toCompoundTag()); + $tag->setInt(self::TAG_CUSTOM_COLOR, Binary::signInt($this->getColor()->toARGB())); + } +} diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index 36fc2c65f..3595d3afc 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -347,8 +347,10 @@ final class ItemTypeIds{ public const SPRUCE_HANGING_SIGN = 20308; public const WARPED_HANGING_SIGN = 20309; public const TRIDENT = 20310; + public const FIREWORK_ROCKET = 20311; + public const FIREWORK_STAR = 20312; - public const FIRST_UNUSED_ITEM_ID = 20311; + public const FIRST_UNUSED_ITEM_ID = 20313; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 5e45ea25d..63fa88538 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1366,6 +1366,9 @@ final class StringToItemParser extends StringToTParser{ $result->register("eye_drops", fn() => Items::MEDICINE()->setType(MedicineType::EYE_DROPS)); $result->register("feather", fn() => Items::FEATHER()); $result->register("fermented_spider_eye", fn() => Items::FERMENTED_SPIDER_EYE()); + $result->register("firework_rocket", fn() => Items::FIREWORK_ROCKET()); + $result->register("firework_star", fn() => Items::FIREWORK_STAR()); + $result->register("fireworks", fn() => Items::FIREWORK_ROCKET()); $result->register("fire_charge", fn() => Items::FIRE_CHARGE()); $result->register("fish", fn() => Items::RAW_FISH()); $result->register("fishing_rod", fn() => Items::FISHING_ROD()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 48ae95c32..31a62d1aa 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -168,6 +168,8 @@ use function strtolower; * @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item FEATHER() * @method static Item FERMENTED_SPIDER_EYE() + * @method static FireworkRocket FIREWORK_ROCKET() + * @method static FireworkStar FIREWORK_STAR() * @method static FireCharge FIRE_CHARGE() * @method static FishingRod FISHING_ROD() * @method static Item FLINT() @@ -511,6 +513,8 @@ final class VanillaItems{ self::register("experience_bottle", fn(IID $id) => new ExperienceBottle($id, "Bottle o' Enchanting")); self::register("feather", fn(IID $id) => new Item($id, "Feather")); self::register("fermented_spider_eye", fn(IID $id) => new Item($id, "Fermented Spider Eye")); + self::register("firework_rocket", fn(IID $id) => new FireworkRocket($id, "Firework Rocket")); + self::register("firework_star", fn(IID $id) => new FireworkStar($id, "Firework Star")); self::register("fire_charge", fn(IID $id) => new FireCharge($id, "Fire Charge")); self::register("fishing_rod", fn(IID $id) => new FishingRod($id, "Fishing Rod", [EnchantmentTags::FISHING_ROD])); self::register("flint", fn(IID $id) => new Item($id, "Flint")); diff --git a/src/world/sound/FireworkCrackleSound.php b/src/world/sound/FireworkCrackleSound.php new file mode 100644 index 000000000..c0e897d70 --- /dev/null +++ b/src/world/sound/FireworkCrackleSound.php @@ -0,0 +1,35 @@ + Date: Sat, 20 Sep 2025 22:40:38 +0100 Subject: [PATCH 330/334] Implement new OpenID authentication system (#6798) Co-authored-by: Dries C <15795262+dries-c@users.noreply.github.com> --- src/Server.php | 8 + src/network/mcpe/JwtUtils.php | 69 +++-- src/network/mcpe/auth/AuthJwtHelper.php | 165 ++++++++++++ src/network/mcpe/auth/AuthKeyProvider.php | 164 ++++++++++++ src/network/mcpe/auth/AuthKeyring.php | 45 ++++ src/network/mcpe/auth/FetchAuthKeysTask.php | 209 +++++++++++++++ .../mcpe/auth/ProcessLegacyLoginTask.php | 121 +++++++++ src/network/mcpe/auth/ProcessLoginTask.php | 213 ---------------- .../mcpe/auth/ProcessOpenIdLoginTask.php | 98 +++++++ .../mcpe/handler/LoginPacketHandler.php | 241 +++++++++++------- tests/phpstan/configs/actual-problems.neon | 6 - 11 files changed, 1012 insertions(+), 327 deletions(-) create mode 100644 src/network/mcpe/auth/AuthJwtHelper.php create mode 100644 src/network/mcpe/auth/AuthKeyProvider.php create mode 100644 src/network/mcpe/auth/AuthKeyring.php create mode 100644 src/network/mcpe/auth/FetchAuthKeysTask.php create mode 100644 src/network/mcpe/auth/ProcessLegacyLoginTask.php delete mode 100644 src/network/mcpe/auth/ProcessLoginTask.php create mode 100644 src/network/mcpe/auth/ProcessOpenIdLoginTask.php diff --git a/src/Server.php b/src/Server.php index d6f0a8415..4f0fa4ce1 100644 --- a/src/Server.php +++ b/src/Server.php @@ -50,6 +50,7 @@ use pocketmine\lang\Language; use pocketmine\lang\LanguageNotFoundException; use pocketmine\lang\Translatable; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\network\mcpe\auth\AuthKeyProvider; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\CompressBatchTask; use pocketmine\network\mcpe\compression\Compressor; @@ -270,6 +271,7 @@ class Server{ private int $maxPlayers; private bool $onlineMode = true; + private AuthKeyProvider $authKeyProvider; private Network $network; private bool $networkCompressionAsync = true; @@ -982,6 +984,8 @@ class Server{ $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled())); } + $this->authKeyProvider = new AuthKeyProvider(new \PrefixedLogger($this->logger, "Minecraft Auth Key Provider"), $this->asyncPool); + if($this->configGroup->getConfigBool(ServerProperties::HARDCORE, false) && $this->getDifficulty() < World::DIFFICULTY_HARD){ $this->configGroup->setConfigInt(ServerProperties::DIFFICULTY, World::DIFFICULTY_HARD); } @@ -1800,6 +1804,10 @@ class Server{ return $this->forceLanguage; } + public function getAuthKeyProvider() : AuthKeyProvider{ + return $this->authKeyProvider; + } + public function getNetwork() : Network{ return $this->network; } diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index 987ed6e61..dfdfada83 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; use function base64_decode; @@ -32,6 +33,7 @@ use function bin2hex; use function chr; use function count; use function explode; +use function hex2bin; use function is_array; use function json_decode; use function json_encode; @@ -54,6 +56,7 @@ use function strlen; use function strtr; use function substr; use const JSON_THROW_ON_ERROR; +use const OPENSSL_ALGO_SHA256; use const OPENSSL_ALGO_SHA384; use const STR_PAD_LEFT; @@ -170,17 +173,17 @@ final class JwtUtils{ /** * @throws JwtException */ - public static function verify(string $jwt, \OpenSSLAsymmetricKey $signingKey) : bool{ + public static function verify(string $jwt, string $signingKeyDer, bool $ec) : bool{ [$header, $body, $signature] = self::split($jwt); $rawSignature = self::b64UrlDecode($signature); - $derSignature = self::rawSignatureToDer($rawSignature); + $derSignature = $ec ? self::rawSignatureToDer($rawSignature) : $rawSignature; $v = openssl_verify( $header . '.' . $body, $derSignature, - $signingKey, - self::SIGNATURE_ALGORITHM + self::derPublicKeyToPem($signingKeyDer), + $ec ? self::SIGNATURE_ALGORITHM : OPENSSL_ALGO_SHA256 ); switch($v){ case 0: return false; @@ -238,22 +241,56 @@ final class JwtUtils{ throw new AssumptionFailedError("OpenSSL resource contains invalid public key"); } + /** + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + */ + private static function encodeDerLength(int $length) : string{ + if ($length <= 0x7F) { + return chr($length); + } + + $lengthBytes = ltrim(Binary::writeInt($length), "\x00"); + + return chr(0x80 | strlen($lengthBytes)) . $lengthBytes; + } + + private static function encodeDerBytes(int $tag, string $data) : string{ + return chr($tag) . self::encodeDerLength(strlen($data)) . $data; + } + public static function parseDerPublicKey(string $derKey) : \OpenSSLAsymmetricKey{ - $signingKeyOpenSSL = openssl_pkey_get_public(sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", base64_encode($derKey))); + $signingKeyOpenSSL = openssl_pkey_get_public(self::derPublicKeyToPem($derKey)); if($signingKeyOpenSSL === false){ throw new JwtException("OpenSSL failed to parse key: " . openssl_error_string()); } - $details = openssl_pkey_get_details($signingKeyOpenSSL); - if($details === false){ - throw new JwtException("OpenSSL failed to get details from key: " . openssl_error_string()); - } - if(!isset($details['ec']['curve_name'])){ - throw new JwtException("Expected an EC key"); - } - $curve = $details['ec']['curve_name']; - if($curve !== self::BEDROCK_SIGNING_KEY_CURVE_NAME){ - throw new JwtException("Key must belong to curve " . self::BEDROCK_SIGNING_KEY_CURVE_NAME . ", got $curve"); - } return $signingKeyOpenSSL; } + + public static function derPublicKeyToPem(string $derKey) : string{ + return sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", base64_encode($derKey)); + } + + /** + * Create a public key represented in DER format from RSA modulus and exponent information + * + * @param string $nBase64 The RSA modulus encoded in Base64 + * @param string $eBase64 The RSA exponent encoded in Base64 + */ + public static function rsaPublicKeyModExpToDer(string $nBase64, string $eBase64) : string{ + $mod = self::b64UrlDecode($nBase64); + $exp = self::b64UrlDecode($eBase64); + + $modulus = self::encodeDerBytes(2, $mod); + $publicExponent = self::encodeDerBytes(2, $exp); + + $rsaPublicKey = self::encodeDerBytes(48, $modulus . $publicExponent); + + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = hex2bin('300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $rsaPublicKey = chr(0) . $rsaPublicKey; + $rsaPublicKey = self::encodeDerBytes(3, $rsaPublicKey); + + return self::encodeDerBytes(48, $rsaOID . $rsaPublicKey); + } } diff --git a/src/network/mcpe/auth/AuthJwtHelper.php b/src/network/mcpe/auth/AuthJwtHelper.php new file mode 100644 index 000000000..5050c396f --- /dev/null +++ b/src/network/mcpe/auth/AuthJwtHelper.php @@ -0,0 +1,165 @@ +nbf) && $claims->nbf > $time + self::CLOCK_DRIFT_MAX){ + throw new VerifyLoginException("JWT not yet valid", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooEarly()); + } + + if(isset($claims->exp) && $claims->exp < $time - self::CLOCK_DRIFT_MAX){ + throw new VerifyLoginException("JWT expired", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooLate()); + } + } + + /** + * @throws VerifyLoginException if errors are encountered + */ + public static function validateOpenIdAuthToken(string $jwt, string $signingKeyDer, string $issuer, string $audience) : XboxAuthJwtBody{ + try{ + if(!JwtUtils::verify($jwt, $signingKeyDer, ec: false)){ + throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature()); + } + }catch(JwtException $e){ + throw new VerifyLoginException($e->getMessage(), null, 0, $e); + } + + try{ + [, $claimsArray, ] = JwtUtils::parse($jwt); + }catch(JwtException $e){ + throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case + $mapper->bExceptionOnMissingData = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + $mapper->bRemoveUndefinedAttributes = true; + + try{ + //nasty dynamic new for JsonMapper + $claims = $mapper->map($claimsArray, new XboxAuthJwtBody()); + }catch(\JsonMapper_Exception $e){ + throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e); + } + + if(!isset($claims->iss) || $claims->iss !== $issuer){ + throw new VerifyLoginException("Invalid JWT issuer"); + } + + if(!isset($claims->aud) || $claims->aud !== $audience){ + throw new VerifyLoginException("Invalid JWT audience"); + } + + self::checkExpiry($claims); + + return $claims; + } + + /** + * @throws VerifyLoginException if errors are encountered + */ + public static function validateLegacyAuthToken(string $jwt, ?string $expectedKeyDer) : LegacyAuthJwtBody{ + self::validateSelfSignedToken($jwt, $expectedKeyDer); + + //TODO: this parses the JWT twice and throws away a bunch of parts, optimize this + [, $claimsArray, ] = JwtUtils::parse($jwt); + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case + $mapper->bExceptionOnMissingData = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + $mapper->bRemoveUndefinedAttributes = true; + try{ + /** @var LegacyAuthJwtBody $claims */ + $claims = $mapper->map($claimsArray, new LegacyAuthJwtBody()); + }catch(\JsonMapper_Exception $e){ + throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e); + } + + self::checkExpiry($claims); + + return $claims; + } + + public static function validateSelfSignedToken(string $jwt, ?string $expectedKeyDer) : void{ + try{ + [$headersArray, ] = JwtUtils::parse($jwt); + }catch(JwtException $e){ + throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnMissingData = true; + $mapper->bExceptionOnUndefinedProperty = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + + try{ + /** @var SelfSignedJwtHeader $headers */ + $headers = $mapper->map($headersArray, new SelfSignedJwtHeader()); + }catch(\JsonMapper_Exception $e){ + throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e); + } + + $headerDerKey = base64_decode($headers->x5u, true); + if($headerDerKey === false){ + throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u"); + } + if($expectedKeyDer !== null && $headerDerKey !== $expectedKeyDer){ + //Fast path: if the header key doesn't match what we expected, the signature isn't going to validate anyway + throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature()); + } + + try{ + if(!JwtUtils::verify($jwt, $headerDerKey, ec: true)){ + throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature()); + } + }catch(JwtException $e){ + throw new VerifyLoginException($e->getMessage(), null, 0, $e); + } + } +} diff --git a/src/network/mcpe/auth/AuthKeyProvider.php b/src/network/mcpe/auth/AuthKeyProvider.php new file mode 100644 index 000000000..67ffe4908 --- /dev/null +++ b/src/network/mcpe/auth/AuthKeyProvider.php @@ -0,0 +1,164 @@ + */ + private ?PromiseResolver $resolver = null; + + private int $lastFetch = 0; + + public function __construct( + private readonly \Logger $logger, + private readonly AsyncPool $asyncPool, + private readonly int $keyRefreshIntervalSeconds = self::ALLOWED_REFRESH_INTERVAL + ){} + + /** + * Fetches the key for the given key ID. + * The promise will be resolved with an array of [issuer, pemPublicKey]. + * + * @phpstan-return Promise + */ + public function getKey(string $keyId) : Promise{ + /** @phpstan-var PromiseResolver $resolver */ + $resolver = new PromiseResolver(); + + if( + $this->keyring === null || //we haven't fetched keys yet + ($this->keyring->getKey($keyId) === null && $this->lastFetch < time() - $this->keyRefreshIntervalSeconds) //we don't recognise this one & keys might be outdated + ){ + //only refresh keys when we see one we don't recognise + $this->fetchKeys()->onCompletion( + onSuccess: fn(AuthKeyring $newKeyring) => $this->resolveKey($resolver, $newKeyring, $keyId), + onFailure: $resolver->reject(...) + ); + }else{ + $this->resolveKey($resolver, $this->keyring, $keyId); + } + + return $resolver->getPromise(); + } + + /** + * @phpstan-param PromiseResolver $resolver + */ + private function resolveKey(PromiseResolver $resolver, AuthKeyring $keyring, string $keyId) : void{ + $key = $keyring->getKey($keyId); + if($key === null){ + $this->logger->debug("Key $keyId not recognised!"); + $resolver->reject(); + return; + } + + $this->logger->debug("Key $keyId found in keychain"); + $resolver->resolve([$keyring->getIssuer(), $key]); + } + + /** + * @phpstan-param array|null $keys + * @phpstan-param string[]|null $errors + */ + private function onKeysFetched(?array $keys, string $issuer, ?array $errors) : void{ + $resolver = $this->resolver; + if($resolver === null){ + throw new AssumptionFailedError("Not expecting this to be called without a resolver present"); + } + if($errors !== null){ + $this->logger->error("The following errors occurred while fetching new keys:\n\t- " . implode("\n\t-", $errors)); + //we might've still succeeded in fetching keys even if there were errors, so don't return + } + + if($keys === null){ + $this->logger->critical("Failed to fetch authentication keys from Mojang's API. Xbox players may not be able to authenticate!"); + $resolver->reject(); + }else{ + $pemKeys = []; + foreach($keys as $keyModel){ + if($keyModel->use !== "sig" || $keyModel->kty !== "RSA"){ + $this->logger->error("Key ID $keyModel->kid doesn't have the expected properties: expected use=sig, kty=RSA, got use=$keyModel->use, kty=$keyModel->kty"); + continue; + } + $derKey = JwtUtils::rsaPublicKeyModExpToDer($keyModel->n, $keyModel->e); + + //make sure the key is valid + try{ + JwtUtils::parseDerPublicKey($derKey); + }catch(JwtException $e){ + $this->logger->error("Failed to parse RSA public key for key ID $keyModel->kid: " . $e->getMessage()); + $this->logger->logException($e); + continue; + } + + //retain PEM keys instead of OpenSSLAsymmetricKey since these are easier and cheaper to copy between threads + $pemKeys[$keyModel->kid] = $derKey; + } + + if(count($keys) === 0){ + $this->logger->critical("No valid authentication keys returned by Mojang's API. Xbox players may not be able to authenticate!"); + $resolver->reject(); + }else{ + $this->logger->info("Successfully fetched " . count($keys) . " new authentication keys from issuer $issuer, key IDs: " . implode(", ", array_keys($pemKeys))); + $this->keyring = new AuthKeyring($issuer, $pemKeys); + $this->lastFetch = time(); + $resolver->resolve($this->keyring); + } + } + } + + /** + * @phpstan-return Promise + */ + private function fetchKeys() : Promise{ + if($this->resolver !== null){ + $this->logger->debug("Key refresh was requested, but it's already in progress"); + return $this->resolver->getPromise(); + } + + $this->logger->notice("Fetching new authentication keys"); + + /** @phpstan-var PromiseResolver $resolver */ + $resolver = new PromiseResolver(); + $this->resolver = $resolver; + //TODO: extract this so it can be polyfilled for unit testing + $this->asyncPool->submitTask(new FetchAuthKeysTask($this->onKeysFetched(...))); + return $this->resolver->getPromise(); + } +} diff --git a/src/network/mcpe/auth/AuthKeyring.php b/src/network/mcpe/auth/AuthKeyring.php new file mode 100644 index 000000000..cd5d29f6e --- /dev/null +++ b/src/network/mcpe/auth/AuthKeyring.php @@ -0,0 +1,45 @@ + $keys + */ + public function __construct( + private string $issuer, + private array $keys + ){} + + public function getIssuer() : string{ return $this->issuer; } + + /** + * Returns a (raw) DER public key associated with the given key ID + */ + public function getKey(string $keyId) : ?string{ + return $this->keys[$keyId] ?? null; + } +} diff --git a/src/network/mcpe/auth/FetchAuthKeysTask.php b/src/network/mcpe/auth/FetchAuthKeysTask.php new file mode 100644 index 000000000..b159d42af --- /dev/null +++ b/src/network/mcpe/auth/FetchAuthKeysTask.php @@ -0,0 +1,209 @@ +> */ + private ?NonThreadSafeValue $keys = null; + private string $issuer; + + /** @phpstan-var ?NonThreadSafeValue> */ + private ?NonThreadSafeValue $errors = null; + + /** + * @phpstan-param \Closure(?array $keys, string $issuer, ?string[] $errors) : void $onCompletion + */ + public function __construct( + \Closure $onCompletion + ){ + $this->storeLocal(self::KEYS_ON_COMPLETION, $onCompletion); + } + + public function onRun() : void{ + /** @var string[] $errors */ + $errors = []; + + try{ + $authServiceUri = $this->getAuthServiceURI(); + }catch(\RuntimeException $e){ + $errors[] = $e->getMessage(); + $authServiceUri = self::AUTHORIZATION_SERVICE_URI_FALLBACK; + } + + try { + $openIdConfig = $this->getOpenIdConfiguration($authServiceUri); + $jwksUri = $openIdConfig->jwks_uri; + + $this->issuer = $openIdConfig->issuer; + } catch (\RuntimeException $e) { + $errors[] = $e->getMessage(); + $jwksUri = $authServiceUri . self::AUTHORIZATION_SERVICE_KEYS_PATH; + + $this->issuer = $authServiceUri; + } + + try{ + $this->keys = new NonThreadSafeValue($this->getKeys($jwksUri)); + }catch(\RuntimeException $e){ + $errors[] = $e->getMessage(); + } + + $this->errors = $errors === [] ? null : new NonThreadSafeValue($errors); + } + + private function getAuthServiceURI() : string{ + $result = Internet::getURL(self::MINECRAFT_SERVICES_DISCOVERY_URL); + if($result === null || $result->getCode() !== 200){ + throw new \RuntimeException("Failed to fetch Minecraft services discovery document"); + } + + try{ + $json = json_decode($result->getBody(), false, flags: JSON_THROW_ON_ERROR); + }catch(\JsonException $e){ + throw new \RuntimeException($e->getMessage(), 0, $e); + } + if(!is_object($json)){ + throw new \RuntimeException("Unexpected root type of schema file " . gettype($json) . ", expected object"); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case + $mapper->bExceptionOnMissingData = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + $mapper->bRemoveUndefinedAttributes = true; + try{ + /** @var MinecraftServicesDiscovery $discovery */ + $discovery = $mapper->map($json, new MinecraftServicesDiscovery()); + }catch(\JsonMapper_Exception $e){ + throw new \RuntimeException("Invalid schema file: " . $e->getMessage(), 0, $e); + } + + return $discovery->result->serviceEnvironments->auth->prod->serviceUri; + } + + private function getOpenIdConfiguration(string $authServiceUri) : AuthServiceOpenIdConfiguration{ + $result = Internet::getURL($authServiceUri . self::AUTHORIZATION_SERVICE_OPENID_CONFIGURATION_PATH); + if($result === null || $result->getCode() !== 200){ + throw new \RuntimeException("Failed to fetch OpenID configuration from authorization service"); + } + + try{ + $json = json_decode($result->getBody(), false, flags: JSON_THROW_ON_ERROR); + }catch(\JsonException $e){ + throw new \RuntimeException($e->getMessage(), 0, $e); + } + if(!is_object($json)){ + throw new \RuntimeException("Unexpected root type of schema file " . gettype($json) . ", expected object"); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case + $mapper->bExceptionOnMissingData = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + $mapper->bRemoveUndefinedAttributes = true; + try{ + /** @var AuthServiceOpenIdConfiguration $configuration */ + $configuration = $mapper->map($json, new AuthServiceOpenIdConfiguration()); + }catch(\JsonMapper_Exception $e){ + throw new \RuntimeException("Invalid schema file: " . $e->getMessage(), 0, $e); + } + + return $configuration; + } + + /** + * @return array keys indexed by key ID + */ + private function getKeys(string $jwksUri) : array{ + $result = Internet::getURL($jwksUri); + if($result === null || $result->getCode() !== 200){ + return throw new \RuntimeException("Failed to fetch keys from authorization service"); + } + + try{ + $json = json_decode($result->getBody(), true, flags: JSON_THROW_ON_ERROR); + }catch(\JsonException $e){ + throw new \RuntimeException($e->getMessage(), 0, $e); + } + + if(!is_array($json) || !isset($json["keys"]) || !is_array($keysArray = $json["keys"])){ + throw new \RuntimeException("Unexpected root type of schema file " . gettype($json) . ", expected object"); + } + + $mapper = new \JsonMapper(); + $mapper->bExceptionOnUndefinedProperty = true; + $mapper->bExceptionOnMissingData = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + $mapper->bRemoveUndefinedAttributes = true; + + $keys = []; + foreach($keysArray as $keyJson){ + if(!is_array($keyJson)){ + throw new \RuntimeException("Unexpected key type in schema file: " . gettype($keyJson) . ", expected object"); + } + + try{ + /** @var AuthServiceKey $key */ + $key = $mapper->map($keyJson, new AuthServiceKey()); + $keys[$key->kid] = $key; + }catch(\JsonMapper_Exception $e){ + throw new \RuntimeException("Invalid schema file: " . $e->getMessage(), 0, $e); + } + } + + return $keys; + } + + public function onCompletion() : void{ + /** + * @var \Closure $callback + * @phpstan-var \Closure(?AuthServiceKey[] $keys, string $issuer, ?string[] $errors) : void $callback + */ + $callback = $this->fetchLocal(self::KEYS_ON_COMPLETION); + $callback($this->keys?->deserialize(), $this->issuer, $this->errors?->deserialize()); + } +} diff --git a/src/network/mcpe/auth/ProcessLegacyLoginTask.php b/src/network/mcpe/auth/ProcessLegacyLoginTask.php new file mode 100644 index 000000000..2be55dab6 --- /dev/null +++ b/src/network/mcpe/auth/ProcessLegacyLoginTask.php @@ -0,0 +1,121 @@ +|string|null + */ + private NonThreadSafeValue|string|null $error = "Unknown"; + /** Whether the player has a certificate chain link signed by the given root public key. */ + private bool $authenticated = false; + private ?string $clientPublicKeyDer = null; + + /** + * @param string[] $chainJwts + * @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, Translatable|string|null $error, ?string $clientPublicKey) : void $onCompletion + */ + public function __construct( + array $chainJwts, + private string $clientDataJwt, + private ?string $rootAuthKeyDer, + private bool $authRequired, + \Closure $onCompletion + ){ + $this->storeLocal(self::TLS_KEY_ON_COMPLETION, $onCompletion); + $this->chain = igbinary_serialize($chainJwts) ?? throw new AssumptionFailedError("This should never fail"); + } + + public function onRun() : void{ + try{ + $this->clientPublicKeyDer = $this->validateChain(); + AuthJwtHelper::validateSelfSignedToken($this->clientDataJwt, $this->clientPublicKeyDer); + $this->error = null; + }catch(VerifyLoginException $e){ + $disconnectMessage = $e->getDisconnectMessage(); + $this->error = $disconnectMessage instanceof Translatable ? new NonThreadSafeValue($disconnectMessage) : $disconnectMessage; + } + } + + private function validateChain() : string{ + /** @var string[] $chain */ + $chain = igbinary_unserialize($this->chain); + + $identityPublicKeyDer = null; + + foreach($chain as $jwt){ + $claims = AuthJwtHelper::validateLegacyAuthToken($jwt, $identityPublicKeyDer); + if($this->rootAuthKeyDer !== null && $identityPublicKeyDer === $this->rootAuthKeyDer){ + $this->authenticated = true; //we're signed into xbox live, according to this root key + } + if(!isset($claims->identityPublicKey)){ + throw new VerifyLoginException("Missing identityPublicKey in chain link", KnownTranslationFactory::pocketmine_disconnect_invalidSession_missingKey()); + } + $identityPublicKey = base64_decode($claims->identityPublicKey, true); + if($identityPublicKey === false){ + throw new VerifyLoginException("Invalid identityPublicKey: base64 error decoding"); + } + $identityPublicKeyDer = $identityPublicKey; + } + + if($identityPublicKeyDer === null){ + throw new VerifyLoginException("No authentication chain links provided"); + } + + return $identityPublicKeyDer; + } + + public function onCompletion() : void{ + /** + * @var \Closure $callback + * @phpstan-var \Closure(bool, bool, Translatable|string|null, ?string) : void $callback + */ + $callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION); + $callback($this->authenticated, $this->authRequired, $this->error instanceof NonThreadSafeValue ? $this->error->deserialize() : $this->error, $this->clientPublicKeyDer); + } +} diff --git a/src/network/mcpe/auth/ProcessLoginTask.php b/src/network/mcpe/auth/ProcessLoginTask.php deleted file mode 100644 index 218edc7a5..000000000 --- a/src/network/mcpe/auth/ProcessLoginTask.php +++ /dev/null @@ -1,213 +0,0 @@ -|string|null - */ - private NonThreadSafeValue|string|null $error = "Unknown"; - /** - * Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang - * root public key. - */ - private bool $authenticated = false; - private ?string $clientPublicKey = null; - - /** - * @param string[] $chainJwts - * @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, Translatable|string|null $error, ?string $clientPublicKey) : void $onCompletion - */ - public function __construct( - array $chainJwts, - private string $clientDataJwt, - private bool $authRequired, - \Closure $onCompletion - ){ - $this->storeLocal(self::TLS_KEY_ON_COMPLETION, $onCompletion); - $this->chain = igbinary_serialize($chainJwts); - } - - public function onRun() : void{ - try{ - $this->clientPublicKey = $this->validateChain(); - $this->error = null; - }catch(VerifyLoginException $e){ - $disconnectMessage = $e->getDisconnectMessage(); - $this->error = $disconnectMessage instanceof Translatable ? new NonThreadSafeValue($disconnectMessage) : $disconnectMessage; - } - } - - private function validateChain() : string{ - /** @var string[] $chain */ - $chain = igbinary_unserialize($this->chain); - - $currentKey = null; - $first = true; - - foreach($chain as $jwt){ - $this->validateToken($jwt, $currentKey, $first); - if($first){ - $first = false; - } - } - - /** @var string $clientKey */ - $clientKey = $currentKey; - - $this->validateToken($this->clientDataJwt, $currentKey); - - return $clientKey; - } - - /** - * @throws VerifyLoginException if errors are encountered - */ - private function validateToken(string $jwt, ?string &$currentPublicKey, bool $first = false) : void{ - try{ - [$headersArray, $claimsArray, ] = JwtUtils::parse($jwt); - }catch(JwtException $e){ - throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e); - } - - $mapper = new \JsonMapper(); - $mapper->bExceptionOnMissingData = true; - $mapper->bExceptionOnUndefinedProperty = true; - $mapper->bStrictObjectTypeChecking = true; - $mapper->bEnforceMapType = false; - - try{ - /** @var SelfSignedJwtHeader $headers */ - $headers = $mapper->map($headersArray, new SelfSignedJwtHeader()); - }catch(\JsonMapper_Exception $e){ - throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e); - } - - $headerDerKey = base64_decode($headers->x5u, true); - if($headerDerKey === false){ - throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u"); - } - - if($currentPublicKey === null){ - if(!$first){ - throw new VerifyLoginException("Missing JWT public key", KnownTranslationFactory::pocketmine_disconnect_invalidSession_missingKey()); - } - }elseif($headerDerKey !== $currentPublicKey){ - //Fast path: if the header key doesn't match what we expected, the signature isn't going to validate anyway - throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature()); - } - - try{ - $signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey); - }catch(JwtException $e){ - throw new VerifyLoginException("Invalid JWT public key: " . $e->getMessage(), null, 0, $e); - } - try{ - if(!JwtUtils::verify($jwt, $signingKeyOpenSSL)){ - throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature()); - } - }catch(JwtException $e){ - throw new VerifyLoginException($e->getMessage(), null, 0, $e); - } - - if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){ - $this->authenticated = true; //we're signed into xbox live - } - - $mapper = new \JsonMapper(); - $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case - $mapper->bExceptionOnMissingData = true; - $mapper->bStrictObjectTypeChecking = true; - $mapper->bEnforceMapType = false; - $mapper->bRemoveUndefinedAttributes = true; - try{ - /** @var LegacyAuthJwtBody $claims */ - $claims = $mapper->map($claimsArray, new LegacyAuthJwtBody()); - }catch(\JsonMapper_Exception $e){ - throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e); - } - - $time = time(); - if(isset($claims->nbf) && $claims->nbf > $time + self::CLOCK_DRIFT_MAX){ - throw new VerifyLoginException("JWT not yet valid", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooEarly()); - } - - if(isset($claims->exp) && $claims->exp < $time - self::CLOCK_DRIFT_MAX){ - throw new VerifyLoginException("JWT expired", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooLate()); - } - - if(isset($claims->identityPublicKey)){ - $identityPublicKey = base64_decode($claims->identityPublicKey, true); - if($identityPublicKey === false){ - throw new VerifyLoginException("Invalid identityPublicKey: base64 error decoding"); - } - try{ - //verify key format and parameters - JwtUtils::parseDerPublicKey($identityPublicKey); - }catch(JwtException $e){ - throw new VerifyLoginException("Invalid identityPublicKey: " . $e->getMessage(), null, 0, $e); - } - $currentPublicKey = $identityPublicKey; //if there are further links, the next link should be signed with this - } - } - - public function onCompletion() : void{ - /** - * @var \Closure $callback - * @phpstan-var \Closure(bool, bool, Translatable|string|null, ?string) : void $callback - */ - $callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION); - $callback($this->authenticated, $this->authRequired, $this->error instanceof NonThreadSafeValue ? $this->error->deserialize() : $this->error, $this->clientPublicKey); - } -} diff --git a/src/network/mcpe/auth/ProcessOpenIdLoginTask.php b/src/network/mcpe/auth/ProcessOpenIdLoginTask.php new file mode 100644 index 000000000..091317a65 --- /dev/null +++ b/src/network/mcpe/auth/ProcessOpenIdLoginTask.php @@ -0,0 +1,98 @@ +|string|null + */ + private NonThreadSafeValue|string|null $error = "Unknown"; + /** + * Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang + * root public key. + */ + private bool $authenticated = false; + private ?string $clientPublicKeyDer = null; + + /** + * @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, Translatable|string|null $error, ?string $clientPublicKey) : void $onCompletion + */ + public function __construct( + private string $jwt, + private string $issuer, + private string $mojangPublicKeyDer, + private string $clientDataJwt, + private bool $authRequired, + \Closure $onCompletion + ){ + $this->storeLocal(self::TLS_KEY_ON_COMPLETION, $onCompletion); + } + + public function onRun() : void{ + try{ + $this->clientPublicKeyDer = $this->validateChain(); + $this->error = null; + }catch(VerifyLoginException $e){ + $disconnectMessage = $e->getDisconnectMessage(); + $this->error = $disconnectMessage instanceof Translatable ? new NonThreadSafeValue($disconnectMessage) : $disconnectMessage; + } + } + + private function validateChain() : string{ + $claims = AuthJwtHelper::validateOpenIdAuthToken($this->jwt, $this->mojangPublicKeyDer, issuer: $this->issuer, audience: self::MOJANG_AUDIENCE); + //validateToken will throw if the JWT is not valid + $this->authenticated = true; + + $clientDerKey = base64_decode($claims->cpk, strict: true); + if($clientDerKey === false){ + throw new VerifyLoginException("Invalid client public key: base64 error decoding"); + } + //no further validation needed - OpenSSL will bail if the key is invalid + AuthJwtHelper::validateSelfSignedToken($this->clientDataJwt, $clientDerKey); + + return $clientDerKey; + } + + public function onCompletion() : void{ + /** + * @var \Closure $callback + * @phpstan-var \Closure(bool, bool, Translatable|string|null, ?string) : void $callback + */ + $callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION); + $callback($this->authenticated, $this->authRequired, $this->error instanceof NonThreadSafeValue ? $this->error->deserialize() : $this->error, $this->clientPublicKeyDer); + } +} diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index c664c4b9f..aa7c1da7a 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -27,7 +27,8 @@ use pocketmine\entity\InvalidSkinException; use pocketmine\event\player\PlayerPreLoginEvent; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; -use pocketmine\network\mcpe\auth\ProcessLoginTask; +use pocketmine\network\mcpe\auth\ProcessLegacyLoginTask; +use pocketmine\network\mcpe\auth\ProcessOpenIdLoginTask; use pocketmine\network\mcpe\JwtException; use pocketmine\network\mcpe\JwtUtils; use pocketmine\network\mcpe\NetworkSession; @@ -38,16 +39,23 @@ use pocketmine\network\mcpe\protocol\types\login\clientdata\ClientData; use pocketmine\network\mcpe\protocol\types\login\clientdata\ClientDataToSkinDataHelper; use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthChain; use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthIdentityData; +use pocketmine\network\mcpe\protocol\types\login\openid\XboxAuthJwtBody; +use pocketmine\network\mcpe\protocol\types\login\openid\XboxAuthJwtHeader; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; use pocketmine\player\PlayerInfo; use pocketmine\player\XboxLivePlayerInfo; use pocketmine\Server; use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; +use function chr; +use function count; use function gettype; use function is_array; use function is_object; use function json_decode; +use function md5; +use function ord; use const JSON_THROW_ON_ERROR; /** @@ -65,15 +73,95 @@ class LoginPacketHandler extends PacketHandler{ private \Closure $authCallback ){} + private static function calculateUuidFromXuid(string $xuid) : UuidInterface{ + $hash = md5("pocket-auth-1-xuid:" . $xuid, binary: true); + $hash[6] = chr((ord($hash[6]) & 0x0f) | 0x30); // set version to 3 + $hash[8] = chr((ord($hash[8]) & 0x3f) | 0x80); // set variant to RFC 4122 + + return Uuid::fromBytes($hash); + } + public function handleLogin(LoginPacket $packet) : bool{ $authInfo = $this->parseAuthInfo($packet->authInfoJson); - $jwtChain = $this->parseJwtChain($authInfo->Certificate); - $extraData = $this->fetchAuthData($jwtChain); - if(!Player::isValidUserName($extraData->displayName)){ + if($authInfo->AuthenticationType === AuthenticationType::FULL->value){ + try{ + [$headerArray, $claimsArray,] = JwtUtils::parse($authInfo->Token); + }catch(JwtException $e){ + throw PacketHandlingException::wrap($e, "Error parsing authentication token"); + } + $header = $this->mapXboxTokenHeader($headerArray); + $claims = $this->mapXboxTokenBody($claimsArray); + + $legacyUuid = self::calculateUuidFromXuid($claims->xid); + $username = $claims->xname; + $xuid = $claims->xid; + + $authRequired = $this->processLoginCommon($packet, $username, $legacyUuid, $xuid); + if($authRequired === null){ + //plugin cancelled + return true; + } + $this->processOpenIdLogin($authInfo->Token, $header->kid, $packet->clientDataJwt, $authRequired); + + }elseif($authInfo->AuthenticationType === AuthenticationType::SELF_SIGNED->value){ + try{ + $chainData = json_decode($authInfo->Certificate, flags: JSON_THROW_ON_ERROR); + }catch(\JsonException $e){ + throw PacketHandlingException::wrap($e, "Error parsing self-signed certificate chain"); + } + if(!is_object($chainData)){ + throw new PacketHandlingException("Unexpected type for self-signed certificate chain: " . gettype($chainData) . ", expected object"); + } + try{ + $chain = $this->defaultJsonMapper()->map($chainData, new LegacyAuthChain()); + }catch(\JsonMapper_Exception $e){ + throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate chain"); + } + if(count($chain->chain) > 1 || !isset($chain->chain[0])){ + throw new PacketHandlingException("Expected exactly one certificate in self-signed certificate chain, got " . count($chain->chain)); + } + + try{ + [, $claimsArray, ] = JwtUtils::parse($chain->chain[0]); + }catch(JwtException $e){ + throw PacketHandlingException::wrap($e, "Error parsing self-signed certificate"); + } + if(!isset($claimsArray["extraData"]) || !is_array($claimsArray["extraData"])){ + throw new PacketHandlingException("Expected \"extraData\" to be present in self-signed certificate"); + } + + try{ + $claims = $this->defaultJsonMapper()->map($claimsArray["extraData"], new LegacyAuthIdentityData()); + }catch(\JsonMapper_Exception $e){ + throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate extraData"); + } + + if(!Uuid::isValid($claims->identity)){ + throw new PacketHandlingException("Invalid UUID string in self-signed certificate: " . $claims->identity); + } + $legacyUuid = Uuid::fromString($claims->identity); + $username = $claims->displayName; + $xuid = ""; + + $authRequired = $this->processLoginCommon($packet, $username, $legacyUuid, $xuid); + if($authRequired === null){ + //plugin cancelled + return true; + } + $this->processSelfSignedLogin($chain->chain, $packet->clientDataJwt, $authRequired); + }else{ + throw new PacketHandlingException("Unsupported authentication type: $authInfo->AuthenticationType"); + } + + return true; + } + + private function processLoginCommon(LoginPacket $packet, string $username, UuidInterface $legacyUuid, string $xuid) : ?bool{ + if(!Player::isValidUserName($username)){ $this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_invalidName()); - return true; + return null; } $clientData = $this->parseClientData($packet->clientDataJwt); @@ -86,32 +174,25 @@ class LoginPacketHandler extends PacketHandler{ disconnectScreenMessage: KnownTranslationFactory::disconnectionScreen_invalidSkin() ); - return true; + return null; } - if(!Uuid::isValid($extraData->identity)){ - throw new PacketHandlingException("Invalid login UUID"); - } - $uuid = Uuid::fromString($extraData->identity); - $arrClientData = (array) $clientData; - $arrClientData["TitleID"] = $extraData->titleId; - - if($extraData->XUID !== ""){ + if($xuid !== ""){ $playerInfo = new XboxLivePlayerInfo( - $extraData->XUID, - $extraData->displayName, - $uuid, + $xuid, + $username, + $legacyUuid, $skin, $clientData->LanguageCode, - $arrClientData + (array) $clientData ); }else{ $playerInfo = new PlayerInfo( - $extraData->displayName, - $uuid, + $username, + $legacyUuid, $skin, $clientData->LanguageCode, - $arrClientData + (array) $clientData ); } ($this->playerInfoConsumer)($playerInfo); @@ -144,12 +225,10 @@ class LoginPacketHandler extends PacketHandler{ $ev->call(); if(!$ev->isAllowed()){ $this->session->disconnect($ev->getFinalDisconnectReason(), $ev->getFinalDisconnectScreenMessage()); - return true; + return null; } - $this->processLogin($authInfo->Token, AuthenticationType::from($authInfo->AuthenticationType), $jwtChain->chain, $packet->clientDataJwt, $ev->isAuthRequired()); - - return true; + return $ev->isAuthRequired(); } /** @@ -162,13 +241,10 @@ class LoginPacketHandler extends PacketHandler{ throw PacketHandlingException::wrap($e); } if(!is_object($authInfoJson)){ - throw new \RuntimeException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object"); + throw new PacketHandlingException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object"); } - $mapper = new \JsonMapper(); - $mapper->bExceptionOnMissingData = true; - $mapper->bExceptionOnUndefinedProperty = true; - $mapper->bStrictObjectTypeChecking = true; + $mapper = $this->defaultJsonMapper(); try{ $clientData = $mapper->map($authInfoJson, new AuthenticationInfo()); }catch(\JsonMapper_Exception $e){ @@ -178,68 +254,31 @@ class LoginPacketHandler extends PacketHandler{ } /** + * @param array $headerArray * @throws PacketHandlingException */ - protected function parseJwtChain(string $chainDataJwt) : LegacyAuthChain{ + protected function mapXboxTokenHeader(array $headerArray) : XboxAuthJwtHeader{ + $mapper = $this->defaultJsonMapper(); try{ - $jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR); - }catch(\JsonException $e){ - throw PacketHandlingException::wrap($e); - } - if(!is_object($jwtChainJson)){ - throw new \RuntimeException("Unexpected type for JWT chain data: " . gettype($jwtChainJson) . ", expected object"); - } - - $mapper = new \JsonMapper(); - $mapper->bExceptionOnMissingData = true; - $mapper->bExceptionOnUndefinedProperty = true; - $mapper->bStrictObjectTypeChecking = true; - try{ - $clientData = $mapper->map($jwtChainJson, new LegacyAuthChain()); + $header = $mapper->map($headerArray, new XboxAuthJwtHeader()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e); } - return $clientData; + return $header; } /** + * @param array $bodyArray * @throws PacketHandlingException */ - protected function fetchAuthData(LegacyAuthChain $chain) : LegacyAuthIdentityData{ - /** @var LegacyAuthIdentityData|null $extraData */ - $extraData = null; - foreach($chain->chain as $jwt){ - //validate every chain element - try{ - [, $claims, ] = JwtUtils::parse($jwt); - }catch(JwtException $e){ - throw PacketHandlingException::wrap($e); - } - if(isset($claims["extraData"])){ - if($extraData !== null){ - throw new PacketHandlingException("Found 'extraData' more than once in chainData"); - } - - if(!is_array($claims["extraData"])){ - throw new PacketHandlingException("'extraData' key should be an array"); - } - $mapper = new \JsonMapper(); - $mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models - $mapper->bExceptionOnMissingData = true; - $mapper->bExceptionOnUndefinedProperty = true; - $mapper->bStrictObjectTypeChecking = true; - try{ - /** @var LegacyAuthIdentityData $extraData */ - $extraData = $mapper->map($claims["extraData"], new LegacyAuthIdentityData()); - }catch(\JsonMapper_Exception $e){ - throw PacketHandlingException::wrap($e); - } - } + protected function mapXboxTokenBody(array $bodyArray) : XboxAuthJwtBody{ + $mapper = $this->defaultJsonMapper(); + try{ + $header = $mapper->map($bodyArray, new XboxAuthJwtBody()); + }catch(\JsonMapper_Exception $e){ + throw PacketHandlingException::wrap($e); } - if($extraData === null){ - throw new PacketHandlingException("'extraData' not found in chain data"); - } - return $extraData; + return $header; } /** @@ -252,11 +291,7 @@ class LoginPacketHandler extends PacketHandler{ throw PacketHandlingException::wrap($e); } - $mapper = new \JsonMapper(); - $mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models - $mapper->bExceptionOnMissingData = true; - $mapper->bExceptionOnUndefinedProperty = true; - $mapper->bStrictObjectTypeChecking = true; + $mapper = $this->defaultJsonMapper(); try{ $clientData = $mapper->map($clientDataClaims, new ClientData()); }catch(\JsonMapper_Exception $e){ @@ -269,15 +304,37 @@ class LoginPacketHandler extends PacketHandler{ * TODO: This is separated for the purposes of allowing plugins (like Specter) to hack it and bypass authentication. * In the future this won't be necessary. * - * @param null|string[] $legacyCertificate - * * @throws \InvalidArgumentException */ - protected function processLogin(string $token, AuthenticationType $authType, ?array $legacyCertificate, string $clientData, bool $authRequired) : void{ - if($legacyCertificate === null){ - throw new PacketHandlingException("Legacy certificate cannot be null"); - } - $this->server->getAsyncPool()->submitTask(new ProcessLoginTask($legacyCertificate, $clientData, $authRequired, $this->authCallback)); + protected function processOpenIdLogin(string $token, string $keyId, string $clientData, bool $authRequired) : void{ $this->session->setHandler(null); //drop packets received during login verification + + $authKeyProvider = $this->server->getAuthKeyProvider(); + + $authKeyProvider->getKey($keyId)->onCompletion( + function(array $issuerAndKey) use ($token, $clientData, $authRequired) : void{ + [$issuer, $mojangPublicKeyPem] = $issuerAndKey; + $this->server->getAsyncPool()->submitTask(new ProcessOpenIdLoginTask($token, $issuer, $mojangPublicKeyPem, $clientData, $authRequired, $this->authCallback)); + }, + fn() => ($this->authCallback)(false, $authRequired, "Unrecognized authentication key ID: $keyId", null) + ); + } + + /** + * @param string[] $legacyCertificate + */ + protected function processSelfSignedLogin(array $legacyCertificate, string $clientDataJwt, bool $authRequired) : void{ + $this->session->setHandler(null); //drop packets received during login verification + + $this->server->getAsyncPool()->submitTask(new ProcessLegacyLoginTask($legacyCertificate, $clientDataJwt, rootAuthKeyDer: null, authRequired: $authRequired, onCompletion: $this->authCallback)); + } + + private function defaultJsonMapper() : \JsonMapper{ + $mapper = new \JsonMapper(); + $mapper->bExceptionOnMissingData = true; + $mapper->bExceptionOnUndefinedProperty = true; + $mapper->bStrictObjectTypeChecking = true; + $mapper->bEnforceMapType = false; + return $mapper; } } diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 76cebf283..fa513de78 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -870,12 +870,6 @@ parameters: count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - - message: '#^Property pocketmine\\network\\mcpe\\auth\\ProcessLoginTask\:\:\$chain \(string\) does not accept string\|null\.$#' - identifier: assign.propertyType - count: 1 - path: ../../../src/network/mcpe/auth/ProcessLoginTask.php - - message: '#^Parameter \#1 \$result of method pocketmine\\network\\mcpe\\compression\\CompressBatchPromise\:\:resolve\(\) expects string, mixed given\.$#' identifier: argument.type From 45698e4bb9012766c1fb0a2bad64d2621707f9b5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 23 Sep 2025 13:07:47 +0100 Subject: [PATCH 331/334] SendUsageTask: fixed php_uname() call with invalid mode this now throws in PHP 8.4. Previously it returned the same as php_uname(a). Presumably the intent was to capture the CPU arch and not a repeat of the machine field. closes #6811 --- src/stats/SendUsageTask.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats/SendUsageTask.php b/src/stats/SendUsageTask.php index d218774b5..c49315a72 100644 --- a/src/stats/SendUsageTask.php +++ b/src/stats/SendUsageTask.php @@ -88,7 +88,7 @@ class SendUsageTask extends AsyncTask{ "phpVersion" => PHP_VERSION, "machine" => php_uname("a"), "release" => php_uname("r"), - "platform" => php_uname("i") + "platform" => php_uname("m") ]; $data["players"] = [ From 65cdf68f59d8cf622ffc2ea3860da2dc5313cc24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:20:37 +0100 Subject: [PATCH 332/334] Bump build/php from `627f8d6` to `8fe1873` (#6812) Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `627f8d6` to `8fe1873`. - [Release notes](https://github.com/pmmp/php-build-scripts/releases) - [Commits](https://github.com/pmmp/php-build-scripts/compare/627f8d670cc12c6fb7ad988f0c85f65cd39d6f7e...8fe187335f666b8fb251927940f66ef372bfd3a6) --- updated-dependencies: - dependency-name: build/php dependency-version: 8fe187335f666b8fb251927940f66ef372bfd3a6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 627f8d670..8fe187335 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 627f8d670cc12c6fb7ad988f0c85f65cd39d6f7e +Subproject commit 8fe187335f666b8fb251927940f66ef372bfd3a6 From 3336cda34ad7f26af1d4511043d6b3badfbc8053 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 25 Sep 2025 17:32:38 +0100 Subject: [PATCH 333/334] First pass ext-encoding support (high-level network I/O and read-only data) (#6799) This implements ext-encoding only in high-level network I/O (only BedrockProtocol and stuff implemented in PM) and read-only data. This should net a significant performance advantage while being low-risk in the case of critical issues with the extension. Any problems affecting protocol won't do permanent damage while being fairly easy to debug. Next passes will integrate ext-encoding versions of RakLib, RakLibIpc and NBT, as well as generally using ext-encoding for writeable data. --- composer.json | 3 +- composer.lock | 16 ++--- src/PocketMine.php | 7 +++ src/block/Block.php | 6 +- src/crafting/CraftingManager.php | 13 ++-- .../block/upgrade/BlockIdMetaUpgrader.php | 21 ++++--- src/network/mcpe/ChunkRequestTask.php | 6 +- src/network/mcpe/JwtUtils.php | 19 +++--- src/network/mcpe/NetworkSession.php | 32 +++++----- .../mcpe/StandardPacketBroadcaster.php | 11 ++-- src/network/mcpe/cache/CraftingDataCache.php | 6 +- src/network/mcpe/convert/TypeConverter.php | 9 +-- .../mcpe/encryption/EncryptionContext.php | 4 +- .../mcpe/serializer/ChunkSerializer.php | 62 +++++++++---------- src/network/query/QueryHandler.php | 48 +++++++------- src/network/query/QueryInfo.php | 4 +- src/world/format/io/FastChunkSerializer.php | 55 ++++++++-------- tools/generate-bedrock-data-from-packets.php | 11 ++-- 18 files changed, 177 insertions(+), 156 deletions(-) diff --git a/composer.json b/composer.json index 0f2ffe95f..9451fd6eb 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "ext-ctype": "*", "ext-curl": "*", "ext-date": "*", + "ext-encoding": "~1.0.0", "ext-gmp": "*", "ext-hash": "*", "ext-igbinary": "^3.0.1", @@ -36,7 +37,7 @@ "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", "pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100", "pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100", - "pocketmine/bedrock-protocol": "~41.0.0+bedrock-1.21.100", + "pocketmine/bedrock-protocol": "~50.0.0+bedrock-1.21.100", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", diff --git a/composer.lock b/composer.lock index ece3c5070..ce923690e 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": "1e7545f6cc226b31d54238602143ba78", + "content-hash": "0d71d3fba23ba8c4734cac59b9e10129", "packages": [ { "name": "adhocore/json-comment", @@ -256,19 +256,20 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "41.0.0+bedrock-1.21.100", + "version": "50.0.0+bedrock-1.21.100", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "920ac291fe1b0143b2ebc90b3374ddab0b8531bf" + "reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/920ac291fe1b0143b2ebc90b3374ddab0b8531bf", - "reference": "920ac291fe1b0143b2ebc90b3374ddab0b8531bf", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2d7aa27a5537ae593fb1c39158648ea462fef72a", + "reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a", "shasum": "" }, "require": { + "ext-encoding": "~1.0.0", "ext-json": "*", "php": "^8.1", "pocketmine/binaryutils": "^0.2.0", @@ -296,9 +297,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/41.0.0+bedrock-1.21.100" + "source": "https://github.com/pmmp/BedrockProtocol/tree/50.0.0+bedrock-1.21.100" }, - "time": "2025-09-09T20:52:18+00:00" + "time": "2025-09-20T23:09:19+00:00" }, { "name": "pocketmine/binaryutils", @@ -2797,6 +2798,7 @@ "ext-ctype": "*", "ext-curl": "*", "ext-date": "*", + "ext-encoding": "~1.0.0", "ext-gmp": "*", "ext-hash": "*", "ext-igbinary": "^3.0.1", diff --git a/src/PocketMine.php b/src/PocketMine.php index a71c9768d..c34b8e221 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -98,6 +98,7 @@ namespace pocketmine { "crypto" => "php-crypto", "ctype" => "ctype", "date" => "Date", + "encoding" => "pmmp/ext-encoding", "gmp" => "GMP", "hash" => "Hash", "igbinary" => "igbinary", @@ -155,6 +156,12 @@ namespace pocketmine { } } + if(($encoding_version = phpversion("encoding")) !== false){ + if(version_compare($encoding_version, "1.0.0") < 0 || version_compare($encoding_version, "2.0.0") >= 0){ + $messages[] = "pmmp/ext-encoding ^1.0.0 is required, while you have $encoding_version."; + } + } + if(extension_loaded("pocketmine")){ $messages[] = "The native PocketMine extension is no longer supported."; } diff --git a/src/block/Block.php b/src/block/Block.php index 36e08fc0b..fd644eae4 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -26,6 +26,8 @@ declare(strict_types=1); */ namespace pocketmine\block; +use pmmp\encoding\BE; +use pmmp\encoding\LE; use pocketmine\block\tile\Spawnable; use pocketmine\block\tile\Tile; use pocketmine\block\utils\SupportType; @@ -49,7 +51,6 @@ use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; -use pocketmine\utils\Binary; use pocketmine\world\BlockTransaction; use pocketmine\world\format\Chunk; use pocketmine\world\Position; @@ -98,9 +99,10 @@ class Block{ * of operations required to compute the state ID (micro optimization). */ private static function computeStateIdXorMask(int $typeId) : int{ + //TODO: the mixed byte order here is probably a mistake, but it doesn't break anything for now return $typeId << self::INTERNAL_STATE_DATA_BITS | - (Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK); + (BE::unpackSignedLong(hash('xxh3', LE::packSignedLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK); } /** diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 93d6e1838..673095c6e 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace pocketmine\crafting; +use pmmp\encoding\ByteBufferWriter; +use pmmp\encoding\VarInt; use pocketmine\item\Item; use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\TreeRoot; -use pocketmine\utils\BinaryStream; use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\ObjectSet; use function array_shift; @@ -114,11 +115,13 @@ class CraftingManager{ } private static function hashOutput(Item $output) : string{ - $write = new BinaryStream(); - $write->putVarInt($output->getStateId()); - $write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag()))); + $write = new ByteBufferWriter(); + VarInt::writeSignedInt($write, $output->getStateId()); + //TODO: the NBT serializer allocates its own ByteBufferWriter, we should change the API in the future to + //allow passing our own to avoid this extra allocation + $write->writeByteArray((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag()))); - return $write->getBuffer(); + return $write->getData(); } /** diff --git a/src/data/bedrock/block/upgrade/BlockIdMetaUpgrader.php b/src/data/bedrock/block/upgrade/BlockIdMetaUpgrader.php index 1c339bd46..379784afb 100644 --- a/src/data/bedrock/block/upgrade/BlockIdMetaUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockIdMetaUpgrader.php @@ -23,11 +23,12 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; +use pmmp\encoding\ByteBufferReader; +use pmmp\encoding\DataDecodeException; +use pmmp\encoding\VarInt; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\nbt\LittleEndianNbtSerializer; -use pocketmine\utils\BinaryDataException; -use pocketmine\utils\BinaryStream; /** * Handles translating legacy 1.12 block ID/meta into modern blockstates. @@ -84,25 +85,25 @@ final class BlockIdMetaUpgrader{ public static function loadFromString(string $data, LegacyBlockIdToStringIdMap $idMap, BlockStateUpgrader $blockStateUpgrader) : self{ $mappingTable = []; - $legacyStateMapReader = new BinaryStream($data); + $legacyStateMapReader = new ByteBufferReader($data); $nbtReader = new LittleEndianNbtSerializer(); - $idCount = $legacyStateMapReader->getUnsignedVarInt(); + $idCount = VarInt::readUnsignedInt($legacyStateMapReader); for($idIndex = 0; $idIndex < $idCount; $idIndex++){ - $id = $legacyStateMapReader->get($legacyStateMapReader->getUnsignedVarInt()); + $id = $legacyStateMapReader->readByteArray(VarInt::readUnsignedInt($legacyStateMapReader)); - $metaCount = $legacyStateMapReader->getUnsignedVarInt(); + $metaCount = VarInt::readUnsignedInt($legacyStateMapReader); for($metaIndex = 0; $metaIndex < $metaCount; $metaIndex++){ - $meta = $legacyStateMapReader->getUnsignedVarInt(); + $meta = VarInt::readUnsignedInt($legacyStateMapReader); $offset = $legacyStateMapReader->getOffset(); - $state = $nbtReader->read($legacyStateMapReader->getBuffer(), $offset)->mustGetCompoundTag(); + $state = $nbtReader->read($legacyStateMapReader->getData(), $offset)->mustGetCompoundTag(); $legacyStateMapReader->setOffset($offset); $mappingTable[$id][$meta] = $blockStateUpgrader->upgrade(BlockStateData::fromNbt($state)); } } - if(!$legacyStateMapReader->feof()){ - throw new BinaryDataException("Unexpected trailing data in legacy state map data"); + if($legacyStateMapReader->getUnreadLength() > 0){ + throw new DataDecodeException("Unexpected trailing data in legacy state map data"); } return new self($mappingTable, $idMap); diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index 13b5db5b7..357904dcb 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; +use pmmp\encoding\ByteBufferWriter; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\Compressor; use pocketmine\network\mcpe\convert\TypeConverter; @@ -33,7 +34,6 @@ use pocketmine\network\mcpe\protocol\types\DimensionIds; use pocketmine\network\mcpe\serializer\ChunkSerializer; use pocketmine\scheduler\AsyncTask; use pocketmine\thread\NonThreadSafeValue; -use pocketmine\utils\BinaryStream; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\FastChunkSerializer; use function chr; @@ -73,11 +73,11 @@ class ChunkRequestTask extends AsyncTask{ $converter = TypeConverter::getInstance(); $payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $this->tiles); - $stream = new BinaryStream(); + $stream = new ByteBufferWriter(); PacketBatch::encodePackets($stream, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]); $compressor = $this->compressor->deserialize(); - $this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getBuffer())); + $this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getData())); } public function onCompletion() : void{ diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index dfdfada83..8c25ee042 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; +use pmmp\encoding\BE; +use pmmp\encoding\Byte; +use pmmp\encoding\ByteBufferReader; use pocketmine\utils\AssumptionFailedError; -use pocketmine\utils\Binary; -use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; use function base64_decode; use function base64_encode; @@ -133,17 +134,17 @@ final class JwtUtils{ return self::ASN1_SEQUENCE_TAG . chr(strlen($sequence)) . $sequence; } - private static function signaturePartFromAsn1(BinaryStream $stream) : string{ - $prefix = $stream->get(1); + private static function signaturePartFromAsn1(ByteBufferReader $stream) : string{ + $prefix = $stream->readByteArray(1); if($prefix !== self::ASN1_INTEGER_TAG){ throw new \InvalidArgumentException("Expected an ASN.1 INTEGER tag, got " . bin2hex($prefix)); } //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed - $length = $stream->getByte(); + $length = Byte::readUnsigned($stream); if($length > self::SIGNATURE_PART_LENGTH + 1){ //each part may have an extra leading 0 byte to prevent it being interpreted as a negative number throw new \InvalidArgumentException("Expected at most 49 bytes for signature R or S, got $length"); } - $part = $stream->get($length); + $part = $stream->readByteArray($length); return str_pad(ltrim($part, "\x00"), self::SIGNATURE_PART_LENGTH, "\x00", STR_PAD_LEFT); } @@ -159,11 +160,11 @@ final class JwtUtils{ throw new \InvalidArgumentException("Invalid DER signature, expected $length sequence bytes, got " . strlen($parts)); } - $stream = new BinaryStream($parts); + $stream = new ByteBufferReader($parts); $rRaw = self::signaturePartFromAsn1($stream); $sRaw = self::signaturePartFromAsn1($stream); - if(!$stream->feof()){ + if($stream->getUnreadLength() > 0){ throw new \InvalidArgumentException("Invalid DER signature, unexpected trailing sequence data"); } @@ -250,7 +251,7 @@ final class JwtUtils{ return chr($length); } - $lengthBytes = ltrim(Binary::writeInt($length), "\x00"); + $lengthBytes = ltrim(BE::packUnsignedInt($length), "\x00"); return chr(0x80 | strlen($lengthBytes)) . $lengthBytes; } diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 234ad4765..75281e426 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; +use pmmp\encoding\ByteBufferReader; +use pmmp\encoding\ByteBufferWriter; +use pmmp\encoding\DataDecodeException; use pocketmine\entity\effect\EffectInstance; use pocketmine\event\player\PlayerDuplicateLoginEvent; use pocketmine\event\player\PlayerResourcePackOfferEvent; @@ -70,7 +73,6 @@ use pocketmine\network\mcpe\protocol\PlayerStartItemCooldownPacket; use pocketmine\network\mcpe\protocol\PlayStatusPacket; use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; use pocketmine\network\mcpe\protocol\SetDifficultyPacket; @@ -109,8 +111,6 @@ use pocketmine\promise\PromiseResolver; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; -use pocketmine\utils\BinaryDataException; -use pocketmine\utils\BinaryStream; use pocketmine\utils\ObjectSet; use pocketmine\utils\TextFormat; use pocketmine\world\format\io\GlobalItemDataHandlers; @@ -401,7 +401,7 @@ class NetworkSession{ } try{ - $stream = new BinaryStream($decompressed); + $stream = new ByteBufferReader($decompressed); foreach(PacketBatch::decodeRaw($stream) as $buffer){ $this->gamePacketLimiter->decrement(); $packet = $this->packetPool->getPacket($buffer); @@ -421,7 +421,7 @@ class NetworkSession{ break; } } - }catch(PacketDecodeException|BinaryDataException $e){ + }catch(PacketDecodeException|DataDecodeException $e){ $this->logger->logException($e); throw PacketHandlingException::wrap($e, "Packet batch decode error"); } @@ -453,14 +453,14 @@ class NetworkSession{ $decodeTimings = Timings::getDecodeDataPacketTimings($packet); $decodeTimings->startTiming(); try{ - $stream = PacketSerializer::decoder($buffer, 0); + $stream = new ByteBufferReader($buffer); try{ $packet->decode($stream); }catch(PacketDecodeException $e){ throw PacketHandlingException::wrap($e); } - if(!$stream->feof()){ - $remains = substr($stream->getBuffer(), $stream->getOffset()); + if($stream->getUnreadLength() > 0){ + $remains = substr($stream->getData(), $stream->getOffset()); $this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains)); } }finally{ @@ -478,7 +478,7 @@ class NetworkSession{ $handlerTimings->startTiming(); try{ if($this->handler === null || !$packet->handle($this->handler)){ - $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer())); + $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getData())); } }finally{ $handlerTimings->stopTiming(); @@ -530,8 +530,10 @@ class NetworkSession{ if($ackReceiptResolver !== null){ $this->sendBufferAckPromises[] = $ackReceiptResolver; } + $writer = new ByteBufferWriter(); foreach($packets as $evPacket){ - $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket)); + $writer->clear(); //memory reuse let's gooooo + $this->addToSendBuffer(self::encodePacketTimed($writer, $evPacket)); } if($immediate){ $this->flushGamePacketQueue(); @@ -564,12 +566,12 @@ class NetworkSession{ /** * @internal */ - public static function encodePacketTimed(PacketSerializer $serializer, ClientboundPacket $packet) : string{ + public static function encodePacketTimed(ByteBufferWriter $serializer, ClientboundPacket $packet) : string{ $timings = Timings::getEncodeDataPacketTimings($packet); $timings->startTiming(); try{ $packet->encode($serializer); - return $serializer->getBuffer(); + return $serializer->getData(); }finally{ $timings->stopTiming(); } @@ -591,13 +593,13 @@ class NetworkSession{ $syncMode = false; } - $stream = new BinaryStream(); + $stream = new ByteBufferWriter(); PacketBatch::encodeRaw($stream, $this->sendBuffer); if($this->enableCompression){ - $batch = $this->server->prepareBatch($stream->getBuffer(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer); + $batch = $this->server->prepareBatch($stream->getData(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer); }else{ - $batch = $stream->getBuffer(); + $batch = $stream->getData(); } $this->sendBuffer = []; $ackPromises = $this->sendBufferAckPromises; diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 7a91b397b..5d9bc533e 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -23,12 +23,11 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; +use pmmp\encoding\ByteBufferWriter; use pocketmine\event\server\DataPacketSendEvent; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\Server; use pocketmine\timings\Timings; -use pocketmine\utils\BinaryStream; use function count; use function log; use function spl_object_id; @@ -64,8 +63,10 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ $totalLength = 0; $packetBuffers = []; + $writer = new ByteBufferWriter(); foreach($packets as $packet){ - $buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder(), $packet); + $writer->clear(); //memory reuse let's gooooo + $buffer = NetworkSession::encodePacketTimed($writer, $packet); //varint length prefix + packet buffer $totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer); $packetBuffers[] = $buffer; @@ -77,9 +78,9 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ $threshold = $compressor->getCompressionThreshold(); if(count($compressorTargets) > 1 && $threshold !== null && $totalLength >= $threshold){ //do not prepare shared batch unless we're sure it will be compressed - $stream = new BinaryStream(); + $stream = new ByteBufferWriter(); PacketBatch::encodeRaw($stream, $packetBuffers); - $batchBuffer = $stream->getBuffer(); + $batchBuffer = $stream->getData(); $batch = $this->server->prepareBatch($batchBuffer, $compressor, timings: Timings::$playerNetworkSendCompressBroadcast); foreach($compressorTargets as $target){ diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php index da0f37c44..d873a53f0 100644 --- a/src/network/mcpe/cache/CraftingDataCache.php +++ b/src/network/mcpe/cache/CraftingDataCache.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\cache; +use pmmp\encoding\BE; use pocketmine\crafting\CraftingManager; use pocketmine\crafting\FurnaceType; use pocketmine\crafting\ShapedRecipe; @@ -41,7 +42,6 @@ use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShaped use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; -use pocketmine\utils\Binary; use pocketmine\utils\SingletonTrait; use Ramsey\Uuid\Uuid; use function array_map; @@ -99,7 +99,7 @@ final class CraftingDataCache{ }; $recipesWithTypeIds[] = new ProtocolShapelessRecipe( CraftingDataPacket::ENTRY_SHAPELESS, - Binary::writeInt($recipeNetId), + BE::packUnsignedInt($recipeNetId), //TODO: this should probably be changed to something human-readable array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()), array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, @@ -118,7 +118,7 @@ final class CraftingDataCache{ } $recipesWithTypeIds[] = $r = new ProtocolShapedRecipe( CraftingDataPacket::ENTRY_SHAPED, - Binary::writeInt($recipeNetId), + BE::packUnsignedInt($recipeNetId), //TODO: this should probably be changed to something human-readable $inputs, array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index 2a3a4e8f3..9f946ac33 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pmmp\encoding\ByteBufferReader; +use pmmp\encoding\ByteBufferWriter; use pocketmine\block\tile\Container; use pocketmine\block\VanillaBlocks; use pocketmine\crafting\ExactRecipeIngredient; @@ -44,7 +46,6 @@ use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; use pocketmine\nbt\UnexpectedTagTypeException; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData; @@ -312,7 +313,7 @@ class TypeConverter{ $extraData = $id === $this->shieldRuntimeId ? new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) : new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []); - $extraDataSerializer = PacketSerializer::encoder(); + $extraDataSerializer = new ByteBufferWriter(); $extraData->write($extraDataSerializer); return new ItemStack( @@ -320,7 +321,7 @@ class TypeConverter{ $meta, $itemStack->getCount(), $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID, - $extraDataSerializer->getBuffer(), + $extraDataSerializer->getData(), ); } @@ -359,7 +360,7 @@ class TypeConverter{ } public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{ - $extraDataDeserializer = PacketSerializer::decoder($extraData, 0); + $extraDataDeserializer = new ByteBufferReader($extraData); return $id === $this->shieldRuntimeId ? ItemStackExtraDataShield::read($extraDataDeserializer) : ItemStackExtraData::read($extraDataDeserializer); diff --git a/src/network/mcpe/encryption/EncryptionContext.php b/src/network/mcpe/encryption/EncryptionContext.php index 8c0b9bf74..6d76a84ad 100644 --- a/src/network/mcpe/encryption/EncryptionContext.php +++ b/src/network/mcpe/encryption/EncryptionContext.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\encryption; use Crypto\Cipher; -use pocketmine\utils\Binary; +use pmmp\encoding\LE; use function bin2hex; use function openssl_digest; use function openssl_error_string; @@ -104,7 +104,7 @@ class EncryptionContext{ } private function calculateChecksum(int $counter, string $payload) : string{ - $hash = openssl_digest(Binary::writeLLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true); + $hash = openssl_digest(LE::packUnsignedLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true); if($hash === false){ throw new \RuntimeException("openssl_digest() error: " . openssl_error_string()); } diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index 9120f34a7..230ff4a94 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -23,16 +23,16 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\serializer; +use pmmp\encoding\Byte; +use pmmp\encoding\ByteBufferWriter; +use pmmp\encoding\VarInt; use pocketmine\block\tile\Spawnable; use pocketmine\data\bedrock\BiomeIds; use pocketmine\data\bedrock\LegacyBiomeIdToStringIdMap; use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\convert\BlockTranslator; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\DimensionIds; -use pocketmine\utils\Binary; -use pocketmine\utils\BinaryStream; use pocketmine\world\format\Chunk; use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; @@ -84,7 +84,7 @@ final class ChunkSerializer{ * @phpstan-param DimensionIds::* $dimensionId */ public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, ?string $tiles = null) : string{ - $stream = PacketSerializer::encoder(); + $stream = new ByteBufferWriter(); $subChunkCount = self::getSubChunkCount($chunk, $dimensionId); $writtenCount = 0; @@ -100,37 +100,34 @@ final class ChunkSerializer{ self::serializeBiomePalette($chunk->getSubChunk($y)->getBiomeArray(), $biomeIdMap, $stream); } - $stream->putByte(0); //border block array count + Byte::writeUnsigned($stream, 0); //border block array count //Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client. if($tiles !== null){ - $stream->put($tiles); + $stream->writeByteArray($tiles); }else{ - $stream->put(self::serializeTiles($chunk)); + $stream->writeByteArray(self::serializeTiles($chunk)); } - return $stream->getBuffer(); + return $stream->getData(); } - public static function serializeSubChunk(SubChunk $subChunk, BlockTranslator $blockTranslator, PacketSerializer $stream, bool $persistentBlockStates) : void{ + public static function serializeSubChunk(SubChunk $subChunk, BlockTranslator $blockTranslator, ByteBufferWriter $stream, bool $persistentBlockStates) : void{ $layers = $subChunk->getBlockLayers(); - $stream->putByte(8); //version + Byte::writeUnsigned($stream, 8); //version - $stream->putByte(count($layers)); + Byte::writeUnsigned($stream, count($layers)); $blockStateDictionary = $blockTranslator->getBlockStateDictionary(); foreach($layers as $blocks){ $bitsPerBlock = $blocks->getBitsPerBlock(); $words = $blocks->getWordArray(); - $stream->putByte(($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1)); - $stream->put($words); + Byte::writeUnsigned($stream, ($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1)); + $stream->writeByteArray($words); $palette = $blocks->getPalette(); if($bitsPerBlock !== 0){ - //these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here - //but since we know they are always unsigned, we can avoid the extra fcall overhead of - //zigzag and just shift directly. - $stream->putUnsignedVarInt(count($palette) << 1); //yes, this is intentionally zigzag + VarInt::writeSignedInt($stream, count($palette)); //yes, this is intentionally zigzag } if($persistentBlockStates){ $nbtSerializer = new NetworkNbtSerializer(); @@ -141,46 +138,43 @@ final class ChunkSerializer{ $state = $blockTranslator->getFallbackStateData(); } - $stream->put($nbtSerializer->write(new TreeRoot($state->toNbt()))); + $stream->writeByteArray($nbtSerializer->write(new TreeRoot($state->toNbt()))); } }else{ + //we would use writeSignedIntArray() here, but the gains of writing in batch are negated by the cost of + //allocating a temporary array for the mapped palette IDs, especially for small palettes foreach($palette as $p){ - $stream->put(Binary::writeUnsignedVarInt($blockTranslator->internalIdToNetworkId($p) << 1)); + VarInt::writeSignedInt($stream, $blockTranslator->internalIdToNetworkId($p)); } } } } - private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, PacketSerializer $stream) : void{ + private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, ByteBufferWriter $stream) : void{ $biomePaletteBitsPerBlock = $biomePalette->getBitsPerBlock(); - $stream->putByte(($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs - $stream->put($biomePalette->getWordArray()); + Byte::writeUnsigned($stream, ($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs + $stream->writeByteArray($biomePalette->getWordArray()); - //these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here - //but since we know they are always unsigned, we can avoid the extra fcall overhead of - //zigzag and just shift directly. $biomePaletteArray = $biomePalette->getPalette(); if($biomePaletteBitsPerBlock !== 0){ - $stream->putUnsignedVarInt(count($biomePaletteArray) << 1); + VarInt::writeSignedInt($stream, count($biomePaletteArray)); } foreach($biomePaletteArray as $p){ - if($biomeIdMap->legacyToString($p) === null){ - //make sure we aren't sending bogus biomes - the 1.18.0 client crashes if we do this - $p = BiomeIds::OCEAN; - } - $stream->put(Binary::writeUnsignedVarInt($p << 1)); + //we would use writeSignedIntArray() here, but the gains of writing in batch are negated by the cost of + //allocating a temporary array for the mapped palette IDs, especially for small palettes + VarInt::writeSignedInt($stream, $biomeIdMap->legacyToString($p) !== null ? $p : BiomeIds::OCEAN); } } public static function serializeTiles(Chunk $chunk) : string{ - $stream = new BinaryStream(); + $stream = new ByteBufferWriter(); foreach($chunk->getTiles() as $tile){ if($tile instanceof Spawnable){ - $stream->put($tile->getSerializedSpawnCompound()->getEncodedNbt()); + $stream->writeByteArray($tile->getSerializedSpawnCompound()->getEncodedNbt()); } } - return $stream->getBuffer(); + return $stream->getData(); } } diff --git a/src/network/query/QueryHandler.php b/src/network/query/QueryHandler.php index 940f64f18..41b633421 100644 --- a/src/network/query/QueryHandler.php +++ b/src/network/query/QueryHandler.php @@ -27,16 +27,16 @@ declare(strict_types=1); */ namespace pocketmine\network\query; +use pmmp\encoding\BE; +use pmmp\encoding\Byte; +use pmmp\encoding\ByteBufferReader; +use pmmp\encoding\ByteBufferWriter; +use pmmp\encoding\DataDecodeException; use pocketmine\network\AdvancedNetworkInterface; use pocketmine\network\RawPacketHandler; use pocketmine\Server; -use pocketmine\utils\Binary; -use pocketmine\utils\BinaryDataException; -use pocketmine\utils\BinaryStream; -use function chr; use function hash; use function random_bytes; -use function strlen; use function substr; class QueryHandler implements RawPacketHandler{ @@ -80,51 +80,53 @@ class QueryHandler implements RawPacketHandler{ } public static function getTokenString(string $token, string $salt) : int{ - return Binary::readInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4)); + return BE::unpackSignedInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4)); } public function handle(AdvancedNetworkInterface $interface, string $address, int $port, string $packet) : bool{ try{ - $stream = new BinaryStream($packet); - $header = $stream->get(2); + $stream = new ByteBufferReader($packet); + $header = $stream->readByteArray(2); if($header !== "\xfe\xfd"){ //TODO: have this filtered by the regex filter we installed above return false; } - $packetType = $stream->getByte(); - $sessionID = $stream->getInt(); + $packetType = Byte::readUnsigned($stream); + $sessionID = BE::readUnsignedInt($stream); switch($packetType){ case self::HANDSHAKE: //Handshake - $reply = chr(self::HANDSHAKE); - $reply .= Binary::writeInt($sessionID); - $reply .= self::getTokenString($this->token, $address) . "\x00"; + $writer = new ByteBufferWriter(); + Byte::writeUnsigned($writer, self::HANDSHAKE); + BE::writeUnsignedInt($writer, $sessionID); + $writer->writeByteArray(self::getTokenString($this->token, $address) . "\x00"); - $interface->sendRawPacket($address, $port, $reply); + $interface->sendRawPacket($address, $port, $writer->getData()); return true; case self::STATISTICS: //Stat - $token = $stream->getInt(); + $token = BE::readUnsignedInt($stream); if($token !== ($t1 = self::getTokenString($this->token, $address)) && $token !== ($t2 = self::getTokenString($this->lastToken, $address))){ $this->logger->debug("Bad token $token from $address $port, expected $t1 or $t2"); return true; } - $reply = chr(self::STATISTICS); - $reply .= Binary::writeInt($sessionID); + $writer = new ByteBufferWriter(); + Byte::writeUnsigned($writer, self::STATISTICS); + BE::writeUnsignedInt($writer, $sessionID); - $remaining = $stream->getRemaining(); - if(strlen($remaining) === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01 - $reply .= $this->server->getQueryInformation()->getLongQuery(); + $remaining = $stream->getUnreadLength(); + if($remaining === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01 + $writer->writeByteArray($this->server->getQueryInformation()->getLongQuery()); }else{ - $reply .= $this->server->getQueryInformation()->getShortQuery(); + $writer->writeByteArray($this->server->getQueryInformation()->getShortQuery()); } - $interface->sendRawPacket($address, $port, $reply); + $interface->sendRawPacket($address, $port, $writer->getData()); return true; default: return false; } - }catch(BinaryDataException $e){ + }catch(DataDecodeException $e){ $this->logger->debug("Bad packet from $address $port: " . $e->getMessage()); return false; } diff --git a/src/network/query/QueryInfo.php b/src/network/query/QueryInfo.php index 0bf5b4f65..30aba5347 100644 --- a/src/network/query/QueryInfo.php +++ b/src/network/query/QueryInfo.php @@ -23,11 +23,11 @@ declare(strict_types=1); namespace pocketmine\network\query; +use pmmp\encoding\LE; use pocketmine\player\GameMode; use pocketmine\player\Player; use pocketmine\plugin\Plugin; use pocketmine\Server; -use pocketmine\utils\Binary; use pocketmine\utils\Utils; use pocketmine\YmlServerProperties; use function array_map; @@ -236,6 +236,6 @@ final class QueryInfo{ } public function getShortQuery() : string{ - return $this->shortQueryCache ?? ($this->shortQueryCache = $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . Binary::writeLShort($this->port) . $this->ip . "\x00"); + return $this->shortQueryCache ?? ($this->shortQueryCache = $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . LE::packUnsignedShort($this->port) . $this->ip . "\x00"); } } diff --git a/src/world/format/io/FastChunkSerializer.php b/src/world/format/io/FastChunkSerializer.php index 35a8ff42f..a186ec07f 100644 --- a/src/world/format/io/FastChunkSerializer.php +++ b/src/world/format/io/FastChunkSerializer.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace pocketmine\world\format\io; -use pocketmine\utils\Binary; -use pocketmine\utils\BinaryStream; +use pmmp\encoding\BE; +use pmmp\encoding\Byte; +use pmmp\encoding\ByteBufferReader; +use pmmp\encoding\ByteBufferWriter; use pocketmine\world\format\Chunk; use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; @@ -45,15 +47,15 @@ final class FastChunkSerializer{ //NOOP } - private static function serializePalettedArray(BinaryStream $stream, PalettedBlockArray $array) : void{ + private static function serializePalettedArray(ByteBufferWriter $stream, PalettedBlockArray $array) : void{ $wordArray = $array->getWordArray(); $palette = $array->getPalette(); - $stream->putByte($array->getBitsPerBlock()); - $stream->put($wordArray); + Byte::writeUnsigned($stream, $array->getBitsPerBlock()); + $stream->writeByteArray($wordArray); $serialPalette = pack("L*", ...$palette); - $stream->putInt(strlen($serialPalette)); - $stream->put($serialPalette); + BE::writeUnsignedInt($stream, strlen($serialPalette)); + $stream->writeByteArray($serialPalette); } /** @@ -61,21 +63,20 @@ final class FastChunkSerializer{ * TODO: tiles and entities */ public static function serializeTerrain(Chunk $chunk) : string{ - $stream = new BinaryStream(); - $stream->putByte( - ($chunk->isPopulated() ? self::FLAG_POPULATED : 0) - ); + $stream = new ByteBufferWriter(); + Byte::writeUnsigned($stream, ($chunk->isPopulated() ? self::FLAG_POPULATED : 0)); //subchunks $subChunks = $chunk->getSubChunks(); $count = count($subChunks); - $stream->putByte($count); + Byte::writeUnsigned($stream, $count); foreach($subChunks as $y => $subChunk){ - $stream->putByte($y); - $stream->putInt($subChunk->getEmptyBlockId()); + Byte::writeSigned($stream, $y); + BE::writeUnsignedInt($stream, $subChunk->getEmptyBlockId()); + $layers = $subChunk->getBlockLayers(); - $stream->putByte(count($layers)); + Byte::writeUnsigned($stream, count($layers)); foreach($layers as $blocks){ self::serializePalettedArray($stream, $blocks); } @@ -83,14 +84,15 @@ final class FastChunkSerializer{ } - return $stream->getBuffer(); + return $stream->getData(); } - private static function deserializePalettedArray(BinaryStream $stream) : PalettedBlockArray{ - $bitsPerBlock = $stream->getByte(); - $words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock)); + private static function deserializePalettedArray(ByteBufferReader $stream) : PalettedBlockArray{ + $bitsPerBlock = Byte::readUnsigned($stream); + $words = $stream->readByteArray(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock)); + $paletteSize = BE::readUnsignedInt($stream); /** @var int[] $unpackedPalette */ - $unpackedPalette = unpack("L*", $stream->get($stream->getInt())); //unpack() will never fail here + $unpackedPalette = unpack("L*", $stream->readByteArray($paletteSize)); //unpack() will never fail here $palette = array_values($unpackedPalette); return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette); @@ -100,20 +102,21 @@ final class FastChunkSerializer{ * Deserializes a fast-serialized chunk */ public static function deserializeTerrain(string $data) : Chunk{ - $stream = new BinaryStream($data); + $stream = new ByteBufferReader($data); - $flags = $stream->getByte(); + $flags = Byte::readUnsigned($stream); $terrainPopulated = (bool) ($flags & self::FLAG_POPULATED); $subChunks = []; - $count = $stream->getByte(); + $count = Byte::readUnsigned($stream); for($subCount = 0; $subCount < $count; ++$subCount){ - $y = Binary::signByte($stream->getByte()); - $airBlockId = $stream->getInt(); + $y = Byte::readSigned($stream); + //TODO: why the heck are we using big-endian here? + $airBlockId = BE::readUnsignedInt($stream); $layers = []; - for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){ + for($i = 0, $layerCount = Byte::readUnsigned($stream); $i < $layerCount; ++$i){ $layers[] = self::deserializePalettedArray($stream); } $biomeArray = self::deserializePalettedArray($stream); diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 01ff368ab..47411701a 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\tools\generate_bedrock_data_from_packets; +use pmmp\encoding\ByteBufferReader; use pocketmine\crafting\json\FurnaceRecipeData; use pocketmine\crafting\json\ItemStackData; use pocketmine\crafting\json\PotionContainerChangeRecipeData; @@ -51,7 +52,6 @@ use pocketmine\network\mcpe\protocol\CreativeContentPacket; use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\StartGamePacket; use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; @@ -190,7 +190,7 @@ class ParserPacketHandler extends PacketHandler{ $rawExtraData = $itemStack->getRawExtraData(); if($rawExtraData !== ""){ - $decoder = PacketSerializer::decoder($rawExtraData, 0); + $decoder = new ByteBufferReader($rawExtraData); $extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder); $nbt = $extraData->getNbt(); if($nbt !== null && count($nbt) > 0){ @@ -645,12 +645,13 @@ function main(array $argv) : int{ fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]); continue; } - $serializer = PacketSerializer::decoder($raw, 0); + $serializer = new ByteBufferReader($raw); $pk->decode($serializer); $pk->handle($handler); - if(!$serializer->feof()){ - echo "Packet on line " . ($lineNum + 1) . ": didn't read all data from " . get_class($pk) . " (stopped at offset " . $serializer->getOffset() . " of " . strlen($serializer->getBuffer()) . " bytes): " . bin2hex($serializer->getRemaining()) . "\n"; + $remaining = strlen($serializer->getData()) - $serializer->getOffset(); + if($remaining > 0){ + echo "Packet on line " . ($lineNum + 1) . ": didn't read all data from " . get_class($pk) . " (stopped at offset " . $serializer->getOffset() . " of " . strlen($serializer->getData()) . " bytes): " . bin2hex($serializer->readByteArray($remaining)) . "\n"; } } return 0; From 5ca8e1502720c8d71bf837688ad0fcb9e773a0b4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 25 Sep 2025 18:18:10 +0100 Subject: [PATCH 334/334] tidy --- src/Server.php | 3 +++ src/entity/projectile/Trident.php | 4 ++-- .../{TridentHitGroundSound.php => TridentHitBlockSound.php} | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) rename src/world/sound/{TridentHitGroundSound.php => TridentHitBlockSound.php} (95%) diff --git a/src/Server.php b/src/Server.php index 4f0fa4ce1..af9cbeda7 100644 --- a/src/Server.php +++ b/src/Server.php @@ -1804,6 +1804,9 @@ class Server{ return $this->forceLanguage; } + /** + * @internal + */ public function getAuthKeyProvider() : AuthKeyProvider{ return $this->authKeyProvider; } diff --git a/src/entity/projectile/Trident.php b/src/entity/projectile/Trident.php index 73b3880ac..20a494e8d 100644 --- a/src/entity/projectile/Trident.php +++ b/src/entity/projectile/Trident.php @@ -38,8 +38,8 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\player\Player; +use pocketmine\world\sound\TridentHitBlockSound; use pocketmine\world\sound\TridentHitEntitySound; -use pocketmine\world\sound\TridentHitGroundSound; class Trident extends Projectile{ @@ -119,7 +119,7 @@ class Trident extends Projectile{ protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{ parent::onHitBlock($blockHit, $hitResult); $this->canCollide = true; - $this->broadcastSound(new TridentHitGroundSound()); + $this->broadcastSound(new TridentHitBlockSound()); } public function getItem() : Item{ diff --git a/src/world/sound/TridentHitGroundSound.php b/src/world/sound/TridentHitBlockSound.php similarity index 95% rename from src/world/sound/TridentHitGroundSound.php rename to src/world/sound/TridentHitBlockSound.php index 361381786..05cd34248 100644 --- a/src/world/sound/TridentHitGroundSound.php +++ b/src/world/sound/TridentHitBlockSound.php @@ -27,7 +27,7 @@ use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; use pocketmine\network\mcpe\protocol\types\LevelSoundEvent; -class TridentHitGroundSound implements Sound{ +class TridentHitBlockSound implements Sound{ public function encode(Vector3 $pos) : array{ return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ITEM_TRIDENT_HIT_GROUND, $pos, false)];