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 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: ["*"] 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 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 0a07a738b..cd1841e4f 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -64,6 +64,7 @@ jobs: id: get-pm-version run: | echo PM_VERSION=$(php build/dump-version-info.php base_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 @@ -72,7 +73,7 @@ jobs: - 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/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-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT - name: Generate build info run: | diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml index 6d71a0e70..e26f7c318 100644 --- a/.github/workflows/main-php-matrix.yml +++ b/.github/workflows/main-php-matrix.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -96,7 +96,7 @@ jobs: submodules: true - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -128,7 +128,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" 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 + 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. diff --git a/build/dump-version-info.php b/build/dump-version-info.php index 8898d7cab..166264d98 100644 --- a/build/dump-version-info.php +++ b/build/dump-version-info.php @@ -36,6 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; */ $options = [ "base_version" => VersionInfo::BASE_VERSION, + "major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0], "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK, "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD, "changelog_file_name" => function() : string{ diff --git a/build/php b/build/php index a51259d7a..5016e0a3d 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a +Subproject commit 5016e0a3d54c714c12b331ea0474a6f500ffc0a3 diff --git a/changelogs/5.21.md b/changelogs/5.21.md index 9b3c2f89a..54bebf5db 100644 --- a/changelogs/5.21.md +++ b/changelogs/5.21.md @@ -110,3 +110,19 @@ Released 12th November 2024. - Fixed garbage collector cycle count increase on player disconnect. - Fixed weakness effect being applied to all attack types, causing damage splash potions to become weaker. - Fixed Enchanted Golden Apple regeneration effect amplifier to match vanilla. + +# 5.21.2 +Released 29th November 2024. + +## Fixes +- Fixed blocks destroyable by water being able to be placed inside water. +- Fixed deprecation warnings about nullable typehints on PHP 8.4. +- Fixed `Utils::getNiceClosureName()` not working correctly on PHP 8.4. +- Fixed incorrect break animations when breaking the block behind an instantly-breakable block like a torch. +- Fixed candle extinguish logic. +- Fixed various documentation issues around array key types. +- Introduced a new PHPStan rule along with `Utils::promoteKeys()` to improve PHPStan error reporting around unspecified array key types. Previously, some errors were missed due to PHPStan's BenevolentUnionType. + +## DevOps +- `pmmp/server-developers` team is now automatically requested for review on any new PR. +- `Status: Waiting on Author` label is now automatically removed from PRs when they are updated. 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/changelogs/5.23.md b/changelogs/5.23.md new file mode 100644 index 000000000..7f40b40af --- /dev/null +++ b/changelogs/5.23.md @@ -0,0 +1,116 @@ +# 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) + - Ice Bomb (from Education Edition) + - 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. + - `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: + - `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. + +# 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/composer.json b/composer.json index 6a15762da..0a96083a1 100644 --- a/composer.json +++ b/composer.json @@ -32,16 +32,16 @@ "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", - "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", "pocketmine/errorhandler": "^0.7.0", - "pocketmine/locale-data": "~2.21.0", + "pocketmine/locale-data": "~2.22.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 c1a0b0073..1cf141588 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": "476374fb3d22e26a97c1dea8c6736faf", + "content-hash": "732102eca72dc1d29e7b67dfbce07653", "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", @@ -153,16 +204,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 +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/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 +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.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 +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.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", @@ -420,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.21.1", + "version": "2.22.0", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167" + "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/fdba0f764d6281f64e5968dca94fdab96bf4e167", - "reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167", + "url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d", + "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d", "shasum": "" }, "type": "library", @@ -437,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.21.1" + "source": "https://github.com/pmmp/Language/tree/2.22.0" }, - "time": "2024-11-14T23:11:22+00:00" + "time": "2024-11-16T13:28:01+00:00" }, { "name": "pocketmine/log", @@ -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", diff --git a/src/Server.php b/src/Server.php index e12a570e6..650329471 100644 --- a/src/Server.php +++ b/src/Server.php @@ -90,6 +90,8 @@ use pocketmine\promise\Promise; use pocketmine\promise\PromiseResolver; use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\scheduler\AsyncPool; +use pocketmine\scheduler\TimingsCollectionTask; +use pocketmine\scheduler\TimingsControlTask; use pocketmine\snooze\SleeperHandler; use pocketmine\stats\SendUsageTask; use pocketmine\thread\log\AttachableThreadSafeLogger; @@ -343,6 +345,10 @@ class Server{ return $this->maxPlayers; } + public function setMaxPlayers(int $maxPlayers) : void{ + $this->maxPlayers = $maxPlayers; + } + /** * Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who * are not logged into Xbox Live will be disconnected. @@ -895,7 +901,36 @@ class Server{ $poolSize = max(1, (int) $poolSize); } + TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false)); + $this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND); + $this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper); + $this->asyncPool->addWorkerStartHook(function(int $i) : void{ + if(TimingsHandler::isEnabled()){ + $this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i); + } + }); + TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{ + foreach($this->asyncPool->getRunningWorkers() as $workerId){ + $this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId); + } + }); + TimingsHandler::getReloadCallbacks()->add(function() : void{ + foreach($this->asyncPool->getRunningWorkers() as $workerId){ + $this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId); + } + }); + TimingsHandler::getCollectCallbacks()->add(function() : array{ + $promises = []; + foreach($this->asyncPool->getRunningWorkers() as $workerId){ + $resolver = new PromiseResolver(); + $this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId); + + $promises[] = $resolver->getPromise(); + } + + return $promises; + }); $netCompressionThreshold = -1; if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){ @@ -969,9 +1004,6 @@ class Server{ ))); $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName()))); - TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false)); - $this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND); - DefaultPermissions::registerCorePermissions(); $this->commandMap = new SimpleCommandMap($this); diff --git a/src/VersionInfo.php b/src/VersionInfo.php index bc1b24c62..1eca900cf 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.21.2"; + public const BASE_VERSION = "5.23.2"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 1fb71025c..b0c7d3c8f 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\AnvilFallSound; use pocketmine\world\sound\Sound; -use function lcg_value; use function round; class Anvil extends Transparent implements Fallable{ @@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{ } public function onHitGround(FallingBlock $blockEntity) : bool{ - if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ + if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ if($this->damage !== self::VERY_DAMAGED){ $this->damage = $this->damage + 1; }else{ diff --git a/src/block/Campfire.php b/src/block/Campfire.php index d25f3f2cd..af167b52f 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -69,6 +69,10 @@ class Campfire extends Transparent{ private const UPDATE_INTERVAL_TICKS = 10; + /** + * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block + * has never been set in the world. + */ protected CampfireInventory $inventory; public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ @@ -136,6 +140,10 @@ class Campfire extends Transparent{ return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)]; } + /** + * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block + * has never been set in the world. + */ public function getInventory() : CampfireInventory{ return $this->inventory; } diff --git a/src/block/Farmland.php b/src/block/Farmland.php index a17a220f0..b7a2500a8 100644 --- a/src/block/Farmland.php +++ b/src/block/Farmland.php @@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; +use pocketmine\utils\Utils; use function intdiv; -use function lcg_value; class Farmland extends Transparent{ public const MAX_WETNESS = 7; @@ -148,7 +148,7 @@ class Farmland extends Transparent{ } public function onEntityLand(Entity $entity) : ?float{ - if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){ + if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){ $ev = new EntityTrampleFarmlandEvent($entity, $this); $ev->call(); if(!$ev->isCancelled()){ diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php index b5b6093c4..c03806a3b 100644 --- a/src/block/ItemFrame.php +++ b/src/block/ItemFrame.php @@ -31,13 +31,13 @@ use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\ItemFrameAddItemSound; use pocketmine\world\sound\ItemFrameRemoveItemSound; use pocketmine\world\sound\ItemFrameRotateItemSound; use function is_infinite; use function is_nan; -use function lcg_value; class ItemFrame extends Flowable{ use AnyFacingTrait; @@ -154,7 +154,7 @@ class ItemFrame extends Flowable{ return false; } $world = $this->position->getWorld(); - if(lcg_value() <= $this->itemDropChance){ + if(Utils::getRandomFloat() <= $this->itemDropChance){ $world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem); $world->addSound($this->position, new ItemFrameRemoveItemSound()); } @@ -185,7 +185,7 @@ class ItemFrame extends Flowable{ public function getDropsForCompatibleTool(Item $item) : array{ $drops = parent::getDropsForCompatibleTool($item); - if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){ + if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){ $drops[] = clone $this->framedItem; } diff --git a/src/block/Liquid.php b/src/block/Liquid.php index 6404cf908..a37019d65 100644 --- a/src/block/Liquid.php +++ b/src/block/Liquid.php @@ -33,9 +33,9 @@ use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +use pocketmine\utils\Utils; use pocketmine\world\sound\FizzSound; use pocketmine\world\sound\Sound; -use function lcg_value; abstract class Liquid extends Transparent{ public const MAX_DECAY = 7; @@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{ protected function liquidCollide(Block $cause, Block $result) : bool{ if(BlockEventHelper::form($this, $result, $cause)){ - $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8)); + $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8)); } return true; } diff --git a/src/block/Magma.php b/src/block/Magma.php index d2f309325..7b3fa5229 100644 --- a/src/block/Magma.php +++ b/src/block/Magma.php @@ -39,7 +39,7 @@ class Magma extends Opaque{ } public function onEntityInside(Entity $entity) : bool{ - if($entity instanceof Living && !$entity->isSneaking()){ + if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){ $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1); $entity->attack($ev); } diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index e9324bc6e..e5a7ef2d8 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); } } diff --git a/src/command/ClosureCommand.php b/src/command/ClosureCommand.php new file mode 100644 index 000000000..289c82853 --- /dev/null +++ b/src/command/ClosureCommand.php @@ -0,0 +1,60 @@ + $args) : mixed + */ +final class ClosureCommand extends Command{ + /** @phpstan-var Execute */ + private \Closure $execute; + + /** + * @param string[] $permissions + * @phpstan-param Execute $execute + */ + public function __construct( + string $name, + \Closure $execute, + array $permissions, + Translatable|string $description = "", + Translatable|string|null $usageMessage = null, + array $aliases = [] + ){ + Utils::validateCallableSignature( + fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1, + $execute, + ); + $this->execute = $execute; + parent::__construct($name, $description, $usageMessage, $aliases); + $this->setPermissions($permissions); + } + + public function execute(CommandSender $sender, string $commandLabel, array $args){ + return ($this->execute)($sender, $this, $commandLabel, $args); + } +} diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php index 3c0701ea4..ec26f3efe 100644 --- a/src/command/defaults/TimingsCommand.php +++ b/src/command/defaults/TimingsCommand.php @@ -26,28 +26,28 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\scheduler\BulkCurlTask; use pocketmine\scheduler\BulkCurlTaskOperation; use pocketmine\timings\TimingsHandler; +use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\InternetException; use pocketmine\utils\InternetRequestResult; -use pocketmine\utils\Utils; use pocketmine\YmlServerProperties; use Symfony\Component\Filesystem\Path; use function count; use function fclose; use function file_exists; use function fopen; -use function fseek; use function fwrite; use function http_build_query; +use function implode; use function is_array; use function json_decode; use function mkdir; -use function stream_get_contents; use function strtolower; use const CURLOPT_AUTOREFERER; use const CURLOPT_FOLLOWLOCATION; @@ -101,82 +101,91 @@ class TimingsCommand extends VanillaCommand{ TimingsHandler::reload(); Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset()); }elseif($mode === "merged" || $mode === "report" || $paste){ - $timings = ""; - if($paste){ - $fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail"); - }else{ - $index = 0; - $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); - - if(!file_exists($timingFolder)){ - mkdir($timingFolder, 0777); - } - $timings = Path::join($timingFolder, "timings.txt"); - while(file_exists($timings)){ - $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); - } - - $fileTimings = fopen($timings, "a+b"); - } - $lines = TimingsHandler::printTimings(); - foreach($lines as $line){ - fwrite($fileTimings, $line . PHP_EOL); - } - - if($paste){ - fseek($fileTimings, 0); - $data = [ - "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), - "data" => $content = stream_get_contents($fileTimings) - ]; - fclose($fileTimings); - - $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); - - $sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask( - [new BulkCurlTaskOperation( - "https://$host?upload=true", - 10, - [], - [ - CURLOPT_HTTPHEADER => [ - "User-Agent: $agent", - "Content-Type: application/x-www-form-urlencoded" - ], - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => http_build_query($data), - CURLOPT_AUTOREFERER => false, - CURLOPT_FOLLOWLOCATION => false - ] - )], - function(array $results) use ($sender, $host) : void{ - /** @phpstan-var array $results */ - if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender - return; - } - $result = $results[0]; - if($result instanceof InternetException){ - $sender->getServer()->getLogger()->logException($result); - return; - } - $response = json_decode($result->getBody(), true); - if(is_array($response) && isset($response["id"])){ - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead( - "https://" . $host . "/?id=" . $response["id"])); - }else{ - $sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody()); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError()); - } - } - )); - }else{ - fclose($fileTimings); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings)); - } + $timingsPromise = TimingsHandler::requestPrintTimings(); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect()); + $timingsPromise->onCompletion( + fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender), + fn() => throw new AssumptionFailedError("This promise is not expected to be rejected") + ); }else{ throw new InvalidCommandSyntaxException(); } return true; } + + /** + * @param string[] $lines + * @phpstan-param list $lines + */ + private function createReportFile(array $lines, CommandSender $sender) : void{ + $index = 0; + $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); + + if(!file_exists($timingFolder)){ + mkdir($timingFolder, 0777); + } + $timings = Path::join($timingFolder, "timings.txt"); + while(file_exists($timings)){ + $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); + } + + $fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b")); + foreach($lines as $line){ + fwrite($fileTimings, $line . PHP_EOL); + } + fclose($fileTimings); + + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings)); + } + + /** + * @param string[] $lines + * @phpstan-param list $lines + */ + private function uploadReport(array $lines, CommandSender $sender) : void{ + $data = [ + "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), + "data" => implode("\n", $lines) + ]; + + $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); + + $sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask( + [new BulkCurlTaskOperation( + "https://$host?upload=true", + 10, + [], + [ + CURLOPT_HTTPHEADER => [ + "User-Agent: $agent", + "Content-Type: application/x-www-form-urlencoded" + ], + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($data), + CURLOPT_AUTOREFERER => false, + CURLOPT_FOLLOWLOCATION => false + ] + )], + function(array $results) use ($sender, $host) : void{ + /** @phpstan-var array $results */ + if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender + return; + } + $result = $results[0]; + if($result instanceof InternetException){ + $sender->getServer()->getLogger()->logException($result); + return; + } + $response = json_decode($result->getBody(), true); + if(is_array($response) && isset($response["id"])){ + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead( + "https://" . $host . "/?id=" . $response["id"])); + }else{ + $sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody()); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError()); + } + } + )); + } } 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/EnchantmentIdMap.php b/src/data/bedrock/EnchantmentIdMap.php index e3d652b19..90a10dc20 100644 --- a/src/data/bedrock/EnchantmentIdMap.php +++ b/src/data/bedrock/EnchantmentIdMap.php @@ -66,5 +66,7 @@ final class EnchantmentIdMap{ $this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING()); $this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK()); + + $this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER()); } } 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/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 8240bf063..df1db4211 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -352,6 +352,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER()); $this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD()); $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::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH()); 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/entity/Entity.php b/src/entity/Entity.php index 51f43ab39..3c9d79ae9 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -75,7 +75,6 @@ use function deg2rad; use function floor; use function fmod; use function get_class; -use function lcg_value; use function sin; use function spl_object_id; use const M_PI_2; @@ -910,7 +909,7 @@ abstract class Entity{ return false; } - $force = lcg_value() * 0.2 + 0.1; + $force = Utils::getRandomFloat() * 0.2 + 0.1; $this->motion = match($direction){ Facing::WEST => $this->motion->withComponents(-$force, null, null), diff --git a/src/entity/FoodSource.php b/src/entity/FoodSource.php index 98478b4a1..028c76783 100644 --- a/src/entity/FoodSource.php +++ b/src/entity/FoodSource.php @@ -34,6 +34,7 @@ interface FoodSource extends Consumable{ /** * Returns whether a Human eating this FoodSource must have a non-full hunger bar. + * This is ignored in creative mode and in peaceful difficulty. */ public function requiresHunger() : bool; } diff --git a/src/entity/Human.php b/src/entity/Human.php index 5f7f57702..bf96bc43d 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -67,6 +67,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; use pocketmine\player\Player; use pocketmine\world\sound\TotemUseSound; +use pocketmine\world\World; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function array_fill; @@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ return $this->hungerManager; } + /** + * Returns whether the Human can eat food. This may return a different result than {@link HungerManager::isHungry()}, + * as HungerManager only handles the hunger bar. + */ + public function canEat() : bool{ + return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === World::DIFFICULTY_PEACEFUL; + } + public function consumeObject(Consumable $consumable) : bool{ - if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){ + if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){ return false; } diff --git a/src/entity/HungerManager.php b/src/entity/HungerManager.php index a31855891..7e3b40e74 100644 --- a/src/entity/HungerManager.php +++ b/src/entity/HungerManager.php @@ -88,7 +88,8 @@ class HungerManager{ } /** - * Returns whether this Human may consume objects requiring hunger. + * Returns whether the food level is below the maximum. + * This doesn't decide if the entity can eat food. Use {@link Human::canEat()} for that. */ public function isHungry() : bool{ return $this->getFood() < $this->getMaxFood(); diff --git a/src/entity/Living.php b/src/entity/Living.php index faed09ef1..9a1a28d83 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -25,6 +25,8 @@ namespace pocketmine\entity; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; +use pocketmine\block\VanillaBlocks; +use pocketmine\block\Water; use pocketmine\data\bedrock\EffectIdMap; use pocketmine\entity\animation\DeathAnimation; use pocketmine\entity\animation\HurtAnimation; @@ -44,6 +46,7 @@ use pocketmine\item\Durable; use pocketmine\item\enchantment\Enchantment; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; +use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\math\VoxelRayTrace; use pocketmine\nbt\tag\CompoundTag; @@ -58,18 +61,19 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; use pocketmine\player\Player; use pocketmine\timings\Timings; use pocketmine\utils\Binary; +use pocketmine\utils\Utils; use pocketmine\world\sound\BurpSound; use pocketmine\world\sound\EntityLandSound; use pocketmine\world\sound\EntityLongFallSound; use pocketmine\world\sound\EntityShortFallSound; use pocketmine\world\sound\ItemBreakSound; +use function abs; use function array_shift; use function atan2; use function ceil; use function count; use function floor; use function ksort; -use function lcg_value; use function max; use function min; use function mt_getrandmax; @@ -128,6 +132,8 @@ abstract class Living extends Entity{ protected bool $gliding = false; protected bool $swimming = false; + private ?int $frostWalkerLevel = null; + protected function getInitialDragMultiplier() : float{ return 0.02; } protected function getInitialGravity() : float{ return 0.08; } @@ -151,6 +157,14 @@ abstract class Living extends Entity{ $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this) ))); + $this->armorInventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: function(Inventory $inventory, int $slot) : void{ + if($slot === ArmorInventory::SLOT_FEET){ + $this->frostWalkerLevel = null; + } + }, + onContentChange: function() : void{ $this->frostWalkerLevel = null; } + )); $health = $this->getMaxHealth(); @@ -490,7 +504,7 @@ abstract class Living extends Entity{ $helmet = $this->armorInventory->getHelmet(); if($helmet instanceof Armor){ $finalDamage = $source->getFinalDamage(); - $this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2)); + $this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2)); $this->armorInventory->setHelmet($helmet); } } @@ -687,6 +701,47 @@ abstract class Living extends Entity{ return $hasUpdate; } + protected function move(float $dx, float $dy, float $dz) : void{ + $oldX = $this->location->x; + $oldZ = $this->location->z; + + parent::move($dx, $dy, $dz); + + $frostWalkerLevel = $this->getFrostWalkerLevel(); + if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){ + $this->applyFrostWalker($frostWalkerLevel); + } + } + + protected function applyFrostWalker(int $level) : void{ + $radius = $level + 2; + $world = $this->getWorld(); + + $baseX = $this->location->getFloorX(); + $y = $this->location->getFloorY() - 1; + $baseZ = $this->location->getFloorZ(); + + $frostedIce = VanillaBlocks::FROSTED_ICE(); + 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() || + $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); + } + } + } + + public function getFrostWalkerLevel() : int{ + return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->getEnchantmentLevel(VanillaEnchantments::FROST_WALKER()); + } + /** * Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water. */ @@ -697,7 +752,7 @@ abstract class Living extends Entity{ $this->setBreathing(false); if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 || - lcg_value() <= (1 / ($respirationLevel + 1)) + Utils::getRandomFloat() <= (1 / ($respirationLevel + 1)) ){ $ticks -= $tickDiff; if($ticks <= -20){ diff --git a/src/item/Armor.php b/src/item/Armor.php index 9e1046d96..b50d151d1 100644 --- a/src/item/Armor.php +++ b/src/item/Armor.php @@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\player\Player; use pocketmine\utils\Binary; -use function lcg_value; +use pocketmine\utils\Utils; use function mt_rand; class Armor extends Durable{ @@ -129,7 +129,7 @@ class Armor extends Durable{ $chance = 1 / ($unbreakingLevel + 1); for($i = 0; $i < $amount; ++$i){ - if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best + if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best $negated++; } } diff --git a/src/item/Durable.php b/src/item/Durable.php index f110f6ea5..069a01202 100644 --- a/src/item/Durable.php +++ b/src/item/Durable.php @@ -25,7 +25,7 @@ namespace pocketmine\item; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\nbt\tag\CompoundTag; -use function lcg_value; +use pocketmine\utils\Utils; use function min; abstract class Durable extends Item{ @@ -87,7 +87,7 @@ abstract class Durable extends Item{ $chance = 1 / ($unbreakingLevel + 1); for($i = 0; $i < $amount; ++$i){ - if(lcg_value() > $chance){ + if(Utils::getRandomFloat() > $chance){ $negated++; } } diff --git a/src/item/Food.php b/src/item/Food.php index 1950c4b14..d01ce9e18 100644 --- a/src/item/Food.php +++ b/src/item/Food.php @@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{ } public function canStartUsingItem(Player $player) : bool{ - return !$this->requiresHunger() || $player->getHungerManager()->isHungry(); + return !$this->requiresHunger() || $player->canEat(); } } diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index f3ad406a6..c93c23e81 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -327,8 +327,9 @@ final class ItemTypeIds{ public const GOAT_HORN = 20288; public const END_CRYSTAL = 20289; public const ICE_BOMB = 20290; + public const RECOVERY_COMPASS = 20291; - public const FIRST_UNUSED_ITEM_ID = 20291; + public const FIRST_UNUSED_ITEM_ID = 20292; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/RottenFlesh.php b/src/item/RottenFlesh.php index 4cecc67fc..2ea3ee955 100644 --- a/src/item/RottenFlesh.php +++ b/src/item/RottenFlesh.php @@ -25,7 +25,7 @@ namespace pocketmine\item; use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\VanillaEffects; -use function lcg_value; +use pocketmine\utils\Utils; class RottenFlesh extends Food{ @@ -38,7 +38,7 @@ class RottenFlesh extends Food{ } public function getAdditionalEffects() : array{ - if(lcg_value() <= 0.8){ + if(Utils::getRandomFloat() <= 0.8){ return [ new EffectInstance(VanillaEffects::HUNGER(), 600) ]; diff --git a/src/item/SpawnEgg.php b/src/item/SpawnEgg.php index 51dcceebd..ab4f0e149 100644 --- a/src/item/SpawnEgg.php +++ b/src/item/SpawnEgg.php @@ -27,15 +27,15 @@ use pocketmine\block\Block; use pocketmine\entity\Entity; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\World; -use function lcg_value; abstract class SpawnEgg extends Item{ abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity; public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{ - $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0); + $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0); if($this->hasCustomName()){ $entity->setNameTag($this->getCustomName()); diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 4cc9d29eb..09c93d5d9 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1481,6 +1481,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("record_strad", fn() => Items::RECORD_STRAD()); $result->register("record_wait", fn() => Items::RECORD_WAIT()); $result->register("record_ward", fn() => Items::RECORD_WARD()); + $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("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index dcf59daf6..6768ed8f0 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -284,6 +284,7 @@ use function strtolower; * @method static Record RECORD_STRAD() * @method static Record RECORD_WAIT() * @method static Record RECORD_WARD() + * @method static Item RECOVERY_COMPASS() * @method static Redstone REDSTONE_DUST() * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static RottenFlesh ROTTEN_FLESH() @@ -574,6 +575,7 @@ final class VanillaItems{ 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")); 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("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh")); self::register("scute", fn(IID $id) => new Item($id, "Scute")); diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php index cae94c666..eed7bff52 100644 --- a/src/item/enchantment/AvailableEnchantmentRegistry.php +++ b/src/item/enchantment/AvailableEnchantmentRegistry.php @@ -57,6 +57,7 @@ final class AvailableEnchantmentRegistry{ $this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]); $this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []); $this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []); + $this->register(Enchantments::FROST_WALKER(), [/* no primary items */], [Tags::BOOTS]); $this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []); $this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []); $this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []); diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php index 47a750ff2..b6763e491 100644 --- a/src/item/enchantment/StringToEnchantmentParser.php +++ b/src/item/enchantment/StringToEnchantmentParser.php @@ -44,6 +44,7 @@ final class StringToEnchantmentParser extends StringToTParser{ $result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION()); $result->register("flame", fn() => VanillaEnchantments::FLAME()); $result->register("fortune", fn() => VanillaEnchantments::FORTUNE()); + $result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER()); $result->register("infinity", fn() => VanillaEnchantments::INFINITY()); $result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK()); $result->register("mending", fn() => VanillaEnchantments::MENDING()); diff --git a/src/item/enchantment/VanillaEnchantments.php b/src/item/enchantment/VanillaEnchantments.php index e86c34d33..5cdcd5225 100644 --- a/src/item/enchantment/VanillaEnchantments.php +++ b/src/item/enchantment/VanillaEnchantments.php @@ -41,6 +41,7 @@ use pocketmine\utils\RegistryTrait; * @method static ProtectionEnchantment FIRE_PROTECTION() * @method static Enchantment FLAME() * @method static Enchantment FORTUNE() + * @method static Enchantment FROST_WALKER() * @method static Enchantment INFINITY() * @method static KnockbackEnchantment KNOCKBACK() * @method static Enchantment MENDING() @@ -131,6 +132,14 @@ final class VanillaEnchantments{ fn(int $level) : int => 10 * $level, 30 )); + + self::register("FROST_WALKER", new Enchantment( + KnownTranslationFactory::enchantment_frostwalker(), + Rarity::RARE, + 2, + fn(int $level) : int => 10 * $level, + 15 + )); self::register("AQUA_AFFINITY", new Enchantment( KnownTranslationFactory::enchantment_waterWorker(), Rarity::RARE, diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php index 8153a80d6..f0b70ffd1 100644 --- a/src/lang/KnownTranslationFactory.php +++ b/src/lang/KnownTranslationFactory.php @@ -1441,6 +1441,10 @@ final class KnownTranslationFactory{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []); } + public static function pocketmine_command_timings_collect() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_COLLECT, []); + } + public static function pocketmine_command_timings_description() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []); } diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php index 4805d0c56..1c1fef20f 100644 --- a/src/lang/KnownTranslationKeys.php +++ b/src/lang/KnownTranslationKeys.php @@ -315,6 +315,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description"; public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage"; public const POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED = "pocketmine.command.timings.alreadyEnabled"; + public const POCKETMINE_COMMAND_TIMINGS_COLLECT = "pocketmine.command.timings.collect"; public const POCKETMINE_COMMAND_TIMINGS_DESCRIPTION = "pocketmine.command.timings.description"; public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable"; public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable"; diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 1d65f14bb..906b5529e 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,10 @@ class InGamePacketHandler extends PacketHandler{ if($inputFlags !== $this->lastPlayerAuthInputFlags){ $this->lastPlayerAuthInputFlags = $inputFlags; - $sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING); + $sneaking = $inputFlags->get(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); @@ -230,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 75c740a5f..7041d3f9c 100644 --- a/src/network/mcpe/handler/ItemStackResponseBuilder.php +++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php @@ -94,6 +94,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"); } diff --git a/src/player/Player.php b/src/player/Player.php index e87fb662d..744fb0fcb 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -109,6 +109,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; +use pocketmine\network\mcpe\protocol\types\DimensionIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; @@ -190,6 +191,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ private const TAG_SPAWN_X = "SpawnX"; //TAG_Int private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int + private const TAG_DEATH_WORLD = "DeathLevel"; //TAG_String + private const TAG_DEATH_X = "DeathPositionX"; //TAG_Int + private const TAG_DEATH_Y = "DeathPositionY"; //TAG_Int + private const TAG_DEATH_Z = "DeathPositionZ"; //TAG_Int public const TAG_LEVEL = "Level"; //TAG_String public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String @@ -272,6 +277,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ private bool $respawnLocked = false; + private ?Position $deathPosition = null; + //TODO: Abilities protected bool $autoJump = true; protected bool $allowFlight = false; @@ -394,6 +401,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){ $this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world); } + if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_DEATH_WORLD, ""))) instanceof World){ + $this->deathPosition = new Position($nbt->getInt(self::TAG_DEATH_X), $nbt->getInt(self::TAG_DEATH_Y), $nbt->getInt(self::TAG_DEATH_Z), $world); + } } public function getLeaveMessage() : Translatable|string{ @@ -1035,6 +1045,30 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } } + public function getDeathPosition() : ?Position{ + if($this->deathPosition !== null && !$this->deathPosition->isValid()){ + $this->deathPosition = null; + } + return $this->deathPosition; + } + + /** + * @param Vector3|Position|null $pos + */ + public function setDeathPosition(?Vector3 $pos) : void{ + if($pos !== null){ + if($pos instanceof Position && $pos->world !== null){ + $world = $pos->world; + }else{ + $world = $this->getWorld(); + } + $this->deathPosition = new Position($pos->x, $pos->y, $pos->z, $world); + }else{ + $this->deathPosition = null; + } + $this->networkPropertiesDirty = true; + } + /** * @return Position */ @@ -1474,6 +1508,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return true; } + public function canEat() : bool{ + return $this->isCreative() || parent::canEat(); + } + public function canBreathe() : bool{ return $this->isCreative() || parent::canBreathe(); } @@ -2333,6 +2371,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected function destroyCycles() : void{ $this->networkSession = null; $this->spawnPosition = null; + $this->deathPosition = null; $this->blockBreakHandler = null; parent::destroyCycles(); } @@ -2374,6 +2413,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ()); } + if($this->deathPosition !== null && $this->deathPosition->isValid()){ + $nbt->setString(self::TAG_DEATH_WORLD, $this->deathPosition->getWorld()->getFolderName()); + $nbt->setInt(self::TAG_DEATH_X, $this->deathPosition->getFloorX()); + $nbt->setInt(self::TAG_DEATH_Y, $this->deathPosition->getFloorY()); + $nbt->setInt(self::TAG_DEATH_Z, $this->deathPosition->getFloorZ()); + } + $nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode)); $nbt->setLong(self::TAG_FIRST_PLAYED, (int) $this->firstPlayed->format('Uv')); $nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000)); @@ -2393,6 +2439,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ //main inventory and drops the rest on the ground. $this->removeCurrentWindow(); + $this->setDeathPosition($this->getPosition()); + $ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null); $ev->call(); @@ -2521,6 +2569,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null); $properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0)); + + if($this->deathPosition !== null && $this->deathPosition->world === $this->location->world){ + $properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, BlockPosition::fromVector3($this->deathPosition)); + //TODO: this should be updated when dimensions are implemented + $properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD); + $properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 1); + }else{ + $properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, new BlockPosition(0, 0, 0)); + $properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD); + $properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 0); + } } public function sendData(?array $targets, ?array $data = null) : void{ diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index af6e4cdc9..eb75130b6 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -27,6 +27,7 @@ use pmmp\thread\Runnable; use pmmp\thread\ThreadSafe; use pmmp\thread\ThreadSafeArray; use pocketmine\thread\NonThreadSafeValue; +use pocketmine\timings\Timings; use function array_key_exists; use function igbinary_serialize; use function igbinary_unserialize; @@ -67,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; @@ -78,7 +82,14 @@ abstract class AsyncTask extends Runnable{ public function run() : void{ $this->result = null; - $this->onRun(); + $timings = Timings::getAsyncTaskRunTimings($this); + $timings->startTiming(); + + try{ + $this->onRun(); + }finally{ + $timings->stopTiming(); + } $this->finished = true; AsyncWorker::getNotifier()->wakeupSleeper(); @@ -132,6 +143,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. * @@ -146,6 +159,7 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated * @internal Only call from AsyncPool.php on the main thread */ public function checkProgressUpdates() : void{ @@ -158,6 +172,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. diff --git a/src/scheduler/TimingsCollectionTask.php b/src/scheduler/TimingsCollectionTask.php new file mode 100644 index 000000000..25cd41166 --- /dev/null +++ b/src/scheduler/TimingsCollectionTask.php @@ -0,0 +1,60 @@ +> + */ +final class TimingsCollectionTask extends AsyncTask{ + private const TLS_KEY_RESOLVER = "resolver"; + + /** + * @phpstan-param PromiseResolver> $promiseResolver + */ + public function __construct(PromiseResolver $promiseResolver){ + $this->storeLocal(self::TLS_KEY_RESOLVER, $promiseResolver); + } + + public function onRun() : void{ + $this->setResult(TimingsHandler::printCurrentThreadRecords()); + } + + public function onCompletion() : void{ + /** + * @var string[] $result + * @phpstan-var list $result + */ + $result = $this->getResult(); + /** + * @var PromiseResolver $promiseResolver + * @phpstan-var PromiseResolver> $promiseResolver + */ + $promiseResolver = $this->fetchLocal(self::TLS_KEY_RESOLVER); + + $promiseResolver->resolve($result); + } +} diff --git a/src/scheduler/TimingsControlTask.php b/src/scheduler/TimingsControlTask.php new file mode 100644 index 000000000..51e906e6b --- /dev/null +++ b/src/scheduler/TimingsControlTask.php @@ -0,0 +1,60 @@ +operation === self::ENABLE){ + TimingsHandler::setEnabled(true); + \GlobalLogger::get()->debug("Enabled timings"); + }elseif($this->operation === self::DISABLE){ + TimingsHandler::setEnabled(false); + \GlobalLogger::get()->debug("Disabled timings"); + }elseif($this->operation === self::RELOAD){ + TimingsHandler::reload(); + \GlobalLogger::get()->debug("Reset timings"); + }else{ + throw new \InvalidArgumentException("Invalid operation $this->operation"); + } + } +} diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 77f8efee6..e71700023 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -37,6 +37,7 @@ use function str_starts_with; abstract class Timings{ public const GROUP_MINECRAFT = "Minecraft"; + /** @deprecated No longer used */ public const GROUP_BREAKDOWN = "Minecraft - Breakdown"; private static bool $initialized = false; @@ -124,11 +125,16 @@ abstract class Timings{ /** @var TimingsHandler[] */ private static array $asyncTaskProgressUpdate = []; + /** @var TimingsHandler[] */ private static array $asyncTaskCompletion = []; /** @var TimingsHandler[] */ private static array $asyncTaskError = []; + private static TimingsHandler $asyncTaskWorkers; + /** @var TimingsHandler[] */ + private static array $asyncTaskRun = []; + public static function init() : void{ if(self::$initialized){ return; @@ -188,6 +194,8 @@ abstract class Timings{ self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync); self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync); + self::$asyncTaskWorkers = new TimingsHandler("Async Task Workers"); + self::$playerCommand = new TimingsHandler("Player Command"); self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache"); @@ -344,6 +352,9 @@ abstract class Timings{ return self::$asyncTaskCompletion[$taskClass]; } + /** + * @deprecated No longer used + */ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ $taskClass = $task::class; if(!isset(self::$asyncTaskError[$taskClass])){ @@ -357,4 +368,18 @@ abstract class Timings{ return self::$asyncTaskError[$taskClass]; } + + public static function getAsyncTaskRunTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ + $taskClass = $task::class; + if(!isset(self::$asyncTaskRun[$taskClass])){ + self::init(); + self::$asyncTaskRun[$taskClass] = new TimingsHandler( + "AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Run", + self::$asyncTaskWorkers, + $group + ); + } + + return self::$asyncTaskRun[$taskClass]; + } } diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php index 574dd6d2b..95f7dbacc 100644 --- a/src/timings/TimingsHandler.php +++ b/src/timings/TimingsHandler.php @@ -23,20 +23,66 @@ declare(strict_types=1); namespace pocketmine\timings; +use pmmp\thread\Thread as NativeThread; +use pocketmine\promise\Promise; +use pocketmine\promise\PromiseResolver; use pocketmine\Server; +use pocketmine\utils\ObjectSet; use pocketmine\utils\Utils; +use function array_merge; +use function array_push; use function hrtime; use function implode; use function spl_object_id; +/** + * @phpstan-type CollectPromise Promise> + */ class TimingsHandler{ - private const FORMAT_VERSION = 2; //peak timings fix + private const FORMAT_VERSION = 3; //thread timings collection private static bool $enabled = false; private static int $timingStart = 0; - /** @return string[] */ - public static function printTimings() : array{ + /** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */ + private static ?ObjectSet $toggleCallbacks = null; + /** @phpstan-var ObjectSet<\Closure() : void> */ + private static ?ObjectSet $reloadCallbacks = null; + /** @phpstan-var ObjectSet<\Closure() : list> */ + private static ?ObjectSet $collectCallbacks = null; + + /** + * @phpstan-template T of object + * @phpstan-param ?ObjectSet $where + * @phpstan-param-out ObjectSet $where + * @phpstan-return ObjectSet + */ + private static function lazyGetSet(?ObjectSet &$where) : ObjectSet{ + //workaround for phpstan bug - allows us to ignore 1 error instead of 6 without suppressing other errors + return $where ??= new ObjectSet(); + } + + /** + * @phpstan-return ObjectSet<\Closure(bool $enable) : void> + */ + public static function getToggleCallbacks() : ObjectSet{ return self::lazyGetSet(self::$toggleCallbacks); } + + /** + * @phpstan-return ObjectSet<\Closure() : void> + */ + public static function getReloadCallbacks() : ObjectSet{ return self::lazyGetSet(self::$reloadCallbacks); } + + /** + * @phpstan-return ObjectSet<\Closure() : list> + */ + public static function getCollectCallbacks() : ObjectSet{ return self::lazyGetSet(self::$collectCallbacks); } + + /** + * @return string[] + * @phpstan-return list + */ + public static function printCurrentThreadRecords() : array{ + $threadId = NativeThread::getCurrentThread()?->getThreadId(); $groups = []; foreach(TimingsRecord::getAll() as $timings){ @@ -49,7 +95,7 @@ class TimingsHandler{ $avg = $time / $count; - $group = $timings->getGroup(); + $group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : ""); $groups[$group][] = implode(" ", [ $timings->getName(), "Time: $time", @@ -72,6 +118,15 @@ class TimingsHandler{ } } + return $result; + } + + /** + * @return string[] + */ + private static function printFooter() : array{ + $result = []; + $result[] = "# Version " . Server::getInstance()->getVersion(); $result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion(); @@ -79,29 +134,95 @@ class TimingsHandler{ $sampleTime = hrtime(true) - self::$timingStart; $result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)"; + return $result; } + /** + * @deprecated This only collects timings from the main thread. Collecting timings from all threads is an async + * operation, so it can't be done synchronously. + * + * @return string[] + */ + public static function printTimings() : array{ + $records = self::printCurrentThreadRecords(); + $footer = self::printFooter(); + + return [...$records, ...$footer]; + } + + /** + * Collects timings asynchronously, allowing timings from multiple threads to be aggregated into a single report. + * + * NOTE: You need to add a callback to collectCallbacks if you want to include timings from other threads. They + * won't be automatically collected if you don't, since the main thread has no way to access them. + * + * This is an asynchronous operation, and the result is returned as a promise. + * The caller must add a callback to the returned promise to get the complete timings report. + * + * @phpstan-return Promise> + */ + public static function requestPrintTimings() : Promise{ + $thisThreadRecords = self::printCurrentThreadRecords(); + + $otherThreadRecordPromises = []; + if(self::$collectCallbacks !== null){ + foreach(self::$collectCallbacks as $callback){ + $callbackPromises = $callback(); + array_push($otherThreadRecordPromises, ...$callbackPromises); + } + } + + $resolver = new PromiseResolver(); + Promise::all($otherThreadRecordPromises)->onCompletion( + function(array $promisedRecords) use ($resolver, $thisThreadRecords) : void{ + $resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]); + }, + function() : void{ + throw new \AssertionError("This promise is not expected to be rejected"); + } + ); + + return $resolver->getPromise(); + } + public static function isEnabled() : bool{ return self::$enabled; } public static function setEnabled(bool $enable = true) : void{ + if($enable === self::$enabled){ + return; + } self::$enabled = $enable; - self::reload(); + self::internalReload(); + if(self::$toggleCallbacks !== null){ + foreach(self::$toggleCallbacks as $callback){ + $callback($enable); + } + } } public static function getStartTime() : float{ return self::$timingStart; } - public static function reload() : void{ + private static function internalReload() : void{ TimingsRecord::reset(); if(self::$enabled){ self::$timingStart = hrtime(true); } } + public static function reload() : void{ + self::internalReload(); + if(self::$reloadCallbacks !== null){ + foreach(self::$reloadCallbacks as $callback){ + $callback(); + } + } + } + public static function tick(bool $measure = true) : void{ if(self::$enabled){ TimingsRecord::tick($measure); diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 38f523ad4..c8be174d6 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -67,6 +67,8 @@ use function is_nan; use function is_object; use function is_string; use function mb_check_encoding; +use function mt_getrandmax; +use function mt_rand; use function ob_end_clean; use function ob_get_contents; use function ob_start; @@ -688,4 +690,12 @@ final class Utils{ //jit not available return null; } + + /** + * Returns a random float between 0.0 and 1.0 + * Drop-in replacement for lcg_value() + */ + public static function getRandomFloat() : float{ + return mt_rand() / mt_getrandmax(); + } } diff --git a/src/world/World.php b/src/world/World.php index a8e624dd5..ff65377c0 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\ReversePriorityQueue; +use pocketmine\utils\Utils; use pocketmine\world\biome\Biome; use pocketmine\world\biome\BiomeRegistry; use pocketmine\world\format\Chunk; @@ -120,7 +121,6 @@ use function get_class; use function gettype; use function is_a; use function is_object; -use function lcg_value; use function max; use function microtime; use function min; @@ -1998,10 +1998,10 @@ class World implements ChunkManager{ return null; } - $itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item); + $itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item); $itemEntity->setPickupDelay($delay); - $itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1)); + $itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1)); $itemEntity->spawnToAll(); return $itemEntity; @@ -2018,9 +2018,9 @@ class World implements ChunkManager{ $orbs = []; foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){ - $orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split); + $orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split); - $orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2)); + $orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2)); $orb->spawnToAll(); $orbs[] = $orb; diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 6f2c94223..ea5e1c62a 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -90,6 +90,11 @@ parameters: count: 1 path: ../../../src/plugin/PluginManager.php + - + message: "#^Method pocketmine\\\\timings\\\\TimingsHandler\\:\\:lazyGetSet\\(\\) should return pocketmine\\\\utils\\\\ObjectSet\\ but returns pocketmine\\\\utils\\\\ObjectSet\\\\.$#" + count: 1 + path: ../../../src/timings/TimingsHandler.php + - message: "#^Casting to int something that's already int\\.$#" count: 1 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));