diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..e41741f3640
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @pmmp/server-developers
diff --git a/.github/ISSUE_TEMPLATE/api-change-request.md b/.github/ISSUE_TEMPLATE/api-change-request.md
index 615ab12ef30..e3d24ea0ffe 100644
--- a/.github/ISSUE_TEMPLATE/api-change-request.md
+++ b/.github/ISSUE_TEMPLATE/api-change-request.md
@@ -7,13 +7,13 @@ assignees: ''
---
-
-## Description
+
+## Problem description
-
-## Justification
+
+## Proposed solution
-## Alternative methods
+## Alternative solutions that don't require API changes
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8a541dfecdf..82fd81a1de6 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,40 +1,32 @@
-## Introduction
+
-### Relevant issues
-
+### Related issues & PRs
## Changes
### API changes
+
### Behavioural changes
+
## Backwards compatibility
+
## Follow-up
-
+
## Tests
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 518a26c7056..97607ab8fa2 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -3,7 +3,7 @@ updates:
- package-ecosystem: composer
directory: "/"
schedule:
- interval: daily
+ interval: weekly
time: "10:00"
open-pull-requests-limit: 10
ignore:
@@ -12,6 +12,22 @@ updates:
update-types:
- "version-update:semver-major"
- "version-update:semver-minor"
+ groups:
+ production-patch-updates:
+ dependency-type: production
+ patterns:
+ - "*"
+ update-types:
+ - "patch"
+ development-patch-updates:
+ dependency-type: development
+ patterns:
+ - "*"
+ update-types:
+ - "patch"
+ phpstan:
+ patterns:
+ - "phpstan/*"
- package-ecosystem: gitsubmodule
directory: "/"
@@ -21,4 +37,4 @@ updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
- interval: daily
+ interval: weekly
diff --git a/.github/readme/pocketmine-dark.png b/.github/readme/legacy-pocketmine-dark.png
similarity index 100%
rename from .github/readme/pocketmine-dark.png
rename to .github/readme/legacy-pocketmine-dark.png
diff --git a/.github/readme/pocketmine.png b/.github/readme/legacy-pocketmine.png
similarity index 100%
rename from .github/readme/pocketmine.png
rename to .github/readme/legacy-pocketmine.png
diff --git a/.github/readme/pocketmine-dark-rgb.gif b/.github/readme/pocketmine-dark-rgb.gif
new file mode 100644
index 00000000000..ccf6152a45f
Binary files /dev/null and b/.github/readme/pocketmine-dark-rgb.gif differ
diff --git a/.github/readme/pocketmine-rgb.gif b/.github/readme/pocketmine-rgb.gif
new file mode 100644
index 00000000000..933891520ba
Binary files /dev/null and b/.github/readme/pocketmine-rgb.gif differ
diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml
index 918ec2cb11a..4325c63f28a 100644
--- a/.github/workflows/build-docker-image.yml
+++ b/.github/workflows/build-docker-image.yml
@@ -12,23 +12,23 @@ jobs:
steps:
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Clone pmmp/PocketMine-Docker repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: pmmp/PocketMine-Docker
fetch-depth: 1
@@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
- uses: docker/build-push-action@v4.0.0
+ uses: docker/build-push-action@v6.9.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@v4.0.0
+ uses: docker/build-push-action@v6.9.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@v4.0.0
+ uses: docker/build-push-action@v6.9.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@v4.0.0
+ uses: docker/build-push-action@v6.9.0
with:
push: true
context: ./pocketmine-mp
diff --git a/.github/workflows/discord-release-embed.php b/.github/workflows/discord-release-embed.php
index 9bcb41bc8c0..5acffdf2e16 100644
--- a/.github/workflows/discord-release-embed.php
+++ b/.github/workflows/discord-release-embed.php
@@ -18,7 +18,12 @@
/**
* @phpstan-return array
*/
-function generateDiscordEmbed(string $version, string $channel, string $description, string $detailsUrl, string $sourceUrl, string $pharDownloadUrl, string $buildLogUrl, int $newsPingRoleId) : array{
+function generateDiscordEmbed(string $version, string $channel, string $description, string $detailsUrl, string $sourceUrl, string $pharDownloadUrl, string $buildLogUrl, int $newsPingRoleId, ?string $phpDownloadUrl) : array{
+ if($phpDownloadUrl !== null){
+ $phpEmbedLink = " | [PHP Binaries]($phpDownloadUrl)";
+ }else{
+ $phpEmbedLink = "";
+ }
return [
"content" => "<@&$newsPingRoleId> New PocketMine-MP release: $version ($channel)",
"embeds" => [
@@ -27,7 +32,7 @@ function generateDiscordEmbed(string $version, string $channel, string $descript
"description" => << $detailsUrl,
"color" => $channel === "stable" ? 0x57ab5a : 0xc69026
@@ -84,10 +89,21 @@ function generateDiscordEmbed(string $version, string $channel, string $descript
$sourceUrl = $buildInfoJson["source_url"];
$pharDownloadUrl = $buildInfoJson["download_url"];
$buildLogUrl = $buildInfoJson["build_log_url"];
+$phpBinaryUrl = $buildInfoJson["php_download_url"] ?? null;
$description = $releaseInfoJson["body"];
-$discordPayload = generateDiscordEmbed($buildInfoJson["base_version"], $buildInfoJson["channel"], $description, $detailsUrl, $sourceUrl, $pharDownloadUrl, $buildLogUrl, (int) $newsPingRoleId);
+$discordPayload = generateDiscordEmbed(
+ $buildInfoJson["base_version"],
+ $buildInfoJson["channel"],
+ $description,
+ $detailsUrl,
+ $sourceUrl,
+ $pharDownloadUrl,
+ $buildLogUrl,
+ (int) $newsPingRoleId,
+ $phpBinaryUrl
+);
$response = Internet::postURL(
$hookURL,
diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml
index 5f4e944a8da..8d0add224e8 100644
--- a/.github/workflows/discord-release-notify.yml
+++ b/.github/workflows/discord-release-notify.yml
@@ -10,15 +10,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup PHP and tools
- uses: shivammathur/setup-php@2.25.2
+ uses: shivammathur/setup-php@2.31.1
with:
- php-version: 8.1
+ php-version: 8.2
- name: Restore Composer package cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.cache/composer/files
diff --git a/.github/workflows/draft-release-from-pr.yml b/.github/workflows/draft-release-from-pr.yml
new file mode 100644
index 00000000000..8a347853bbc
--- /dev/null
+++ b/.github/workflows/draft-release-from-pr.yml
@@ -0,0 +1,65 @@
+name: Draft release from PR
+
+on:
+ #presume that pull_request_target is safe at this point, since the PR was approved and merged
+ #we need write access to prepare the release & create comments
+ pull_request_target:
+ types:
+ - closed
+ branches:
+ - stable
+ - minor-next
+ - major-next
+ - "legacy/*"
+ paths:
+ - "src/VersionInfo.php"
+
+jobs:
+ check:
+ name: Check release
+ uses: ./.github/workflows/draft-release-pr-check.yml
+
+ draft:
+ name: Create GitHub draft release
+ needs: [check]
+ if: needs.check.outputs.valid == 'true'
+
+ uses: ./.github/workflows/draft-release.yml
+
+ post-draft-url-comment:
+ name: Post draft release URL as comment
+ needs: [draft]
+
+ runs-on: ubuntu-20.04
+
+ steps:
+ - name: Post draft release URL on PR
+ uses: thollander/actions-comment-pull-request@v3
+ with:
+ message: "[Draft release ${{ needs.draft.outputs.version }}](${{ needs.draft.outputs.draft-url }}) has been created for commit ${{ github.sha }}. Please review and publish it."
+
+ trigger-post-release-workflow:
+ name: Trigger post-release RestrictedActions workflow
+ # Not sure if needs is actually needed here
+ needs: [check]
+ if: needs.check.outputs.valid == 'true'
+
+ runs-on: ubuntu-20.04
+
+ steps:
+ - name: Generate access token
+ id: generate-token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
+ private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: RestrictedActions
+
+ - name: Dispatch post-release restricted action
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ token: ${{ steps.generate-token.outputs.token }}
+ repository: ${{ github.repository_owner }}/RestrictedActions
+ event-type: pocketmine_mp_post_release
+ client-payload: '{"branch": "${{ github.ref }}"}'
diff --git a/.github/workflows/draft-release-from-tag.yml b/.github/workflows/draft-release-from-tag.yml
new file mode 100644
index 00000000000..f7a5df54444
--- /dev/null
+++ b/.github/workflows/draft-release-from-tag.yml
@@ -0,0 +1,13 @@
+#Allows creating a release by pushing a tag
+#This might be useful for retroactive releases
+name: Draft release from git tag
+
+on:
+ push:
+ tags: "*"
+
+jobs:
+ draft:
+ name: Create GitHub draft release
+ if: "startsWith(github.event.head_commit.message, 'Release ')"
+ uses: ./.github/workflows/draft-release.yml
diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml
new file mode 100644
index 00000000000..4c8d0f68546
--- /dev/null
+++ b/.github/workflows/draft-release-pr-check.yml
@@ -0,0 +1,111 @@
+name: Release PR checks
+
+on:
+ #do checks on every PR update
+ pull_request:
+ branches:
+ - stable
+ - minor-next
+ - major-next
+ - "legacy/*"
+ paths:
+ - "src/VersionInfo.php"
+
+ #allow this workflow to be invoked on PR merge, prior to creating the release
+ workflow_call:
+ outputs:
+ valid:
+ description: Whether this commit is valid for release
+ value: ${{ jobs.check-intent.outputs.valid && jobs.check-validity.result == 'success' }}
+
+permissions:
+ contents: read #for user access check
+
+jobs:
+ check-intent:
+ name: Check release trigger
+ runs-on: ubuntu-20.04
+
+ outputs:
+ valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Check IS_DEVELOPMENT_BUILD flag
+ id: validate
+ run: |
+ echo DEV_BUILD=$(sed -n "s/^\s*public const IS_DEVELOPMENT_BUILD = \(true\|false\);$/\1/p" src/VersionInfo.php) >> $GITHUB_OUTPUT
+
+ check-validity:
+ name: Validate release info
+ needs: [check-intent]
+ #don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses
+ if: needs.check-intent.outputs.valid == 'true'
+
+ runs-on: ubuntu-20.04
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@2.31.1
+ with:
+ php-version: 8.2
+
+ - name: Restore Composer package cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cache/composer/files
+ ~/.cache/composer/vcs
+ key: "composer-v2-cache-${{ hashFiles('./composer.lock') }}"
+ restore-keys: |
+ composer-v2-cache-
+
+ - name: Install Composer dependencies
+ run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs
+
+ - name: Check author permissions
+ id: check-permission
+ uses: actions-cool/check-user-permission@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ require: write
+ username: ${{ github.event.pull_request.user.login }}
+ #technically this would be fine for dependabot but generally bots don't count as team members
+ check-bot: true
+
+ - name: Abort if user permissions are insufficient
+ #user doesn't have permission or is a bot
+ if: steps.check-permission.outputs.require-result != 'true' || steps.check-permission.outputs.check-result != 'false'
+ run: |
+ echo "::error::This user is not authorized to trigger releases"
+ exit 1
+
+ - name: Check changelog file is present
+ id: file-presence
+ run: |
+ CHANGELOG_FILE="changelogs/$(php build/dump-version-info.php changelog_file_name)"
+ if [ ! -f "${{ github.workspace }}/$CHANGELOG_FILE" ]; then
+ echo "::error::$CHANGELOG_FILE does not exist"
+ exit 1
+ fi
+ echo FILE="$CHANGELOG_FILE" >> $GITHUB_OUTPUT
+
+ - name: Check header is present in changelog file
+ run: |
+ FILE="${{ steps.file-presence.outputs.FILE }}"
+ VERSION="$(php build/dump-version-info.php base_version)"
+ if ! grep -Fqx "# $VERSION" "${{ github.workspace }}/$FILE"; then
+ echo "::error::Header for $VERSION not found in $FILE"
+ exit 1
+ fi
+
+ - name: Check version is valid for the selected channel
+ run: |
+ CHANNEL="$(php build/dump-version-info.php channel)"
+ if [ "$(php build/dump-version-info.php suffix_valid)" != "true" ]; then
+ echo "::error::Version $(php build/dump-version-info.php base_version) is not allowed on the $CHANNEL channel"
+ exit 1
+ fi
diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml
index ac2e65b2f35..0a07a738bbc 100644
--- a/.github/workflows/draft-release.yml
+++ b/.github/workflows/draft-release.yml
@@ -1,29 +1,41 @@
name: Draft release
on:
- push:
- tags: "*"
+ workflow_call:
+ outputs:
+ draft-url:
+ description: 'The URL of the draft release'
+ value: ${{ jobs.draft.outputs.draft-url }}
+ version:
+ description: 'PocketMine-MP version'
+ value: ${{ jobs.draft.outputs.version }}
jobs:
draft:
name: Create GitHub draft release
- if: "startsWith(github.event.head_commit.message, 'Release ')"
+
runs-on: ubuntu-20.04
strategy:
fail-fast: false
+ matrix:
+ php-version: [8.2]
+
+ outputs:
+ draft-url: ${{ steps.create-draft.outputs.html_url }}
+ version: ${{ steps.get-pm-version.outputs.PM_VERSION }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
submodules: true
- name: Setup PHP
- uses: shivammathur/setup-php@2.25.2
+ uses: shivammathur/setup-php@2.31.1
with:
- php-version: 8.1
+ php-version: ${{ matrix.php-version }}
- name: Restore Composer package cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.cache/composer/files
@@ -51,36 +63,56 @@ jobs:
- name: Get PocketMine-MP release version
id: get-pm-version
run: |
- echo PM_VERSION=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BASE_VERSION;') >> $GITHUB_OUTPUT
- echo MCPE_VERSION=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK;') >> $GITHUB_OUTPUT
- echo PM_VERSION_SHORT=$(php -r 'require "vendor/autoload.php"; $v = explode(".", \pocketmine\VersionInfo::BASE_VERSION); array_pop($v); echo implode(".", $v);') >> $GITHUB_OUTPUT
- echo PM_VERSION_MD=$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);') >> $GITHUB_OUTPUT
- echo CHANGELOG_SUFFIX=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BUILD_CHANNEL === "stable" ? "" : "-" . \pocketmine\VersionInfo::BUILD_CHANNEL;') >> $GITHUB_OUTPUT
- echo PRERELEASE=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BUILD_CHANNEL === "stable" ? "false" : "true";') >> $GITHUB_OUTPUT
+ echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
+ echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
+ echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
+ echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
+ echo PRERELEASE=$(php build/dump-version-info.php prerelease) >> $GITHUB_OUTPUT
+
+ - 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
- name: Generate build info
- run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} ${{ github.run_id }} > build_info.json
+ run: |
+ php build/generate-build-info-json.php \
+ ${{ github.sha }} \
+ ${{ steps.get-pm-version.outputs.PM_VERSION }} \
+ ${{ github.repository }} \
+ ${{ steps.build-number.outputs.BUILD_NUMBER }} \
+ ${{ github.run_id }} \
+ ${{ steps.php-binary-url.outputs.PHP_BINARY_URL }} \
+ > build_info.json
+
+ - name: Generate core permission doc for doc.pmmp.io
+ run: php tools/generate-permission-doc.php rst
- name: Upload release artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: release_artifacts
path: |
${{ github.workspace }}/PocketMine-MP.phar
${{ github.workspace }}/start.*
${{ github.workspace }}/build_info.json
+ ${{ github.workspace }}/core-permissions.rst
- name: Create draft release
- uses: ncipollo/release-action@v1.12.0
+ uses: ncipollo/release-action@v1.14.0
+ id: create-draft
with:
- artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
+ artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
commit: ${{ github.sha }}
draft: true
prerelease: ${{ steps.get-pm-version.outputs.PRERELEASE }}
name: PocketMine-MP ${{ steps.get-pm-version.outputs.PM_VERSION }}
tag: ${{ steps.get-pm-version.outputs.PM_VERSION }}
token: ${{ secrets.GITHUB_TOKEN }}
+ skipIfReleaseExists: true #for release PRs, tags will be created on release publish and trigger the tag release workflow - don't create a second draft
body: |
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
- Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}${{ steps.get-pm-version.outputs.CHANGELOG_SUFFIX }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
+ Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.CHANGELOG_FILE_NAME }}#${{ steps.get-pm-version.outputs.CHANGELOG_MD_HEADER }}) for details.
+
+ :information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml
new file mode 100644
index 00000000000..6d71a0e70ca
--- /dev/null
+++ b/.github/workflows/main-php-matrix.yml
@@ -0,0 +1,156 @@
+name: CI (all supported PHP versions)
+
+on:
+ workflow_call:
+ inputs:
+ php:
+ description: 'PHP version in X.Y format'
+ required: true
+ type: string
+
+ #these are parameterized to ease updating
+ pm-version-major:
+ description: 'PocketMine-MP major version'
+ default: 5
+ type: number
+ image:
+ description: 'Runner image to use'
+ default: 'ubuntu-20.04'
+ type: string
+
+jobs:
+ phpstan:
+ name: PHPStan analysis
+ runs-on: ${{ inputs.image }}
+
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: pmmp/setup-php-action@3.1.0
+ with:
+ php-version: ${{ inputs.php }}
+ install-path: "./bin"
+ pm-version-major: ${{ inputs.pm-version-major }}
+
+ - name: Restore Composer package cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cache/composer/files
+ ~/.cache/composer/vcs
+ key: "composer-v2-cache-${{ inputs.php }}-${{ hashFiles('./composer.lock') }}"
+ restore-keys: |
+ composer-v2-cache-
+
+ - name: Install Composer dependencies
+ run: composer install --prefer-dist --no-interaction
+
+ - name: Run PHPStan
+ run: ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
+
+ phpunit:
+ name: PHPUnit tests
+ runs-on: ${{ inputs.image }}
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: pmmp/setup-php-action@3.1.0
+ with:
+ php-version: ${{ inputs.php }}
+ install-path: "./bin"
+ pm-version-major: ${{ inputs.pm-version-major }}
+
+ - name: Restore Composer package cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cache/composer/files
+ ~/.cache/composer/vcs
+ key: "composer-v2-cache-${{ inputs.php }}-${{ hashFiles('./composer.lock') }}"
+ restore-keys: |
+ composer-v2-cache-
+
+ - name: Install Composer dependencies
+ run: composer install --prefer-dist --no-interaction
+
+ - name: Run PHPUnit tests
+ run: ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
+
+ integration:
+ name: Integration tests
+ runs-on: ${{ inputs.image }}
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: true
+
+ - name: Setup PHP
+ uses: pmmp/setup-php-action@3.1.0
+ with:
+ php-version: ${{ inputs.php }}
+ install-path: "./bin"
+ pm-version-major: ${{ inputs.pm-version-major }}
+
+ - name: Restore Composer package cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cache/composer/files
+ ~/.cache/composer/vcs
+ key: "composer-v2-cache-${{ inputs.php }}-${{ hashFiles('./composer.lock') }}"
+ restore-keys: |
+ composer-v2-cache-
+
+ - name: Install Composer dependencies
+ run: composer install --no-dev --prefer-dist --no-interaction
+
+ - name: Run integration tests
+ run: ./tests/travis.sh -t4
+
+ codegen:
+ name: Generated Code consistency checks
+ runs-on: ${{ inputs.image }}
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: pmmp/setup-php-action@3.1.0
+ with:
+ php-version: ${{ inputs.php }}
+ install-path: "./bin"
+ pm-version-major: ${{ inputs.pm-version-major }}
+
+ - name: Restore Composer package cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cache/composer/files
+ ~/.cache/composer/vcs
+ key: "composer-v2-cache-${{ inputs.php }}-${{ hashFiles('./composer.lock') }}"
+ restore-keys: |
+ composer-v2-cache-
+
+ - name: Install Composer dependencies
+ run: composer install --no-dev --prefer-dist --no-interaction
+
+ - name: Update generated code
+ run: composer update-codegen
+
+ - name: Verify code is unchanged
+ run: |
+ git diff
+ git diff --quiet
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d7e93fce0a7..b5a9740b5c1 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,162 +6,17 @@ on:
workflow_dispatch:
jobs:
- phpstan:
- name: PHPStan analysis
- runs-on: ${{ matrix.image }}
-
- strategy:
- fail-fast: false
- matrix:
- image: [ubuntu-20.04]
- php: ["8.1", "8.2"]
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup PHP
- uses: pmmp/setup-php-action@2.0.0
- with:
- php-version: ${{ matrix.php }}
- install-path: "./bin"
- pm-version-major: "5"
-
- - name: Restore Composer package cache
- uses: actions/cache@v3
- with:
- path: |
- ~/.cache/composer/files
- ~/.cache/composer/vcs
- key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
- restore-keys: |
- composer-v2-cache-
-
- - name: Install Composer dependencies
- run: composer install --prefer-dist --no-interaction
-
- - name: Run PHPStan
- run: ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
-
- phpunit:
- name: PHPUnit tests
- runs-on: ${{ matrix.image }}
- strategy:
- fail-fast: false
- matrix:
- image: [ubuntu-20.04]
- php: ["8.1", "8.2"]
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup PHP
- uses: pmmp/setup-php-action@2.0.0
- with:
- php-version: ${{ matrix.php }}
- install-path: "./bin"
- pm-version-major: "5"
-
- - name: Restore Composer package cache
- uses: actions/cache@v3
- with:
- path: |
- ~/.cache/composer/files
- ~/.cache/composer/vcs
- key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
- restore-keys: |
- composer-v2-cache-
-
- - name: Install Composer dependencies
- run: composer install --prefer-dist --no-interaction
-
- - name: Run PHPUnit tests
- run: ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
-
- integration:
- name: Integration tests
- runs-on: ${{ matrix.image }}
- strategy:
- fail-fast: false
- matrix:
- image: [ubuntu-20.04]
- php: ["8.1", "8.2"]
-
- steps:
- - uses: actions/checkout@v3
- with:
- submodules: true
-
- - name: Setup PHP
- uses: pmmp/setup-php-action@2.0.0
- with:
- php-version: ${{ matrix.php }}
- install-path: "./bin"
- pm-version-major: "5"
-
- - name: Restore Composer package cache
- uses: actions/cache@v3
- with:
- path: |
- ~/.cache/composer/files
- ~/.cache/composer/vcs
- key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
- restore-keys: |
- composer-v2-cache-
-
- - name: Install Composer dependencies
- run: composer install --no-dev --prefer-dist --no-interaction
-
- - name: Run integration tests
- run: ./tests/travis.sh -t4
-
- codegen:
- name: Generated Code consistency checks
- runs-on: ${{ matrix.image }}
+ all-php-versions:
+ name: PHP ${{ matrix.php }}
strategy:
fail-fast: false
matrix:
- image: [ubuntu-20.04]
- php: ["8.1", "8.2"]
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup PHP
- uses: pmmp/setup-php-action@2.0.0
- with:
- php-version: ${{ matrix.php }}
- install-path: "./bin"
- pm-version-major: "5"
+ php: ["8.1", "8.2", "8.3"]
- - name: Restore Composer package cache
- uses: actions/cache@v3
- with:
- path: |
- ~/.cache/composer/files
- ~/.cache/composer/vcs
- key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
- restore-keys: |
- composer-v2-cache-
-
- - name: Install Composer dependencies
- run: composer install --no-dev --prefer-dist --no-interaction
-
- - name: Regenerate registry annotations
- run: php build/generate-registry-annotations.php src
-
- - name: Regenerate KnownTranslation APIs
- run: php build/generate-known-translation-apis.php
-
- - name: Regenerate RuntimeEnum(De)serializer
- run: php build/generate-runtime-enum-serializers.php
-
- - name: Regenerate BedrockData available files constants
- run: php build/generate-bedrockdata-path-consts.php
-
- - name: Verify code is unchanged
- run: |
- git diff
- git diff --quiet
+ uses: ./.github/workflows/main-php-matrix.yml
+ with:
+ php: ${{ matrix.php }}
+ secrets: inherit
codestyle:
name: Code Style checks
@@ -170,15 +25,27 @@ jobs:
fail-fast: false
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup PHP and tools
- uses: shivammathur/setup-php@2.25.2
+ uses: shivammathur/setup-php@2.31.1
with:
- php-version: 8.1
- tools: php-cs-fixer:3.16
+ php-version: 8.2
+ tools: php-cs-fixer:3.49
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff --ansi
+
+ shellcheck:
+ name: ShellCheck
+ runs-on: ubuntu-20.04
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Run ShellCheck
+ uses: ludeeus/action-shellcheck@2.0.0
diff --git a/.github/workflows/pr-remove-waiting-label.yml b/.github/workflows/pr-remove-waiting-label.yml
new file mode 100644
index 00000000000..0e411fe1aea
--- /dev/null
+++ b/.github/workflows/pr-remove-waiting-label.yml
@@ -0,0 +1,24 @@
+name: Remove waiting label from PRs
+
+on:
+ pull_request_target:
+ types: synchronize
+
+jobs:
+ delabel:
+ name: Remove label
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Remove label
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ github.token }}
+ script: |
+ const [owner, repo] = context.payload.repository.full_name.split('/');
+ await github.rest.issues.removeLabel({
+ owner: owner,
+ repo: repo,
+ issue_number: context.payload.number,
+ name: "Status: Waiting on Author",
+ });
diff --git a/.github/workflows/support.yml b/.github/workflows/support.yml
index fe726dfef12..68da365cb51 100644
--- a/.github/workflows/support.yml
+++ b/.github/workflows/support.yml
@@ -8,7 +8,7 @@ jobs:
support:
runs-on: ubuntu-latest
steps:
- - uses: dessant/support-requests@v3
+ - uses: dessant/support-requests@v4
with:
github-token: ${{ github.token }}
support-label: "Support request"
diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml
new file mode 100644
index 00000000000..f1458121380
--- /dev/null
+++ b/.github/workflows/team-pr-auto-approve.yml
@@ -0,0 +1,39 @@
+#Due to GitHub awkwardness, it's not easy to reduce the review requirement for collaborators.
+#Our policy is that 2 collaborators should be aware of every change.
+#For outside PRs, this means 2 collaborator reviews.
+#For PRs made by collaborators, this means 1 reviewer + the author.
+#We trust that collaborators don't need as much oversight.
+name: Auto approve collaborator PRs
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - ready_for_review
+ pull_request_review:
+ types: dismissed
+
+jobs:
+ dispatch:
+ name: Request approval
+ runs-on: ubuntu-latest
+ if: '! github.event.pull_request.draft'
+
+ steps:
+ - name: Generate access token
+ id: generate-token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
+ private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: RestrictedActions
+
+ - name: Dispatch restricted action
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ token: ${{ steps.generate-token.outputs.token }}
+ repository: ${{ github.repository_owner }}/RestrictedActions
+ event-type: auto_approve_collaborator_pr
+ client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "${{ github.event.review.user.id || 0 }}" }'
diff --git a/.github/workflows/update-updater-api.yml b/.github/workflows/update-updater-api.yml
index d3a40013609..3f42062fdba 100644
--- a/.github/workflows/update-updater-api.yml
+++ b/.github/workflows/update-updater-api.yml
@@ -8,12 +8,13 @@ on:
jobs:
build:
runs-on: ubuntu-latest
+ concurrency: update-updater-api # only one job can run at a time, to avoid git conflicts when updating the repository
steps:
- name: Install jq
run: sudo apt update && sudo apt install jq -y
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/update.pmmp.io
ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }}
diff --git a/.gitignore b/.gitignore
index 13c3473764f..6456250a6db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,3 +53,6 @@ Documentation/*
# php-cs-fixer
/.php_cs.cache
/.php-cs-fixer.cache
+
+# install-local-protocol.sh
+/composer-local-protocol.*
diff --git a/BUILDING.md b/BUILDING.md
index 95197de6bd3..986f098e2e3 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -2,7 +2,7 @@
## Pre-requisites
- A bash shell (git bash is sufficient for Windows)
- [`git`](https://git-scm.com) available in your shell
-- PHP 8.1 or newer available in your shell
+- PHP 8.2 or newer available in your shell
- [`composer`](https://getcomposer.org) available in your shell
## Custom PHP binaries
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6b47a7fad45..3490542c5b2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -88,45 +88,58 @@ Depending on the changes, maintainers might ask you to make changes to the PR to
### Requirements
The following are required as a minimum for pull requests. PRs that don't meet these requirements will be declined unless updated to meet them.
-#### Licensing
-PocketMine-MP is licensed under [LGPLv3 license](LICENSE).
-By proposing a pull request, you agree to your code being distributed within PocketMine-MP under the same license.
-If you take code from other projects, that code MUST be licensed under an LGPL-compatible license.
-
-#### PRs should be about exactly ONE thing
-If you want to make multiple changes, those changes should each be contributed as separate pull requests. **DO NOT** mix unrelated changes.
-
-#### PRs must not include unnecessary/unrelated changes
-Do not include changes which aren't strictly necessary. This makes it harder to review a PR, because the code diff becomes larger and harder to review.
-This means:
-- don't reformat or rearrange existing code
-- don't change things that aren't related to the PR's objective
-- don't rewrite existing code just to make it "look nicer"
-- don't change PhpDocs to native types in code you didn't write
-
-#### Tests must be provided
-Where possible, PHPUnit tests should be written for new or changed code.
-If that's not possible (e.g. for in-game functionality), the code must be tested manually and details of the tests done must be provided.
-**Simply saying "Tested" is not acceptable** and will lead to your PR being declined.
-
-#### Comments and documentation must be written in American English
-English is the shared languages of all current maintainers.
-
-#### Code must be in the PocketMine-MP style
-It's your responsibility to ensure your code matches the formatting and styling of the rest of the code.
-If you use PhpStorm, a `Project` code style is provided, which you can use to automatically format new code.
-You can also use [`php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to format your code.
+- **All code must be licensed under the [LGPLv3 license](LICENSE)** as per PocketMine-MP's own license, or a compatible license.
+ - By proposing a pull request, you agree to your code being distributed within PocketMine-MP under the same license.
+ - If you take code from other projects, that code MUST be licensed under an LGPL-compatible license.
+- **PRs should be about ONE thing**
+ - If you want to make multiple changes, those changes should each be contributed as separate pull requests. **DO NOT** mix unrelated changes.
+ - **Do not include unnecessary changes.** This makes the code diff larger and more noisy, making it harder to review.
+ - Don't change things that aren't related to the PR's objective
+ - Don't reformat or rearrange existing code without a good reason related to the PR's objective
+ - Don't rewrite existing code just to make it "look nicer"
+ - Don't change PhpDocs to native types in code you didn't write, unless that's the objective of the PR
+- **Test code changes, and tell us what tests have been done.**
+ - Where possible, PHPUnit tests should be written for new or changed code. If that's not possible (e.g. for in-game functionality), the code must be tested manually and details of the tests done must be provided.
+ - **Simply saying "Tested" is not acceptable** and could lead to your PR being declined.
+- **Code, comments and documentation must be written in American English.** English is the shared languages of all current maintainers.
+- **Code must be in the PocketMine-MP style.**
+ - It's your responsibility to ensure your code matches the formatting and styling of the rest of the code.
+ - If you use PhpStorm, a `Project` code style is provided, which you can use to automatically format new code.
+ - You can also use [`php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to format your code.
+- **Use `final` and `private` wherever possible**.
+ - Changing from `private` to `protected` or `final` to non-`final` doesn't break backwards compatibility, but the opposite does.
+ - `private` and `final` also enable certain performance optimizations which are otherwise not possible.
+ - `private` members can be freely changed, added and removed in the future, so it's ideal for internal functions. Abusing `protected` makes internal improvements inconvenient.
+ - "Let's leave it protected/public in case someone needs it for ... idk what" is **not a valid reason to expose things**. If there isn't a clear reason for something to be accessible from the outside, don't expose it.
+ - **This is a lesson learned through years of experience.** You may not like it, but it's for the best.
+- **Immutable things are almost always preferred.**
+ - Do not add unnecessary setters or public writable properties to classes. As above, "Let's leave it in case someone needs it" is **not a valid reason to expose things**.
+ - Mutable classes and properties are unpredictable, since code has no way to know if the object it's working with might be randomly modified by another part of the code. This makes it harder to maintain code and debug issues.
+ - Most classes exist only to hold some data. These are called "data transfer objects" (DTOs). These types of classes should pretty much always be immutable.
+ - Make use of `final`, `private` and `readonly` modifiers.
### Recommendations
-
+- **Be patient.** Reviewing pull requests takes a lot of time and energy, and maintainers are often unavailable or busy. Your PR might not receive attention for a while.
+ - 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.
+- **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.
+- **Do not make large pull requests without an RFC.**
+ - Large changes should be discussed beforehand using the [RFC / Change Proposal](#rfcs--change-proposals) process.
+ - Large changes are much harder to review, and are more likely to be declined if maintainers don't have a good idea what you're trying to do in advance.
- **Create a new branch on your fork for each pull request.** This allows you to use the same fork to make multiple pull requests at the same time.
+- **Make your PR diff as small as possible.** Smaller PRs are **much more likely** to be accepted, as they are easier to review.
+ - Avoid moving code around in files if possible.
+ - Don't make random CS changes. This makes the diff noisier and harder to review.
- **Use descriptive commit titles.** You can see an example [here](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
-- **Do not include multiple unrelated changes in one commit.** An atomic style for commits is preferred - this means that changes included in a commit should be part of a single distinct change set. See [this link](https://www.freshconsulting.com/atomic-commits/) for more information on atomic commits. See the [documentation on `git add`](https://git-scm.com/docs/git-add) for information on how to isolate local changes for committing.
-- **Your pull request will be checked and discussed in due time.** Since the team is scattered all around the world, your PR may not receive any attention for some time.
-- **Do not make large pull requests without an RFC.** Large changes should be discussed beforehand using the [RFC / Change Proposal](#rfcs--change-proposals) process. Large changes are much harder to review and are more likely to be declined if maintainers don't have a good idea what you're trying to do in advance.
-- **Do not copy-paste code**. There are potential license issues implicit with copy-pasting, and copy-paste usually indicates a lack of understanding of the actual code. Copy-pasted code is obvious a mile off and **any PR like this is likely to be closed**. If you want to use somebody else's code from a Git repository, **use [GIT's cherry-pick feature](https://git-scm.com/docs/git-cherry-pick)** to cherry-pick the commit.
+- **Split unrelated changes into multiple commits.**
+ - An atomic style for commits is preferred - this means that changes included in a commit should be part of a single distinct change set.
+ - If you need to use "and" or "multiple changes" in your commit message, the commit probably needs to be split up. There are exceptions, but this is a good rule of thumb.
+ - See [this link](https://www.freshconsulting.com/atomic-commits/) for more information on atomic commits.
+ - See the [documentation on `git add -i` or `git add -p`](https://git-scm.com/docs/git-add) for information on how to split up local changes for committing.
**Thanks for contributing to PocketMine-MP!**
diff --git a/README.md b/README.md
index fb3a0945933..b9e2e18887b 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@
-
-
+
+
A highly customisable, open source server software for Minecraft: Bedrock Edition written in PHP
@@ -20,31 +20,61 @@
-## Getting started
+## What is this?
+PocketMine-MP is a highly customisable server software for Minecraft: Bedrock Edition, built from scratch in PHP, with over 10 years of history.
+
+If you're looking to create a Minecraft: Bedrock server with **custom functionality**, look no further.
+
+- 🧩 **Powerful plugin API** - extend and customise gameplay as you see fit
+- 🗺️ **Rich ecosystem** and **large developer community** - find plugins easily and learn to develop your own
+- 🌐 **Multi-world support** - offer a more varied game experience to players without transferring them to other server nodes
+- 🏎️ **Performance** - get 100+ players onto one server (depending on hardware and plugins)
+- ⤴️ **Continuously updated** - new Minecraft versions are usually supported within days
+
+## :x: PocketMine-MP is NOT a vanilla Minecraft server software.
+**It is poorly suited to hosting vanilla survival servers.**
+It doesn't have many features from the vanilla game, such as vanilla world generation, redstone, mob AI, and various other things.
+
+If you just want to play **vanilla survival multiplayer**, consider using the [official Minecraft: Bedrock server software](https://minecraft.net/download/server/bedrock) instead of PocketMine-MP.
+
+If that's not an option for you, you may be able to add some of PocketMine-MP's missing features using plugins from [Poggit](https://poggit.pmmp.io/plugins), or write plugins to implement them yourself.
+
+## Getting Started
- [Documentation](http://pmmp.readthedocs.org/)
- [Installation instructions](https://pmmp.readthedocs.io/en/rtfd/installation.html)
- [Docker image](https://github.com/pmmp/PocketMine-MP/pkgs/container/pocketmine-mp)
- [Plugin repository](https://poggit.pmmp.io/plugins)
-## Discussion/Help
-- [Forums](https://forums.pmmp.io/)
-- [Discord](https://discord.gg/bmSAZBG)
-- [StackOverflow](https://stackoverflow.com/tags/pocketmine)
+## Community & Support
+Join our [Discord](https://discord.gg/bmSAZBG) server to chat with other users and developers.
+
+You can also post questions on [StackOverflow](https://stackoverflow.com/tags/pocketmine) under the tag `pocketmine`.
+
+## Developing Plugins
+If you want to write your own plugins, the following resources may be useful.
+Don't forget you can always ask our community if you need help.
-## For developers
- * [Building and running from source](BUILDING.md)
* [Developer documentation](https://devdoc.pmmp.io) - General documentation for PocketMine-MP plugin developers
* [Latest release API documentation](https://apidoc.pmmp.io) - Doxygen API documentation generated for each release
* [Latest bleeding-edge API documentation](https://apidoc-dev.pmmp.io) - Doxygen API documentation generated weekly from `major-next` branch
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
+
+## Contributing to PocketMine-MP
+PocketMine-MP accepts community contributions! The following resources will be useful if you want to contribute to PocketMine-MP.
+ * [Building and running PocketMine-MP from source](BUILDING.md)
* [Contributing Guidelines](CONTRIBUTING.md)
## Donate
-- Bitcoin Cash (BCH): `qq3r46hn6ljnhnqnfwxt5pg3g447eq9jhvw5ddfear`
+PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love.
+
+You can support development using the following methods:
+
+- [Patreon](https://www.patreon.com/pocketminemp)
- Bitcoin (BTC): `171u8K9e4FtU6j3e5sqNoxKUgEw9qWQdRV`
- Stellar Lumens (XLM): `GAAC5WZ33HCTE3BFJFZJXONMEIBNHFLBXM2HJVAZHXXPYA3HP5XPPS7T`
-- [Patreon](https://www.patreon.com/pocketminemp)
+
+Thanks for your support!
## Licensing information
This project is licensed under LGPL-3.0. Please see the [LICENSE](/LICENSE) file for details.
diff --git a/build/dump-version-info.php b/build/dump-version-info.php
new file mode 100644
index 00000000000..8898d7cabf5
--- /dev/null
+++ b/build/dump-version-info.php
@@ -0,0 +1,86 @@
+ $options
+ */
+$options = [
+ "base_version" => VersionInfo::BASE_VERSION,
+ "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
+ "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
+ "changelog_file_name" => function() : string{
+ $version = VersionInfo::VERSION();
+ $result = $version->getMajor() . "." . $version->getMinor();
+ $suffix = $version->getSuffix();
+ if($suffix !== ""){
+ if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){
+ fwrite(STDERR, "error: invalid current version suffix \"$suffix\"; aborting" . PHP_EOL);
+ exit(1);
+ }
+ $baseSuffix = $matches[1];
+ $result .= "-" . strtolower($baseSuffix);
+ }
+ return $result . ".md";
+ },
+ "changelog_md_header" => fn() : string => str_replace(".", "", VersionInfo::BASE_VERSION),
+ "prerelease" => fn() : bool => VersionInfo::VERSION()->getSuffix() !== "",
+ "channel" => VersionInfo::BUILD_CHANNEL,
+ "suffix_valid" => function() : bool{
+ //TODO: maybe this should be put into its own script?
+ $suffix = VersionInfo::VERSION()->getSuffix();
+ if(VersionInfo::BUILD_CHANNEL === "stable"){
+ //stable builds may not have suffixes
+ return $suffix === "";
+ }
+ if(VersionInfo::BUILD_CHANNEL === "alpha" || VersionInfo::BUILD_CHANNEL === "beta"){
+ $upperChannel = strtoupper(VersionInfo::BUILD_CHANNEL);
+ $upperSuffix = strtoupper($suffix);
+ return str_starts_with($upperSuffix, $upperChannel) && is_numeric(substr($upperSuffix, strlen($upperChannel)));
+ }
+ return true;
+ }
+];
+if(count($argv) !== 2 || !isset($options[$argv[1]])){
+ fwrite(STDERR, "Please provide an option (one of: " . implode(", ", array_keys($options)) . PHP_EOL);
+ exit(1);
+}
+
+$result = $options[$argv[1]];
+if($result instanceof Closure){
+ $result = $result();
+}
+if(is_bool($result)){
+ echo $result ? "true" : "false";
+}else{
+ echo $result;
+}
diff --git a/build/generate-biome-ids.php b/build/generate-biome-ids.php
new file mode 100644
index 00000000000..f36591fe4de
--- /dev/null
+++ b/build/generate-biome-ids.php
@@ -0,0 +1,133 @@
+ $map
+ */
+function generate(array $map, string $outputFile) : void{
+ $file = safe_fopen($outputFile, 'wb');
+ fwrite($file, HEADER);
+ fwrite($file, <<<'CLASSHEADER'
+namespace pocketmine\data\bedrock;
+
+final class BiomeIds{
+
+ private function __construct(){
+ //NOOP
+ }
+
+
+CLASSHEADER
+);
+ $list = $map;
+ asort($list, SORT_NUMERIC);
+ $lastId = -1;
+ foreach(Utils::stringifyKeys($list) as $name => $id){
+ if($name === ""){
+ continue;
+ }
+ if($id !== $lastId + 1){
+ fwrite($file, "\n");
+ }
+ $lastId = $id;
+ fwrite($file, "\tpublic const " . make_const_name($name) . ' = ' . $id . ';' . "\n");
+ }
+ fwrite($file, "}\n");
+ fclose($file);
+}
+
+$ids = json_decode(Filesystem::fileGetContents(BedrockDataFiles::BIOME_ID_MAP_JSON), true);
+if(!is_array($ids)){
+ throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
+}
+$cleanedIds = [];
+foreach($ids as $name => $id){
+ if(!is_string($name) || !is_int($id)){
+ throw new \RuntimeException("Invalid biome ID map, expected string => int map");
+ }
+ $cleanedIds[$name] = $id;
+}
+generate($cleanedIds, dirname(__DIR__) . '/src/data/bedrock/BiomeIds.php');
+
+echo "Done. Don't forget to run CS fixup after generating code.\n";
diff --git a/build/generate-block-serializer-consts.php b/build/generate-block-serializer-consts.php
index 279ffc78880..875729fcf42 100644
--- a/build/generate-block-serializer-consts.php
+++ b/build/generate-block-serializer-consts.php
@@ -44,6 +44,7 @@
use function is_string;
use function ksort;
use function mb_strtoupper;
+use function preg_replace;
use function sort;
use function strrpos;
use function strtoupper;
@@ -101,6 +102,25 @@ function generateClassHeader(string $className) : string{
return <<seenStateValues) as $state => $values){
- $constName = mb_strtoupper($state, 'US-ASCII');
+ $constName = mb_strtoupper(preg_replace("/^minecraft:/", "mc_", $state) ?? throw new AssumptionFailedError("This regex is not invalid"), 'US-ASCII');
fwrite($output, "\tpublic const $constName = \"$state\";\n");
}
@@ -158,7 +178,7 @@ function generateBlockStringValues(BlockPaletteReport $data) : void{
continue;
}
$anyWritten = true;
- $constName = mb_strtoupper($stateName . "_" . $value, 'US-ASCII');
+ $constName = mb_strtoupper(preg_replace("/^minecraft:/", "mc_", $stateName) . "_" . $value, 'US-ASCII');
fwrite($output, "\tpublic const $constName = \"$value\";\n");
}
if($anyWritten){
diff --git a/build/generate-build-info-json.php b/build/generate-build-info-json.php
index f0ffe7df870..a2e7489f3a6 100644
--- a/build/generate-build-info-json.php
+++ b/build/generate-build-info-json.php
@@ -21,24 +21,28 @@
declare(strict_types=1);
+use pocketmine\network\mcpe\protocol\ProtocolInfo;
+use pocketmine\VersionInfo;
+
require dirname(__DIR__) . '/vendor/autoload.php';
-if(count($argv) !== 6){
- fwrite(STDERR, "required args: \n");
+if(count($argv) !== 7){
+ fwrite(STDERR, "required args: \n");
exit(1);
}
echo json_encode([
- "php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
- "base_version" => \pocketmine\VersionInfo::BASE_VERSION,
+ "php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION), //deprecated
+ "base_version" => VersionInfo::BASE_VERSION,
"build" => (int) $argv[4],
- "is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD,
- "channel" => \pocketmine\VersionInfo::BUILD_CHANNEL,
+ "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
+ "channel" => VersionInfo::BUILD_CHANNEL,
"git_commit" => $argv[1],
- "mcpe_version" => \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK,
+ "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"date" => time(), //TODO: maybe we should embed this in VersionInfo?
"details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]",
"download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar",
"source_url" => "https://github.com/$argv[3]/tree/$argv[2]",
"build_log_url" => "https://github.com/$argv[3]/actions/runs/$argv[5]",
+ "php_download_url" => $argv[6],
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
diff --git a/build/generate-item-type-names.php b/build/generate-item-type-names.php
index 2cbc07c2d70..8d37e949b19 100644
--- a/build/generate-item-type-names.php
+++ b/build/generate-item-type-names.php
@@ -23,6 +23,7 @@
namespace pocketmine\build\generate_item_serializer_ids;
+use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\network\mcpe\convert\ItemTypeDictionaryFromDataHelper;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
@@ -45,10 +46,10 @@ function constifyMcId(string $id) : string{
return strtoupper(explode(":", $id, 2)[1]);
}
-function generateItemIds(ItemTypeDictionary $dictionary) : void{
+function generateItemIds(ItemTypeDictionary $dictionary, BlockItemIdMap $blockItemIdMap) : void{
$ids = [];
foreach($dictionary->getEntries() as $entry){
- if($entry->getNumericId() < 256){ //blockitems are serialized via BlockStateSerializer
+ if($entry->getStringId() === "minecraft:air" || $blockItemIdMap->lookupBlockId($entry->getStringId()) !== null){ //blockitems are serialized via BlockStateSerializer
continue;
}
$ids[$entry->getStringId()] = $entry->getStringId();
@@ -60,6 +61,25 @@ function generateItemIds(ItemTypeDictionary $dictionary) : void{
fwrite($file, <<<'HEADER'
$constants
+ * @phpstan-param-out array $constants
+ */
+function collectProperties(string $prefix, array $properties, array &$constants) : void{
+ foreach($properties as $propertyName => $property){
+ $fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName;
+
+ $constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName));
+ $constants[$constName] = $fullPropertyName;
+
+ if(is_array($property)){
+ collectProperties($fullPropertyName, $property, $constants);
+ }
+ }
+}
+
+collectProperties("", $defaultConfig, $constants);
+ksort($constants, SORT_STRING);
+
+$file = fopen(dirname(__DIR__) . '/src/YmlServerProperties.php', 'wb');
+if($file === false){
+ fwrite(STDERR, "Failed to open output file\n");
+ exit(1);
+}
+fwrite($file, " $propertyName){
+ fwrite($file, "\tpublic const $constName = '$propertyName';\n");
+}
+fwrite($file, "}\n");
+
+fclose($file);
+
+echo "Done. Don't forget to run CS fixup after generating code.\n";
diff --git a/build/generate-runtime-enum-serializers.php b/build/generate-runtime-enum-serializers.php
deleted file mode 100644
index 1273e26f825..00000000000
--- a/build/generate-runtime-enum-serializers.php
+++ /dev/null
@@ -1,263 +0,0 @@
- $memberNames
- *
- * @return string[]
- * @phpstan-return list
- */
-function buildWriterFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string $functionName) : array{
- $bits = getBitsRequired($memberNames);
- $lines = [];
-
- $lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
- $lines[] = "\t\$this->writeInt($bits, match(\$value){";
-
- foreach($memberNames as $key => $memberName){
- $lines[] = "\t\t$memberName => $key,";
- }
- $lines[] = "\t\tdefault => throw new \pocketmine\utils\AssumptionFailedError(\"All $virtualTypeName cases should be covered\")";
- $lines[] = "\t});";
- $lines[] = "}";
-
- return $lines;
-}
-
-/**
- * @param string[] $memberNames
- * @phpstan-param list $memberNames
- *
- * @return string[]
- * @phpstan-return list
- */
-function buildReaderFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string $functionName) : array{
- $bits = getBitsRequired($memberNames);
- $lines = [];
-
- $lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
- $lines[] = "\t\$value = match(\$this->readInt($bits)){";
-
- foreach($memberNames as $key => $memberName){
- $lines[] = "\t\t$key => $memberName,";
- }
- $lines[] = "\t\tdefault => throw new InvalidSerializedRuntimeDataException(\"Invalid serialized value for $virtualTypeName\")";
- $lines[] = "\t};";
- $lines[] = "}";
-
- return $lines;
-}
-
-function buildInterfaceFunc(string $nativeTypeName, string $functionName) : string{
- return "public function $functionName(\\$nativeTypeName &\$value) : void;";
-}
-
-/**
- * @param string[] $memberNames
- * @phpstan-param list $memberNames
- *
- * @return string[]
- * @phpstan-return list
- */
-function buildSizeCalculationFunc(string $nativeTypeName, string $functionName, array $memberNames) : array{
- $lines = [];
- $lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
- $lines[] = "\t\$this->addBits(" . getBitsRequired($memberNames) . ");";
- $lines[] = "}";
-
- return $lines;
-}
-
-/**
- * @param mixed[] $members
- */
-function getBitsRequired(array $members) : int{
- return (int) ceil(log(count($members), 2));
-}
-
-/**
- * @param object[] $members
- * @phpstan-param array $members
- *
- * @return string[]
- * @phpstan-return list
- */
-function stringifyEnumMembers(array $members, string $enumClass) : array{
- ksort($members, SORT_STRING);
- return array_map(fn(string $enumCaseName) => "\\$enumClass::$enumCaseName()", array_keys($members));
-}
-
-$enumsUsed = [
- BellAttachmentType::getAll(),
- CopperOxidation::getAll(),
- CoralType::getAll(),
- DirtType::getAll(),
- DyeColor::getAll(),
- FroglightType::getAll(),
- LeverFacing::getAll(),
- MedicineType::getAll(),
- MushroomBlockType::getAll(),
- MobHeadType::getAll(),
- SlabType::getAll(),
- SuspiciousStewType::getAll(),
- PotionType::getAll()
-];
-
-$readerFuncs = [
- "" => [
- "abstract protected function readInt(int \$bits) : int;"
- ]
-];
-$writerFuncs = [
- "" => [
- "abstract protected function writeInt(int \$bits, int \$value) : void;"
- ]
-];
-$interfaceFuncs = [];
-$sizeCalculationFuncs = [
- "" => [
- "abstract protected function addBits(int \$bits) : void;"
- ]
-];
-
-foreach($enumsUsed as $enumMembers){
- if(count($enumMembers) === 0){
- throw new \InvalidArgumentException("Enum members cannot be empty");
- }
- $reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]);
- $virtualTypeName = $reflect->getShortName();
- $nativeTypeName = $reflect->getName();
- $functionName = lcfirst($virtualTypeName);
-
- $stringifiedMembers = stringifyEnumMembers($enumMembers, $nativeTypeName);
- $writerFuncs[$functionName] = buildWriterFunc(
- $virtualTypeName,
- $nativeTypeName,
- $stringifiedMembers,
- $functionName
- );
- $readerFuncs[$functionName] = buildReaderFunc(
- $virtualTypeName,
- $nativeTypeName,
- $stringifiedMembers,
- $functionName
- );
- $interfaceFuncs[$functionName] = [buildInterfaceFunc(
- $nativeTypeName,
- $functionName
- )];
- $sizeCalculationFuncs[$functionName] = buildSizeCalculationFunc(
- $nativeTypeName,
- $functionName,
- $stringifiedMembers
- );
-}
-
-/**
- * @param string[][] $functions
- * @phpstan-param array> $functions
- */
-function printFunctions(array $functions, string $className, string $classType) : void{
- ksort($functions, SORT_STRING);
-
- ob_start();
-
- echo <<<'HEADER'
- "\t" . implode("\n\t", $functionLines), $functions));
- echo "\n\n}\n";
-
- file_put_contents(dirname(__DIR__) . '/src/data/runtime/' . $className . '.php', ob_get_clean());
-}
-
-printFunctions($writerFuncs, "RuntimeEnumSerializerTrait", "trait");
-printFunctions($readerFuncs, "RuntimeEnumDeserializerTrait", "trait");
-printFunctions($interfaceFuncs, "RuntimeEnumDescriber", "interface");
-printFunctions($sizeCalculationFuncs, "RuntimeEnumSizeCalculatorTrait", "trait");
-
-echo "Done. Don't forget to run CS fixup after generating code.\n";
diff --git a/build/make-release.php b/build/make-release.php
index 7a570eb3570..741f9d787ad 100644
--- a/build/make-release.php
+++ b/build/make-release.php
@@ -86,7 +86,8 @@ function systemWrapper(string $command, string $errorMessage) : void{
function main() : void{
$filteredOpts = [];
- foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help"])) as $optName => $optValue){
+ $postCommitOnly = false;
+ foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help", "post"])) as $optName => $optValue){
if($optName === "help"){
fwrite(STDOUT, "Options:\n");
@@ -96,6 +97,10 @@ function main() : void{
}
exit(0);
}
+ if($optName === "post"){
+ $postCommitOnly = true;
+ continue;
+ }
if(!is_string($optValue)){
fwrite(STDERR, "--$optName expects exactly 1 value\n");
exit(1);
@@ -141,20 +146,25 @@ function main() : void{
$channel ??= "stable";
}
- echo "About to tag version $currentVer. Next version will be $nextVer.\n";
- echo "$currentVer will be published on release channel \"$channel\".\n";
- echo "please add appropriate notes to the changelog and press enter...";
- fgets(STDIN);
- systemWrapper('git add "' . dirname(__DIR__) . '/changelogs"', "failed to stage changelog changes");
- system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result);
- if($result === 0){
- echo "error: no changelog changes detected; aborting\n";
- exit(1);
- }
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
- replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
- systemWrapper('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"', "failed to create release commit");
- systemWrapper('git tag ' . $currentVer->getBaseVersion(), "failed to create release tag");
+
+ if($postCommitOnly){
+ echo "Skipping release commit & tag. Bumping to next version $nextVer directly.\n";
+ }else{
+ echo "About to tag version $currentVer. Next version will be $nextVer.\n";
+ echo "$currentVer will be published on release channel \"$channel\".\n";
+ echo "please add appropriate notes to the changelog and press enter...";
+ fgets(STDIN);
+ systemWrapper('git add "' . dirname(__DIR__) . '/changelogs"', "failed to stage changelog changes");
+ system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result);
+ if($result === 0){
+ echo "error: no changelog changes detected; aborting\n";
+ exit(1);
+ }
+ replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
+ systemWrapper('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"', "failed to create release commit");
+ systemWrapper('git tag ' . $currentVer->getBaseVersion(), "failed to create release tag");
+ }
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit");
diff --git a/build/php b/build/php
index fcbc15f23e7..a51259d7a6e 160000
--- a/build/php
+++ b/build/php
@@ -1 +1 @@
-Subproject commit fcbc15f23e70d01b618cf21d090146e02254e735
+Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a
diff --git a/build/server-phar-stub.php b/build/server-phar-stub.php
new file mode 100644
index 00000000000..b4018e3a78b
--- /dev/null
+++ b/build/server-phar-stub.php
@@ -0,0 +1,168 @@
+convertToData(\Phar::TAR, \Phar::NONE);
+ unset($phar);
+ \Phar::unlinkArchive($tmpPharPath);
+
+ return $tmpName . ".tar";
+}
+
+/**
+ * Locks a phar tmp cache to prevent it from being deleted by other server instances.
+ * This code looks similar to Filesystem::createLockFile(), but we can't use that because it's inside the compressed
+ * phar.
+ */
+function lockPharCache(string $lockFilePath) : void{
+ //this static variable will keep the file(s) locked until the process ends
+ static $lockFiles = [];
+
+ $lockFile = fopen($lockFilePath, "wb");
+ if($lockFile === false){
+ throw new \RuntimeException("Failed to open temporary file");
+ }
+ flock($lockFile, LOCK_EX); //this tells other server instances not to delete this cache file
+ fwrite($lockFile, (string) getmypid()); //maybe useful for debugging
+ fflush($lockFile);
+ $lockFiles[$lockFilePath] = $lockFile;
+}
+
+/**
+ * Prepares a decompressed .tar of PocketMine-MP.phar in the system temp directory for loading code from.
+ *
+ * @return string path to the temporary decompressed phar (actually a .tar)
+ */
+function preparePharCache(string $tmpPath, string $pharPath) : string{
+ clearstatcache();
+
+ $tmpName = tempnam($tmpPath, "PMMP");
+ if($tmpName === false){
+ throw new \RuntimeException("Failed to create temporary file");
+ }
+
+ lockPharCache($tmpName . ".lock");
+ return convertPharToTar($tmpName, $pharPath);
+}
+
+$tmpDir = preparePharCacheDirectory();
+cleanupPharCache($tmpDir);
+echo "Preparing PocketMine-MP.phar decompressed cache...\n";
+$start = hrtime(true);
+$cacheName = preparePharCache($tmpDir, __FILE__);
+echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n";
+
+require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php';
diff --git a/build/server-phar.php b/build/server-phar.php
index 0665c89d5d9..f6bb29d5146 100644
--- a/build/server-phar.php
+++ b/build/server-phar.php
@@ -23,7 +23,9 @@
namespace pocketmine\build\server_phar;
+use pocketmine\utils\Filesystem;
use pocketmine\utils\Git;
+use Symfony\Component\Filesystem\Path;
use function array_map;
use function count;
use function dirname;
@@ -32,6 +34,7 @@
use function getopt;
use function implode;
use function ini_get;
+use function is_string;
use function microtime;
use function preg_quote;
use function realpath;
@@ -147,8 +150,17 @@ function main() : void{
}else{
$build = 0;
}
+ if(isset($opts["out"])){
+ if(!is_string($opts["out"])){
+ echo "--out cannot be specified multiple times" . PHP_EOL;
+ exit(1);
+ }
+ $pharPath = $opts["out"];
+ }else{
+ $pharPath = getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar";
+ }
foreach(buildPhar(
- $opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar",
+ $pharPath,
dirname(__DIR__) . DIRECTORY_SEPARATOR,
[
'resources',
@@ -159,21 +171,7 @@ function main() : void{
'git' => $gitHash,
'build' => $build
],
- <<<'STUB'
-__destruct()` when thread-local storage referenced other `AsyncTask` objects.
+
+## Internals
+- Added a concurrency lock to prevent the `update-updater-api` GitHub Action from running for multiple releases at the same time (which would have caused one of them to fail due to git conflicts).
diff --git a/changelogs/4.23.md b/changelogs/4.23.md
new file mode 100644
index 00000000000..317b6b458d6
--- /dev/null
+++ b/changelogs/4.23.md
@@ -0,0 +1,68 @@
+# 4.23.0
+Released 12th July 2023.
+
+**For Minecraft: Bedrock Edition 1.20.10**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.10.
+
+**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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.20.10.
+- Removed support for older versions.
+
+## Fixes
+- Fixed Docker image build failure due to outdated `build/php` submodule.
+
+# 4.23.1
+Released 14th July 2023.
+
+## Fixes
+- Hardened validation of JWT signing keys in `LoginPacket`.
+- Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper).
+
+# 4.23.2
+Released 18th July 2023.
+
+## Fixes
+- Fixed login errors due to a new `sandboxId` field appearing in the Xbox Live authentication data in `LoginPacket`. All clients, regardless of version, are affected by this change.
+
+# 4.23.3
+Released 24th July 2023.
+
+## Documentation
+- Fixed typo in `ChunkSelector::selectChunks()` documentation.
+
+## Fixes
+- Fixed the server not stopping properly during crash conditions on *nix platforms.
+- Fixed `HORSE_EQUIP` and `SMITHING_TABLE_TEMPLATE` container UI types not being handled by `ItemStackContainerIdTranslator`. This bug prevented plugins from implementing missing inventory types.
+- Player emotes no longer broadcast messages to other players. This was unintended behaviour caused by a client-side behavioural change.
+- Shulker boxes no longer support the placement of torches or other similar blocks.
+- Fire can now be placed on upper slabs and the top of upside-down stairs.
+
+# 4.23.4
+Released 1st August 2023.
+
+## Fixes
+- Fixed exponentially increasing lag when many hundreds of non-mergeable dropped items occupied the same space. This disproportionately affected SkyBlock servers due to large cactus farms using water to collect items together.
+
+# 4.23.5
+Released 9th August 2023.
+
+## General
+- Updated translation data to [pmmp/Language 2.19.6](https://github.com/pmmp/Language/releases/tag/2.19.6).
+
+## Fixes
+- Fixed `PluginBase->saveResource()` leaking file resources when the data file already exists in the plugin's data folder. This bug existed since 2014 and was only discovered recently.
+- Fixed coral blocks becoming dead after calling `getDropsForCompatibleTool()` on them.
+- Fixed `BlockDeathEvent->getOldState()` returning a block which is already dead.
+
+# 4.23.6
+Released 21st August 2023.
+
+## Fixes
+- Added a workaround for armor and other inventories not working correctly after inventory sync. This is caused by a client bug.
diff --git a/changelogs/4.24.md b/changelogs/4.24.md
new file mode 100644
index 00000000000..2d28e906497
--- /dev/null
+++ b/changelogs/4.24.md
@@ -0,0 +1,17 @@
+# 4.24.0
+Released 20th September 2023.
+
+**For Minecraft: Bedrock Edition 1.20.30**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.30.
+
+**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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.20.30.
+- Removed support for older versions.
+- Updated 4.x obsoletion message.
diff --git a/changelogs/4.25.md b/changelogs/4.25.md
new file mode 100644
index 00000000000..032c09ce8fe
--- /dev/null
+++ b/changelogs/4.25.md
@@ -0,0 +1,16 @@
+# 4.25.0
+Released 26th October 2023.
+
+**For Minecraft: Bedrock Edition 1.20.40**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.40.
+
+**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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.20.40.
+- Removed support for older versions.
diff --git a/changelogs/4.26.md b/changelogs/4.26.md
new file mode 100644
index 00000000000..2244c6cae60
--- /dev/null
+++ b/changelogs/4.26.md
@@ -0,0 +1,16 @@
+# 4.26.0
+Released 6th December 2023.
+
+**For Minecraft: Bedrock Edition 1.20.50**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.50.
+
+**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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.20.50.
+- Removed support for older versions.
diff --git a/changelogs/5.1.md b/changelogs/5.1.md
new file mode 100644
index 00000000000..9ef8e7b9b82
--- /dev/null
+++ b/changelogs/5.1.md
@@ -0,0 +1,52 @@
+# 5.1.0
+Released 7th June 2023.
+
+**For Minecraft: Bedrock Edition 1.20.0**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.0.
+
+**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` 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.20.0.
+- Removed support for older versions.
+
+# 5.1.1
+Released 7th June 2023.
+
+## Fixes
+- Fixed blockstates being saved with the wrong version ID for 1.20.0.
+
+# 5.1.2
+Released 9th June 2023.
+
+**This release includes changes from the following releases:**
+- [4.22.1](https://github.com/pmmp/PocketMine-MP/blob/4.22.1/changelogs/4.22.md#4221) - Teleportation client bug workarounds
+
+This release contains no other changes.
+
+# 5.1.3
+Released 1st July 2023.
+
+**This release includes changes from the following releases:**
+- [4.22.2](https://github.com/pmmp/PocketMine-MP/blob/4.22.2/changelogs/4.22.md#4222) - Authentication time bomb fix
+
+## General
+- Updated logos to new RGB-style logo. Thanks to @MrCakeSlayer and @HBIDamian for their efforts.
+- Improved error messages generated by the world system when some version tags are missing from `level.dat` in Bedrock worlds.
+- Outsourced Composer dependencies now only receive patch updates automatically (pinned using the `~` constraint).
+ - Minor and major updates now require manually updating `composer.json`, to ensure that the plugin API is not broken by libraries getting randomly updated from one patch release to the next.
+
+## Documentation
+- Updated doc comment for `Player->setGamemode()` to remove outdated information.
+- Added documentation for the `$clickVector` parameter of `Block->onInteract()` to specify that it is relative to the block's position.
+- Added missing `@required` tag for `BlockStateUpgradeSchemaModelBlockRemap->newState`.
+
+## Fixes
+- Fixed blue candles not appearing in the creative inventory.
+- Fixed server crash when block-picking candle cakes.
+- `World->useItemOn()` now ensures that the `$clickVector` components are always in the range of 0-1. Previously, any invalid values were accepted, potentially leading to a crash.
diff --git a/changelogs/5.10.md b/changelogs/5.10.md
new file mode 100644
index 00000000000..551bc40ede8
--- /dev/null
+++ b/changelogs/5.10.md
@@ -0,0 +1,46 @@
+# 5.10.0
+Released 14th December 2023.
+
+**For Minecraft: Bedrock Edition 1.20.50**
+
+This is a minor feature release, including new gameplay features and minor performance improvements.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- PHP 8.2 is now used by default. PHP 8.1 is still supported, but will be removed in a future 5.x release.
+- Improved timings reports by removing `Breakdown` timings group. This group serves no purpose with tree timings and made for confusing reading.
+
+## Performance
+- Improved performance of `Block::encodeFullState()` in most conditions. This in turn improves performance of `World::setBlock()` and `World::setBlockAt()`.
+- Improved network compression performance by avoiding unnecessary object allocations.
+- Timings now report time spent in individual `Snooze` handlers, making it easier to debug performance issues.
+
+## Gameplay
+### Blocks
+- Implemented crop growth speed modifiers.
+ - The following things now positively affect crop growth speed:
+ - Being planted on or being adjacent to farmland (hydrated farmland offers a larger benefit than dry farmland)
+ - Potential light level of at least 9
+ - Being planted in rows with space between them (or a different type of crop)
+ - The following things now negatively affect crop growth speed:
+ - Improper arrangement (e.g. the same crop on all sides)
+ - Insufficient light level (below 9)
+ - Poorly arranged crops will grow slower in this version. Past versions behaved as if crops were always planted in ideal conditions.
+ - Crops planted in ideal conditions will grow at the same speed as before.
+
+### Items
+- Added the following new items:
+ - All types of Smithing Template
+- Pitcher Pod is now correctly registered. In previous versions, it was mapped to the Pitcher Crop block, causing incorrect name display in commands.
+
+## Internals
+- Cleaned up various getter usages where direct property access is possible.
+- Avoided unnecessary repeated getter calls in some loops.
+- `NetworkSession` may now track `string` instead of `CompressBatchPromise` when a batch was synchronously compressed. This significantly reduces object allocations and improves performance.
+- `NetworkSession` now sends less information to clients on login validation failure. This avoids leaking potentially sensitive error information to clients.
+ - Clients can correlate their disconnects with server-side logs using the `Error ID` shown on the disconnect screen.
diff --git a/changelogs/5.11.md b/changelogs/5.11.md
new file mode 100644
index 00000000000..e993dceec13
--- /dev/null
+++ b/changelogs/5.11.md
@@ -0,0 +1,45 @@
+# 5.11.0
+Released 7th February 2024.
+
+**For Minecraft: Bedrock Edition 1.20.60**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.60.
+
+**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.20.60.
+- Removed support for earlier versions.
+
+## Fixes
+- Fixed `tools/generate-item-upgrade-schema.php` not correctly handling items whose IDs were changed multiple times.
+- Fixed `ServerKiller` not working correctly in some cases (incorrectly handled wake-up conditions).
+- `ItemBlock`s of `Air` blocks are now always considered as "null" items regardless of count, and don't occupy inventory slots.
+
+## Internals
+- Restructured GitHub Actions CI workflows to make them easier to maintain (no need to update PHP versions in multiple places anymore).
+- GitHub Actions CodeStyle workflow now uses php-cs-fixer 3.49.x.
+- Dependabot updates are now processed weekly instead of daily.
+
+# 5.11.1
+Released 23rd February 2024.
+
+## Fixes
+- Fixed subchunk count calculation in `ChunkSerializer` for non-overworld dimension (useful for dimension plugins).
+- Harden options used for processing JSON data, particularly on the network, to close security issues.
+
+## Documentation
+- Fixed PHPStan signature for `Utils::cloneObjectArray()`.
+
+## Internals
+- Updated GitHub Actions versions to get rid of deprecation warnings.
+
+# 5.11.2
+Released 26th February 2024.
+
+## Fixes
+- Added extra checks for `BookEditPacket` handling.
diff --git a/changelogs/5.12.md b/changelogs/5.12.md
new file mode 100644
index 00000000000..a23f83e56d4
--- /dev/null
+++ b/changelogs/5.12.md
@@ -0,0 +1,63 @@
+# 5.12.0
+Released 28th February 2024
+
+**For Minecraft: Bedrock Edition 1.20.60**
+
+This is a minor feature release, with a few new features and improvements.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- Added a `--version` command-line option to display the server version and exit.
+
+## Tools
+- Added `tools/generate-biome-ids.php` to generate `pocketmine\data\bedrock\BiomeIds`.
+- Fixed ordering of property values generated by `tools/generate-block-palette-spec.php`.
+
+## API
+### `pocketmine\block`
+- The following new classes have been added:
+ - `utils\LightableTrait` - used by blocks with `getLit()` and `setLit()` methods
+- The following methods have been deprecated:
+ - `Block->isSolid()` - this method returns confusing results which don't match expectations and no one really knows what it actually means
+- `CocoaBlock` now extends `Flowable` to match vanilla Minecraft behaviour.
+
+### `pocketmine\plugin`
+- `PluginManager->registerEvent()` now throws an exception when given a generator function for the event handler.
+- `PluginManager->registerEvents()` now throws an exception if any of the detected event handlers are generator functions. Use `@notHandler` to have the function ignored if intended.
+
+### `pocketmine\promise`
+- The following methods have been added:
+ - `public static Promise::all(list $promises) : Promise` - returns a promise that is resolved once all given promises are resolved, or is rejected if any of the promises are rejected.
+
+### `pocketmine\scheduler`
+- The following methods have been deprecated:
+ - `AsyncWorker->getFromThreadStore()` - use class static properties for thread-local storage
+ - `AsyncWorker->removeFromThreadStore()`
+ - `AsyncWorker->saveToThreadStore()`
+
+## Documentation
+- Improved documentation of various methods in `Block`.
+
+## Gameplay
+- The following new items have been added:
+ - Name Tag
+
+## Internals
+- Removed specialization of shutdown logic for `Thread` vs `Worker` (no specialization is required).
+- Authentication system no longer accepts logins signed with the old Mojang root public key.
+- ID to enum mappings in `pocketmine\data` now use a new `match` convention to allow static analysis to ensure that all enum cases are handled.
+- Updated version of `pocketmine/bedrock-protocol` allows avoiding decoding of some itemstack data from the client in most cases, improving performance.
+
+# 5.12.1
+Released 13th March 2024.
+
+## Fixes
+- Fixed `Player Network Receive - Decompression` timings not being stopped correctly when receiving an uncompressed packet.
+
+## Internals
+- Removed hardcoded batch packet size limit. This was already covered by other limits anyway.
diff --git a/changelogs/5.13.md b/changelogs/5.13.md
new file mode 100644
index 00000000000..1a38fce9288
--- /dev/null
+++ b/changelogs/5.13.md
@@ -0,0 +1,16 @@
+# 5.13.0
+Released 13th March 2024.
+
+**For Minecraft: Bedrock Edition 1.20.70**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.70.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- Added support for Minecraft: Bedrock Edition 1.20.70.
+- Removed support for earlier versions.
diff --git a/changelogs/5.14.md b/changelogs/5.14.md
new file mode 100644
index 00000000000..7b9b003f69f
--- /dev/null
+++ b/changelogs/5.14.md
@@ -0,0 +1,94 @@
+# 5.14.0
+Released 5th April 2024.
+
+**For Minecraft: Bedrock Edition 1.20.70**
+
+This is a minor feature release, including performance improvements, minor gameplay features, new API features, and various internal improvements.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- Added support for a `--no-log-file` command-line option, which disables the creation of a `server.log` file.
+ - **Use this with caution.** If you don't have another mechanism for collecting logs (e.g. Docker), this may make debugging harder.
+- Added support for automatic `server.log` rotation. When the `server.log` exceeds 32 MB, it will be renamed and moved to the `log_archive` folder in the server's data directory.
+ - Files in the `log_archive` folder can be safely modified or deleted without stopping the server.
+ - We suggest a cron job or similar to manage old log files (e.g. deleting or compressing them).
+- Added a new cache mechanism for `PocketMine-MP.phar`. This has several advantages:
+ - Caches are now reused by all threads - this significantly reduces `/tmp` usage (previously every thread generated its own cache, wasting lots of space)
+ - Dead cache files are automatically cleaned up by new servers - this means that a server crash loop won't flood `/tmp` anymore
+- `/status` now reports a more accurate number of threads on Windows.
+- Large resource packs are now able to be properly downloaded from the server.
+- Larger player skin sizes are now accepted by the server.
+- Improved logging from world providers to reduce spam when chunks contain invalid data.
+- Added more error logging for Anvil, PMAnvil and MCRegion worlds.
+- PHP deprecation warnings no longer cause the server to crash. This should make it easier for server owners to update to newer PHP versions.
+
+## Performance
+- Improved world loading performance. This was achieved through a combination of changes:
+ - Improvements to `BlockStateUpgrader` to avoid unnecessary work
+ - Improvements to `BlockStateUpgradeSchema` to clean up stupid code
+ - Improvements to `BlockStateReader` unused state handling
+ - Optimizations to `RegistryTrait` (see below)
+- Improved performance of `RegistryTrait::__callStatic()` accessor by introducing a fast-path optimization. Ensure that you access registries with the correct function name case to benefit from this.
+ - This improves the performance of `VanillaBlocks::WHATEVER()`, `VanillaItems`, etc.
+
+## Tools
+- `tools/generate-blockstate-upgrade-schema.php` now supports generating schemas using `flattenedValueRemaps` (described in [BlockStateUpgradeSchema](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/4.0.0)).
+
+## Gameplay
+- Added sounds for armour equipping and unequipping.
+- Added sound for picking berries from a sweet berry bush.
+
+## API
+### `pocketmine\block\utils`
+- The following enum cases have been added:
+ - `BannerPatternType::GLOBE`
+ - `BannerPatternType::PIGLIN`
+
+### `pocketmine\event\player`
+- The following classes have been added:
+ - `PlayerResourcePackOfferEvent` - called before the server tells a connecting client which resource packs are available to download - allows customizing the pack list and other options
+
+### `pocketmine\item`
+- The following API methods have been added:
+ - `public ArmorMaterial->getEquipSound() : ?\pocketmine\world\Sound` - returns the sound to play when this armour is equipped or unequipped
+- The following API methods have signature changes:
+ - `ArmorMaterial->__construct()` now accepts an optional `?Sound $equipSound` parameter
+
+### `pocketmine\utils`
+- The following API methods have signature changes:
+ - `MainLogger->__construct()` now accepts `null` for the `$logFile` parameter - this disables the creation of a logger thread and log file
+ - `MainLogger->__construct()` now accepts an optional `?string $logArchiveDir` parameter. If set, this enables log archiving in the specified directory when the current log file exceeds 32 MB.
+
+## Dependencies
+- Now uses [`pocketmine/bedrock-block-upgrade-schema` version 4.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/4.0.0).
+- Now uses [`pmmp/ext-pmmpthread` version 6.1.0](https://github.com/pmmp/ext-pmmpthread/releases/tag/6.1.0).
+- Now uses [`pocketmine/errorhandler` version 0.7.0](https://github.com/pmmp/ErrorHandler/releases/tag/0.7.0).
+- Now uses [`pocketmine/raklib` version 1.1.0](https://github.com/pmmp/RakLib/releases/tag/1.1.0).
+- Now uses [`pocketmine/raklib-ipc` version 1.0.0](https://github.com/pmmp/RakLibIpc/releases/tag/1.0.0).
+
+## Internals
+- (Re)Added support for RakLib packet ACK receipts. This was used to throttle resource pack sending and prevent network overloading.
+ - Added `NetworkSession->sendDataPacketWithReceipt()` to make use of this feature.
+ - `PacketSender` now requires an additional `?int $receiptId` parameter.
+- `ResourcePackPacketHandler` now uses `sendDataPacketWithReceipt()` to send resource packs, and delays sending the next chunk until the current one is acknowledged.
+- `ResourcePackPacketHandler` now accepts resource pack info directly in the constructor, instead of `ResourcePackManager`. This eases the implementation of `PlayerResourcePackOfferEvent`.
+- Increased `ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE` to 8 MB (previously 2 MB). While this weakens server security, it appears to be necessary to deal with extremely bloated Persona skins.
+- Increased max split packet parts accepted by `RakLib` to 512 (previously 128). Again, this is necessary to deal with extremely bloated Persona skins.
+- Added a new cache mechanism for `PocketMine-MP.phar`.
+ - `ext-phar`'s default mechanism is extremely wasteful (generating a separate cache file per thread), and doesn't clean up after itself.
+ - The new cache mechanism is shared between all threads, and automatically cleans up stale caches.
+ - The phar stub (`build/server-phar-stub.php`) now converts the phar contents into a `.tar`, and decompresses all the files into `$TMPDIR/PocketMine-MP-phar-cache./`.
+ - `phar://` URIs still work with this system, but `new Phar(__FILE__)` must be replaced by `new PharData(__FILE__)` within PocketMine-MP core code.
+ - Backtraces from a `phar`'d server will now point to a location in the extracted phar cache, rather than the phar itself.
+- `block_factory_consistency_check` test (actually for `RuntimeBlockStateRegistry`) now stores less data, and is no longer affected by changes to internal state ID construction.
+
+# 5.14.1
+Released 5th April 2024.
+
+## Fixes
+- Fixed incorrect `pmmpthread` version check in server bootstrap.
diff --git a/changelogs/5.15.md b/changelogs/5.15.md
new file mode 100644
index 00000000000..536411ec597
--- /dev/null
+++ b/changelogs/5.15.md
@@ -0,0 +1,16 @@
+# 5.15.0
+Released 25th April 2024.
+
+**For Minecraft: Bedrock Edition 1.20.80**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.80.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- Added support for Minecraft: Bedrock Edition 1.20.80.
+- Removed support for earlier versions.
diff --git a/changelogs/5.16.md b/changelogs/5.16.md
new file mode 100644
index 00000000000..8b4251d76d2
--- /dev/null
+++ b/changelogs/5.16.md
@@ -0,0 +1,26 @@
+# 5.16.0
+Released 13th June 2024.
+
+**For Minecraft: Bedrock Edition 1.21.0**
+
+This is a support release for Minecraft: Bedrock Edition 1.21.0.
+
+**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.0.
+- Removed support for earlier versions.
+- Generated permission docs are now included with every release.
+- Crash throttle message (which appears when the server crashed after being up for less than 120 seconds) now shows the server uptime as well as the wait time. This should make it clearer how the wait time is decided.
+
+## Tools
+- Added `install-local-protocol.sh` script. This allows installing local copies of protocol dependencies without needing to create releases. Useful for integration testing when doing protocol updates.
+
+## Fixes
+- Attacking an entity with a higher damage weapon while it's on attack cooldown from a lower damage weapon (switching) no longer causes additional knockback to the victim.
+- Wooden stairs can now be used as fuel in furnaces.
+- Fixed incorrect description of the permission `pocketmine.command.save.perform`.
diff --git a/changelogs/5.17.md b/changelogs/5.17.md
new file mode 100644
index 00000000000..b26f38b8a0a
--- /dev/null
+++ b/changelogs/5.17.md
@@ -0,0 +1,38 @@
+# 5.17.0
+Released 10th July 2024.
+
+**For Minecraft: Bedrock Edition 1.21.2**
+
+This is a support release for Minecraft: Bedrock Edition 1.21.2.
+
+**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.2.
+- Removed support for earlier versions.
+
+## API
+### `pocketmine\player`
+- The following methods have been added:
+ - `public function closeAllForms() : void` - closes the current viewing form and forms in queue.
+
+## Fixes
+- Bowl can now be used as fuel.
+- Bells always drops themselves even when using an incompatible tool.
+
+# 5.17.1
+Released 13th August 2024.
+
+## Documentation
+- Added a note about `BlockStateData::CURRENT_VERSION`.
+
+## Fixes
+- Fixed anvil placement rotation to match vanilla.
+- Fixed outdated `BedrockWorldData` version, this was preventing use newer worlds.
+
+## Internals
+- Dependabot: PHPStan and patch updates are now grouped into a single PR.
diff --git a/changelogs/5.18.md b/changelogs/5.18.md
new file mode 100644
index 00000000000..35aa237afec
--- /dev/null
+++ b/changelogs/5.18.md
@@ -0,0 +1,30 @@
+# 5.18.0
+Released 16th August 2024.
+
+**For Minecraft: Bedrock Edition 1.21.20**
+
+This is a support release for Minecraft: Bedrock Edition 1.21.20.
+
+**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.20.
+- Removed support for earlier versions.
+
+## Fixes
+- Use `VISIBLE_MOB_EFFECTS` actor metadata property to send effect bubbles, this fixes effect bubbles not showing
+
+# 5.18.1
+Released 3rd September 2024.
+
+## Fixes
+- Fixed shift-crafting.
+- Blue Ice block no longer emits light & it's now dropped when mined with a tool with silk touch enchantment.
+
+## Internals
+- Pull Requests from team members now get an approval automatically. This means that if a team member makes a PR, only one other approval should be needed.
+- Added [ShellCheck](https://github.com/koalaman/shellcheck) to the CI tests.
diff --git a/changelogs/5.19.md b/changelogs/5.19.md
new file mode 100644
index 00000000000..6768c38e5d9
--- /dev/null
+++ b/changelogs/5.19.md
@@ -0,0 +1,16 @@
+# 5.19.0
+Released 21st September 2024.
+
+**For Minecraft: Bedrock Edition 1.21.30**
+
+This is a support release for Minecraft: Bedrock Edition 1.21.30.
+
+**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.30.
+- Removed support for earlier versions.
diff --git a/changelogs/5.2.md b/changelogs/5.2.md
new file mode 100644
index 00000000000..75f31a076ae
--- /dev/null
+++ b/changelogs/5.2.md
@@ -0,0 +1,79 @@
+# 5.2.0
+Released 4th July 2023.
+
+**For Minecraft: Bedrock Edition 1.20.0**
+
+This is a minor technical update, including changes to AsyncTask error handling and support for BedrockBlockUpgradeSchema version 3.0.0.
+
+**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.
+
+## Core
+- [BedrockBlockUpgradeSchema version 3.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/3.0.0) is now supported.
+- [`ext-pmmpthread` version 6.0.4](https://github.com/pmmp/ext-pmmpthread/releases/tag/6.0.4) is now required (bug fixes required to support technical changes in this release).
+
+## Performance
+- Improved performance of `AsyncPool->submitTask()` and `AsyncPool->submitTaskToWorker()`.
+- Added new timings for `AsyncTask->onProgressUpdate()` and `AsyncTask->onCompletion()`.
+
+## Gameplay
+### Blocks
+- Added the following new blocks:
+ - Cherry Button
+ - Cherry Door
+ - Cherry Fence
+ - Cherry Fence Gate
+ - Cherry Leaves
+ - Cherry Log
+ - Cherry Planks
+ - Cherry Pressure Plate
+ - Cherry Sign
+ - Cherry Slab
+ - Cherry Stairs
+ - Cherry Trapdoor
+ - Cherry Wood
+ - Glow Lichen
+ - Piglin Head
+
+## Tools
+- `generate-block-upgrade-schema.php` now supports generating schemas a la BedrockBlockUpgradeSchema version 3.0.0, using `newFlattenedName` to reduce schema size.
+- Improved property remapping detection in `generate-block-upgrade-schema.php`. It now detects related properties with more confidence (even when multiple properties were change), and no longer considers unrelated properties as mapped (e.g. `mapped_type` and `deprecated` in 1.9->1.10).
+
+## API
+### `pocketmine\data\bedrock\block`
+- The following new API methods have been added:
+ - `public BlockStateData->toVanillaNbt() : CompoundTag` - returns the NBT for the blockstate without any PMMP extra metadata (`toNbt()` will normally include a `PMMPDataVersion` tag).
+
+### `pocketmine\data\runtime`
+- The following new API methods have been added:
+ - `public RuntimeDataDescriber->facingFlags(list $faces) : void`
+
+### `pocketmine\scheduler`
+- `AsyncTask->onRun()` no longer tolerates uncaught exceptions.
+ - This means that any uncaught exceptions thrown from `AsyncTask->onRun()` will now crash the worker thread, and by extension, the server.
+ - This change makes it easier to debug errors by detecting them earlier.
+- The following API methods have been deprecated:
+ - `AsyncTask->onError()`
+
+## Internals
+- `AsyncTask->progressUpdates` is now lazily initialized when a task publishes a progress update.
+ - This was previously not possible due to technical limitations of the `ext-pmmpthread` extension.
+ - This change improves performance of `AsyncPool->submitTask()` and `AsyncPool->submitTaskToWorker()`, as well as reducing the amount of work needed to check for progress updates on tick.
+- Errors in `AsyncWorker` now cascade and crash the whole server.
+ - This makes it easier to debug errors by detecting them earlier.
+ - This includes all types of unexpected errors, such as OOM, uncaught exceptions, etc.
+ - This change is not expected to affect normal server operation, as worker threads are not expected to crash under normal circumstances.
+- `AsyncTask::$threadLocalStorage` now uses a plain `array` instead of `ArrayObject`. The `ArrayObject` was a workaround for `ext-pthreads` to prevent thread-locals getting copied to the worker thread, and is no longer necessary.
+- Regenerated `pocketmine\data\bedrock\item\ItemTypeNames` for Bedrock 1.20 (BC breaking, some item names have changed).
+- Fixed `build/generate-item-type-names.php` not including some newer blockitems, such as doors and hanging signs.
+
+# 5.2.1
+Released 11th July 2023.
+
+**This release includes changes from the following releases:**
+- [4.22.3](https://github.com/pmmp/PocketMine-MP/blob/4.22.3/changelogs/4.22.md#4223) - Fixes for some crash issues
+
+This release contains no other changes.
diff --git a/changelogs/5.20.md b/changelogs/5.20.md
new file mode 100644
index 00000000000..b0da701d67d
--- /dev/null
+++ b/changelogs/5.20.md
@@ -0,0 +1,25 @@
+# 5.20.0
+Released 26th October 2024.
+
+**For Minecraft: Bedrock Edition 1.21.40**
+
+This is a support release for Minecraft: Bedrock Edition 1.21.40.
+
+**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.40.
+- Removed support for earlier versions.
+
+## Fixes
+- Fixed a bug in `tools/generate-blockstate-upgrade-schema.php` that caused it to fail on 1.21.40 with the new mushroom block changes.
+
+# 5.20.1
+Released 31st October 2024.
+
+## Fixes
+- Workaround old mob heads in world saves not being upgraded correctly and causing crashes.
diff --git a/changelogs/5.21.md b/changelogs/5.21.md
new file mode 100644
index 00000000000..9b3c2f89adf
--- /dev/null
+++ b/changelogs/5.21.md
@@ -0,0 +1,112 @@
+# 5.21.0
+Released 3rd November 2024.
+
+This is a minor feature release, including gameplay features and minor internals improvements.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## Gameplay
+- Added the following new blocks:
+ - Campfire
+ - Chiseled Copper
+ - Chiseled Tuff
+ - Chiseled Tuff Bricks
+ - Copper Bulb
+ - Copper Door
+ - Copper Grate
+ - Copper Trapdoor
+ - Polished Tuff, Slabs, Stairs and Walls
+ - Soul Campfire
+ - Tuff Bricks, Slabs, Stairs and Walls
+ - Tuff Slab, Stairs and Walls
+- Added the following new types of painting:
+ - backyard
+ - baroque
+ - bouquet
+ - cavebird
+ - changing
+ - cotan
+ - endboss
+ - fern
+ - finding
+ - humble
+ - lowmist
+ - meditative
+ - orb
+ - owlemons
+ - passage
+ - pond
+ - prairie_ride
+ - sunflowers
+ - tides
+ - unpacked
+- Armor slots are now properly restricted (on the server side) to only contain the appropriate type of armor or headwear.
+- Implemented Aqua Affinity enchantment. Since the server doesn't currently enforce any movement restrictions in water, this enchantment works based on client-side behaviour only.
+
+## API
+### `pocketmine\block`
+- The following new API methods have been added:
+ - `public ChiseledBookshelf->getLastInteractedSlot() : ?ChiseledBookshelfSlot`
+ - `public ChiseledBookshelf->setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : $this`
+- The following new classes have been added:
+ - `utils\CopperMaterial` - interface implemented by all copper-like blocks with oxidation and waxed properties
+ - `CopperBulb`
+ - `CopperDoor`
+ - `CopperGrate`
+ - `CopperTrapdoor`
+ - `SoulCampfire`
+ - `Campfire`
+- The following enums have new cases:
+ - `utils\BannerPatternType` has new cases `FLOW` and `GUSTER`
+
+### `pocketmine\crafting`
+- The following enums have new cases:
+ - `FurnaceType` has new cases `CAMPFIRE` and `SOUL_CAMPFIRE`
+
+### `pocketmine\event`
+- The following new classes have been added:
+ - `block\CampfireCookEvent` - called when a campfire finishes cooking an item
+
+### `pocketmine\inventory`
+- Added support for slot validators, which permit restricting the types of items a player can put into an inventory slot.
+ - The following new classes have been added:
+ - `transaction\action\SlotValidator` - interface
+ - `transaction\action\CallbackSlotValidator` - class allowing a closure to be used for slot content validation
+ - `SlotValidatedInventory` - implemented by inventories which support the use of slot validators
+
+### `pocketmine\item`
+- The following new API methods have been added:
+ - `public Item->getCooldownTag() : ?string` - returns the cooldown group this item belongs to, used for ensuring that, for example, different types of goat horns all respect a general horn cooldown
+- The following new classes have been added:
+ - `ItemCooldownTags` - list of cooldown group tags used by PocketMine-MP
+
+### `pocketmine\world\sound`
+- The following new classes have been added
+ - `CampfireSound` - sound made by campfires while lit
+
+## Tools
+- `tools/blockstate-upgrade-schema-utils.php` (formerly `generate-blockstate-upgrade-schema.php`) has several improvements:
+ - Support for generating `flattenedProperties` rules as per [BedrockBlockUpgradeSchema 5.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/5.0.0)
+ - Improved criteria for flattened property selection to minimize the amount of rules required
+ - Several subcommands are now available:
+ - `generate` - generates a schema from provided data
+ - `update` - regenerates an existing schema in a newer format
+ - `update-all` - regenerates a folder of existing schemas in a newer format (useful for updating `BedrockBlockUpgradeSchema` en masse)
+ - `test` - verifies that a schema produces the results expected by provided data
+
+## Internals
+- Fixed incorrect visibility of `createEntity` in spawn eggs.
+- Added support for newer `BedrockBlockUpgradeSchema` in `BlockStateUpgrader`.
+
+# 5.21.1
+Released 12th November 2024.
+
+## Fixes
+- Fixed server crash when applying a cooldown to an item with 1 count.
+- 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.
diff --git a/changelogs/5.3.md b/changelogs/5.3.md
new file mode 100644
index 00000000000..7d789165d66
--- /dev/null
+++ b/changelogs/5.3.md
@@ -0,0 +1,81 @@
+# 5.3.0
+Released 12th July 2023.
+
+**For Minecraft: Bedrock Edition 1.20.10**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.10.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## Interim releases
+If you're upgrading directly from 5.1.x to 5.3.x, please also read the following changelogs, as the interim releases contain important changes:
+- [5.2.0](https://github.com/pmmp/PocketMine-MP/blob/5.2.0/changelogs/5.2.md#520)
+
+## Included releases
+**This release includes changes from the following releases:**
+- [4.23.0](https://github.com/pmmp/PocketMine-MP/blob/4.23.0/changelogs/4.23.md#4230) - Support for Minecraft: Bedrock Edition 1.20.10
+
+## Internals
+- `BlockTypeNames`, `BlockStateNames`, `BlockStateStringValues` and `ItemTypeNames` in the `pocketmine\data\bedrock` package have BC-breaking changes to accommodate Bedrock 1.20.10.
+
+# 5.3.1
+Released 14th July 2023.
+
+## Included releases
+**This release includes changes from the following releases:**
+- [4.23.1](https://github.com/pmmp/PocketMine-MP/blob/4.23.1/changelogs/4.23.md#4231) - Security fixes
+
+## General
+- Updated `build/php` submodule to pmmp/PHP-Binaries@e0c918d1379465964acefd562d9e48f87cfc2c9e.
+
+# 5.3.2
+Released 18th July 2023.
+
+## Included releases
+**This release includes changes from the following releases:**
+- [4.23.2](https://github.com/pmmp/PocketMine-MP/blob/4.23.2/changelogs/4.23.md#4232) - Fix for `sandboxId`-related login errors
+
+## Documentation
+- Fixed documentation error in `StringToTParser`.
+
+## Fixes
+- Fixed turtle helmet not being able to be unequipped.
+
+## Internals
+- Armor pieces are no longer set back into the armor inventory if no change was made. This reduces the number of slot updates sent to clients, as well as avoiding unnecessary updates for armor pieces which have Unbreaking enchantments.
+
+# 5.3.3
+Released 24th July 2023.
+
+## Included releases
+**This release includes changes from the following releases:**
+- [4.23.3](https://github.com/pmmp/PocketMine-MP/blob/4.23.3/changelogs/4.23.md#4233) - Various bug fixes
+
+## Fixes
+- Added a workaround for PM4 worlds with chunks corrupted by [Refaltor77/CustomItemAPI](https://github.com/Refaltor77/CustomItemAPI).
+ - While this was not the fault of PocketMine-MP itself, a significant number of users were affected by this problem.
+ - This error was not detected by PM4 due to missing validation of certain data which should not have existed in 1.12.
+ - An error will now be logged when this corruption is detected, but the affected chunks should otherwise load normally.
+- Relaxed validation of expected 3D biome array counts per chunk in LevelDB worlds.
+ - Vanilla Bedrock currently saves 24 palettes (and only 24 are required), but it saved 25, 32, or 64 biome palettes per chunk in older versions.
+ - Core validation for these padding biomes was very strict, enforcing exact compliance with vanilla.
+ - Since only 24 palettes are actually required, the remaining palettes may now be omitted irrespective of the chunk version.
+ - An error will still be logged when this mistake is detected, but the affected chunks will otherwise load normally.
+- Fixed `/kill` not working on players who had (re)spawned in the 3 seconds immediately after (re)spawning (due to `noDamageTicks`).
+- Fixed `/kill` not working correctly for players with high levels of Health Boost or other health-altering effects.
+- Fixed netherite items being destroyed by lava.
+- Fireproof entities no longer display the burning animation when in fire or lava. This does not apply to creative players, who are immortal rather than being fireproof.
+- Fixed frosted ice melting in certain conditions which didn't match vanilla Bedrock.
+
+# 5.3.4
+Released 1st August 2023.
+
+## Included releases
+This release includes changes from the following releases:
+- [4.23.4](https://github.com/pmmp/PocketMine-MP/blob/4.23.4/changelogs/4.23.md#4234) - Item entity lag fix
+
+This release contains no other significant changes.
diff --git a/changelogs/5.4.md b/changelogs/5.4.md
new file mode 100644
index 00000000000..43f60962079
--- /dev/null
+++ b/changelogs/5.4.md
@@ -0,0 +1,133 @@
+# 5.4.0
+Released 1st August 2023.
+
+**For Minecraft: Bedrock Edition 1.20.10**
+
+This is a minor feature update, including a handful of new gameplay features, new plugin APIs and improvements to error reporting.
+
+**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
+- Improved error reporting for async task and thread crashes.
+- Players may now have different creative inventories.
+
+## Gameplay
+### General
+- Added support for 1.5-block height sneaking.
+- Fixed missing player arm swing and sounds when punching the air.
+
+### Blocks
+- Implemented the following new blocks:
+ - Big Dripleaf Head
+ - Big Dripleaf Stem
+ - Small Dripleaf
+- Acacia saplings now grow into acacia trees.
+- Fixed melon and pumpkin stems not attaching to the correct block when growing.
+- Various blocks now drop more items when mined with a compatible tool enchanted with Fortune.
+
+### Items
+- Implemented Strong Slowness potion.
+- Implemented Fortune enchantment.
+
+## API
+### `pocketmine\block`
+- The following new classes have been added:
+ - `utils\FortuneDropHelper` - utility methods for calculating the drop counts for Fortune-affected blocks
+- The following new API methods have been added:
+ - `protected Block->getAdjacentSupportType(int $facing) : utils\SupportType` - returns the type of support provided by the block in the given direction on the adjacent face
+
+### `pocketmine\entity`
+- The following new API constants have been added:
+ - `Living::DEFAULT_KNOCKBACK_FORCE`
+ - `Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT`
+
+### `pocketmine\entity\animation`
+- `ConsumingItemAnimation` now accepts `Living` instances instead of just `Human`.
+
+### `pocketmine\event`
+- The following new classes have been added:
+ - `PlayerMissSwingEvent` - called when the player attempts the attack action (left click on desktop) without any target
+ - This is possible thanks to the introduction of new flags in `PlayerAuthInputPacket` in Bedrock 1.20.10
+- The following new API methods have been added:
+ - `public EntityDamageByEntityEvent->getVerticalKnockBackLimit() : float`
+ - `public EntityDamageByEntityEvent->setVerticalKnockBackLimit(float $verticalKnockBackLimit) : void` - sets the max vertical velocity that can result from the victim being knocked back
+
+### `pocketmine\player`
+- The following new API methods have been added:
+ - `public Player->getCreativeInventory() : pocketmine\inventory\CreativeInventory`
+ - `public Player->setCreativeInventory(pocketmine\inventory\CreativeInventory $inventory) : void`
+ - `public Player->missSwing() : void` - performs actions associated with the attack action when there is no target (see `PlayerMissSwingEvent`)
+
+### `pocketmine\scheduler`
+- Cancellation functionality has been removed from `AsyncTask`, as it didn't make any sense and wasn't used by anything for what it was intended for.
+ - It broke sequential task execution - later tasks might depend on state from earlier tasks
+ - It didn't actually cancel the task anyway - at best, it prevented it from running, but couldn't interrupt it (though interrupting a task does not make sense either)
+- The following API methods have been deprecated, and their functionality has been removed:
+ - `AsyncTask->hasCancelledRun()`
+ - `AsyncTask->cancelRun()`
+
+## Internals
+- Uncaught exceptions and fatal errors in `AsyncTask`, threads extending `pocketmine\thread\Thread`, and `pocketmine\thread\Worker` are now recorded in crashdumps, making it significantly easier to debug errors in these areas.
+- JWT signature DER <-> raw conversions are now handled in-house using code in `JwtUtils`
+ - Due to the simplicity of the conversion and only requiring a tiny subset of the ASN.1 spec, it didn't make much sense to introduce another dependency.
+ - `fgrosse/phpasn1` is no longer required. This package was abandoned by its author and only used by PocketMine-MP for this one purpose.
+- Various usages of `Closure::fromCallable()` have been replaced by PHP 8.1 first-class callable syntax.
+- Blocks requiring support shifted to a "can be supported at" model, rather than "can be supported by".
+ - This model reduces repeated logic when placing and performing nearby block updates (no need to hardcode facing everywhere).
+ - In addition, this change facilitates the use of the newly introduced `Block->getAdjacentSupportType()` API method, reducing boilerplate support-type checking code.
+- Bell block code has been simplified and cleaned up.
+- `TallGrass` and `DoubleTallGrass` now use a shared `TallGrassTrait` to reduce code duplication.
+
+# 5.4.1
+Released 8th August 2023.
+
+## General
+- Updated translation data to [pmmp/Language 2.19.6](https://github.com/pmmp/Language/releases/tag/2.19.6).
+- [`ext-pmmpthread` 6.0.7](https://github.com/pmmp/ext-pmmpthread/releases/tag/6.0.7) is now required (needed for bug fixes).
+
+## Fixes
+- Fixed Podzol not dropping itself when mined with Silk Touch.
+- Fixed `World->getSafeSpawn()` not accepting positions below `y=0` (world height limit change).
+- Fixed `pocketmine\thread\Thread` and `pocketmine\thread\Worker` instances not logging any information when they crash.
+- Fixed `CraftItemEvent` not cloning returned items.
+
+## Internals
+- Foreach by-reference is now disallowed via a custom PHPStan rule.
+
+# 5.4.2
+Released 9th August 2023.
+
+## Included releases
+- [4.23.5](https://github.com/pmmp/PocketMine-MP/blob/4.23.5/changelogs/4.23.md#4235) - Minor bug fixes
+
+## Fixes
+- Fixed cake accepting candle placement when slices have already been eaten.
+- Fixed fire charges not lighting candles.
+
+# 5.4.3
+Released 21st August 2023.
+
+## Included releases
+- [4.23.6](https://github.com/pmmp/PocketMine-MP/blob/4.23.6/changelogs/4.23.md#4236) - Armor inventory client bug workaround
+
+## Fixes
+- Fixed crashdumps not generating correctly on fatal errors.
+- Fixed `PotionCauldron::setPotionItem()` not validating the item type.
+- Fixed chorus fruit not considering teleport destinations below y=0.
+- Fixed cake dropping itself when mined.
+
+# 5.4.4
+Released 6th September 2023.
+
+## General
+- Crashdumps caused by non-phar plugins are now submitted to the Crash Archive, the same as other plugins. Previously, non-phar plugin crashes would not be submitted, causing maintainers to potentially miss important issues.
+
+## Fixes
+- Fixed player Y coordinates sometimes being slightly below the top of the block they were standing on (floating point error due to subtracting eye height).
+- Fixed template slot of smithing tables not accepting any items.
+- `tools/generate-bedrock-data-from-packets.php` is now significantly less spammy when warning about duplicated recipes.
+- Fixed empty stack traces in `lastError` data of crashdumps.
diff --git a/changelogs/5.5-beta.md b/changelogs/5.5-beta.md
new file mode 100644
index 00000000000..84153c2796e
--- /dev/null
+++ b/changelogs/5.5-beta.md
@@ -0,0 +1,156 @@
+# 5.5.0-BETA1
+Released 23rd August 2023.
+
+**For Minecraft: Bedrock Edition 1.20.10**
+
+This is a minor feature release, including performance improvements, new API methods, and new gameplay features.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## Dependencies
+- Updated `pocketmine/math` dependency to [`1.0.0`](https://github.com/pmmp/Math/releases/tag/1.0.0).
+- Updated `pocketmine/nbt` dependency to [`1.0.0`](https://github.com/pmmp/NBT/releases/tag/1.0.0).
+
+## Performance
+- Some events are now no longer fired if no handlers are registered.
+ - This improves performance by avoiding unnecessary object allocations and function calls.
+ - Events such as `DataPacketReceiveEvent`, `DataPacketSendEvent` and `PlayerMoveEvent` are optimized away almost completely by this change, offering some much-needed performance gains.
+- Significantly improved performance of small moving entities, such as dropped items.
+ - This was achieved by a combination of changes, which together improved observed performance with 2000 item entities moving in water by 30-40%.
+ - The benefit of this will be most noticeable in SkyBlock servers, where large cactus farms can generate thousands of dropped items.
+- `World->getCollisionBoxes()` now uses an improved search method, which reduces the work done by the function by almost 90% for small entities.
+ - This improves performance of collision detection for small entities, such as dropped items.
+
+## Gameplay
+### General
+- Implemented enchanting using an enchanting table (yes, finally!)
+ - Thanks to [@S3v3Nice](https://github.com/S3v3Nice) for investing lots of time and effort into developing this.
+ - Since this feature is quite complex, it's possible there may be bugs. Please be vigilant and report any issues you find.
+
+### Blocks
+- The following new blocks have been implemented:
+ - Pink Petals
+- Pressure plates are now functional, in the sense that they react when entities stand on them and perform the correct logic.
+ - Note that since redstone is not yet implemented, pressure plates do not activate any redstone devices, similar to buttons and levers.
+- Signs can now be edited by right-clicking them.
+- Signs can now be waxed using a honeycomb, which prevents them from being edited.
+
+### Items
+- The following new items have been implemented:
+ - Enchanted Book
+
+## API
+### `pocketmine\block`
+- The following new API methods have been added:
+ - `public Block->getEnchantmentTags() : list` returns a list of strings indicating which types of enchantment can be applied to the block when in item form
+ - `public BlockTypeInfo->getEnchantmentTags() : list`
+ - `protected PressurePlate->getActivationBox() : AxisAlignedBB` - returns the AABB entities must intersect with in order to activate the pressure plate (not the same as the visual shape)
+ - `protected PressurePlate->hasOutputSignal() : bool` - returns whether the pressure plate has an output signal - this should be implemented by subclasses
+ - `protected PressurePlate->calculatePlateState() : array{Block, ?bool}` - returns the state the pressure plate will change to if the given list of entities are standing on it, and a bool indicating whether the plate activated or deactivated this tick
+ - `protected PressurePlate->filterIrrelevantEntities(list $entities) : list` - returns the given list filtered of entities that don't affect the plate's state (e.g. dropped items don't affect stone pressure plates)
+ - `public BaseSign->isWaxed() : bool`
+ - `public BaseSign->setWaxed(bool $waxed) : $this`
+ - `public inventory\EnchantInventory->getInput() : Item`
+ - `public inventory\EnchantInventory->getLapis() : Item`
+ - `public inventory\EnchantInventory->getOutput(int $optionId) : ?Item` - returns the item that would be produced if the input item was enchanted with the selected option, or `null` if the option is invalid
+ - `public inventory\EnchantInventory->getOption(int $optionId) : EnchantOption` - returns the enchanting option at the given index
+- The following API methods have signature changes:
+ - `BlockTypeInfo->__construct()` now accepts an optional `list $enchantmentTags` parameter
+ - `PressurePlate->__construct()` now accepts an optional `int $deactivationDelayTicks` parameter
+ - `WeightedPressurePlate->__construct()` now accepts optional `int $deactivationDelayTicks` and `float $signalStrengthFactor` parameters
+ - `SimplePressurePlate->__construct()` now accepts an optional `int $deactivationDelayTicks` parameter
+- The following new classes have been added:
+ - `PinkPetals`
+ - `utils\BlockEventHelper` - provides helper methods for calling block-related events
+- The following classes have been deprecated:
+ - `WeightedPressurePlateLight`
+ - `WeightedPressurePlateHeavy`
+
+### `pocketmine\entity`
+- The following new API methods have been added:
+ - `public Human->getEnchantmentSeed() : int` - returns the current seed used to randomize options shown on the enchanting table for this human
+ - `public Human->setEnchantmentSeed(int $seed) : void`
+ - `public Human->regenerateEnchantmentSeed() : void` - returns a new randomly generated seed which can be set with `setEnchantmentSeed()`
+
+### `pocketmine\event`
+- The following new classes have been added:
+ - `block\FarmlandHydrationChangeEvent` - called when farmland is hydrated or dehydrated
+ - `block\PressurePlateUpdateEvent` - called when a pressure plate is activated or changes its power output
+ - `player\PlayerEnchantingOptionsRequestEvent` - called when a player puts an item to be enchanted into an enchanting table, to allow plugins to modify the enchanting options shown
+ - `player\PlayerItemEnchantEvent` - called when a player enchants an item in an enchanting table
+ - `world\WorldDifficultyChangeEvent` - called when a world's difficulty is changed
+- The following new API methods have been added:
+ - `public static Event::hasHandlers() : bool` - returns whether the event class has any registered handlers - used like `SomeEvent::hasHandlers()`
+ - `public HandlerListManager->getHandlersFor(class-string extends Event> $event) : list` - returns a list of all registered listeners for the given event class, using cache if available
+
+### `pocketmine\inventory\transaction`
+- The following new classes have been added:
+ - `EnchantingTransaction` - used when a player enchants an item in an enchanting table
+
+### `pocketmine\item`
+- The following new API methods have been added:
+ - `public Armor->getMaterial() : ArmorMaterial` - returns an object containing properties shared by all items of the same armor material
+ - `public ArmorTypeInfo->getMaterial() : ArmorMaterial`
+ - `public Item->getEnchantability() : int` - returns the enchantability value of the item - higher values increase the chance of more powerful enchantments being offered by an enchanting table
+ - `public Item->getEnchantmentTags() : list` - returns a list of strings indicating which types of enchantment can be applied to the item
+ - `public ToolTier->getEnchantability() : int`
+- The following API methods have signature changes:
+ - `Item->__construct()` now accepts an optional `list $enchantmentTags` parameter
+ - `ArmorTypeInfo->__construct()` now accepts an optional `?ArmorMaterial $material` parameter
+- The following new classes have been added:
+ - `ArmorMaterial` - container for shared armor properties
+ - `VanillaArmorMaterials` - all vanilla armor materials
+ - `EnchantedBook` - represents an enchanted book item
+
+### `pocketmine\item\enchantment`
+- The following new classes have been added:
+ - `AvailableEnchantmentRegistry` - enchantments to be displayed on the enchanting table are selected from here - custom enchantments may be added
+ - `EnchantingHelper` - static class containing various helper methods for enchanting tables
+ - `EnchantingOption` - represents an option on the enchanting table menu
+ - `IncompatibleEnchantmentGroups` - list of constants naming groups of enchantments that are incompatible with each other - custom enchantments may be added using these group names to make them incompatible with existing enchantments in the same group
+ - `IncompatibleEnchantmentRegistry` - manages which enchantments are considered incompatible with each other - custom enchantments may be added using existing group names to make them incompatible with existing enchantments in the same group, or to entirely new groups
+ - `ItemEnchantmentTagRegistry` - manages item enchantment compatibility tags and which tags include which other tags
+ - `ItemEnchantmentTags` - list of constants naming item types for enchantment compatibility checks
+- The following classes have been deprecated
+ - `ItemFlags`
+- The following API methods have been added:
+ - `public Enchantment->isCompatibleWith(Enchantment $other) : bool`
+ - `public Enchantment->getMinEnchantingPower()` - returns the minimum enchanting power (derived from enchantability and number of bookshelves) needed to allow this enchantment to show on the enchanting table with a given level
+ - `public Enchantment->getMaxEnchantingPower()` - upper limit of enchanting power for this enchantment to be offered on the enchanting table with a given level
+- The following API methods have signature changes:
+ - `Enchantment->__construct()` now accepts optional `(\Closure(int $level) : int)|null $minEnchantingPower` and `int $enchantingPowerRange` parameters
+ - `Enchantment->__construct()` parameters `$primaryItemFlags` and `$secondaryItemFlags` are now deprecated and no longer used
+ - `ProtectionEnchantment->__construct()` has extra parameters to reflect `Enchantment->__construct()` changes
+- The following API methods have been deprecated:
+ - `Enchantment->getPrimaryItemFlags()` - use API methods provided by `AvailableEnchantmentRegistry` instead
+ - `Enchantment->getSecondaryItemFlags()` - use API methods provided by `AvailableEnchantmentRegistry` instead
+ - `Enchantment->hasPrimaryItemType()`
+ - `Enchantment->hasSecondaryItemType()`
+
+### `pocketmine\plugin`
+- The following new API methods have been added:
+ - `public PluginBase->getResourcePath(string $filename) : string` - returns a URI to an embedded resource file that can be used with `file_get_contents()` and similar functions
+ - `public PluginBase->getResourceFolder() : string` - returns a URI to the plugin's folder of embedded resources
+- The following API methods have been deprecated:
+ - `PluginBase->getResource()` - prefer using `getResourcePath()` with `file_get_contents()` or other PHP built-in functions instead
+
+### `pocketmine\resourcepacks`
+- The following new API methods have been added:
+ - `public ResourcePackManager->setResourcePacksRequired(bool $value) : void` - sets whether players must accept resource packs in order to join
+
+### `pocketmine\world\generator`
+- The following new API methods have been added:
+ - `public GeneratorManager->addAlias(string $name, string $alias) : void` - allows registering a generator alias without copying the generator registration parameters
+
+### `pocketmine\world\sound`
+- The following new classes have been added:
+- `PressurePlateActivateSound`
+- `PressurePlateDeactivateSound`
+
+### `pocketmine\utils`
+- The following new API methods have been added:
+ - `public StringToTParser->registerAlias(string $existing, string $alias) : void` - allows registering a string alias without copying registration parameters
diff --git a/changelogs/5.5.md b/changelogs/5.5.md
new file mode 100644
index 00000000000..750e2112900
--- /dev/null
+++ b/changelogs/5.5.md
@@ -0,0 +1,162 @@
+# 5.5.0
+Released 6th September 2023.
+
+**For Minecraft: Bedrock Edition 1.20.10**
+
+This is a minor feature release, including performance improvements, new API methods, and new gameplay features.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## Dependencies
+- Updated `pocketmine/math` dependency to [`1.0.0`](https://github.com/pmmp/Math/releases/tag/1.0.0).
+- Updated `pocketmine/nbt` dependency to [`1.0.0`](https://github.com/pmmp/NBT/releases/tag/1.0.0).
+
+## Performance
+- Some events are now no longer fired if no handlers are registered.
+ - This improves performance by avoiding unnecessary object allocations and function calls.
+ - Events such as `DataPacketReceiveEvent`, `DataPacketSendEvent` and `PlayerMoveEvent` are optimized away almost completely by this change, offering some much-needed performance gains.
+- Significantly improved performance of small moving entities, such as dropped items.
+ - This was achieved by a combination of changes, which together improved observed performance with 2000 item entities moving in water by 30-40%.
+ - The benefit of this will be most noticeable in SkyBlock servers, where large cactus farms can generate thousands of dropped items.
+- `World->getCollisionBoxes()` now uses an improved search method, which reduces the work done by the function by almost 90% for small entities.
+ - This improves performance of collision detection for small entities, such as dropped items.
+
+## Gameplay
+### General
+- Implemented enchanting using an enchanting table (yes, finally!)
+ - Thanks to [@S3v3Nice](https://github.com/S3v3Nice) for investing lots of time and effort into developing this.
+ - Since this feature is quite complex, it's possible there may be bugs. Please be vigilant and report any issues you find.
+
+### Blocks
+- The following new blocks have been implemented:
+ - Pink Petals
+- Pressure plates are now functional, in the sense that they react when entities stand on them and perform the correct logic.
+ - Note that since redstone is not yet implemented, pressure plates do not activate any redstone devices, similar to buttons and levers.
+- Signs can now be edited by right-clicking them.
+- Signs can now be waxed using a honeycomb, which prevents them from being edited.
+
+### Items
+- The following new items have been implemented:
+ - Enchanted Book
+
+## API
+### `pocketmine\block`
+- The following new API methods have been added:
+ - `public Block->getEnchantmentTags() : list` returns a list of strings indicating which types of enchantment can be applied to the block when in item form
+ - `public BlockTypeInfo->getEnchantmentTags() : list`
+ - `protected PressurePlate->getActivationBox() : AxisAlignedBB` - returns the AABB entities must intersect with in order to activate the pressure plate (not the same as the visual shape)
+ - `protected PressurePlate->hasOutputSignal() : bool` - returns whether the pressure plate has an output signal - this should be implemented by subclasses
+ - `protected PressurePlate->calculatePlateState() : array{Block, ?bool}` - returns the state the pressure plate will change to if the given list of entities are standing on it, and a bool indicating whether the plate activated or deactivated this tick
+ - `protected PressurePlate->filterIrrelevantEntities(list $entities) : list` - returns the given list filtered of entities that don't affect the plate's state (e.g. dropped items don't affect stone pressure plates)
+ - `public BaseSign->isWaxed() : bool`
+ - `public BaseSign->setWaxed(bool $waxed) : $this`
+ - `public inventory\EnchantInventory->getInput() : Item`
+ - `public inventory\EnchantInventory->getLapis() : Item`
+ - `public inventory\EnchantInventory->getOutput(int $optionId) : ?Item` - returns the item that would be produced if the input item was enchanted with the selected option, or `null` if the option is invalid
+ - `public inventory\EnchantInventory->getOption(int $optionId) : EnchantOption` - returns the enchanting option at the given index
+- The following API methods have signature changes:
+ - `BlockTypeInfo->__construct()` now accepts an optional `list $enchantmentTags` parameter
+ - `PressurePlate->__construct()` now accepts an optional `int $deactivationDelayTicks` parameter
+ - `WeightedPressurePlate->__construct()` now accepts optional `int $deactivationDelayTicks` and `float $signalStrengthFactor` parameters
+ - `SimplePressurePlate->__construct()` now accepts an optional `int $deactivationDelayTicks` parameter
+- The following new classes have been added:
+ - `PinkPetals`
+ - `utils\BlockEventHelper` - provides helper methods for calling block-related events
+- The following classes have been deprecated:
+ - `WeightedPressurePlateLight`
+ - `WeightedPressurePlateHeavy`
+
+### `pocketmine\entity`
+- The following new API methods have been added:
+ - `public Human->getEnchantmentSeed() : int` - returns the current seed used to randomize options shown on the enchanting table for this human
+ - `public Human->setEnchantmentSeed(int $seed) : void`
+ - `public Human->regenerateEnchantmentSeed() : void` - returns a new randomly generated seed which can be set with `setEnchantmentSeed()`
+
+### `pocketmine\event`
+- The following new classes have been added:
+ - `block\FarmlandHydrationChangeEvent` - called when farmland is hydrated or dehydrated
+ - `block\PressurePlateUpdateEvent` - called when a pressure plate is activated or changes its power output
+ - `player\PlayerEnchantingOptionsRequestEvent` - called when a player puts an item to be enchanted into an enchanting table, to allow plugins to modify the enchanting options shown
+ - `player\PlayerItemEnchantEvent` - called when a player enchants an item in an enchanting table
+ - `world\WorldDifficultyChangeEvent` - called when a world's difficulty is changed
+- The following new API methods have been added:
+ - `public static Event::hasHandlers() : bool` - returns whether the event class has any registered handlers - used like `SomeEvent::hasHandlers()`
+ - `public HandlerListManager->getHandlersFor(class-string extends Event> $event) : list` - returns a list of all registered listeners for the given event class, using cache if available
+
+### `pocketmine\inventory\transaction`
+- The following new classes have been added:
+ - `EnchantingTransaction` - used when a player enchants an item in an enchanting table
+
+### `pocketmine\item`
+- The following new API methods have been added:
+ - `public Armor->getMaterial() : ArmorMaterial` - returns an object containing properties shared by all items of the same armor material
+ - `public ArmorTypeInfo->getMaterial() : ArmorMaterial`
+ - `public Item->getEnchantability() : int` - returns the enchantability value of the item - higher values increase the chance of more powerful enchantments being offered by an enchanting table
+ - `public Item->getEnchantmentTags() : list` - returns a list of strings indicating which types of enchantment can be applied to the item
+ - `public ToolTier->getEnchantability() : int`
+- The following API methods have signature changes:
+ - `Item->__construct()` now accepts an optional `list $enchantmentTags` parameter
+ - `ArmorTypeInfo->__construct()` now accepts an optional `?ArmorMaterial $material` parameter
+- The following new classes have been added:
+ - `ArmorMaterial` - container for shared armor properties
+ - `VanillaArmorMaterials` - all vanilla armor materials
+ - `EnchantedBook` - represents an enchanted book item
+
+### `pocketmine\item\enchantment`
+- The following new classes have been added:
+ - `AvailableEnchantmentRegistry` - enchantments to be displayed on the enchanting table are selected from here - custom enchantments may be added
+ - `EnchantingHelper` - static class containing various helper methods for enchanting tables
+ - `EnchantingOption` - represents an option on the enchanting table menu
+ - `IncompatibleEnchantmentGroups` - list of constants naming groups of enchantments that are incompatible with each other - custom enchantments may be added using these group names to make them incompatible with existing enchantments in the same group
+ - `IncompatibleEnchantmentRegistry` - manages which enchantments are considered incompatible with each other - custom enchantments may be added using existing group names to make them incompatible with existing enchantments in the same group, or to entirely new groups
+ - `ItemEnchantmentTagRegistry` - manages item enchantment compatibility tags and which tags include which other tags
+ - `ItemEnchantmentTags` - list of constants naming item types for enchantment compatibility checks
+- The following classes have been deprecated
+ - `ItemFlags`
+- The following API methods have been added:
+ - `public Enchantment->isCompatibleWith(Enchantment $other) : bool`
+ - `public Enchantment->getMinEnchantingPower()` - returns the minimum enchanting power (derived from enchantability and number of bookshelves) needed to allow this enchantment to show on the enchanting table with a given level
+ - `public Enchantment->getMaxEnchantingPower()` - upper limit of enchanting power for this enchantment to be offered on the enchanting table with a given level
+- The following API methods have signature changes:
+ - `Enchantment->__construct()` now accepts optional `(\Closure(int $level) : int)|null $minEnchantingPower` and `int $enchantingPowerRange` parameters
+ - `Enchantment->__construct()` parameters `$primaryItemFlags` and `$secondaryItemFlags` are now deprecated and no longer used
+ - `ProtectionEnchantment->__construct()` has extra parameters to reflect `Enchantment->__construct()` changes
+- The following API methods have been deprecated:
+ - `Enchantment->getPrimaryItemFlags()` - use API methods provided by `AvailableEnchantmentRegistry` instead
+ - `Enchantment->getSecondaryItemFlags()` - use API methods provided by `AvailableEnchantmentRegistry` instead
+ - `Enchantment->hasPrimaryItemType()`
+ - `Enchantment->hasSecondaryItemType()`
+
+### `pocketmine\plugin`
+- The following new API methods have been added:
+ - `public PluginBase->getResourcePath(string $filename) : string` - returns a URI to an embedded resource file that can be used with `file_get_contents()` and similar functions
+ - `public PluginBase->getResourceFolder() : string` - returns a URI to the plugin's folder of embedded resources
+- The following API methods have been deprecated:
+ - `PluginBase->getResource()` - prefer using `getResourcePath()` with `file_get_contents()` or other PHP built-in functions instead
+
+### `pocketmine\resourcepacks`
+- The following new API methods have been added:
+ - `public ResourcePackManager->setResourcePacksRequired(bool $value) : void` - sets whether players must accept resource packs in order to join
+
+### `pocketmine\world\generator`
+- The following new API methods have been added:
+ - `public GeneratorManager->addAlias(string $name, string $alias) : void` - allows registering a generator alias without copying the generator registration parameters
+
+### `pocketmine\world\sound`
+- The following new classes have been added:
+- `PressurePlateActivateSound`
+- `PressurePlateDeactivateSound`
+
+### `pocketmine\utils`
+- The following new API methods have been added:
+ - `public StringToTParser->registerAlias(string $existing, string $alias) : void` - allows registering a string alias without copying registration parameters
+
+## Internals
+- Various `TypeIdMap` classes in the `pocketmine\data\bedrock` package now use the new `IntSaveIdMapTrait` to reduce code duplication.
+- Added a new `ServerProperties` class containing constants for all known `server.properties` keys.
+- Added a new `YmlServerProperties` class containing generated constants for all known `pocketmine.yml` keys. These keys can be used with `Config->getNested()`.
+
diff --git a/changelogs/5.6.md b/changelogs/5.6.md
new file mode 100644
index 00000000000..257289a4a8f
--- /dev/null
+++ b/changelogs/5.6.md
@@ -0,0 +1,34 @@
+# 5.6.0
+Released 20th September 2023.
+
+**For Minecraft: Bedrock Edition 1.20.30**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.30.
+
+**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.20.30.
+- Removed support for older versions.
+
+## Fixes
+- Fixed support conditions for hanging roots, cave vines and dead bushes.
+- Fixed connection conditions for fences, glass panes, iron bars, and walls.
+
+# 5.6.1
+Released 20th October 2023.
+
+## Performance
+- Improved performance of cactus growth by disabling neighbour updates when only the age property was updated. While this isn't a perfect solution, it provides significant performance gains for servers with large cactus farms.
+
+## Fixes
+- Fixed `tools/generate-bedrock-data-from-packets.php` incorrectly interpreting network meta as blockstates in some cases (broken crafting recipes).
+- Fixed crafting recipes involving beds, skulls and some other items not working correctly (incorrectly interpreted data).
+- Fixed crashes when flower pot or cauldron blockentities exist in places where they shouldn't (leftovers from upgraded PM3 worlds).
+- Fixed `Entity->broadcastSound()` not firing `WorldSoundEvent` (bypassing internal sound system).
+- Fixed wooden signs, buttons and doors not being able to be used as furnace fuel.
+- Fixed bone meal and tools only working when used on the top side of dirt and grass. Bone meal now works from any side, and tools work on any side except the bottom.
diff --git a/changelogs/5.7.md b/changelogs/5.7.md
new file mode 100644
index 00000000000..9134f299e2b
--- /dev/null
+++ b/changelogs/5.7.md
@@ -0,0 +1,27 @@
+# 5.7.0
+Released 26th October 2023.
+
+**For Minecraft: Bedrock Edition 1.20.40**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.40.
+
+**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.20.40.
+- Removed support for older versions.
+
+## Fixes
+- Fixed `cartography_table`, `smithing_table`, `stripped_cherry_log` and `stripped_cherry_wood` not working in `StringToItemParser`.
+- Fixed `Promise::onCompletion()` always calling the reject handler if the promise was already completed.
+
+# 5.7.1
+Released 1st November 2023.
+
+## Fixes
+- Fixed non-reentrant-safe code in `PermissionManager` and various other subscriber subsystems.
+ - These issues caused server crashes when deleting a subscriber indirectly triggered the deletion of other subscribers (e.g. due to the GC activating in `unset()`).
diff --git a/changelogs/5.8.md b/changelogs/5.8.md
new file mode 100644
index 00000000000..7ae64742c8c
--- /dev/null
+++ b/changelogs/5.8.md
@@ -0,0 +1,138 @@
+# 5.8.0
+Released 1st November 2023.
+
+**Borked release, forgot to merge branches.**
+
+# 5.8.1
+Released 1st November 2023.
+
+**For Minecraft: Bedrock Edition 1.20.40**
+
+This is a minor feature release, including new gameplay features, various performance improvements to internal `World` and `Block` systems, and changes to the API.
+
+**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
+- Neighbour block updates now have a separate timer for timings. Previously, these were counted under `Scheduled Block Updates`, which was misleading.
+
+## Performance
+- `LightUpdate` now avoids attempting to propagate back in the same direction the light came from. This produces a small performance improvement of around 6% in light propagation.
+- Improved worst-case (non-cached) performance of `World::getCollisionBlocks()` (and its successor `World::getBlockCollisionBlocks()`).
+ - While 5.5.0 introduced caching at the `World` level for AABBs, the cache was rarely useful due to entity and player movement being too unpredictable. This meant that most users saw a performance degradation with lots of moving entities, except in specific situations.
+ - Performance for fetching non-cached AABBs for a cell is now improved by 2x. Overall performance benefit to a server depends on the number of entities and players.
+- Added cache for hydrated farmland blocks to remember the last known location of nearby water.
+ - If nearby water sources are not changed, this cache allows hydrated farmland to completely avoid checking up to 161 nearby blocks for water after the first check.
+ - Tests with large wheat farms showed a 25% performance improvement in overall server performance compared to previous 5.x versions.
+- Migrated various internal enums to native PHP 8.1 enums. Bypassing magic `__callStatic()` accessors improved performance in many areas, although it's hard to quantify the exact benefit.
+- Made use of `Facing::OFFSET` constant in various places to avoid unnecessary `Vector3` and `Position` object allocations. Many pathways benefit from this, including neighbour block updates (due to faster `Block::getSide()` and less useless objects).
+- Avoided clearing block AABB caches except when strictly necessary. Previously, the cache was wiped every time blocks were read from the world, making them mostly useless.
+- Avoided random updates on blocks which have reached their final state, such as fully-grown crops. This produces a minimal performance improvement.
+- Removed useless checks in some `World` hot paths.
+
+## API
+### General
+- All enums have been migrated to native PHP 8.1 enums.
+ - For now, the old APIs and accessors are still usable (via `LegacyEnumShimTrait`), but these will be removed in the next major release.
+ - `EnumTrait` has been deprecated, and will be removed in the next major release.
+ - Migration for most plugin developers will simply involve deleting `()` from the end of enum case usages, which is a trivial change and also improves performance.
+ - Plugin usages of `EnumTrait` are encouraged to move to native enums, optionally using `LegacyEnumShimTrait` to provide backwards compatibility.
+ - See [this code](https://github.com/pmmp/PocketMine-MP/blob/9832fe899f13a8ea47cc9d73de7088f7775a12f5/src/block/utils/DyeColor.php#L85-L107) for an example of how to associate properties with enum cases (since native enums don't support this directly).
+ - Thanks to improvements in `RuntimeDataDescriber`, any native enum can now be used as a custom block's state property.
+
+### `pocketmine\block`
+- The following classes have been added:
+ - `utils\AgeableTrait` - used by blocks which have an age property, such as crops
+ - `utils\StaticSupportTrait` - used by blocks which have the same support requirements regardless of their state, such as crops
+
+### `pocketmine\data\runtime`
+- The following API methods have been added:
+ - `public RuntimeDataDescriber->boundedIntAuto(int $min, int $max, int &$value) : void` - similar to `boundedInt()`, but automatically calculates the needed number of bits based on the given min/max
+ - `public RuntimeDataDescriber->enum(T extends \UnitEnum &$case) : void` - describes any native PHP 8.1 enum case
+ - `public RuntimeDataDescriber->enumSet(array &$set, array $allCases) : void` - describes a set of enum cases (similar to bitflags)
+- The following API methods have been deprecated:
+ - `RuntimeDataDescriber->bellAttachmentType()` - use `enum()` instead
+ - `RuntimeDataDescriber->boundedInt()` - use `boundedIntAuto()` instead
+ - `RuntimeDataDescriber->brewingStandSlots()` - use `enumSet()` instead
+ - `RuntimeDataDescriber->copperOxidation()` - use `enum()` instead
+ - `RuntimeDataDescriber->coralType()` - use `enum()` instead
+ - `RuntimeDataDescriber->dirtType()` - use `enum()` instead
+ - `RuntimeDataDescriber->dripleafState()` - use `enum()` instead
+ - `RuntimeDataDescriber->dyeColor()` - use `enum()` instead
+ - `RuntimeDataDescriber->froglightType()` - use `enum()` instead
+ - `RuntimeDataDescriber->leverFacing()` - use `enum()` instead
+ - `RuntimeDataDescriber->medicineType()` - use `enum()` instead
+ - `RuntimeDataDescriber->mobHeadType()` - use `enum()` instead
+ - `RuntimeDataDescriber->mushroomBlockType()` - use `enum()` instead
+ - `RuntimeDataDescriber->potionType()` - use `enum()` instead
+ - `RuntimeDataDescriber->slabType()` - use `enum()` instead
+ - `RuntimeDataDescriber->suspiciousStewType()` - use `enum()` instead
+
+### `pocketmine\player`
+- `TitleID` is now included in `PlayerInfo` metadata for plugin consumption.
+
+### `pocketmine\world`
+- The following API methods have been added:
+ - `public World->getBlockCollisionBoxes(AxisAlignedBB $bb) : list` - similar to `getCollisionBoxes` but exclusively for blocks, avoiding the need for conditionally useless parameters
+- The following API methods have been deprecated:
+ - `World->getCollisionBoxes()` - use `getBlockCollisionBoxes()` instead (alongside `getCollidingEntities()` if entity collision boxes are also required)
+
+### `pocketmine\utils`
+- The following classes have been deprecated:
+ - `EnumTrait` - use native PHP 8.1 enums instead
+- The following classes have been added:
+ - `LegacyEnumShimTrait` - can be `use`d by native PHP 8.1 enums to provide the same API as `EnumTrait`
+
+## Gameplay
+### Blocks
+- Implemented the following blocks:
+ - Amethyst
+ - Amethyst Cluster
+ - Chiseled Bookshelf
+ - Crimson Roots
+ - Pitcher Crop
+ - Pitcher Plant
+ - Torchflower
+ - Torchflower Crop
+ - Warped Roots
+
+### Items
+- Implemented the following items:
+ - Pitcher Pod
+ - Torchflower Seeds
+
+## Internals
+- `Farmland` block now has an extra property (`waterPositionIndex`) stored in its blockstate ID to track the position of nearby water. This property is not saved to disk, and is only used for caching.
+- The format of internal blockstate ID has been updated.
+ - The lower `11` bits are now reserved for state data (previously `8` bits). This increase facilitates the new cache for `Farmland` blocks.
+ - The state data bits are now XOR'd with the `xxh3` of the block's type ID, instead of being directly XOR'd with the type ID.
+ - This XOR improves the distribution of the lower bits of the blockstate ID, which is important for hashtable indexing to minimize collisions.
+ - Previously, the lower bits were XOR'd with the type ID directly, but the effectiveness of this reduced as more state data bits were added.
+ - `xxh3` produces consistently good results for this purpose regardless of the number of state data bits allocated.
+ - Hash collisions with blockstate IDs are reduced by 50% with this change, which is a happy side effect.
+ - This is backwards-incompatible, so anyone saving internal blockstate IDs on disk or in a DB will be burned by this change (though they shouldn't have been doing that anyway).
+- Removed code generation step for `RuntimeDataDescriber` enum serialization. All described enums now use PHP 8.1 native enums, which can be described without codegen using `RuntimeDataDescriber->enum()`.
+- Added `DeprecatedLegacyEnumAccessRule` custom PHPStan rule to flag legacy `EnumTrait` case accessors.
+- Cleaned up remaining hardcoded `Config` keys in `SetupWizard`. These usages now use auto-generated constants like the rest of the codebase.
+
+# 5.8.2
+Released 9th November 2023.
+
+## Performance
+- Improved performance of small packet zero-compression (unintended use of slow zlib compressor instead of fast libdeflate one).
+ - This affected the majority of outbound packets, as most packets are below the 256-byte threshold for compression.
+ - This faster method is over 20x faster than the old method, producing noticeable performance gains for large servers.
+
+## Fixes
+- Fixed melons and pumpkins not growing.
+- Fixed melon and pumpkin stems not attaching to the grown melon/pumpkin.
+- Fixed iron and gold ores not being affected by the Fortune enchantment.
+- Fixed ancient debris burning in lava.
+- Fixed sign (front) text loading from vanilla world saves (back text is not yet supported).
+
+## Internals
+- Removed bogus optimization from `tools/generate-blockstate-upgrade-schema.php` that could cause incorrect `remappedStates` generation when some of the states stayed under the old ID.
+- Fixed possible crash in `BlockStateUpgrader` name flattening rule handling with invalid blockstate NBT data.
diff --git a/changelogs/5.9.md b/changelogs/5.9.md
new file mode 100644
index 00000000000..2467be638eb
--- /dev/null
+++ b/changelogs/5.9.md
@@ -0,0 +1,30 @@
+# 5.9.0
+Released 6th December 2023.
+
+**For Minecraft: Bedrock Edition 1.20.50**
+
+This is a support release for Minecraft: Bedrock Edition 1.20.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.20.50.
+- Removed support for older versions.
+
+## Fixes
+- Fixed `pitcher_plant` and `pitcher_pod` not being accepted by `StringToItemParser` (and therefore not being usable in commands).
+- Rotation of items in item frames in worlds from newer versions of Bedrock is now correctly loaded.
+- Fixed possible crash in block update sending if a chunk was unloaded during a previous chunk's block update syncing.
+- Fixed `AsyncTask::fetchLocal()` throwing an exception if `null` was stored in the local storage.
+
+## Documentation
+- `Server::prepareBatch()` is now correctly marked as `@internal`.
+- Updated documentation for `Server::prepareBatch()` to accurately reflect its behaviour.
+- Fixed incorrect path in doc comments of `EnumTrait` and `RegistryTrait`.
+
+## Internals
+- Added PHP 8.3 to the test matrix. This has not been thoroughly tested yet, so it should only be used for testing purposes.
diff --git a/composer.json b/composer.json
index 415ff392f0e..9747cb56786 100644
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
- "ext-pmmpthread": "^6.0.1",
+ "ext-pmmpthread": "^6.1.0",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@@ -31,32 +31,31 @@
"ext-zip": "*",
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0",
- "adhocore/json-comment": "^1.1",
- "fgrosse/phpasn1": "^2.3",
- "pocketmine/netresearch-jsonmapper": "~v4.2.999",
- "pocketmine/bedrock-block-upgrade-schema": "~2.1.0+bedrock-1.19.80",
- "pocketmine/bedrock-data": "~2.2.0+bedrock-1.19.80",
- "pocketmine/bedrock-item-upgrade-schema": "~1.2.0+bedrock-1.19.80",
- "pocketmine/bedrock-protocol": "~21.0.0+bedrock-1.19.80",
+ "adhocore/json-comment": "~1.2.0",
+ "pocketmine/netresearch-jsonmapper": "~v4.4.999",
+ "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
+ "pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40",
+ "pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40",
+ "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
- "pocketmine/errorhandler": "^0.6.0",
- "pocketmine/locale-data": "~2.19.0",
+ "pocketmine/errorhandler": "^0.7.0",
+ "pocketmine/locale-data": "~2.21.0",
"pocketmine/log": "^0.4.0",
- "pocketmine/math": "^0.4.0",
- "pocketmine/nbt": "^0.3.2",
- "pocketmine/raklib": "^0.15.0",
- "pocketmine/raklib-ipc": "^0.2.0",
+ "pocketmine/math": "~1.0.0",
+ "pocketmine/nbt": "~1.0.0",
+ "pocketmine/raklib": "~1.1.0",
+ "pocketmine/raklib-ipc": "~1.0.0",
"pocketmine/snooze": "^0.5.0",
- "ramsey/uuid": "^4.1",
- "symfony/filesystem": "^6.2"
+ "ramsey/uuid": "~4.7.0",
+ "symfony/filesystem": "~6.4.0"
},
"require-dev": {
- "phpstan/phpstan": "1.10.15",
+ "phpstan/phpstan": "1.11.11",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
- "phpunit/phpunit": "^10.1"
+ "phpunit/phpunit": "^10.5.24"
},
"autoload": {
"psr-4": {
@@ -84,11 +83,14 @@
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
"@php -dphar.readonly=0 build/server-phar.php"
],
- "update-registry-annotations": [
+ "update-codegen": [
+ "@php build/generate-bedrockdata-path-consts.php",
+ "@php build/generate-biome-ids.php",
+ "@php build/generate-block-serializer-consts.php vendor/pocketmine/bedrock-data/canonical_block_states.nbt",
+ "@php build/generate-item-type-names.php vendor/pocketmine/bedrock-data/required_item_list.json",
+ "@php build/generate-known-translation-apis.php",
+ "@php build/generate-pocketmine-yml-property-consts.php",
"@php build/generate-registry-annotations.php src"
- ],
- "update-translation-apis": [
- "@php build/generate-known-translation-apis.php"
]
}
}
diff --git a/composer.lock b/composer.lock
index 085f9f3cf1b..c1a0b0073ab 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": "f48120203fe576d1ef1b7eddd8460d8f",
+ "content-hash": "476374fb3d22e26a97c1dea8c6736faf",
"packages": [
{
"name": "adhocore/json-comment",
@@ -67,25 +67,25 @@
},
{
"name": "brick/math",
- "version": "0.11.0",
+ "version": "0.12.1",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
- "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478"
+ "reference": "f510c0a40911935b77b86859eb5223d58d660df1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478",
- "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478",
+ "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
+ "reference": "f510c0a40911935b77b86859eb5223d58d660df1",
"shasum": ""
},
"require": {
- "php": "^8.0"
+ "php": "^8.1"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
- "phpunit/phpunit": "^9.0",
- "vimeo/psalm": "5.0.0"
+ "phpunit/phpunit": "^10.1",
+ "vimeo/psalm": "5.16.0"
},
"type": "library",
"autoload": {
@@ -105,12 +105,17 @@
"arithmetic",
"bigdecimal",
"bignum",
+ "bignumber",
"brick",
- "math"
+ "decimal",
+ "integer",
+ "math",
+ "mathematics",
+ "rational"
],
"support": {
"issues": "https://github.com/brick/math/issues",
- "source": "https://github.com/brick/math/tree/0.11.0"
+ "source": "https://github.com/brick/math/tree/0.12.1"
},
"funding": [
{
@@ -118,96 +123,20 @@
"type": "github"
}
],
- "time": "2023-01-15T23:15:59+00:00"
- },
- {
- "name": "fgrosse/phpasn1",
- "version": "v2.5.0",
- "source": {
- "type": "git",
- "url": "https://github.com/fgrosse/PHPASN1.git",
- "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b",
- "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b",
- "shasum": ""
- },
- "require": {
- "php": "^7.1 || ^8.0"
- },
- "require-dev": {
- "php-coveralls/php-coveralls": "~2.0",
- "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
- },
- "suggest": {
- "ext-bcmath": "BCmath is the fallback extension for big integer calculations",
- "ext-curl": "For loading OID information from the web if they have not bee defined statically",
- "ext-gmp": "GMP is the preferred extension for big integer calculations",
- "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "FG\\": "lib/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Friedrich Große",
- "email": "friedrich.grosse@gmail.com",
- "homepage": "https://github.com/FGrosse",
- "role": "Author"
- },
- {
- "name": "All contributors",
- "homepage": "https://github.com/FGrosse/PHPASN1/contributors"
- }
- ],
- "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
- "homepage": "https://github.com/FGrosse/PHPASN1",
- "keywords": [
- "DER",
- "asn.1",
- "asn1",
- "ber",
- "binary",
- "decoding",
- "encoding",
- "x.509",
- "x.690",
- "x509",
- "x690"
- ],
- "support": {
- "issues": "https://github.com/fgrosse/PHPASN1/issues",
- "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0"
- },
- "abandoned": true,
- "time": "2022-12-19T11:08:26+00:00"
+ "time": "2023-11-29T23:19:16+00:00"
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
- "version": "2.1.0",
+ "version": "5.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
- "reference": "1c07ced86be7d185551082441b5a2b9b7fbd6b21"
+ "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/1c07ced86be7d185551082441b5a2b9b7fbd6b21",
- "reference": "1c07ced86be7d185551082441b5a2b9b7fbd6b21",
+ "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
+ "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
"shasum": ""
},
"type": "library",
@@ -218,22 +147,22 @@
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
- "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/2.1.0"
+ "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0"
},
- "time": "2023-04-19T17:58:49+00:00"
+ "time": "2024-11-03T14:13:50+00:00"
},
{
"name": "pocketmine/bedrock-data",
- "version": "2.2.0+bedrock-1.19.80",
+ "version": "2.14.1+bedrock-1.21.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
- "reference": "33dd83601442b377af42ac91473278243cafd576"
+ "reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/33dd83601442b377af42ac91473278243cafd576",
- "reference": "33dd83601442b377af42ac91473278243cafd576",
+ "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/4a41864ed09613ecec6791e2ae076a8ec7089cc4",
+ "reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4",
"shasum": ""
},
"type": "library",
@@ -244,22 +173,22 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
- "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.80"
+ "source": "https://github.com/pmmp/BedrockData/tree/2.14.1+bedrock-1.21.40"
},
- "time": "2023-04-26T20:00:35+00:00"
+ "time": "2024-11-12T21:36:20+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
- "version": "1.2.0",
+ "version": "1.13.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
- "reference": "dd804c3f2b1e8990434812627e62eb5bde9670a5"
+ "reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/dd804c3f2b1e8990434812627e62eb5bde9670a5",
- "reference": "dd804c3f2b1e8990434812627e62eb5bde9670a5",
+ "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/1cf81305f2ffcf7dde9577c4f16a55c765192b03",
+ "reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03",
"shasum": ""
},
"type": "library",
@@ -270,39 +199,38 @@
"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.2.0"
+ "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.13.1"
},
- "time": "2023-04-19T18:16:14+00:00"
+ "time": "2024-11-12T21:33:17+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
- "version": "21.0.1+bedrock-1.19.80",
+ "version": "35.0.0+bedrock-1.21.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
- "reference": "981ea2e76e207a25c1361df858c639feba5cf348"
+ "reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/981ea2e76e207a25c1361df858c639feba5cf348",
- "reference": "981ea2e76e207a25c1361df858c639feba5cf348",
+ "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459",
+ "reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459",
"shasum": ""
},
"require": {
"ext-json": "*",
- "netresearch/jsonmapper": "^4.0",
- "php": "^8.0",
+ "php": "^8.1",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/color": "^0.2.0 || ^0.3.0",
- "pocketmine/math": "^0.3.0 || ^0.4.0",
- "pocketmine/nbt": "^0.3.0",
+ "pocketmine/math": "^0.3.0 || ^0.4.0 || ^1.0.0",
+ "pocketmine/nbt": "^1.0.0",
"ramsey/uuid": "^4.1"
},
"require-dev": {
- "phpstan/phpstan": "1.10.7",
+ "phpstan/phpstan": "1.11.9",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
- "phpunit/phpunit": "^9.5"
+ "phpunit/phpunit": "^9.5 || ^10.0"
},
"type": "library",
"autoload": {
@@ -317,22 +245,22 @@
"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/21.0.1+bedrock-1.19.80"
+ "source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.40"
},
- "time": "2023-04-26T21:00:01+00:00"
+ "time": "2024-10-24T15:45:43+00:00"
},
{
"name": "pocketmine/binaryutils",
- "version": "0.2.4",
+ "version": "0.2.6",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
- "reference": "5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a"
+ "reference": "ccfc1899b859d45814ea3592e20ebec4cb731c84"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a",
- "reference": "5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a",
+ "url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/ccfc1899b859d45814ea3592e20ebec4cb731c84",
+ "reference": "ccfc1899b859d45814ea3592e20ebec4cb731c84",
"shasum": ""
},
"require": {
@@ -341,10 +269,10 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "1.3.0",
+ "phpstan/phpstan": "~1.10.3",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
- "phpunit/phpunit": "^9.5"
+ "phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
},
"type": "library",
"autoload": {
@@ -359,9 +287,9 @@
"description": "Classes and methods for conveniently handling binary data",
"support": {
"issues": "https://github.com/pmmp/BinaryUtils/issues",
- "source": "https://github.com/pmmp/BinaryUtils/tree/0.2.4"
+ "source": "https://github.com/pmmp/BinaryUtils/tree/0.2.6"
},
- "time": "2022-01-12T18:06:33+00:00"
+ "time": "2024-03-04T15:04:17+00:00"
},
{
"name": "pocketmine/callback-validator",
@@ -453,25 +381,25 @@
},
{
"name": "pocketmine/errorhandler",
- "version": "0.6.0",
+ "version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/ErrorHandler.git",
- "reference": "dae214a04348b911e8219ebf125ff1c5589cc878"
+ "reference": "cae94884368a74ece5294b9ff7fef18732dcd921"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/dae214a04348b911e8219ebf125ff1c5589cc878",
- "reference": "dae214a04348b911e8219ebf125ff1c5589cc878",
+ "url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/cae94884368a74ece5294b9ff7fef18732dcd921",
+ "reference": "cae94884368a74ece5294b9ff7fef18732dcd921",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
- "phpstan/phpstan": "0.12.99",
- "phpstan/phpstan-strict-rules": "^0.12.2",
- "phpunit/phpunit": "^9.5"
+ "phpstan/phpstan": "~1.10.3",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
},
"type": "library",
"autoload": {
@@ -486,22 +414,22 @@
"description": "Utilities to handle nasty PHP E_* errors in a usable way",
"support": {
"issues": "https://github.com/pmmp/ErrorHandler/issues",
- "source": "https://github.com/pmmp/ErrorHandler/tree/0.6.0"
+ "source": "https://github.com/pmmp/ErrorHandler/tree/0.7.0"
},
- "time": "2022-01-08T21:05:46+00:00"
+ "time": "2024-04-02T18:29:54+00:00"
},
{
"name": "pocketmine/locale-data",
- "version": "2.19.5",
+ "version": "2.21.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
- "reference": "71af5f9bd23b4e4bad8920dac7f4fe08e5205f7d"
+ "reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/Language/zipball/71af5f9bd23b4e4bad8920dac7f4fe08e5205f7d",
- "reference": "71af5f9bd23b4e4bad8920dac7f4fe08e5205f7d",
+ "url": "https://api.github.com/repos/pmmp/Language/zipball/fdba0f764d6281f64e5968dca94fdab96bf4e167",
+ "reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167",
"shasum": ""
},
"type": "library",
@@ -509,9 +437,9 @@
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
- "source": "https://github.com/pmmp/Language/tree/2.19.5"
+ "source": "https://github.com/pmmp/Language/tree/2.21.1"
},
- "time": "2023-03-19T16:45:15+00:00"
+ "time": "2024-11-14T23:11:22+00:00"
},
{
"name": "pocketmine/log",
@@ -556,16 +484,16 @@
},
{
"name": "pocketmine/math",
- "version": "0.4.3",
+ "version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Math.git",
- "reference": "47a243d320b01c8099d65309967934c188111549"
+ "reference": "dc132d93595b32e9f210d78b3c8d43c662a5edbf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/Math/zipball/47a243d320b01c8099d65309967934c188111549",
- "reference": "47a243d320b01c8099d65309967934c188111549",
+ "url": "https://api.github.com/repos/pmmp/Math/zipball/dc132d93595b32e9f210d78b3c8d43c662a5edbf",
+ "reference": "dc132d93595b32e9f210d78b3c8d43c662a5edbf",
"shasum": ""
},
"require": {
@@ -574,7 +502,7 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "1.8.2",
+ "phpstan/phpstan": "~1.10.3",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.5"
},
@@ -591,22 +519,22 @@
"description": "PHP library containing math related code used in PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Math/issues",
- "source": "https://github.com/pmmp/Math/tree/0.4.3"
+ "source": "https://github.com/pmmp/Math/tree/1.0.0"
},
- "time": "2022-08-25T18:43:37+00:00"
+ "time": "2023-08-03T12:56:33+00:00"
},
{
"name": "pocketmine/nbt",
- "version": "0.3.4",
+ "version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
- "reference": "62c02464c6708b2467c1e1a2af01af09d5114eda"
+ "reference": "20540271cb59e04672cb163dca73366f207974f1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/NBT/zipball/62c02464c6708b2467c1e1a2af01af09d5114eda",
- "reference": "62c02464c6708b2467c1e1a2af01af09d5114eda",
+ "url": "https://api.github.com/repos/pmmp/NBT/zipball/20540271cb59e04672cb163dca73366f207974f1",
+ "reference": "20540271cb59e04672cb163dca73366f207974f1",
"shasum": ""
},
"require": {
@@ -616,7 +544,7 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "1.10.3",
+ "phpstan/phpstan": "1.10.25",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5"
},
@@ -633,22 +561,22 @@
"description": "PHP library for working with Named Binary Tags",
"support": {
"issues": "https://github.com/pmmp/NBT/issues",
- "source": "https://github.com/pmmp/NBT/tree/0.3.4"
+ "source": "https://github.com/pmmp/NBT/tree/1.0.0"
},
- "time": "2023-04-10T11:31:20+00:00"
+ "time": "2023-07-14T13:01:49+00:00"
},
{
"name": "pocketmine/netresearch-jsonmapper",
- "version": "v4.2.999",
+ "version": "v4.4.999",
"source": {
"type": "git",
"url": "https://github.com/pmmp/netresearch-jsonmapper.git",
- "reference": "f700806dec756ed825a8200dc2950ead98265956"
+ "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/f700806dec756ed825a8200dc2950ead98265956",
- "reference": "f700806dec756ed825a8200dc2950ead98265956",
+ "url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833",
+ "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833",
"shasum": ""
},
"require": {
@@ -662,7 +590,7 @@
"netresearch/jsonmapper": "~4.2.0"
},
"require-dev": {
- "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0",
+ "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
@@ -687,34 +615,34 @@
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
- "source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.2.999"
+ "source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999"
},
- "time": "2023-06-01T13:43:01+00:00"
+ "time": "2024-02-23T13:17:01+00:00"
},
{
"name": "pocketmine/raklib",
- "version": "0.15.1",
+ "version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
- "reference": "79b7b4d1d7516dc6e322514453645ad9452b20ca"
+ "reference": "be2783be516bf6e2872ff5c81fb9048596617b97"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/RakLib/zipball/79b7b4d1d7516dc6e322514453645ad9452b20ca",
- "reference": "79b7b4d1d7516dc6e322514453645ad9452b20ca",
+ "url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97",
+ "reference": "be2783be516bf6e2872ff5c81fb9048596617b97",
"shasum": ""
},
"require": {
"ext-sockets": "*",
- "php": "^8.0",
+ "php": "^8.1",
"php-64bit": "*",
"php-ipv6": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/log": "^0.3.0 || ^0.4.0"
},
"require-dev": {
- "phpstan/phpstan": "1.9.17",
+ "phpstan/phpstan": "1.10.1",
"phpstan/phpstan-strict-rules": "^1.0"
},
"type": "library",
@@ -730,32 +658,32 @@
"description": "A RakNet server implementation written in PHP",
"support": {
"issues": "https://github.com/pmmp/RakLib/issues",
- "source": "https://github.com/pmmp/RakLib/tree/0.15.1"
+ "source": "https://github.com/pmmp/RakLib/tree/1.1.1"
},
- "time": "2023-03-07T15:10:34+00:00"
+ "time": "2024-03-04T14:02:14+00:00"
},
{
"name": "pocketmine/raklib-ipc",
- "version": "0.2.0",
+ "version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLibIpc.git",
- "reference": "26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c"
+ "reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c",
- "reference": "26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c",
+ "url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/ce632ef2c6743e71eddb5dc329c49af6555f90bc",
+ "reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc",
"shasum": ""
},
"require": {
"php": "^8.0",
"php-64bit": "*",
"pocketmine/binaryutils": "^0.2.0",
- "pocketmine/raklib": "^0.15.0"
+ "pocketmine/raklib": "^0.15.0 || ^1.0.0"
},
"require-dev": {
- "phpstan/phpstan": "1.9.17",
+ "phpstan/phpstan": "1.10.1",
"phpstan/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@@ -771,9 +699,9 @@
"description": "Channel-based protocols for inter-thread/inter-process communication with RakLib",
"support": {
"issues": "https://github.com/pmmp/RakLibIpc/issues",
- "source": "https://github.com/pmmp/RakLibIpc/tree/0.2.0"
+ "source": "https://github.com/pmmp/RakLibIpc/tree/1.0.1"
},
- "time": "2023-02-13T13:40:40+00:00"
+ "time": "2024-03-01T15:55:05+00:00"
},
{
"name": "pocketmine/snooze",
@@ -906,20 +834,20 @@
},
{
"name": "ramsey/uuid",
- "version": "4.7.4",
+ "version": "4.7.6",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
- "reference": "60a4c63ab724854332900504274f6150ff26d286"
+ "reference": "91039bc1faa45ba123c4328958e620d382ec7088"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286",
- "reference": "60a4c63ab724854332900504274f6150ff26d286",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088",
+ "reference": "91039bc1faa45ba123c4328958e620d382ec7088",
"shasum": ""
},
"require": {
- "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11",
+ "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
@@ -982,7 +910,7 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
- "source": "https://github.com/ramsey/uuid/tree/4.7.4"
+ "source": "https://github.com/ramsey/uuid/tree/4.7.6"
},
"funding": [
{
@@ -994,20 +922,20 @@
"type": "tidelift"
}
],
- "time": "2023-04-15T23:01:58+00:00"
+ "time": "2024-04-27T21:32:50+00:00"
},
{
"name": "symfony/filesystem",
- "version": "v6.2.10",
+ "version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894"
+ "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894",
- "reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3",
+ "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3",
"shasum": ""
},
"require": {
@@ -1015,6 +943,9 @@
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
+ "require-dev": {
+ "symfony/process": "^5.4|^6.4|^7.0"
+ },
"type": "library",
"autoload": {
"psr-4": {
@@ -1041,7 +972,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v6.2.10"
+ "source": "https://github.com/symfony/filesystem/tree/v6.4.13"
},
"funding": [
{
@@ -1057,24 +988,24 @@
"type": "tidelift"
}
],
- "time": "2023-04-18T13:46:08+00:00"
+ "time": "2024-10-25T15:07:50+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.27.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
- "reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@@ -1084,9 +1015,6 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.27-dev"
- },
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
@@ -1123,7 +1051,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@@ -1139,24 +1067,24 @@
"type": "tidelift"
}
],
- "time": "2022-11-03T14:55:06+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.27.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
- "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@@ -1166,9 +1094,6 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.27-dev"
- },
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
@@ -1206,7 +1131,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@@ -1222,22 +1147,22 @@
"type": "tidelift"
}
],
- "time": "2022-11-03T14:55:06+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
}
],
"packages-dev": [
{
"name": "myclabs/deep-copy",
- "version": "1.11.1",
+ "version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": ""
},
"require": {
@@ -1245,11 +1170,12 @@
},
"conflict": {
"doctrine/collections": "<1.6.8",
- "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
@@ -1275,7 +1201,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
},
"funding": [
{
@@ -1283,29 +1209,31 @@
"type": "tidelift"
}
],
- "time": "2023-03-08T13:26:56+00:00"
+ "time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nikic/php-parser",
- "version": "v4.15.5",
+ "version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
- "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
"ext-tokenizer": "*",
- "php": ">=7.0"
+ "php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@@ -1313,7 +1241,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.9-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
@@ -1337,26 +1265,27 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
- "time": "2023-05-19T20:20:00+00:00"
+ "time": "2024-10-08T18:51:32+00:00"
},
{
"name": "phar-io/manifest",
- "version": "2.0.3",
+ "version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
- "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
- "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": ""
},
"require": {
"ext-dom": "*",
+ "ext-libxml": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
@@ -1397,9 +1326,15 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
- "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
},
- "time": "2021-07-20T11:28:43+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
@@ -1454,16 +1389,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.10.15",
+ "version": "1.11.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd"
+ "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd",
- "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3",
+ "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3",
"shasum": ""
},
"require": {
@@ -1506,31 +1441,27 @@
{
"url": "https://github.com/phpstan",
"type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
- "type": "tidelift"
}
],
- "time": "2023-05-09T15:28:01+00:00"
+ "time": "2024-08-19T14:37:29+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
- "version": "1.3.13",
+ "version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
- "reference": "d8bdab0218c5eb0964338d24a8511b65e9c94fa5"
+ "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d8bdab0218c5eb0964338d24a8511b65e9c94fa5",
- "reference": "d8bdab0218c5eb0964338d24a8511b65e9c94fa5",
+ "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11",
+ "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
- "phpstan/phpstan": "^1.10"
+ "phpstan/phpstan": "^1.11"
},
"conflict": {
"phpunit/phpunit": "<7.0"
@@ -1538,7 +1469,7 @@
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
- "phpstan/phpstan-strict-rules": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.5.1",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
@@ -1562,27 +1493,27 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
- "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.13"
+ "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0"
},
- "time": "2023-05-26T11:05:59+00:00"
+ "time": "2024-04-20T06:39:00+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
- "version": "1.5.1",
+ "version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
- "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6"
+ "reference": "363f921dd8441777d4fc137deb99beb486c77df1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6",
- "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6",
+ "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1",
+ "reference": "363f921dd8441777d4fc137deb99beb486c77df1",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
- "phpstan/phpstan": "^1.10"
+ "phpstan/phpstan": "^1.11"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
@@ -1611,38 +1542,38 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
- "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1"
+ "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0"
},
- "time": "2023-03-29T14:47:40+00:00"
+ "time": "2024-04-20T06:37:51+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "10.1.2",
+ "version": "10.1.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "db1497ec8dd382e82c962f7abbe0320e4882ee4e"
+ "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/db1497ec8dd382e82c962f7abbe0320e4882ee4e",
- "reference": "db1497ec8dd382e82c962f7abbe0320e4882ee4e",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
+ "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.15",
+ "nikic/php-parser": "^4.19.1 || ^5.1.0",
"php": ">=8.1",
- "phpunit/php-file-iterator": "^4.0",
- "phpunit/php-text-template": "^3.0",
- "sebastian/code-unit-reverse-lookup": "^3.0",
- "sebastian/complexity": "^3.0",
- "sebastian/environment": "^6.0",
- "sebastian/lines-of-code": "^2.0",
- "sebastian/version": "^4.0",
- "theseer/tokenizer": "^1.2.0"
+ "phpunit/php-file-iterator": "^4.1.0",
+ "phpunit/php-text-template": "^3.0.1",
+ "sebastian/code-unit-reverse-lookup": "^3.0.0",
+ "sebastian/complexity": "^3.2.0",
+ "sebastian/environment": "^6.1.0",
+ "sebastian/lines-of-code": "^2.0.2",
+ "sebastian/version": "^4.0.1",
+ "theseer/tokenizer": "^1.2.3"
},
"require-dev": {
"phpunit/phpunit": "^10.1"
@@ -1654,7 +1585,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "10.1-dev"
+ "dev-main": "10.1.x-dev"
}
},
"autoload": {
@@ -1683,7 +1614,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.2"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
},
"funding": [
{
@@ -1691,20 +1622,20 @@
"type": "github"
}
],
- "time": "2023-05-22T09:04:27+00:00"
+ "time": "2024-08-22T04:31:57+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "4.0.2",
+ "version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "5647d65443818959172645e7ed999217360654b6"
+ "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/5647d65443818959172645e7ed999217360654b6",
- "reference": "5647d65443818959172645e7ed999217360654b6",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
+ "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
"shasum": ""
},
"require": {
@@ -1744,7 +1675,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
- "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.0.2"
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0"
},
"funding": [
{
@@ -1752,7 +1683,7 @@
"type": "github"
}
],
- "time": "2023-05-07T09:13:23+00:00"
+ "time": "2023-08-31T06:24:48+00:00"
},
{
"name": "phpunit/php-invoker",
@@ -1819,16 +1750,16 @@
},
{
"name": "phpunit/php-text-template",
- "version": "3.0.0",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d"
+ "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/9f3d3709577a527025f55bcf0f7ab8052c8bb37d",
- "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+ "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
"shasum": ""
},
"require": {
@@ -1866,7 +1797,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
- "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.0"
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
},
"funding": [
{
@@ -1874,7 +1806,7 @@
"type": "github"
}
],
- "time": "2023-02-03T06:56:46+00:00"
+ "time": "2023-08-31T14:07:24+00:00"
},
{
"name": "phpunit/php-timer",
@@ -1937,16 +1869,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "10.2.0",
+ "version": "10.5.38",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "3aad97fde1f3e490e2b316ba56bc4680310e3c3f"
+ "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3aad97fde1f3e490e2b316ba56bc4680310e3c3f",
- "reference": "3aad97fde1f3e490e2b316ba56bc4680310e3c3f",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
+ "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"shasum": ""
},
"require": {
@@ -1956,26 +1888,26 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.10.1",
- "phar-io/manifest": "^2.0.3",
- "phar-io/version": "^3.0.2",
+ "myclabs/deep-copy": "^1.12.0",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
"php": ">=8.1",
- "phpunit/php-code-coverage": "^10.1.1",
- "phpunit/php-file-iterator": "^4.0",
- "phpunit/php-invoker": "^4.0",
- "phpunit/php-text-template": "^3.0",
- "phpunit/php-timer": "^6.0",
- "sebastian/cli-parser": "^2.0",
- "sebastian/code-unit": "^2.0",
- "sebastian/comparator": "^5.0",
- "sebastian/diff": "^5.0",
- "sebastian/environment": "^6.0",
- "sebastian/exporter": "^5.0",
- "sebastian/global-state": "^6.0",
- "sebastian/object-enumerator": "^5.0",
- "sebastian/recursion-context": "^5.0",
- "sebastian/type": "^4.0",
- "sebastian/version": "^4.0"
+ "phpunit/php-code-coverage": "^10.1.16",
+ "phpunit/php-file-iterator": "^4.1.0",
+ "phpunit/php-invoker": "^4.0.0",
+ "phpunit/php-text-template": "^3.0.1",
+ "phpunit/php-timer": "^6.0.0",
+ "sebastian/cli-parser": "^2.0.1",
+ "sebastian/code-unit": "^2.0.0",
+ "sebastian/comparator": "^5.0.3",
+ "sebastian/diff": "^5.1.1",
+ "sebastian/environment": "^6.1.0",
+ "sebastian/exporter": "^5.1.2",
+ "sebastian/global-state": "^6.0.2",
+ "sebastian/object-enumerator": "^5.0.0",
+ "sebastian/recursion-context": "^5.0.0",
+ "sebastian/type": "^4.0.0",
+ "sebastian/version": "^4.0.1"
},
"suggest": {
"ext-soap": "To be able to generate mocks based on WSDL files"
@@ -1986,7 +1918,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "10.2-dev"
+ "dev-main": "10.5-dev"
}
},
"autoload": {
@@ -2018,7 +1950,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.0"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
},
"funding": [
{
@@ -2034,20 +1966,20 @@
"type": "tidelift"
}
],
- "time": "2023-06-02T05:42:13+00:00"
+ "time": "2024-10-28T13:06:21+00:00"
},
{
"name": "sebastian/cli-parser",
- "version": "2.0.0",
+ "version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae"
+ "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae",
- "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+ "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
"shasum": ""
},
"require": {
@@ -2082,7 +2014,8 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
- "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0"
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
},
"funding": [
{
@@ -2090,7 +2023,7 @@
"type": "github"
}
],
- "time": "2023-02-03T06:58:15+00:00"
+ "time": "2024-03-02T07:12:49+00:00"
},
{
"name": "sebastian/code-unit",
@@ -2205,16 +2138,16 @@
},
{
"name": "sebastian/comparator",
- "version": "5.0.0",
+ "version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c"
+ "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/72f01e6586e0caf6af81297897bd112eb7e9627c",
- "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
+ "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"shasum": ""
},
"require": {
@@ -2225,7 +2158,7 @@
"sebastian/exporter": "^5.0"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
@@ -2269,7 +2202,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
- "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.0"
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
},
"funding": [
{
@@ -2277,24 +2211,24 @@
"type": "github"
}
],
- "time": "2023-02-03T07:07:16+00:00"
+ "time": "2024-10-18T14:56:07+00:00"
},
{
"name": "sebastian/complexity",
- "version": "3.0.0",
+ "version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
- "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6"
+ "reference": "68ff824baeae169ec9f2137158ee529584553799"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/e67d240970c9dc7ea7b2123a6d520e334dd61dc6",
- "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
+ "reference": "68ff824baeae169ec9f2137158ee529584553799",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^4.10",
+ "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@@ -2303,7 +2237,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-main": "3.2-dev"
}
},
"autoload": {
@@ -2326,7 +2260,8 @@
"homepage": "https://github.com/sebastianbergmann/complexity",
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
- "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.0"
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
},
"funding": [
{
@@ -2334,20 +2269,20 @@
"type": "github"
}
],
- "time": "2023-02-03T06:59:47+00:00"
+ "time": "2023-12-21T08:37:17+00:00"
},
{
"name": "sebastian/diff",
- "version": "5.0.3",
+ "version": "5.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b"
+ "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
- "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
+ "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
"shasum": ""
},
"require": {
@@ -2355,12 +2290,12 @@
},
"require-dev": {
"phpunit/phpunit": "^10.0",
- "symfony/process": "^4.2 || ^5"
+ "symfony/process": "^6.4"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.0-dev"
+ "dev-main": "5.1-dev"
}
},
"autoload": {
@@ -2393,7 +2328,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
- "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3"
+ "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
},
"funding": [
{
@@ -2401,20 +2336,20 @@
"type": "github"
}
],
- "time": "2023-05-01T07:48:21+00:00"
+ "time": "2024-03-02T07:15:17+00:00"
},
{
"name": "sebastian/environment",
- "version": "6.0.1",
+ "version": "6.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951"
+ "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951",
- "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
+ "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
"shasum": ""
},
"require": {
@@ -2429,7 +2364,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "6.0-dev"
+ "dev-main": "6.1-dev"
}
},
"autoload": {
@@ -2457,7 +2392,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/6.0.1"
+ "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
},
"funding": [
{
@@ -2465,20 +2400,20 @@
"type": "github"
}
],
- "time": "2023-04-11T05:39:26+00:00"
+ "time": "2024-03-23T08:47:14+00:00"
},
{
"name": "sebastian/exporter",
- "version": "5.0.0",
+ "version": "5.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0"
+ "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0",
- "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
+ "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
"shasum": ""
},
"require": {
@@ -2492,7 +2427,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.0-dev"
+ "dev-main": "5.1-dev"
}
},
"autoload": {
@@ -2534,7 +2469,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.0"
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
},
"funding": [
{
@@ -2542,20 +2478,20 @@
"type": "github"
}
],
- "time": "2023-02-03T07:06:49+00:00"
+ "time": "2024-03-02T07:17:12+00:00"
},
{
"name": "sebastian/global-state",
- "version": "6.0.0",
+ "version": "6.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "aab257c712de87b90194febd52e4d184551c2d44"
+ "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44",
- "reference": "aab257c712de87b90194febd52e4d184551c2d44",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+ "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
"shasum": ""
},
"require": {
@@ -2589,13 +2525,14 @@
}
],
"description": "Snapshotting of global state",
- "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
- "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0"
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
},
"funding": [
{
@@ -2603,24 +2540,24 @@
"type": "github"
}
],
- "time": "2023-02-03T07:07:38+00:00"
+ "time": "2024-03-02T07:19:19+00:00"
},
{
"name": "sebastian/lines-of-code",
- "version": "2.0.0",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
- "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130"
+ "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/17c4d940ecafb3d15d2cf916f4108f664e28b130",
- "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
+ "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^4.10",
+ "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@@ -2652,7 +2589,8 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
- "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.0"
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
},
"funding": [
{
@@ -2660,7 +2598,7 @@
"type": "github"
}
],
- "time": "2023-02-03T07:08:02+00:00"
+ "time": "2023-12-21T08:38:20+00:00"
},
{
"name": "sebastian/object-enumerator",
@@ -2948,16 +2886,16 @@
},
{
"name": "theseer/tokenizer",
- "version": "1.2.1",
+ "version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
- "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
- "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"shasum": ""
},
"require": {
@@ -2986,7 +2924,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
- "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
},
"funding": [
{
@@ -2994,7 +2932,7 @@
"type": "github"
}
],
- "time": "2021-07-28T10:34:58+00:00"
+ "time": "2024-03-03T12:36:25+00:00"
}
],
"aliases": [],
@@ -3020,7 +2958,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
- "ext-pmmpthread": "^6.0.1",
+ "ext-pmmpthread": "^6.1.0",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@@ -3034,5 +2972,5 @@
"platform-overrides": {
"php": "8.1.0"
},
- "plugin-api-version": "2.3.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/install-local-protocol.sh b/install-local-protocol.sh
new file mode 100644
index 00000000000..0f25a039005
--- /dev/null
+++ b/install-local-protocol.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+echo "--- Installing BedrockProtocol, BedrockData, BedrockBlockUpgradeSchema, BedrockItemUpgradeSchema dependencies from local repositories."
+echo "--- This allows you to perform integration tests using PocketMine-MP, without immediately publishing new versions of these libraries."
+
+cp composer.json composer-local-protocol.json
+cp composer.lock composer-local-protocol.lock
+
+export COMPOSER=composer-local-protocol.json
+composer config repositories.bedrock-protocol path ../deps/BedrockProtocol
+composer config repositories.bedrock-data path ../deps/BedrockData
+composer config repositories.bedrock-block-upgrade-schema path ../deps/BedrockBlockUpgradeSchema
+composer config repositories.bedrock-item-upgrade-schema path ../deps/BedrockItemUpgradeSchema
+
+composer require pocketmine/bedrock-protocol:*@dev pocketmine/bedrock-data:*@dev pocketmine/bedrock-block-upgrade-schema:*@dev pocketmine/bedrock-item-upgrade-schema:*@dev
+
+composer install
+
+echo "--- Local dependencies have been successfully installed."
+echo "--- This script does not modify composer.json. To go back to the original dependency versions, simply run 'composer install'."
+
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index b3aeaf4f6dc..6e857865251 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -10,7 +10,9 @@ includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
rules:
+ - pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
+ - pocketmine\phpstan\rules\DisallowForeachByReferenceRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
@@ -38,16 +40,14 @@ parameters:
- build/php
dynamicConstantNames:
- pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD
+ - pocketmine\VersionInfo::BUILD_CHANNEL
- pocketmine\DEBUG
- pocketmine\IS_DEVELOPMENT_BUILD
stubFiles:
- tests/phpstan/stubs/JsonMapper.stub
- tests/phpstan/stubs/leveldb.stub
- - tests/phpstan/stubs/phpasn1.stub
- tests/phpstan/stubs/pmmpthread.stub
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
- staticReflectionClassNamePatterns:
- - "#^COM$#"
typeAliases:
#variadics don't work for this - mixed probably shouldn't work either, but for now it does
#what we actually need is something that accepts an infinite number of parameters, but in the absence of that,
diff --git a/src/BootstrapOptions.php b/src/BootstrapOptions.php
new file mode 100644
index 00000000000..6a146962523
--- /dev/null
+++ b/src/BootstrapOptions.php
@@ -0,0 +1,52 @@
+memoryLimit = $config->getPropertyInt("memory.main-limit", 0) * 1024 * 1024;
+ $this->memoryLimit = $config->getPropertyInt(Yml::MEMORY_MAIN_LIMIT, 0) * 1024 * 1024;
$defaultMemory = 1024;
@@ -127,7 +128,7 @@ private function init(ServerConfigGroup $config) : void{
}
}
- $hardLimit = $config->getPropertyInt("memory.main-hard-limit", $defaultMemory);
+ $hardLimit = $config->getPropertyInt(Yml::MEMORY_MAIN_HARD_LIMIT, $defaultMemory);
if($hardLimit <= 0){
ini_set("memory_limit", '-1');
@@ -135,22 +136,22 @@ private function init(ServerConfigGroup $config) : void{
ini_set("memory_limit", $hardLimit . "M");
}
- $this->globalMemoryLimit = $config->getPropertyInt("memory.global-limit", 0) * 1024 * 1024;
- $this->checkRate = $config->getPropertyInt("memory.check-rate", self::DEFAULT_CHECK_RATE);
- $this->continuousTrigger = $config->getPropertyBool("memory.continuous-trigger", true);
- $this->continuousTriggerRate = $config->getPropertyInt("memory.continuous-trigger-rate", self::DEFAULT_CONTINUOUS_TRIGGER_RATE);
+ $this->globalMemoryLimit = $config->getPropertyInt(Yml::MEMORY_GLOBAL_LIMIT, 0) * 1024 * 1024;
+ $this->checkRate = $config->getPropertyInt(Yml::MEMORY_CHECK_RATE, self::DEFAULT_CHECK_RATE);
+ $this->continuousTrigger = $config->getPropertyBool(Yml::MEMORY_CONTINUOUS_TRIGGER, true);
+ $this->continuousTriggerRate = $config->getPropertyInt(Yml::MEMORY_CONTINUOUS_TRIGGER_RATE, self::DEFAULT_CONTINUOUS_TRIGGER_RATE);
- $this->garbageCollectionPeriod = $config->getPropertyInt("memory.garbage-collection.period", self::DEFAULT_TICKS_PER_GC);
- $this->garbageCollectionTrigger = $config->getPropertyBool("memory.garbage-collection.low-memory-trigger", true);
- $this->garbageCollectionAsync = $config->getPropertyBool("memory.garbage-collection.collect-async-worker", true);
+ $this->garbageCollectionPeriod = $config->getPropertyInt(Yml::MEMORY_GARBAGE_COLLECTION_PERIOD, self::DEFAULT_TICKS_PER_GC);
+ $this->garbageCollectionTrigger = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER, true);
+ $this->garbageCollectionAsync = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER, true);
- $this->lowMemChunkRadiusOverride = $config->getPropertyInt("memory.max-chunks.chunk-radius", 4);
- $this->lowMemChunkGC = $config->getPropertyBool("memory.max-chunks.trigger-chunk-collect", true);
+ $this->lowMemChunkRadiusOverride = $config->getPropertyInt(Yml::MEMORY_MAX_CHUNKS_CHUNK_RADIUS, 4);
+ $this->lowMemChunkGC = $config->getPropertyBool(Yml::MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT, true);
- $this->lowMemDisableChunkCache = $config->getPropertyBool("memory.world-caches.disable-chunk-cache", true);
- $this->lowMemClearWorldCache = $config->getPropertyBool("memory.world-caches.low-memory-trigger", true);
+ $this->lowMemDisableChunkCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE, true);
+ $this->lowMemClearWorldCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER, true);
- $this->dumpWorkers = $config->getPropertyBool("memory.memory-dump.dump-async-worker", true);
+ $this->dumpWorkers = $config->getPropertyBool(Yml::MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER, true);
gc_enable();
}
@@ -359,7 +360,7 @@ public static function dumpMemory(mixed $startingObject, string $outputFolder, i
'_SESSION' => true
];
- foreach(Utils::stringifyKeys($GLOBALS) as $varName => $value){
+ foreach($GLOBALS as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
diff --git a/src/PocketMine.php b/src/PocketMine.php
index d5d2f732800..b2e1cd04695 100644
--- a/src/PocketMine.php
+++ b/src/PocketMine.php
@@ -25,6 +25,7 @@
use Composer\InstalledVersions;
use pocketmine\errorhandler\ErrorToExceptionHandler;
+ use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\thread\ThreadManager;
use pocketmine\thread\ThreadSafeClassLoader;
use pocketmine\utils\Filesystem;
@@ -40,14 +41,17 @@
use function extension_loaded;
use function function_exists;
use function getcwd;
+ use function getopt;
use function is_dir;
use function mkdir;
use function phpversion;
use function preg_match;
use function preg_quote;
+ use function printf;
use function realpath;
use function version_compare;
use const DIRECTORY_SEPARATOR;
+ use const PHP_EOL;
require_once __DIR__ . '/VersionInfo.php';
@@ -120,8 +124,8 @@ function check_platform_dependencies(){
}
if(($pmmpthread_version = phpversion("pmmpthread")) !== false){
- if(version_compare($pmmpthread_version, "6.0.1") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
- $messages[] = "pmmpthread ^6.0.1 is required, while you have $pmmpthread_version.";
+ if(version_compare($pmmpthread_version, "6.1.0") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
+ $messages[] = "pmmpthread ^6.1.0 is required, while you have $pmmpthread_version.";
}
}
@@ -144,6 +148,13 @@ function check_platform_dependencies(){
$messages[] = "chunkutils2 ^$wantedVersionMin is required, while you have $chunkutils2_version.";
}
+ if(($libdeflate_version = phpversion("libdeflate")) !== false){
+ //make sure level 0 compression is available
+ if(version_compare($libdeflate_version, "0.2.0") < 0 || version_compare($libdeflate_version, "0.3.0") >= 0){
+ $messages[] = "php-libdeflate ^0.2.0 is required, while you have $libdeflate_version.";
+ }
+ }
+
if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported.";
}
@@ -159,7 +170,7 @@ function check_platform_dependencies(){
* @return void
*/
function emit_performance_warnings(\Logger $logger){
- if(PHP_DEBUG !== 0){
+ if(ZEND_DEBUG_BUILD){
$logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
}
if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){
@@ -266,9 +277,14 @@ function server(){
ErrorToExceptionHandler::set();
+ if(count(getopt("", [BootstrapOptions::VERSION])) > 0){
+ printf("%s %s (git hash %s) for Minecraft: Bedrock Edition %s\n", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true), VersionInfo::GIT_HASH(), ProtocolInfo::MINECRAFT_VERSION);
+ exit(0);
+ }
+
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
- $dataPath = getopt_string("data") ?? $cwd;
- $pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
+ $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
+ $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
@@ -301,23 +317,28 @@ function server(){
//Logger has a dependency on timezone
Timezone::init();
- $opts = getopt("", ["no-wizard", "enable-ansi", "disable-ansi"]);
- if(isset($opts["enable-ansi"])){
+ $opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI, BootstrapOptions::NO_LOG_FILE]);
+ if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
Terminal::init(true);
- }elseif(isset($opts["disable-ansi"])){
+ }elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
Terminal::init(false);
}else{
Terminal::init();
}
+ $logFile = isset($opts[BootstrapOptions::NO_LOG_FILE]) ? null : Path::join($dataPath, "server.log");
+
+ $logger = new MainLogger($logFile, Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()), false, Path::join($dataPath, "log_archive"));
+ if($logFile === null){
+ $logger->notice("Logging to file disabled. Ensure logs are collected by other means (e.g. Docker logs).");
+ }
- $logger = new MainLogger(Path::join($dataPath, "server.log"), Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()));
\GlobalLogger::set($logger);
emit_performance_warnings($logger);
$exitCode = 0;
do{
- if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts["no-wizard"])){
+ if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts[BootstrapOptions::NO_WIZARD])){
$installer = new SetupWizard($dataPath);
if(!$installer->run()){
$exitCode = -1;
@@ -341,7 +362,7 @@ function server(){
if(ThreadManager::getInstance()->stopAll() > 0){
$logger->debug("Some threads could not be stopped, performing a force-kill");
- Process::kill(Process::pid(), true);
+ Process::kill(Process::pid());
}
}while(false);
diff --git a/src/Server.php b/src/Server.php
index f604c93d7eb..a34349bb5cd 100644
--- a/src/Server.php
+++ b/src/Server.php
@@ -59,7 +59,7 @@
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\PacketBroadcaster;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
+use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
use pocketmine\network\mcpe\raklib\RakLibInterface;
use pocketmine\network\mcpe\StandardEntityEventBroadcaster;
use pocketmine\network\mcpe\StandardPacketBroadcaster;
@@ -80,7 +80,6 @@
use pocketmine\player\PlayerDataSaveException;
use pocketmine\player\PlayerInfo;
use pocketmine\plugin\PharPluginLoader;
-use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginEnableOrder;
use pocketmine\plugin\PluginGraylist;
use pocketmine\plugin\PluginManager;
@@ -93,6 +92,7 @@
use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask;
use pocketmine\thread\log\AttachableThreadSafeLogger;
+use pocketmine\thread\ThreadCrashException;
use pocketmine\thread\ThreadSafeClassLoader;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
@@ -119,11 +119,13 @@
use pocketmine\world\World;
use pocketmine\world\WorldCreationOptions;
use pocketmine\world\WorldManager;
+use pocketmine\YmlServerProperties as Yml;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Filesystem\Path;
use function array_fill;
use function array_sum;
use function base64_encode;
+use function chr;
use function cli_set_process_title;
use function copy;
use function count;
@@ -356,15 +358,15 @@ public function requiresAuthentication() : bool{
}
public function getPort() : int{
- return $this->configGroup->getConfigInt("server-port", self::DEFAULT_PORT_IPV4);
+ return $this->configGroup->getConfigInt(ServerProperties::SERVER_PORT_IPV4, self::DEFAULT_PORT_IPV4);
}
public function getPortV6() : int{
- return $this->configGroup->getConfigInt("server-portv6", self::DEFAULT_PORT_IPV6);
+ return $this->configGroup->getConfigInt(ServerProperties::SERVER_PORT_IPV6, self::DEFAULT_PORT_IPV6);
}
public function getViewDistance() : int{
- return max(2, $this->configGroup->getConfigInt("view-distance", self::DEFAULT_MAX_VIEW_DISTANCE));
+ return max(2, $this->configGroup->getConfigInt(ServerProperties::VIEW_DISTANCE, self::DEFAULT_MAX_VIEW_DISTANCE));
}
/**
@@ -375,12 +377,12 @@ public function getAllowedViewDistance(int $distance) : int{
}
public function getIp() : string{
- $str = $this->configGroup->getConfigString("server-ip");
+ $str = $this->configGroup->getConfigString(ServerProperties::SERVER_IPV4);
return $str !== "" ? $str : "0.0.0.0";
}
public function getIpV6() : string{
- $str = $this->configGroup->getConfigString("server-ipv6");
+ $str = $this->configGroup->getConfigString(ServerProperties::SERVER_IPV6);
return $str !== "" ? $str : "::";
}
@@ -389,30 +391,30 @@ public function getServerUniqueId() : UuidInterface{
}
public function getGamemode() : GameMode{
- return GameMode::fromString($this->configGroup->getConfigString("gamemode", GameMode::SURVIVAL()->name())) ?? GameMode::SURVIVAL();
+ return GameMode::fromString($this->configGroup->getConfigString(ServerProperties::GAME_MODE)) ?? GameMode::SURVIVAL;
}
public function getForceGamemode() : bool{
- return $this->configGroup->getConfigBool("force-gamemode", false);
+ return $this->configGroup->getConfigBool(ServerProperties::FORCE_GAME_MODE, false);
}
/**
* Returns Server global difficulty. Note that this may be overridden in individual worlds.
*/
public function getDifficulty() : int{
- return $this->configGroup->getConfigInt("difficulty", World::DIFFICULTY_NORMAL);
+ return $this->configGroup->getConfigInt(ServerProperties::DIFFICULTY, World::DIFFICULTY_NORMAL);
}
public function hasWhitelist() : bool{
- return $this->configGroup->getConfigBool("white-list", false);
+ return $this->configGroup->getConfigBool(ServerProperties::WHITELIST, false);
}
public function isHardcore() : bool{
- return $this->configGroup->getConfigBool("hardcore", false);
+ return $this->configGroup->getConfigBool(ServerProperties::HARDCORE, false);
}
public function getMotd() : string{
- return $this->configGroup->getConfigString("motd", self::DEFAULT_SERVER_NAME);
+ return $this->configGroup->getConfigString(ServerProperties::MOTD, self::DEFAULT_SERVER_NAME);
}
public function getLoader() : ThreadSafeClassLoader{
@@ -495,7 +497,7 @@ public function getOnlinePlayers() : array{
}
public function shouldSavePlayerData() : bool{
- return $this->configGroup->getPropertyBool("player.save-player-data", true);
+ return $this->configGroup->getPropertyBool(Yml::PLAYER_SAVE_PLAYER_DATA, true);
}
public function getOfflinePlayer(string $name) : Player|OfflinePlayer|null{
@@ -522,7 +524,7 @@ public function getOfflinePlayerData(string $name) : ?CompoundTag{
return $this->playerDataProvider->loadData($name);
}catch(PlayerDataLoadException $e){
$this->logger->debug("Failed to load player data for $name: " . $e->getMessage());
- $this->logger->error($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
+ $this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
return null;
}
});
@@ -541,7 +543,7 @@ public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag) : void{
try{
$this->playerDataProvider->saveData($name, $ev->getSaveData());
}catch(PlayerDataSaveException $e){
- $this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
+ $this->logger->critical($this->language->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
$this->logger->logException($e);
}
});
@@ -569,6 +571,7 @@ public function createPlayer(NetworkSession $session, PlayerInfo $playerInfo, bo
$playerPromiseResolver = new PromiseResolver();
$createPlayer = function(Location $location) use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $offlinePlayerData) : void{
+ /** @see Player::__construct() */
$player = new $class($this, $session, $playerInfo, $authenticated, $location, $offlinePlayerData);
if(!$player->hasPlayedBefore()){
$player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move
@@ -735,7 +738,7 @@ public function getOps() : Config{
* @return string[][]
*/
public function getCommandAliases() : array{
- $section = $this->configGroup->getProperty("aliases");
+ $section = $this->configGroup->getProperty(Yml::ALIASES);
$result = [];
if(is_array($section)){
foreach($section as $key => $value){
@@ -810,36 +813,36 @@ public function __construct(
$this->configGroup = new ServerConfigGroup(
new Config($pocketmineYmlPath, Config::YAML, []),
new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [
- "motd" => self::DEFAULT_SERVER_NAME,
- "server-port" => self::DEFAULT_PORT_IPV4,
- "server-portv6" => self::DEFAULT_PORT_IPV6,
- "enable-ipv6" => true,
- "white-list" => false,
- "max-players" => self::DEFAULT_MAX_PLAYERS,
- "gamemode" => GameMode::SURVIVAL()->name(),
- "force-gamemode" => false,
- "hardcore" => false,
- "pvp" => true,
- "difficulty" => World::DIFFICULTY_NORMAL,
- "generator-settings" => "",
- "level-name" => "world",
- "level-seed" => "",
- "level-type" => "DEFAULT",
- "enable-query" => true,
- "auto-save" => true,
- "view-distance" => self::DEFAULT_MAX_VIEW_DISTANCE,
- "xbox-auth" => true,
- "language" => "eng"
+ ServerProperties::MOTD => self::DEFAULT_SERVER_NAME,
+ ServerProperties::SERVER_PORT_IPV4 => self::DEFAULT_PORT_IPV4,
+ ServerProperties::SERVER_PORT_IPV6 => self::DEFAULT_PORT_IPV6,
+ ServerProperties::ENABLE_IPV6 => true,
+ ServerProperties::WHITELIST => false,
+ ServerProperties::MAX_PLAYERS => self::DEFAULT_MAX_PLAYERS,
+ ServerProperties::GAME_MODE => GameMode::SURVIVAL->name, //TODO: this probably shouldn't use the enum name directly
+ ServerProperties::FORCE_GAME_MODE => false,
+ ServerProperties::HARDCORE => false,
+ ServerProperties::PVP => true,
+ ServerProperties::DIFFICULTY => World::DIFFICULTY_NORMAL,
+ ServerProperties::DEFAULT_WORLD_GENERATOR_SETTINGS => "",
+ ServerProperties::DEFAULT_WORLD_NAME => "world",
+ ServerProperties::DEFAULT_WORLD_SEED => "",
+ ServerProperties::DEFAULT_WORLD_GENERATOR => "DEFAULT",
+ ServerProperties::ENABLE_QUERY => true,
+ ServerProperties::AUTO_SAVE => true,
+ ServerProperties::VIEW_DISTANCE => self::DEFAULT_MAX_VIEW_DISTANCE,
+ ServerProperties::XBOX_AUTH => true,
+ ServerProperties::LANGUAGE => "eng"
])
);
- $debugLogLevel = $this->configGroup->getPropertyInt("debug.level", 1);
+ $debugLogLevel = $this->configGroup->getPropertyInt(Yml::DEBUG_LEVEL, 1);
if($this->logger instanceof MainLogger){
$this->logger->setLogDebug($debugLogLevel > 1);
}
- $this->forceLanguage = $this->configGroup->getPropertyBool("settings.force-language", false);
- $selectedLang = $this->configGroup->getConfigString("language", $this->configGroup->getPropertyString("settings.language", Language::FALLBACK_LANGUAGE));
+ $this->forceLanguage = $this->configGroup->getPropertyBool(Yml::SETTINGS_FORCE_LANGUAGE, false);
+ $selectedLang = $this->configGroup->getConfigString(ServerProperties::LANGUAGE, $this->configGroup->getPropertyString("settings.language", Language::FALLBACK_LANGUAGE));
try{
$this->language = new Language($selectedLang);
}catch(LanguageNotFoundException $e){
@@ -852,14 +855,14 @@ public function __construct(
}
}
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::language_selected($this->getLanguage()->getName(), $this->getLanguage()->getLang())));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::language_selected($this->language->getName(), $this->language->getLang())));
if(VersionInfo::IS_DEVELOPMENT_BUILD){
- if(!$this->configGroup->getPropertyBool("settings.enable-dev-builds", false)){
+ if(!$this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_DEV_BUILDS, false)){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error1(VersionInfo::NAME)));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error2()));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error3()));
- $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error4("settings.enable-dev-builds")));
+ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error4(Yml::SETTINGS_ENABLE_DEV_BUILDS)));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error5("https://github.com/pmmp/PocketMine-MP/releases")));
$this->forceShutdownExit();
@@ -875,9 +878,9 @@ public function __construct(
$this->memoryManager = new MemoryManager($this);
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_start(TextFormat::AQUA . $this->getVersion() . TextFormat::RESET)));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_start(TextFormat::AQUA . $this->getVersion() . TextFormat::RESET)));
- if(($poolSize = $this->configGroup->getPropertyString("settings.async-workers", "auto")) === "auto"){
+ if(($poolSize = $this->configGroup->getPropertyString(Yml::SETTINGS_ASYNC_WORKERS, "auto")) === "auto"){
$poolSize = 2;
$processors = Utils::getCoreCount() - 2;
@@ -888,32 +891,32 @@ public function __construct(
$poolSize = max(1, (int) $poolSize);
}
- $this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt("memory.async-worker-hard-limit", 256)), $this->autoloader, $this->logger, $this->tickSleeper);
+ $this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
$netCompressionThreshold = -1;
- if($this->configGroup->getPropertyInt("network.batch-threshold", 256) >= 0){
- $netCompressionThreshold = $this->configGroup->getPropertyInt("network.batch-threshold", 256);
+ if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
+ $netCompressionThreshold = $this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256);
}
if($netCompressionThreshold < 0){
$netCompressionThreshold = null;
}
- $netCompressionLevel = $this->configGroup->getPropertyInt("network.compression-level", 6);
+ $netCompressionLevel = $this->configGroup->getPropertyInt(Yml::NETWORK_COMPRESSION_LEVEL, 6);
if($netCompressionLevel < 1 || $netCompressionLevel > 9){
$this->logger->warning("Invalid network compression level $netCompressionLevel set, setting to default 6");
$netCompressionLevel = 6;
}
ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
- $this->networkCompressionAsync = $this->configGroup->getPropertyBool("network.async-compression", true);
+ $this->networkCompressionAsync = $this->configGroup->getPropertyBool(Yml::NETWORK_ASYNC_COMPRESSION, true);
$this->networkCompressionAsyncThreshold = max(
- $this->configGroup->getPropertyInt("network.async-compression-threshold", self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
+ $this->configGroup->getPropertyInt(Yml::NETWORK_ASYNC_COMPRESSION_THRESHOLD, self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
$netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD
);
- EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool("network.enable-encryption", true);
+ EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool(Yml::NETWORK_ENABLE_ENCRYPTION, true);
- $this->doTitleTick = $this->configGroup->getPropertyBool("console.title-tick", true) && Terminal::hasFormattingCodes();
+ $this->doTitleTick = $this->configGroup->getPropertyBool(Yml::CONSOLE_TITLE_TICK, true) && Terminal::hasFormattingCodes();
$this->operators = new Config(Path::join($this->dataPath, "ops.txt"), Config::ENUM);
$this->whitelist = new Config(Path::join($this->dataPath, "white-list.txt"), Config::ENUM);
@@ -931,39 +934,39 @@ public function __construct(
$this->banByIP = new BanList($bannedIpsTxt);
$this->banByIP->load();
- $this->maxPlayers = $this->configGroup->getConfigInt("max-players", self::DEFAULT_MAX_PLAYERS);
+ $this->maxPlayers = $this->configGroup->getConfigInt(ServerProperties::MAX_PLAYERS, self::DEFAULT_MAX_PLAYERS);
- $this->onlineMode = $this->configGroup->getConfigBool("xbox-auth", true);
+ $this->onlineMode = $this->configGroup->getConfigBool(ServerProperties::XBOX_AUTH, true);
if($this->onlineMode){
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_auth_enabled()));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_auth_enabled()));
}else{
- $this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_auth_disabled()));
- $this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_authWarning()));
- $this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled()));
+ $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_auth_disabled()));
+ $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authWarning()));
+ $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled()));
}
- if($this->configGroup->getConfigBool("hardcore", false) && $this->getDifficulty() < World::DIFFICULTY_HARD){
- $this->configGroup->setConfigInt("difficulty", World::DIFFICULTY_HARD);
+ if($this->configGroup->getConfigBool(ServerProperties::HARDCORE, false) && $this->getDifficulty() < World::DIFFICULTY_HARD){
+ $this->configGroup->setConfigInt(ServerProperties::DIFFICULTY, World::DIFFICULTY_HARD);
}
@cli_set_process_title($this->getName() . " " . $this->getPocketMineVersion());
$this->serverID = Utils::getMachineUniqueId($this->getIp() . $this->getPort());
- $this->getLogger()->debug("Server unique id: " . $this->getServerUniqueId());
- $this->getLogger()->debug("Machine unique id: " . Utils::getMachineUniqueId());
+ $this->logger->debug("Server unique id: " . $this->getServerUniqueId());
+ $this->logger->debug("Machine unique id: " . Utils::getMachineUniqueId());
$this->network = new Network($this->logger);
$this->network->setName($this->getMotd());
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_info(
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_info(
$this->getName(),
(VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : "") . $this->getPocketMineVersion() . TextFormat::RESET
)));
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
- TimingsHandler::setEnabled($this->configGroup->getPropertyBool("settings.enable-profiling", false));
- $this->profilingTickRate = $this->configGroup->getPropertyInt("settings.profile-report-trigger", self::TARGET_TICKS_PER_SECOND);
+ 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();
@@ -971,7 +974,7 @@ public function __construct(
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes"));
- $this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger);
+ $this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger);
$pluginGraylist = null;
$graylistFile = Path::join($this->dataPath, "plugin_list.yml");
@@ -985,13 +988,13 @@ public function __construct(
$this->forceShutdownExit();
return;
}
- $this->pluginManager = new PluginManager($this, $this->configGroup->getPropertyBool("plugins.legacy-data-dir", true) ? null : Path::join($this->getDataPath(), "plugin_data"), $pluginGraylist);
+ $this->pluginManager = new PluginManager($this, $this->configGroup->getPropertyBool(Yml::PLUGINS_LEGACY_DATA_DIR, true) ? null : Path::join($this->dataPath, "plugin_data"), $pluginGraylist);
$this->pluginManager->registerInterface(new PharPluginLoader($this->autoloader));
$this->pluginManager->registerInterface(new ScriptPluginLoader());
$providerManager = new WorldProviderManager();
if(
- ($format = $providerManager->getProviderByName($formatName = $this->configGroup->getPropertyString("level-settings.default-format", ""))) !== null &&
+ ($format = $providerManager->getProviderByName($formatName = $this->configGroup->getPropertyString(Yml::LEVEL_SETTINGS_DEFAULT_FORMAT, ""))) !== null &&
$format instanceof WritableWorldProviderManagerEntry
){
$providerManager->setDefault($format);
@@ -1000,16 +1003,16 @@ public function __construct(
}
$this->worldManager = new WorldManager($this, Path::join($this->dataPath, "worlds"), $providerManager);
- $this->worldManager->setAutoSave($this->configGroup->getConfigBool("auto-save", $this->worldManager->getAutoSave()));
- $this->worldManager->setAutoSaveInterval($this->configGroup->getPropertyInt("ticks-per.autosave", $this->worldManager->getAutoSaveInterval()));
+ $this->worldManager->setAutoSave($this->configGroup->getConfigBool(ServerProperties::AUTO_SAVE, $this->worldManager->getAutoSave()));
+ $this->worldManager->setAutoSaveInterval($this->configGroup->getPropertyInt(Yml::TICKS_PER_AUTOSAVE, $this->worldManager->getAutoSaveInterval()));
- $this->updater = new UpdateChecker($this, $this->configGroup->getPropertyString("auto-updater.host", "update.pmmp.io"));
+ $this->updater = new UpdateChecker($this, $this->configGroup->getPropertyString(Yml::AUTO_UPDATER_HOST, "update.pmmp.io"));
$this->queryInfo = new QueryInfo($this);
$this->playerDataProvider = new DatFilePlayerDataProvider(Path::join($this->dataPath, "players"));
- register_shutdown_function([$this, "crashDump"]);
+ register_shutdown_function($this->crashDump(...));
$loadErrorCount = 0;
$this->pluginManager->loadPlugins($this->pluginPath, $loadErrorCount);
@@ -1018,7 +1021,7 @@ public function __construct(
$this->forceShutdownExit();
return;
}
- if(!$this->enablePlugins(PluginEnableOrder::STARTUP())){
+ if(!$this->enablePlugins(PluginEnableOrder::STARTUP)){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someEnableErrors()));
$this->forceShutdownExit();
return;
@@ -1029,7 +1032,7 @@ public function __construct(
return;
}
- if(!$this->enablePlugins(PluginEnableOrder::POSTWORLD())){
+ if(!$this->enablePlugins(PluginEnableOrder::POSTWORLD)){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someEnableErrors()));
$this->forceShutdownExit();
return;
@@ -1040,23 +1043,23 @@ public function __construct(
return;
}
- if($this->configGroup->getPropertyBool("anonymous-statistics.enabled", true)){
+ if($this->configGroup->getPropertyBool(Yml::ANONYMOUS_STATISTICS_ENABLED, true)){
$this->sendUsageTicker = self::TICKS_PER_STATS_REPORT;
$this->sendUsage(SendUsageTask::TYPE_OPEN);
}
$this->configGroup->save();
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder);
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder);
//TODO: move console parts to a separate component
- if($this->configGroup->getPropertyBool("console.enable-input", true)){
+ if($this->configGroup->getPropertyBool(Yml::CONSOLE_ENABLE_INPUT, true)){
$this->console = new ConsoleReaderChildProcessDaemon($this->logger);
}
@@ -1091,7 +1094,7 @@ private function startupPrepareWorlds() : bool{
$anyWorldFailedToLoad = false;
- foreach((array) $this->configGroup->getProperty("worlds", []) as $name => $options){
+ foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){
if($options === null){
$options = [];
}elseif(!is_array($options)){
@@ -1135,30 +1138,30 @@ private function startupPrepareWorlds() : bool{
}
if($this->worldManager->getDefaultWorld() === null){
- $default = $this->configGroup->getConfigString("level-name", "world");
+ $default = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world");
if(trim($default) == ""){
- $this->getLogger()->warning("level-name cannot be null, using default");
+ $this->logger->warning("level-name cannot be null, using default");
$default = "world";
- $this->configGroup->setConfigString("level-name", "world");
+ $this->configGroup->setConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world");
}
if(!$this->worldManager->loadWorld($default, true)){
if($this->worldManager->isWorldGenerated($default)){
- $this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
+ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
return false;
}
- $generatorName = $this->configGroup->getConfigString("level-type");
- $generatorOptions = $this->configGroup->getConfigString("generator-settings");
+ $generatorName = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_GENERATOR);
+ $generatorOptions = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_GENERATOR_SETTINGS);
$generatorClass = $getGenerator($generatorName, $generatorOptions, $default);
if($generatorClass === null){
- $this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
+ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
return false;
}
$creationOptions = WorldCreationOptions::create()
->setGeneratorClass($generatorClass)
->setGeneratorOptions($generatorOptions);
- $convertedSeed = Generator::convertSeed($this->configGroup->getConfigString("level-seed"));
+ $convertedSeed = Generator::convertSeed($this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_SEED));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
@@ -1183,12 +1186,11 @@ private function startupPrepareConnectableNetworkInterfaces(
bool $useQuery,
PacketBroadcaster $packetBroadcaster,
EntityEventBroadcaster $entityEventBroadcaster,
- PacketSerializerContext $packetSerializerContext,
TypeConverter $typeConverter
) : bool{
$prettyIp = $ipV6 ? "[$ip]" : $ip;
try{
- $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter));
+ $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $typeConverter));
}catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$ip,
@@ -1198,7 +1200,7 @@ private function startupPrepareConnectableNetworkInterfaces(
return false;
}
if($rakLibRegistered){
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port)));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port)));
}
if($useQuery){
if(!$rakLibRegistered){
@@ -1206,24 +1208,23 @@ private function startupPrepareConnectableNetworkInterfaces(
//if it's not registered we need to make sure Query still works
$this->network->registerInterface(new DedicatedQueryNetworkInterface($ip, $port, $ipV6, new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
}
- $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (string) $port)));
+ $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (string) $port)));
}
return true;
}
private function startupPrepareNetworkInterfaces() : bool{
- $useQuery = $this->configGroup->getConfigBool("enable-query", true);
+ $useQuery = $this->configGroup->getConfigBool(ServerProperties::ENABLE_QUERY, true);
$typeConverter = TypeConverter::getInstance();
- $packetSerializerContext = new PacketSerializerContext($typeConverter->getItemTypeDictionary());
- $packetBroadcaster = new StandardPacketBroadcaster($this, $packetSerializerContext);
+ $packetBroadcaster = new StandardPacketBroadcaster($this);
$entityEventBroadcaster = new StandardEntityEventBroadcaster($packetBroadcaster, $typeConverter);
if(
- !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter) ||
+ !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter) ||
(
- $this->configGroup->getConfigBool("enable-ipv6", true) &&
- !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter)
+ $this->configGroup->getConfigBool(ServerProperties::ENABLE_IPV6, true) &&
+ !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter)
)
){
return false;
@@ -1237,7 +1238,7 @@ private function startupPrepareNetworkInterfaces() : bool{
$this->network->blockAddress($entry->getName(), -1);
}
- if($this->configGroup->getPropertyBool("network.upnp-forwarding", false)){
+ if($this->configGroup->getPropertyBool(Yml::NETWORK_UPNP_FORWARDING, false)){
$this->network->registerInterface(new UPnPNetworkInterface($this->logger, Internet::getInternalIP(), $this->getPort()));
}
@@ -1257,9 +1258,10 @@ public function subscribeToBroadcastChannel(string $channelId, CommandSender $su
*/
public function unsubscribeFromBroadcastChannel(string $channelId, CommandSender $subscriber) : void{
if(isset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)])){
- unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]);
- if(count($this->broadcastSubscribers[$channelId]) === 0){
+ if(count($this->broadcastSubscribers[$channelId]) === 1){
unset($this->broadcastSubscribers[$channelId]);
+ }else{
+ unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]);
}
}
}
@@ -1353,29 +1355,43 @@ public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn
}
/**
- * Broadcasts a list of packets in a batch to a list of players
+ * @internal
+ * Promises to compress the given batch buffer using the selected compressor, optionally on a separate thread.
+ *
+ * If the buffer is smaller than the batch-threshold (usually 256), the buffer will be compressed at level 0 if supported
+ * by the compressor. This means that the payload will be wrapped with the appropriate header and footer, but not
+ * actually compressed.
+ *
+ * If the buffer is larger than the async-compression-threshold (usually 10,000), the buffer may be compressed in
+ * a separate thread (if available).
*
* @param bool|null $sync Compression on the main thread (true) or workers (false). Default is automatic (null).
*/
- public function prepareBatch(string $buffer, Compressor $compressor, ?bool $sync = null, ?TimingsHandler $timings = null) : CompressBatchPromise{
+ public function prepareBatch(string $buffer, Compressor $compressor, ?bool $sync = null, ?TimingsHandler $timings = null) : CompressBatchPromise|string{
$timings ??= Timings::$playerNetworkSendCompress;
try{
$timings->startTiming();
- if($sync === null){
- $threshold = $compressor->getCompressionThreshold();
- $sync = !$this->networkCompressionAsync || $threshold === null || strlen($buffer) < $threshold;
- }
+ $threshold = $compressor->getCompressionThreshold();
+ if($threshold === null || strlen($buffer) < $compressor->getCompressionThreshold()){
+ $compressionType = CompressionAlgorithm::NONE;
+ $compressed = $buffer;
- $promise = new CompressBatchPromise();
- if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
- $task = new CompressBatchTask($buffer, $promise, $compressor);
- $this->asyncPool->submitTask($task);
}else{
- $promise->resolve($compressor->compress($buffer));
+ $sync ??= !$this->networkCompressionAsync;
+
+ if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
+ $promise = new CompressBatchPromise();
+ $task = new CompressBatchTask($buffer, $promise, $compressor);
+ $this->asyncPool->submitTask($task);
+ return $promise;
+ }
+
+ $compressionType = $compressor->getNetworkId();
+ $compressed = $compressor->compress($buffer);
}
- return $promise;
+ return chr($compressionType) . $compressed;
}finally{
$timings->stopTiming();
}
@@ -1384,14 +1400,14 @@ public function prepareBatch(string $buffer, Compressor $compressor, ?bool $sync
public function enablePlugins(PluginEnableOrder $type) : bool{
$allSuccess = true;
foreach($this->pluginManager->getPlugins() as $plugin){
- if(!$plugin->isEnabled() && $plugin->getDescription()->getOrder()->equals($type)){
+ if(!$plugin->isEnabled() && $plugin->getDescription()->getOrder() === $type){
if(!$this->pluginManager->enablePlugin($plugin)){
$allSuccess = false;
}
}
}
- if($type->equals(PluginEnableOrder::POSTWORLD())){
+ if($type === PluginEnableOrder::POSTWORLD){
$this->commandMap->registerServerAliases();
}
@@ -1427,7 +1443,7 @@ public function shutdown() : void{
private function forceShutdownExit() : void{
$this->forceShutdown();
- Process::kill(Process::pid(), true);
+ Process::kill(Process::pid());
}
public function forceShutdown() : void{
@@ -1452,50 +1468,50 @@ public function forceShutdown() : void{
$this->shutdown();
if(isset($this->pluginManager)){
- $this->getLogger()->debug("Disabling all plugins");
+ $this->logger->debug("Disabling all plugins");
$this->pluginManager->disablePlugins();
}
if(isset($this->network)){
- $this->network->getSessionManager()->close($this->configGroup->getPropertyString("settings.shutdown-message", "Server closed"));
+ $this->network->getSessionManager()->close($this->configGroup->getPropertyString(Yml::SETTINGS_SHUTDOWN_MESSAGE, "Server closed"));
}
if(isset($this->worldManager)){
- $this->getLogger()->debug("Unloading all worlds");
+ $this->logger->debug("Unloading all worlds");
foreach($this->worldManager->getWorlds() as $world){
$this->worldManager->unloadWorld($world, true);
}
}
- $this->getLogger()->debug("Removing event handlers");
+ $this->logger->debug("Removing event handlers");
HandlerListManager::global()->unregisterAll();
if(isset($this->asyncPool)){
- $this->getLogger()->debug("Shutting down async task worker pool");
+ $this->logger->debug("Shutting down async task worker pool");
$this->asyncPool->shutdown();
}
if(isset($this->configGroup)){
- $this->getLogger()->debug("Saving properties");
+ $this->logger->debug("Saving properties");
$this->configGroup->save();
}
if($this->console !== null){
- $this->getLogger()->debug("Closing console");
+ $this->logger->debug("Closing console");
$this->console->quit();
}
if(isset($this->network)){
- $this->getLogger()->debug("Stopping network interfaces");
+ $this->logger->debug("Stopping network interfaces");
foreach($this->network->getInterfaces() as $interface){
- $this->getLogger()->debug("Stopping network interface " . get_class($interface));
+ $this->logger->debug("Stopping network interface " . get_class($interface));
$this->network->unregisterInterface($interface);
}
}
}catch(\Throwable $e){
$this->logger->logException($e);
$this->logger->emergency("Crashed while crashing, killing process");
- @Process::kill(Process::pid(), true);
+ @Process::kill(Process::pid());
}
}
@@ -1516,23 +1532,38 @@ public function exceptionHandler(\Throwable $e, ?array $trace = null) : void{
$trace = $e->getTrace();
}
- $errstr = $e->getMessage();
- $errfile = $e->getFile();
- $errline = $e->getLine();
-
- $errstr = preg_replace('/\s+/', ' ', trim($errstr));
+ //If this is a thread crash, this logs where the exception came from on the main thread, as opposed to the
+ //crashed thread. This is intentional, and might be useful for debugging
+ //Assume that the thread already logged the original exception with the correct stack trace
+ $this->logger->logException($e, $trace);
- $errfile = Filesystem::cleanPath($errfile);
+ if($e instanceof ThreadCrashException){
+ $info = $e->getCrashInfo();
+ $type = $info->getType();
+ $errstr = $info->getMessage();
+ $errfile = $info->getFile();
+ $errline = $info->getLine();
+ $printableTrace = $info->getTrace();
+ $thread = $info->getThreadName();
+ }else{
+ $type = get_class($e);
+ $errstr = $e->getMessage();
+ $errfile = $e->getFile();
+ $errline = $e->getLine();
+ $printableTrace = Utils::printableTraceWithMetadata($trace);
+ $thread = "Main";
+ }
- $this->logger->logException($e, $trace);
+ $errstr = preg_replace('/\s+/', ' ', trim($errstr));
$lastError = [
- "type" => get_class($e),
+ "type" => $type,
"message" => $errstr,
- "fullFile" => $e->getFile(),
- "file" => $errfile,
+ "fullFile" => $errfile,
+ "file" => Filesystem::cleanPath($errfile),
"line" => $errline,
- "trace" => $trace
+ "trace" => $printableTrace,
+ "thread" => $thread
];
global $lastExceptionError, $lastError;
@@ -1541,7 +1572,7 @@ public function exceptionHandler(\Throwable $e, ?array $trace = null) : void{
}
private function writeCrashDumpFile(CrashDump $dump) : string{
- $crashFolder = Path::join($this->getDataPath(), "crashdumps");
+ $crashFolder = Path::join($this->dataPath, "crashdumps");
if(!is_dir($crashFolder)){
mkdir($crashFolder);
}
@@ -1572,17 +1603,17 @@ public function crashDump() : void{
ini_set("error_reporting", '0');
ini_set("memory_limit", '-1'); //Fix error dump not dumped on memory problems
try{
- $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
+ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_create()));
$dump = new CrashDump($this, $this->pluginManager ?? null);
$crashDumpPath = $this->writeCrashDumpFile($dump);
- $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
+ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
- if($this->configGroup->getPropertyBool("auto-report.enabled", true)){
+ if($this->configGroup->getPropertyBool(Yml::AUTO_REPORT_ENABLED, true)){
$report = true;
- $stamp = Path::join($this->getDataPath(), "crashdumps", ".last_crash");
+ $stamp = Path::join($this->dataPath, "crashdumps", ".last_crash");
$crashInterval = 120; //2 minutes
if(($lastReportTime = @filemtime($stamp)) !== false && $lastReportTime + $crashInterval >= time()){
$report = false;
@@ -1590,15 +1621,6 @@ public function crashDump() : void{
}
@touch($stamp); //update file timestamp
- $plugin = $dump->getData()->plugin;
- if($plugin !== ""){
- $p = $this->pluginManager->getPlugin($plugin);
- if($p instanceof Plugin && !($p->getPluginLoader() instanceof PharPluginLoader)){
- $this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
- $report = false;
- }
- }
-
if($dump->getData()->error["type"] === \ParseError::class){
$report = false;
}
@@ -1609,7 +1631,7 @@ public function crashDump() : void{
}
if($report){
- $url = ($this->configGroup->getPropertyBool("auto-report.use-https", true) ? "https" : "http") . "://" . $this->configGroup->getPropertyString("auto-report.host", "crash.pmmp.io") . "/submit/api";
+ $url = ($this->configGroup->getPropertyBool(Yml::AUTO_REPORT_USE_HTTPS, true) ? "https" : "http") . "://" . $this->configGroup->getPropertyString(Yml::AUTO_REPORT_HOST, "crash.pmmp.io") . "/submit/api";
$postUrlError = "Unknown error";
$reply = Internet::postURL($url, [
"report" => "yes",
@@ -1622,7 +1644,7 @@ public function crashDump() : void{
if(isset($data->crashId) && is_int($data->crashId) && isset($data->crashUrl) && is_string($data->crashUrl)){
$reportId = $data->crashId;
$reportUrl = $data->crashUrl;
- $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
+ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
}elseif(isset($data->error) && is_string($data->error)){
$this->logger->emergency("Automatic crash report submission failed: $data->error");
}else{
@@ -1636,7 +1658,7 @@ public function crashDump() : void{
}catch(\Throwable $e){
$this->logger->logException($e);
try{
- $this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_error($e->getMessage())));
+ $this->logger->critical($this->language->translate(KnownTranslationFactory::pocketmine_crash_error($e->getMessage())));
}catch(\Throwable $e){}
}
@@ -1644,12 +1666,14 @@ public function crashDump() : void{
$this->isRunning = false;
//Force minimum uptime to be >= 120 seconds, to reduce the impact of spammy crash loops
- $spacing = ((int) $this->startTime) - time() + 120;
+ $uptime = time() - ((int) $this->startTime);
+ $minUptime = 120;
+ $spacing = $minUptime - $uptime;
if($spacing > 0){
- echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
+ echo "--- Uptime {$uptime}s - waiting {$spacing}s to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
sleep($spacing);
}
- @Process::kill(Process::pid(), true);
+ @Process::kill(Process::pid());
exit(1);
}
@@ -1720,7 +1744,7 @@ public function removeOnlinePlayer(Player $player) : void{
}
public function sendUsage(int $type = SendUsageTask::TYPE_STATUS) : void{
- if($this->configGroup->getPropertyBool("anonymous-statistics.enabled", true)){
+ if($this->configGroup->getPropertyBool(Yml::ANONYMOUS_STATISTICS_ENABLED, true)){
$this->asyncPool->submitTask(new SendUsageTask($this, $type, $this->uniquePlayers));
}
$this->uniquePlayers = [];
@@ -1754,7 +1778,7 @@ private function titleTick() : void{
echo "\x1b]0;" . $this->getName() . " " .
$this->getPocketMineVersion() .
- " | Online $online/" . $this->getMaxPlayers() .
+ " | Online $online/" . $this->maxPlayers .
($connecting > 0 ? " (+$connecting connecting)" : "") .
" | Memory " . $usage .
" | U " . round($bandwidthStats->getSend()->getAverageBytes() / 1024, 2) .
@@ -1819,10 +1843,10 @@ private function tick() : void{
}
if(($this->tickCounter % self::TICKS_PER_TPS_OVERLOAD_WARNING) === 0 && $this->getTicksPerSecondAverage() < self::TPS_OVERLOAD_WARNING_THRESHOLD){
- $this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_tickOverload()));
+ $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_tickOverload()));
}
- $this->getMemoryManager()->check();
+ $this->memoryManager->check();
if($this->console !== null){
Timings::$serverCommand->startTiming();
diff --git a/src/ServerProperties.php b/src/ServerProperties.php
new file mode 100644
index 00000000000..ddbbd81340a
--- /dev/null
+++ b/src/ServerProperties.php
@@ -0,0 +1,58 @@
+
+ */
+ private static array $handlerTimings = [];
+
public function __construct(
private TimingsHandler $timings
){
parent::__construct();
}
+ public function addNotifier(\Closure $handler) : SleeperHandlerEntry{
+ $name = Utils::getNiceClosureName($handler);
+ $timings = self::$handlerTimings[$name] ??= new TimingsHandler("Snooze Handler: " . $name, $this->timings);
+
+ return parent::addNotifier(function() use ($timings, $handler) : void{
+ $timings->startTiming();
+ $handler();
+ $timings->stopTiming();
+ });
+ }
+
/**
* Returns the time in nanoseconds spent processing notifications since the last reset.
*/
diff --git a/src/VersionInfo.php b/src/VersionInfo.php
index 33af08c080d..bc1b24c62df 100644
--- a/src/VersionInfo.php
+++ b/src/VersionInfo.php
@@ -31,7 +31,7 @@
final class VersionInfo{
public const NAME = "PocketMine-MP";
- public const BASE_VERSION = "5.2.0";
+ public const BASE_VERSION = "5.21.2";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";
@@ -63,7 +63,8 @@ public static function GIT_HASH() : string{
if(\Phar::running(true) === ""){
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
}else{
- $phar = new \Phar(\Phar::running(false));
+ $pharPath = \Phar::running(false);
+ $phar = \Phar::isValidPharFilename($pharPath) ? new \Phar($pharPath) : new \PharData($pharPath);
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
@@ -82,7 +83,8 @@ public static function BUILD_NUMBER() : int{
if(self::$buildNumber === null){
self::$buildNumber = 0;
if(\Phar::running(true) !== ""){
- $phar = new \Phar(\Phar::running(false));
+ $pharPath = \Phar::running(false);
+ $phar = \Phar::isValidPharFilename($pharPath) ? new \Phar($pharPath) : new \PharData($pharPath);
$meta = $phar->getMetadata();
if(is_array($meta) && isset($meta["build"]) && is_int($meta["build"])){
self::$buildNumber = $meta["build"];
diff --git a/src/YmlServerProperties.php b/src/YmlServerProperties.php
new file mode 100644
index 00000000000..9bd203eef85
--- /dev/null
+++ b/src/YmlServerProperties.php
@@ -0,0 +1,118 @@
+boundedIntAuto(self::STAGE_SMALL_BUD, self::STAGE_CLUSTER, $this->stage);
+ }
+
+ public function getStage() : int{ return $this->stage; }
+
+ public function setStage(int $stage) : self{
+ if($stage < self::STAGE_SMALL_BUD || $stage > self::STAGE_CLUSTER){
+ throw new \InvalidArgumentException("Size must be in range " . self::STAGE_SMALL_BUD . " ... " . self::STAGE_CLUSTER);
+ }
+ $this->stage = $stage;
+ return $this;
+ }
+
+ public function getLightLevel() : int{
+ return match($this->stage){
+ self::STAGE_SMALL_BUD => 1,
+ self::STAGE_MEDIUM_BUD => 2,
+ self::STAGE_LARGE_BUD => 4,
+ self::STAGE_CLUSTER => 5,
+ default => throw new AssumptionFailedError("Invalid stage $this->stage"),
+ };
+ }
+
+ protected function recalculateCollisionBoxes() : array{
+ $myAxis = Facing::axis($this->facing);
+
+ $box = AxisAlignedBB::one();
+ foreach([Axis::Y, Axis::Z, Axis::X] as $axis){
+ if($axis === $myAxis){
+ continue;
+ }
+ $box->squash($axis, $this->stage === self::STAGE_SMALL_BUD ? 4 / 16 : 3 / 16);
+ }
+ $box->trim($this->facing, 1 - ($this->stage === self::STAGE_CLUSTER ? 7 / 16 : ($this->stage + 3) / 16));
+
+ return [$box];
+ }
+
+ private function canBeSupportedAt(Block $block, int $facing) : bool{
+ return $block->getAdjacentSupportType($facing) === SupportType::FULL;
+ }
+
+ public function getSupportType(int $facing) : SupportType{
+ return SupportType::NONE;
+ }
+
+ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+ if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
+ return false;
+ }
+
+ $this->facing = $face;
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ }
+
+ public function onNearbyBlockChange() : void{
+ if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){
+ $this->position->getWorld()->useBreakOn($this->position);
+ }
+ }
+
+ public function isAffectedBySilkTouch() : bool{
+ return true;
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ if($this->stage === self::STAGE_CLUSTER){
+ return [VanillaItems::AMETHYST_SHARD()->setCount(FortuneDropHelper::weighted($item, min: 4, maxBase: 4))];
+ }
+
+ return [];
+ }
+
+ public function getDropsForIncompatibleTool(Item $item) : array{
+ if($this->stage === self::STAGE_CLUSTER){
+ return [VanillaItems::AMETHYST_SHARD()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 2))];
+ }
+
+ return [];
+ }
+}
diff --git a/src/block/Anvil.php b/src/block/Anvil.php
index de8b6d33cc9..4b4afef6153 100644
--- a/src/block/Anvil.php
+++ b/src/block/Anvil.php
@@ -52,7 +52,7 @@ class Anvil extends Transparent implements Fallable{
private int $damage = self::UNDAMAGED;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(2, self::UNDAMAGED, self::VERY_DAMAGED, $this->damage);
+ $w->boundedIntAuto(self::UNDAMAGED, self::VERY_DAMAGED, $this->damage);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@@ -78,7 +78,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@@ -91,7 +91,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
- $this->facing = Facing::rotateY($player->getHorizontalFacing(), true);
+ $this->facing = Facing::rotateY($player->getHorizontalFacing(), false);
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
diff --git a/src/block/Bamboo.php b/src/block/Bamboo.php
index 980f4382d13..9f605bca69b 100644
--- a/src/block/Bamboo.php
+++ b/src/block/Bamboo.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
@@ -46,6 +47,7 @@
use const PHP_INT_MAX;
class Bamboo extends Transparent{
+ use StaticSupportTrait;
public const NO_LEAVES = 0;
public const SMALL_LEAVES = 1;
@@ -56,7 +58,7 @@ class Bamboo extends Transparent{
protected int $leafSize = self::NO_LEAVES;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(2, self::NO_LEAVES, self::LARGE_LEAVES, $this->leafSize);
+ $w->boundedIntAuto(self::NO_LEAVES, self::LARGE_LEAVES, $this->leafSize);
$w->bool($this->thick);
$w->bool($this->ready);
}
@@ -95,7 +97,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
private static function getOffsetSeed(int $x, int $y, int $z) : int{
@@ -120,12 +122,14 @@ public function getModelPositionOffset() : ?Vector3{
return new Vector3($retX, 0, $retZ);
}
- private function canBeSupportedBy(Block $block) : bool{
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
return
- $block->getTypeId() === BlockTypeIds::GRAVEL ||
- $block->hasTypeTag(BlockTypeTags::DIRT) ||
- $block->hasTypeTag(BlockTypeTags::MUD) ||
- $block->hasTypeTag(BlockTypeTags::SAND);
+ $supportBlock->hasSameTypeId($this) ||
+ $supportBlock->getTypeId() === BlockTypeIds::GRAVEL ||
+ $supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::SAND);
}
private function seekToTop() : Bamboo{
@@ -153,14 +157,6 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return false;
}
- public function onNearbyBlockChange() : void{
- $world = $this->position->getWorld();
- $below = $world->getBlock($this->position->down());
- if(!$this->canBeSupportedBy($below) && !$below->hasSameTypeId($this)){
- $world->useBreakOn($this->position);
- }
- }
-
private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
@@ -177,7 +173,7 @@ private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$newHeight = $height + $growAmount;
$stemBlock = (clone $this)->setReady(false)->setLeafSize(self::NO_LEAVES);
- if($newHeight >= 4 && !$stemBlock->isThick()){ //don't change it to false if height is less, because it might have been chopped
+ if($newHeight >= 4 && !$stemBlock->thick){ //don't change it to false if height is less, because it might have been chopped
$stemBlock = $stemBlock->setThick(true);
}
$smallLeavesBlock = (clone $stemBlock)->setLeafSize(self::SMALL_LEAVES);
diff --git a/src/block/BambooSapling.php b/src/block/BambooSapling.php
index 6be42546ab6..67c8a24e0b0 100644
--- a/src/block/BambooSapling.php
+++ b/src/block/BambooSapling.php
@@ -23,17 +23,21 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Bamboo as ItemBamboo;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
+use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class BambooSapling extends Flowable{
+ use StaticSupportTrait;
+
private bool $ready = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@@ -48,19 +52,13 @@ public function setReady(bool $ready) : self{
return $this;
}
- private function canBeSupportedBy(Block $block) : bool{
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
return
- $block->getTypeId() === BlockTypeIds::GRAVEL ||
- $block->hasTypeTag(BlockTypeTags::DIRT) ||
- $block->hasTypeTag(BlockTypeTags::MUD) ||
- $block->hasTypeTag(BlockTypeTags::SAND);
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->position->getWorld()->getBlock($blockReplace->position->down()))){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ $supportBlock->getTypeId() === BlockTypeIds::GRAVEL ||
+ $supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::SAND);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@@ -73,13 +71,6 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return false;
}
- public function onNearbyBlockChange() : void{
- $world = $this->position->getWorld();
- if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){
- $world->useBreakOn($this->position);
- }
- }
-
private function grow(?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
diff --git a/src/block/Barrel.php b/src/block/Barrel.php
index 1dce2376b1b..0f0499ab93b 100644
--- a/src/block/Barrel.php
+++ b/src/block/Barrel.php
@@ -55,12 +55,12 @@ public function setOpen(bool $open) : Barrel{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
- if(abs($player->getPosition()->getX() - $this->position->getX()) < 2 && abs($player->getPosition()->getZ() - $this->position->getZ()) < 2){
- $y = $player->getEyePos()->getY();
+ if(abs($player->getPosition()->x - $this->position->x) < 2 && abs($player->getPosition()->z - $this->position->z) < 2){
+ $y = $player->getEyePos()->y;
- if($y - $this->position->getY() > 2){
+ if($y - $this->position->y > 2){
$this->facing = Facing::UP;
- }elseif($this->position->getY() - $y > 0){
+ }elseif($this->position->y - $y > 0){
$this->facing = Facing::DOWN;
}else{
$this->facing = Facing::opposite($player->getHorizontalFacing());
diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php
index fdfad3b0956..6b9e493d19e 100644
--- a/src/block/BaseBanner.php
+++ b/src/block/BaseBanner.php
@@ -26,7 +26,6 @@
use pocketmine\block\tile\Banner as TileBanner;
use pocketmine\block\utils\BannerPatternLayer;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Banner as ItemBanner;
use pocketmine\item\Item;
@@ -35,7 +34,6 @@
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
-use function array_filter;
use function assert;
use function count;
@@ -48,11 +46,6 @@ abstract class BaseBanner extends Transparent{
*/
protected array $patterns = [];
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::BLACK();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$tile = $this->position->getWorld()->getTile($this->position);
@@ -95,11 +88,12 @@ public function getPatterns() : array{
* @return $this
*/
public function setPatterns(array $patterns) : self{
- $checked = array_filter($patterns, fn($v) => $v instanceof BannerPatternLayer);
- if(count($checked) !== count($patterns)){
- throw new \TypeError("Deque must only contain " . BannerPatternLayer::class . " objects");
+ foreach($patterns as $pattern){
+ if(!$pattern instanceof BannerPatternLayer){
+ throw new \TypeError("Array must only contain " . BannerPatternLayer::class . " objects");
+ }
}
- $this->patterns = $checked;
+ $this->patterns = $patterns;
return $this;
}
@@ -111,7 +105,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
private function canBeSupportedBy(Block $block) : bool{
diff --git a/src/block/BaseBigDripleaf.php b/src/block/BaseBigDripleaf.php
new file mode 100644
index 00000000000..f0ff59cf08b
--- /dev/null
+++ b/src/block/BaseBigDripleaf.php
@@ -0,0 +1,136 @@
+isHead() === $head) ||
+ $block->getTypeId() === BlockTypeIds::CLAY ||
+ $block->hasTypeTag(BlockTypeTags::DIRT) ||
+ $block->hasTypeTag(BlockTypeTags::MUD);
+ }
+
+ public function onNearbyBlockChange() : void{
+ if(
+ (!$this->isHead() && !$this->getSide(Facing::UP) instanceof BaseBigDripleaf) ||
+ !$this->canBeSupportedBy($this->getSide(Facing::DOWN), false)
+ ){
+ $this->position->getWorld()->useBreakOn($this->position);
+ }
+ }
+
+ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+ $block = $blockReplace->getSide(Facing::DOWN);
+ if(!$this->canBeSupportedBy($block, true)){
+ return false;
+ }
+ if($player !== null){
+ $this->facing = Facing::opposite($player->getHorizontalFacing());
+ }
+ if($block instanceof BaseBigDripleaf){
+ $this->facing = $block->facing;
+ $tx->addBlock($block->position, VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($this->facing));
+ }
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($item instanceof Fertilizer && $this->grow($player)){
+ $item->pop();
+ return true;
+ }
+ return false;
+ }
+
+ private function seekToHead() : ?BaseBigDripleaf{
+ if($this->isHead()){
+ return $this;
+ }
+ $step = 1;
+ while(($next = $this->getSide(Facing::UP, $step)) instanceof BaseBigDripleaf){
+ if($next->isHead()){
+ return $next;
+ }
+ $step++;
+ }
+ return null;
+ }
+
+ private function grow(?Player $player) : bool{
+ $head = $this->seekToHead();
+ if($head === null){
+ return false;
+ }
+ $pos = $head->position;
+ $up = $pos->up();
+ $world = $pos->getWorld();
+ if(
+ !$world->isInWorld($up->getFloorX(), $up->getFloorY(), $up->getFloorZ()) ||
+ $world->getBlock($up)->getTypeId() !== BlockTypeIds::AIR
+ ){
+ return false;
+ }
+
+ $tx = new BlockTransaction($world);
+
+ $tx->addBlock($pos, VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($head->facing));
+ $tx->addBlock($up, VanillaBlocks::BIG_DRIPLEAF_HEAD()->setFacing($head->facing));
+
+ $ev = new StructureGrowEvent($head, $tx, $player);
+ $ev->call();
+
+ if(!$ev->isCancelled()){
+ return $tx->apply();
+ }
+ return false;
+ }
+
+ public function getFlameEncouragement() : int{
+ return 15;
+ }
+
+ public function getFlammability() : int{
+ return 100;
+ }
+
+ public function getSupportType(int $facing) : SupportType{
+ return SupportType::NONE;
+ }
+}
diff --git a/src/block/BaseCake.php b/src/block/BaseCake.php
index 21fd6336a06..4b390384026 100644
--- a/src/block/BaseCake.php
+++ b/src/block/BaseCake.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\FoodSource;
@@ -31,27 +32,16 @@
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
abstract class BaseCake extends Transparent implements FoodSource{
+ use StaticSupportTrait;
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $this->getSide(Facing::DOWN);
- if($down->getTypeId() !== BlockTypeIds::AIR){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
- }
-
- public function onNearbyBlockChange() : void{
- if($this->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::AIR){ //Replace with common break method
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::AIR;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
diff --git a/src/block/BaseCoral.php b/src/block/BaseCoral.php
index 73b2c48d67a..b9c595a97d4 100644
--- a/src/block/BaseCoral.php
+++ b/src/block/BaseCoral.php
@@ -23,21 +23,15 @@
namespace pocketmine\block;
-use pocketmine\block\utils\CoralType;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\block\utils\SupportType;
-use pocketmine\event\block\BlockDeathEvent;
use pocketmine\item\Item;
use function mt_rand;
abstract class BaseCoral extends Transparent{
use CoralTypeTrait;
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->coralType = CoralType::TUBE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
public function onNearbyBlockChange() : void{
if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));
@@ -46,11 +40,7 @@ public function onNearbyBlockChange() : void{
public function onScheduledUpdate() : void{
if(!$this->dead && !$this->isCoveredWithWater()){
- $ev = new BlockDeathEvent($this, $this->setDead(true));
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::die($this, (clone $this)->setDead(true));
}
}
@@ -82,6 +72,6 @@ protected function isCoveredWithWater() : bool{
protected function recalculateCollisionBoxes() : array{ return []; }
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/BaseRail.php b/src/block/BaseRail.php
index 971beffac7d..0bcb2f340b3 100644
--- a/src/block/BaseRail.php
+++ b/src/block/BaseRail.php
@@ -38,7 +38,7 @@
abstract class BaseRail extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($blockReplace->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){
+ if($blockReplace->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -222,7 +222,7 @@ private function setConnections(array $connections) : void{
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
- if(!$this->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){
+ if(!$this->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){
$world->useBreakOn($this->position);
}else{
foreach($this->getCurrentShapeConnections() as $connection){
diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php
index 7f65b59f42f..5a905f8b865 100644
--- a/src/block/BaseSign.php
+++ b/src/block/BaseSign.php
@@ -49,6 +49,8 @@ abstract class BaseSign extends Transparent{
use WoodTypeTrait;
protected SignText $text;
+ private bool $waxed = false;
+
protected ?int $editorEntityRuntimeId = null;
/** @var \Closure() : Item */
@@ -69,6 +71,7 @@ public function readStateFromWorld() : Block{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileSign){
$this->text = $tile->getText();
+ $this->waxed = $tile->isWaxed();
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
}
@@ -80,6 +83,7 @@ public function writeStateToWorld() : void{
$tile = $this->position->getWorld()->getTile($this->position);
assert($tile instanceof TileSign);
$tile->setText($this->text);
+ $tile->setWaxed($this->waxed);
$tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId);
}
@@ -99,7 +103,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
abstract protected function getSupportingFace() : int;
@@ -147,32 +151,53 @@ private function changeSignGlowingState(bool $glowing, Player $player, Item $ite
return false;
}
+ private function wax(Player $player, Item $item) : bool{
+ if($this->waxed){
+ return false;
+ }
+
+ $this->waxed = true;
+ $this->position->getWorld()->setBlock($this->position, $this);
+ $item->pop();
+
+ return true;
+ }
+
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($player === null){
return false;
}
+ if($this->waxed){
+ return true;
+ }
+
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
- ItemTypeIds::BONE_MEAL => DyeColor::WHITE(),
- ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE(),
- ItemTypeIds::COCOA_BEANS => DyeColor::BROWN(),
+ ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
+ ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
+ ItemTypeIds::COCOA_BEANS => DyeColor::BROWN,
default => null
};
if($dyeColor !== null){
- $color = $dyeColor->equals(DyeColor::BLACK()) ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
- if($color->toARGB() === $this->text->getBaseColor()->toARGB()){
- return false;
- }
-
- if($this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)){
+ $color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
+ if(
+ $color->toARGB() !== $this->text->getBaseColor()->toARGB() &&
+ $this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)
+ ){
$this->position->getWorld()->addSound($this->position, new DyeUseSound());
return true;
}
- }elseif($item->getTypeId() === ItemTypeIds::INK_SAC){
- return $this->changeSignGlowingState(false, $player, $item);
- }elseif($item->getTypeId() === ItemTypeIds::GLOW_INK_SAC){
- return $this->changeSignGlowingState(true, $player, $item);
+ }elseif(match($item->getTypeId()){
+ ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item),
+ ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item),
+ ItemTypeIds::HONEYCOMB => $this->wax($player, $item),
+ default => false
+ }){
+ return true;
}
- return false;
+
+ $player->openSignEditor($this->position);
+
+ return true;
}
/**
@@ -188,6 +213,17 @@ public function setText(SignText $text) : self{
return $this;
}
+ /**
+ * Returns whether the sign has been waxed using a honeycomb. If true, the sign cannot be edited by a player.
+ */
+ public function isWaxed() : bool{ return $this->waxed; }
+
+ /** @return $this */
+ public function setWaxed(bool $waxed) : self{
+ $this->waxed = $waxed;
+ return $this;
+ }
+
/**
* Sets the runtime entity ID of the player editing this sign. Only this player will be able to edit the sign.
* This is used to prevent multiple players from editing the same sign at the same time, and to prevent players
@@ -217,8 +253,8 @@ public function updateText(Player $author, SignText $text) : bool{
}
$ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{
return TextFormat::clean($line, false);
- }, $text->getLines())));
- if($this->editorEntityRuntimeId === null || $this->editorEntityRuntimeId !== $author->getId()){
+ }, $text->getLines()), $this->text->getBaseColor(), $this->text->isGlowing()));
+ if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){
$ev->cancel();
}
$ev->call();
@@ -235,4 +271,8 @@ public function updateText(Player $author, SignText $text) : bool{
public function asItem() : Item{
return ($this->asItemCallback)();
}
+
+ public function getFuelTime() : int{
+ return $this->woodType->isFlammable() ? 200 : 0;
+ }
}
diff --git a/src/block/Bed.php b/src/block/Bed.php
index 13b466026bd..8efbdfe0175 100644
--- a/src/block/Bed.php
+++ b/src/block/Bed.php
@@ -48,11 +48,6 @@ class Bed extends Transparent{
protected bool $occupied = false;
protected bool $head = false;
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::RED();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->bool($this->occupied);
@@ -65,6 +60,8 @@ public function readStateFromWorld() : Block{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileBed){
$this->color = $tile->getColor();
+ }else{
+ $this->color = DyeColor::RED; //legacy pre-1.1 beds don't have tiles
}
return $this;
@@ -87,7 +84,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function isHeadPart() : bool{
@@ -148,7 +145,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
$b = ($this->isHeadPart() ? $this : $other);
- if($b->isOccupied()){
+ if($b->occupied){
$player->sendMessage(KnownTranslationFactory::tile_bed_occupied()->prefix(TextFormat::GRAY));
return true;
@@ -177,11 +174,11 @@ public function onEntityLand(Entity $entity) : ?float{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
+ if($this->canBeSupportedAt($blockReplace)){
$this->facing = $player !== null ? $player->getHorizontalFacing() : Facing::NORTH;
$next = $this->getSide($this->getOtherHalfSide());
- if($next->canBeReplaced() && $this->canBeSupportedBy($next->getSide(Facing::DOWN))){
+ if($next->canBeReplaced() && $this->canBeSupportedAt($next)){
$nextState = clone $this;
$nextState->head = true;
$tx->addBlock($blockReplace->position, $this)->addBlock($next->position, $nextState);
@@ -208,8 +205,8 @@ public function getAffectedBlocks() : array{
return parent::getAffectedBlocks();
}
- private function canBeSupportedBy(Block $block) : bool{
- return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
public function getMaxStackSize() : int{ return 1; }
diff --git a/src/block/Beetroot.php b/src/block/Beetroot.php
index b87a841ea2e..cf92fdda20c 100644
--- a/src/block/Beetroot.php
+++ b/src/block/Beetroot.php
@@ -23,9 +23,9 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
class Beetroot extends Crops{
@@ -33,7 +33,7 @@ public function getDropsForCompatibleTool(Item $item) : array{
if($this->age >= self::MAX_AGE){
return [
VanillaItems::BEETROOT(),
- VanillaItems::BEETROOT_SEEDS()->setCount(mt_rand(0, 3))
+ VanillaItems::BEETROOT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0))
];
}
diff --git a/src/block/Bell.php b/src/block/Bell.php
index 753d6453d30..53a6fc7fbb4 100644
--- a/src/block/Bell.php
+++ b/src/block/Bell.php
@@ -41,25 +41,20 @@
final class Bell extends Transparent{
use HorizontalFacingTrait;
- private BellAttachmentType $attachmentType;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->attachmentType = BellAttachmentType::FLOOR();
- parent::__construct($idInfo, $name, $typeInfo);
- }
+ private BellAttachmentType $attachmentType = BellAttachmentType::FLOOR;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->bellAttachmentType($this->attachmentType);
+ $w->enum($this->attachmentType);
$w->horizontalFacing($this->facing);
}
protected function recalculateCollisionBoxes() : array{
- if($this->attachmentType->equals(BellAttachmentType::FLOOR())){
+ if($this->attachmentType === BellAttachmentType::FLOOR){
return [
AxisAlignedBB::one()->squash(Facing::axis($this->facing), 1 / 4)->trim(Facing::UP, 3 / 16)
];
}
- if($this->attachmentType->equals(BellAttachmentType::CEILING())){
+ if($this->attachmentType === BellAttachmentType::CEILING){
return [
AxisAlignedBB::one()->contract(1 / 4, 0, 1 / 4)->trim(Facing::DOWN, 1 / 4)
];
@@ -71,12 +66,12 @@ protected function recalculateCollisionBoxes() : array{
->trim(Facing::DOWN, 1 / 4);
return [
- $this->attachmentType->equals(BellAttachmentType::ONE_WALL()) ? $box->trim($this->facing, 3 / 16) : $box
+ $this->attachmentType === BellAttachmentType::ONE_WALL ? $box->trim($this->facing, 3 / 16) : $box
];
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function getAttachmentType() : BellAttachmentType{ return $this->attachmentType; }
@@ -87,46 +82,43 @@ public function setAttachmentType(BellAttachmentType $attachmentType) : self{
return $this;
}
- private function canBeSupportedBy(Block $block, int $face) : bool{
- return !$block->getSupportType($face)->equals(SupportType::NONE());
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType($face) !== SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+ if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
+ return false;
+ }
if($face === Facing::UP){
- if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()), Facing::UP)){
- return false;
- }
if($player !== null){
$this->setFacing(Facing::opposite($player->getHorizontalFacing()));
}
- $this->setAttachmentType(BellAttachmentType::FLOOR());
+ $this->setAttachmentType(BellAttachmentType::FLOOR);
}elseif($face === Facing::DOWN){
- if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()), Facing::DOWN)){
- return false;
- }
- $this->setAttachmentType(BellAttachmentType::CEILING());
+ $this->setAttachmentType(BellAttachmentType::CEILING);
}else{
$this->setFacing($face);
- if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))), $face)){
- $this->setAttachmentType(BellAttachmentType::ONE_WALL());
- }else{
- return false;
- }
- if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)), Facing::opposite($face))){
- $this->setAttachmentType(BellAttachmentType::TWO_WALLS());
- }
+ $this->setAttachmentType(
+ $this->canBeSupportedAt($blockReplace, $face) ?
+ BellAttachmentType::TWO_WALLS :
+ BellAttachmentType::ONE_WALL
+ );
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
- if(
- ($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP), Facing::DOWN)) ||
- ($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN), Facing::UP)) ||
- ($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)) ||
- ($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing), Facing::opposite($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)))
- ){
- $this->position->getWorld()->useBreakOn($this->position);
+ foreach(match($this->attachmentType){
+ BellAttachmentType::CEILING => [Facing::UP],
+ BellAttachmentType::FLOOR => [Facing::DOWN],
+ BellAttachmentType::ONE_WALL => [Facing::opposite($this->facing)],
+ BellAttachmentType::TWO_WALLS => [$this->facing, Facing::opposite($this->facing)]
+ } as $supportBlockDirection){
+ if(!$this->canBeSupportedAt($this, $supportBlockDirection)){
+ $this->position->getWorld()->useBreakOn($this->position);
+ break;
+ }
}
}
@@ -158,14 +150,15 @@ public function ring(int $faceHit) : void{
}
}
+ public function getDropsForIncompatibleTool(Item $item) : array{
+ return [$this->asItem()];
+ }
+
private function isValidFaceToRing(int $faceHit) : bool{
- return (
- $this->attachmentType->equals(BellAttachmentType::CEILING()) ||
- ($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)) ||
- (
- ($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) &&
- ($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true))
- )
- );
+ return match($this->attachmentType){
+ BellAttachmentType::CEILING => true,
+ BellAttachmentType::FLOOR => Facing::axis($faceHit) === Facing::axis($this->facing),
+ BellAttachmentType::ONE_WALL, BellAttachmentType::TWO_WALLS => $faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true),
+ };
}
}
diff --git a/src/block/BigDripleafHead.php b/src/block/BigDripleafHead.php
new file mode 100644
index 00000000000..a9b87bf7f76
--- /dev/null
+++ b/src/block/BigDripleafHead.php
@@ -0,0 +1,125 @@
+enum($this->leafState);
+ }
+
+ protected function isHead() : bool{
+ return true;
+ }
+
+ public function getLeafState() : DripleafState{
+ return $this->leafState;
+ }
+
+ /** @return $this */
+ public function setLeafState(DripleafState $leafState) : self{
+ $this->leafState = $leafState;
+ return $this;
+ }
+
+ public function hasEntityCollision() : bool{
+ return true;
+ }
+
+ private function setTiltAndScheduleTick(DripleafState $tilt) : void{
+ $this->position->getWorld()->setBlock($this->position, $this->setLeafState($tilt));
+ $delay = $tilt->getScheduledUpdateDelayTicks();
+ if($delay !== null){
+ $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $delay);
+ }
+ }
+
+ private function getLeafTopOffset() : float{
+ return match($this->leafState){
+ DripleafState::STABLE, DripleafState::UNSTABLE => 1 / 16,
+ DripleafState::PARTIAL_TILT => 3 / 16,
+ default => 0
+ };
+ }
+
+ public function onEntityInside(Entity $entity) : bool{
+ if(!$entity instanceof Projectile && $this->leafState === DripleafState::STABLE){
+ //the entity must be standing on top of the leaf - do not collapse if the entity is standing underneath
+ $intersection = AxisAlignedBB::one()
+ ->offset($this->position->x, $this->position->y, $this->position->z)
+ ->trim(Facing::DOWN, 1 - $this->getLeafTopOffset());
+ if($entity->getBoundingBox()->intersectsWith($intersection)){
+ $this->setTiltAndScheduleTick(DripleafState::UNSTABLE);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
+ if($this->leafState !== DripleafState::FULL_TILT){
+ $this->setTiltAndScheduleTick(DripleafState::FULL_TILT);
+ $this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound());
+ }
+ }
+
+ public function onScheduledUpdate() : void{
+ if($this->leafState !== DripleafState::STABLE){
+ if($this->leafState === DripleafState::FULL_TILT){
+ $this->position->getWorld()->setBlock($this->position, $this->setLeafState(DripleafState::STABLE));
+ $this->position->getWorld()->addSound($this->position, new DripleafTiltUpSound());
+ }else{
+ $this->setTiltAndScheduleTick(match($this->leafState){
+ DripleafState::UNSTABLE => DripleafState::PARTIAL_TILT,
+ DripleafState::PARTIAL_TILT => DripleafState::FULL_TILT,
+ });
+ $this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound());
+ }
+ }
+ }
+
+ protected function recalculateCollisionBoxes() : array{
+ if($this->leafState !== DripleafState::FULL_TILT){
+ return [
+ AxisAlignedBB::one()
+ ->trim(Facing::DOWN, 11 / 16)
+ ->trim(Facing::UP, $this->getLeafTopOffset())
+ ];
+ }
+ return [];
+ }
+}
diff --git a/src/block/BigDripleafStem.php b/src/block/BigDripleafStem.php
new file mode 100644
index 00000000000..d44c47ccbef
--- /dev/null
+++ b/src/block/BigDripleafStem.php
@@ -0,0 +1,41 @@
+asItem();
+ }
+}
diff --git a/src/block/Block.php b/src/block/Block.php
index f2268aed931..89fe3926584 100644
--- a/src/block/Block.php
+++ b/src/block/Block.php
@@ -36,32 +36,39 @@
use pocketmine\data\runtime\RuntimeDataWriter;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Projectile;
+use pocketmine\item\enchantment\AvailableEnchantmentRegistry;
+use pocketmine\item\enchantment\ItemEnchantmentTagRegistry;
+use pocketmine\item\enchantment\ItemEnchantmentTags;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\ItemBlock;
-use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
+use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
+use pocketmine\utils\Binary;
use pocketmine\world\BlockTransaction;
use pocketmine\world\format\Chunk;
use pocketmine\world\Position;
use pocketmine\world\World;
use function count;
use function get_class;
+use function hash;
use const PHP_INT_MAX;
class Block{
- public const INTERNAL_STATE_DATA_BITS = 8;
+ public const INTERNAL_STATE_DATA_BITS = 11;
public const INTERNAL_STATE_DATA_MASK = ~(~0 << self::INTERNAL_STATE_DATA_BITS);
/**
* @internal
+ * Hardcoded int is `Binary::readLong(hash('xxh3', Binary::writeLLong(BlockTypeIds::AIR), binary: true))`
+ * TODO: it would be much easier if we could just make this 0 or some other easy value
*/
- public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (BlockTypeIds::AIR & self::INTERNAL_STATE_DATA_MASK);
+ public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (-7482769108513497636 & self::INTERNAL_STATE_DATA_MASK);
protected BlockIdentifier $idInfo;
protected string $fallbackName;
@@ -76,6 +83,23 @@ class Block{
private Block $defaultState;
+ private int $stateIdXorMask;
+
+ /**
+ * Computes the mask to be XOR'd with the state data.
+ * This is to improve distribution of the state data bits, which occupy the least significant bits of the state ID.
+ * Improved distribution improves PHP array performance when using the state ID as a key, as PHP arrays use some of
+ * the lower bits of integer keys directly without hashing.
+ *
+ * The type ID is included in the XOR mask. This is not necessary to improve distribution, but it reduces the number
+ * of operations required to compute the state ID (micro optimization).
+ */
+ private static function computeStateIdXorMask(int $typeId) : int{
+ return
+ $typeId << self::INTERNAL_STATE_DATA_BITS |
+ (Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
+ }
+
/**
* @param string $name English name of the block type (TODO: implement translations)
*/
@@ -93,6 +117,9 @@ public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo
$this->describeBlockOnlyState($calculator);
$this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
+ $this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
+
+ //this must be done last, otherwise the defaultState could have uninitialized fields
$defaultState = clone $this;
$this->defaultState = $defaultState;
$defaultState->defaultState = $defaultState;
@@ -148,13 +175,7 @@ public function getTypeId() : int{
* {@link RuntimeBlockStateRegistry::fromStateId()}.
*/
public function getStateId() : int{
- $typeId = $this->getTypeId();
- //TODO: this XOR mask improves hashtable distribution, but it's only effective if the number of unique block
- //type IDs is larger than the number of available state data bits. We should probably hash (e.g. using xxhash)
- //the type ID to create a better mask.
- //Alternatively, we could hash the whole state ID, but this is currently problematic, since we currently need
- //to be able to recover the state data from the state ID because of UnknownBlock.
- return ($typeId << self::INTERNAL_STATE_DATA_BITS) | ($this->encodeFullState() ^ ($typeId & self::INTERNAL_STATE_DATA_MASK));
+ return $this->encodeFullState() ^ $this->stateIdXorMask;
}
/**
@@ -224,12 +245,6 @@ private function decodeBlockOnlyState(int $data) : void{
}
}
- private function decodeFullState(int $data) : void{
- $reader = new RuntimeDataReader($this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits, $data);
- $this->decodeBlockItemState($reader->readInt($this->requiredBlockItemStateDataBits));
- $this->decodeBlockOnlyState($reader->readInt($this->requiredBlockOnlyStateDataBits));
- }
-
private function encodeBlockItemState() : int{
$writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
@@ -255,11 +270,22 @@ private function encodeBlockOnlyState() : int{
}
private function encodeFullState() : int{
- $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits);
- $writer->writeInt($this->requiredBlockItemStateDataBits, $this->encodeBlockItemState());
- $writer->writeInt($this->requiredBlockOnlyStateDataBits, $this->encodeBlockOnlyState());
+ $blockItemBits = $this->requiredBlockItemStateDataBits;
+ $blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
- return $writer->getValue();
+ if($blockOnlyBits === 0 && $blockItemBits === 0){
+ return 0;
+ }
+
+ $result = 0;
+ if($blockItemBits > 0){
+ $result |= $this->encodeBlockItemState();
+ }
+ if($blockOnlyBits > 0){
+ $result |= $this->encodeBlockOnlyState() << $blockItemBits;
+ }
+
+ return $result;
}
/**
@@ -300,34 +326,46 @@ public function generateStatePermutations() : \Generator{
if($bits > Block::INTERNAL_STATE_DATA_BITS){
throw new \LogicException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
}
- for($stateData = 0; $stateData < (1 << $bits); ++$stateData){
- $v = clone $this;
+ for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
+ $withType = clone $this;
try{
- $v->decodeFullState($stateData);
- if($v->encodeFullState() !== $stateData){
- throw new \LogicException(static::class . "::decodeStateData() accepts invalid state data (returned " . $v->encodeFullState() . " for input $stateData)");
+ $withType->decodeBlockItemState($blockItemStateData);
+ $encoded = $withType->encodeBlockItemState();
+ if($encoded !== $blockItemStateData){
+ throw new \LogicException(static::class . "::decodeBlockItemState() accepts invalid inputs (returned $encoded for input $blockItemStateData)");
}
}catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
continue;
}
- yield $v;
+ for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
+ $withState = clone $withType;
+ try{
+ $withState->decodeBlockOnlyState($blockOnlyStateData);
+ $encoded = $withState->encodeBlockOnlyState();
+ if($encoded !== $blockOnlyStateData){
+ throw new \LogicException(static::class . "::decodeBlockOnlyState() accepts invalid inputs (returned $encoded for input $blockOnlyStateData)");
+ }
+ }catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
+ continue;
+ }
+
+ yield $withState;
+ }
}
}
/**
* Called when this block is created, set, or has a neighbouring block update, to re-detect dynamic properties which
- * are not saved on the world.
- *
- * Clears any cached precomputed objects, such as bounding boxes. Remove any outdated precomputed things such as
- * AABBs and force recalculation.
+ * are not saved in the blockstate ID.
+ * If any such properties are updated, don't forget to clear things like AABB caches if necessary.
*
* A replacement block may be returned. This is useful if the block type changed due to reading of world data (e.g.
* data from a block entity).
+ *
+ * @phpstan-impure
*/
public function readStateFromWorld() : Block{
- $this->collisionBoxes = null;
-
return $this;
}
@@ -366,7 +404,7 @@ public function writeStateToWorld() : void{
}
/**
- * AKA: Block->isPlaceable
+ * Returns whether this block can be placed when obtained as an item.
*/
public function canBePlaced() : bool{
return true;
@@ -421,6 +459,19 @@ public function getBreakInfo() : BlockBreakInfo{
return $this->typeInfo->getBreakInfo();
}
+ /**
+ * Returns tags that represent the type of item being enchanted and are used to determine
+ * what enchantments can be applied to the item of this block during in-game enchanting (enchanting table, anvil, fishing, etc.).
+ * @see ItemEnchantmentTags
+ * @see ItemEnchantmentTagRegistry
+ * @see AvailableEnchantmentRegistry
+ *
+ * @return string[]
+ */
+ public function getEnchantmentTags() : array{
+ return $this->typeInfo->getEnchantmentTags();
+ }
+
/**
* Do the actions needed so the block is broken with the Item
*
@@ -467,7 +518,8 @@ public function onScheduledUpdate() : void{
/**
* Do actions when interacted by Item. Returns if it has done anything
*
- * @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
+ * @param Vector3 $clickVector Exact position where the click occurred, relative to the block's integer position
+ * @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
*/
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
return false;
@@ -522,16 +574,28 @@ public function blocksDirectSkyLight() : bool{
return $this->getLightFilter() > 0;
}
+ /**
+ * Returns whether this block allows any light to pass through it.
+ */
public function isTransparent() : bool{
return false;
}
+ /**
+ * @deprecated TL;DR: Don't use this function. Its results are confusing and inconsistent.
+ *
+ * No one is sure what the meaning of this property actually is. It's borrowed from Minecraft Java Edition, and is
+ * used by various blocks for support checks.
+ *
+ * Things like signs and banners are considered "solid" despite having no collision box, and things like skulls and
+ * flower pots are considered non-solid despite obviously being "solid" in the conventional, real-world sense.
+ */
public function isSolid() : bool{
return true;
}
/**
- * AKA: Block->isFlowable
+ * Returns whether this block can be destroyed by liquid flowing into its cell.
*/
public function canBeFlowedInto() : bool{
return false;
@@ -553,6 +617,7 @@ final public function getPosition() : Position{
*/
final public function position(World $world, int $x, int $y, int $z) : void{
$this->position = new Position($x, $y, $z, $world);
+ $this->collisionBoxes = null;
}
/**
@@ -703,8 +768,14 @@ public function onIncinerate() : void{
* @return Block
*/
public function getSide(int $side, int $step = 1){
- if($this->position->isValid()){
- return $this->position->getWorld()->getBlock($this->position->getSide($side, $step));
+ $position = $this->position;
+ if($position->isValid()){
+ [$dx, $dy, $dz] = Facing::OFFSET[$side] ?? [0, 0, 0];
+ return $position->getWorld()->getBlockAt(
+ $position->x + ($dx * $step),
+ $position->y + ($dy * $step),
+ $position->z + ($dz * $step)
+ );
}
throw new \LogicException("Block does not have a valid world");
@@ -718,8 +789,14 @@ public function getSide(int $side, int $step = 1){
*/
public function getHorizontalSides() : \Generator{
$world = $this->position->getWorld();
- foreach($this->position->sidesAroundAxis(Axis::Y) as $vector3){
- yield $world->getBlock($vector3);
+ foreach(Facing::HORIZONTAL as $facing){
+ [$dx, $dy, $dz] = Facing::OFFSET[$facing];
+ //TODO: yield Facing as the key?
+ yield $world->getBlockAt(
+ $this->position->x + $dx,
+ $this->position->y + $dy,
+ $this->position->z + $dz
+ );
}
}
@@ -731,8 +808,13 @@ public function getHorizontalSides() : \Generator{
*/
public function getAllSides() : \Generator{
$world = $this->position->getWorld();
- foreach($this->position->sides() as $vector3){
- yield $world->getBlock($vector3);
+ foreach(Facing::OFFSET as [$dx, $dy, $dz]){
+ //TODO: yield Facing as the key?
+ yield $world->getBlockAt(
+ $this->position->x + $dx,
+ $this->position->y + $dy,
+ $this->position->z + $dz
+ );
}
}
@@ -859,7 +941,11 @@ protected function recalculateCollisionBoxes() : array{
* blocks placed on the given face can be supported by this block.
*/
public function getSupportType(int $facing) : SupportType{
- return SupportType::FULL();
+ return SupportType::FULL;
+ }
+
+ protected function getAdjacentSupportType(int $facing) : SupportType{
+ return $this->getSide($facing)->getSupportType(Facing::opposite($facing));
}
public function isFullCube() : bool{
diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php
index 52f5b5d1c65..3914a4b74a1 100644
--- a/src/block/BlockTypeIds.php
+++ b/src/block/BlockTypeIds.php
@@ -562,10 +562,10 @@ private function __construct(){
public const WEIGHTED_PRESSURE_PLATE_HEAVY = 10532;
public const WEIGHTED_PRESSURE_PLATE_LIGHT = 10533;
public const WHEAT = 10534;
-
+ public const BUDDING_AMETHYST = 10535;
public const WHITE_TULIP = 10536;
public const WOOL = 10537;
-
+ public const AMETHYST_CLUSTER = 10538;
public const GLAZED_TERRACOTTA = 10539;
public const AMETHYST = 10540;
public const ANCIENT_DEBRIS = 10541;
@@ -718,8 +718,55 @@ private function __construct(){
public const REINFORCED_DEEPSLATE = 10688;
public const CAVE_VINES = 10689;
public const GLOW_LICHEN = 10690;
+ public const CHERRY_BUTTON = 10691;
+ public const CHERRY_DOOR = 10692;
+ public const CHERRY_FENCE = 10693;
+ public const CHERRY_FENCE_GATE = 10694;
+ public const CHERRY_LEAVES = 10695;
+ public const CHERRY_LOG = 10696;
+ public const CHERRY_PLANKS = 10697;
+ public const CHERRY_PRESSURE_PLATE = 10698;
+ public const CHERRY_SAPLING = 10699;
+ public const CHERRY_SIGN = 10700;
+ public const CHERRY_SLAB = 10701;
+ public const CHERRY_STAIRS = 10702;
+ public const CHERRY_TRAPDOOR = 10703;
+ public const CHERRY_WALL_SIGN = 10704;
+ public const CHERRY_WOOD = 10705;
+ public const SMALL_DRIPLEAF = 10706;
+ public const BIG_DRIPLEAF_HEAD = 10707;
+ public const BIG_DRIPLEAF_STEM = 10708;
+ public const PINK_PETALS = 10709;
+ public const CRIMSON_ROOTS = 10710;
+ public const WARPED_ROOTS = 10711;
+ public const CHISELED_BOOKSHELF = 10712;
+ public const TORCHFLOWER = 10713;
+ public const TORCHFLOWER_CROP = 10714;
+ public const PITCHER_PLANT = 10715;
+ public const PITCHER_CROP = 10716;
+ public const DOUBLE_PITCHER_CROP = 10717;
+ public const CAMPFIRE = 10718;
+ public const SOUL_CAMPFIRE = 10719;
+ public const TUFF_SLAB = 10720;
+ public const TUFF_STAIRS = 10721;
+ public const TUFF_WALL = 10722;
+ public const CHISELED_TUFF = 10723;
+ public const TUFF_BRICKS = 10724;
+ public const TUFF_BRICK_SLAB = 10725;
+ public const TUFF_BRICK_STAIRS = 10726;
+ public const TUFF_BRICK_WALL = 10727;
+ public const CHISELED_TUFF_BRICKS = 10728;
+ public const POLISHED_TUFF = 10729;
+ public const POLISHED_TUFF_SLAB = 10730;
+ public const POLISHED_TUFF_STAIRS = 10731;
+ public const POLISHED_TUFF_WALL = 10732;
+ public const COPPER_BULB = 10733;
+ public const COPPER_DOOR = 10734;
+ public const COPPER_TRAPDOOR = 10735;
+ public const CHISELED_COPPER = 10736;
+ public const COPPER_GRATE = 10737;
- public const FIRST_UNUSED_BLOCK_ID = 10691;
+ public const FIRST_UNUSED_BLOCK_ID = 10738;
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
diff --git a/src/block/BlockTypeInfo.php b/src/block/BlockTypeInfo.php
index eb6b89ad1aa..a1424ef3cf0 100644
--- a/src/block/BlockTypeInfo.php
+++ b/src/block/BlockTypeInfo.php
@@ -35,10 +35,12 @@ final class BlockTypeInfo{
/**
* @param string[] $typeTags
+ * @param string[] $enchantmentTags
*/
public function __construct(
private BlockBreakInfo $breakInfo,
- array $typeTags = []
+ array $typeTags = [],
+ private array $enchantmentTags = []
){
$this->typeTags = array_fill_keys($typeTags, true);
}
@@ -49,4 +51,17 @@ public function getBreakInfo() : BlockBreakInfo{ return $this->breakInfo; }
public function getTypeTags() : array{ return array_keys($this->typeTags); }
public function hasTypeTag(string $tag) : bool{ return isset($this->typeTags[$tag]); }
+
+ /**
+ * Returns tags that represent the type of item being enchanted and are used to determine
+ * what enchantments can be applied to the item of this block during in-game enchanting (enchanting table, anvil, fishing, etc.).
+ * @see ItemEnchantmentTags
+ * @see ItemEnchantmentTagRegistry
+ * @see AvailableEnchantmentRegistry
+ *
+ * @return string[]
+ */
+ public function getEnchantmentTags() : array{
+ return $this->enchantmentTags;
+ }
}
diff --git a/src/block/BlueIce.php b/src/block/BlueIce.php
index f7e9a040468..11e3498dd16 100644
--- a/src/block/BlueIce.php
+++ b/src/block/BlueIce.php
@@ -27,10 +27,6 @@
class BlueIce extends Opaque{
- public function getLightLevel() : int{
- return 1;
- }
-
public function getFrictionFactor() : float{
return 0.99;
}
@@ -38,4 +34,8 @@ public function getFrictionFactor() : float{
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
+
+ public function isAffectedBySilkTouch() : bool{
+ return true;
+ }
}
diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php
index 497d282d750..03969316494 100644
--- a/src/block/BrewingStand.php
+++ b/src/block/BrewingStand.php
@@ -34,6 +34,7 @@
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function array_key_exists;
+use function spl_object_id;
class BrewingStand extends Transparent{
@@ -44,7 +45,7 @@ class BrewingStand extends Transparent{
protected array $slots = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->brewingStandSlots($this->slots);
+ $w->enumSet($this->slots, BrewingStandSlot::cases());
}
protected function recalculateCollisionBoxes() : array{
@@ -61,18 +62,18 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function hasSlot(BrewingStandSlot $slot) : bool{
- return array_key_exists($slot->id(), $this->slots);
+ return array_key_exists(spl_object_id($slot), $this->slots);
}
public function setSlot(BrewingStandSlot $slot, bool $occupied) : self{
if($occupied){
- $this->slots[$slot->id()] = $slot;
+ $this->slots[spl_object_id($slot)] = $slot;
}else{
- unset($this->slots[$slot->id()]);
+ unset($this->slots[spl_object_id($slot)]);
}
return $this;
}
@@ -89,7 +90,7 @@ public function getSlots() : array{
public function setSlots(array $slots) : self{
$this->slots = [];
foreach($slots as $slot){
- $this->slots[$slot->id()] = $slot;
+ $this->slots[spl_object_id($slot)] = $slot;
}
return $this;
}
@@ -114,7 +115,7 @@ public function onScheduledUpdate() : void{
}
$changed = false;
- foreach(BrewingStandSlot::getAll() as $slot){
+ foreach(BrewingStandSlot::cases() as $slot){
$occupied = !$brewing->getInventory()->isSlotEmpty($slot->getSlotNumber());
if($occupied !== $this->hasSlot($slot)){
$this->setSlot($slot, $occupied);
diff --git a/src/block/BuddingAmethyst.php b/src/block/BuddingAmethyst.php
new file mode 100644
index 00000000000..215a038f98e
--- /dev/null
+++ b/src/block/BuddingAmethyst.php
@@ -0,0 +1,68 @@
+getSide($face);
+ //TODO: amethyst buds can spawn in water - we need waterlogging support for this
+
+ $newStage = null;
+
+ if($adjacent->getTypeId() === BlockTypeIds::AIR){
+ $newStage = AmethystCluster::STAGE_SMALL_BUD;
+ }elseif(
+ $adjacent->getTypeId() === BlockTypeIds::AMETHYST_CLUSTER &&
+ $adjacent instanceof AmethystCluster &&
+ $adjacent->getStage() < AmethystCluster::STAGE_CLUSTER &&
+ $adjacent->getFacing() === $face
+ ){
+ $newStage = $adjacent->getStage() + 1;
+ }
+ if($newStage !== null){
+ BlockEventHelper::grow($adjacent, VanillaBlocks::AMETHYST_CLUSTER()->setStage($newStage)->setFacing($face), null);
+ }
+ }
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [];
+ }
+}
diff --git a/src/block/Button.php b/src/block/Button.php
index 85d1d3e091e..73bd1d556bd 100644
--- a/src/block/Button.php
+++ b/src/block/Button.php
@@ -52,7 +52,7 @@ public function setPressed(bool $pressed) : self{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
+ if($this->canBeSupportedAt($blockReplace, $face)){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -83,12 +83,12 @@ public function onScheduledUpdate() : void{
}
public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)){
+ if(!$this->canBeSupportedAt($this, $this->facing)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
- private function canBeSupportedBy(Block $support, int $face) : bool{
- return $support->getSupportType($face)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType(Facing::opposite($face))->hasCenterSupport();
}
}
diff --git a/src/block/Cactus.php b/src/block/Cactus.php
index 8fff294f6f8..6f2b04c8a38 100644
--- a/src/block/Cactus.php
+++ b/src/block/Cactus.php
@@ -23,38 +23,21 @@
namespace pocketmine\block;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
-use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
-use pocketmine\event\block\BlockGrowEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
-use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
class Cactus extends Transparent{
- public const MAX_AGE = 15;
-
- protected int $age = 0;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, 0, self::MAX_AGE, $this->age);
- }
-
- public function getAge() : int{ return $this->age; }
+ use AgeableTrait;
+ use StaticSupportTrait;
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
+ public const MAX_AGE = 15;
public function hasEntityCollision() : bool{
return true;
@@ -69,7 +52,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onEntityInside(Entity $entity) : bool{
@@ -78,23 +61,18 @@ public function onEntityInside(Entity $entity) : bool{
return true;
}
- private function canBeSupportedBy(Block $block) : bool{
- return $block->hasSameTypeId($this) || $block->hasTypeTag(BlockTypeTags::SAND);
- }
-
- public function onNearbyBlockChange() : void{
- $world = $this->position->getWorld();
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $world->useBreakOn($this->position);
- }else{
- foreach(Facing::HORIZONTAL as $side){
- $b = $this->getSide($side);
- if($b->isSolid()){
- $world->useBreakOn($this->position);
- break;
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ if(!$supportBlock->hasSameTypeId($this) && !$supportBlock->hasTypeTag(BlockTypeTags::SAND)){
+ return false;
+ }
+ foreach(Facing::HORIZONTAL as $side){
+ if($block->getSide($side)->isSolid()){
+ return false;
}
}
+
+ return true;
}
public function ticksRandomly() : bool{
@@ -111,36 +89,17 @@ public function onRandomTick() : void{
}
$b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
if($b->getTypeId() === BlockTypeIds::AIR){
- $ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS());
- $ev->call();
- if($ev->isCancelled()){
- break;
- }
- $world->setBlock($b->position, $ev->getNewState());
+ BlockEventHelper::grow($b, VanillaBlocks::CACTUS(), null);
}else{
break;
}
}
$this->age = 0;
- $world->setBlock($this->position, $this);
+ $world->setBlock($this->position, $this, update: false);
}else{
++$this->age;
- $world->setBlock($this->position, $this);
- }
- }
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- foreach(Facing::HORIZONTAL as $side){
- if($this->getSide($side)->isSolid()){
- return false;
- }
+ $world->setBlock($this->position, $this, update: false);
}
-
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
-
- return false;
}
}
diff --git a/src/block/Cake.php b/src/block/Cake.php
index 3e26e59b06a..073fc62ac90 100644
--- a/src/block/Cake.php
+++ b/src/block/Cake.php
@@ -37,7 +37,7 @@ class Cake extends BaseCake{
protected int $bites = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, 0, self::MAX_BITES, $this->bites);
+ $w->boundedIntAuto(0, self::MAX_BITES, $this->bites);
}
/**
@@ -64,7 +64,7 @@ public function setBites(int $bites) : self{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
- if($item instanceof ItemBlock){
+ if($this->bites === 0 && $item instanceof ItemBlock){
$block = $item->getBlock();
$resultBlock = null;
if($block->getTypeId() === BlockTypeIds::CANDLE){
@@ -83,6 +83,10 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return parent::onInteract($item, $face, $clickVector, $player, $returnedItems);
}
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [];
+ }
+
public function getResidue() : Block{
$clone = clone $this;
$clone->bites++;
diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php
index 187442cd98e..4479faee103 100644
--- a/src/block/CakeWithCandle.php
+++ b/src/block/CakeWithCandle.php
@@ -64,7 +64,7 @@ public function getDropsForCompatibleTool(Item $item) : array{
}
public function getPickedItem(bool $addUserData = false) : Item{
- return VanillaBlocks::CAKE()->getPickedItem($addUserData);
+ return VanillaBlocks::CAKE()->asItem();
}
public function getResidue() : Block{
diff --git a/src/block/CakeWithDyedCandle.php b/src/block/CakeWithDyedCandle.php
index e01ad3e1b97..0dff358e164 100644
--- a/src/block/CakeWithDyedCandle.php
+++ b/src/block/CakeWithDyedCandle.php
@@ -30,7 +30,7 @@ class CakeWithDyedCandle extends CakeWithCandle{
use ColoredTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
+ $this->color = DyeColor::WHITE;
parent::__construct($idInfo, $name, $typeInfo);
}
diff --git a/src/block/Campfire.php b/src/block/Campfire.php
new file mode 100644
index 00000000000..ce759ee87f7
--- /dev/null
+++ b/src/block/Campfire.php
@@ -0,0 +1,277 @@
+ ticks
+ * @phpstan-var array
+ */
+ protected array $cookingTimes = [];
+
+ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
+ $this->encodeFacingState($w);
+ $this->encodeLitState($w);
+ }
+
+ public function readStateFromWorld() : Block{
+ parent::readStateFromWorld();
+ $tile = $this->position->getWorld()->getTile($this->position);
+ if($tile instanceof TileCampfire){
+ $this->inventory = $tile->getInventory();
+ $this->cookingTimes = $tile->getCookingTimes();
+ }else{
+ $this->inventory = new CampfireInventory($this->position);
+ }
+
+ return $this;
+ }
+
+ public function writeStateToWorld() : void{
+ parent::writeStateToWorld();
+ $tile = $this->position->getWorld()->getTile($this->position);
+ if($tile instanceof TileCampfire){
+ $tile->setCookingTimes($this->cookingTimes);
+ }
+ }
+
+ public function hasEntityCollision() : bool{
+ return true;
+ }
+
+ public function getLightLevel() : int{
+ return $this->lit ? 15 : 0;
+ }
+
+ public function isAffectedBySilkTouch() : bool{
+ return true;
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [
+ VanillaItems::CHARCOAL()->setCount(2)
+ ];
+ }
+
+ public function getSupportType(int $facing) : SupportType{
+ return SupportType::NONE;
+ }
+
+ protected function recalculateCollisionBoxes() : array{
+ return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)];
+ }
+
+ public function getInventory() : CampfireInventory{
+ return $this->inventory;
+ }
+
+ protected function getFurnaceType() : FurnaceType{
+ return FurnaceType::CAMPFIRE;
+ }
+
+ protected function getEntityCollisionDamage() : int{
+ return 1;
+ }
+
+ /**
+ * Sets the number of ticks during the item in the given slot has been cooked.
+ */
+ public function setCookingTime(int $slot, int $time) : void{
+ if($slot < 0 || $slot > 3){
+ throw new \InvalidArgumentException("Slot must be in range 0-3");
+ }
+ if($time < 0 || $time > $this->getFurnaceType()->getCookDurationTicks()){
+ throw new \InvalidArgumentException("CookingTime must be in range 0-" . $this->getFurnaceType()->getCookDurationTicks());
+ }
+ $this->cookingTimes[$slot] = $time;
+ }
+
+ /**
+ * Returns the number of ticks during the item in the given slot has been cooked.
+ */
+ public function getCookingTime(int $slot) : int{
+ return $this->cookingTimes[$slot] ?? 0;
+ }
+
+ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+ if($this->getSide(Facing::DOWN) instanceof Campfire){
+ return false;
+ }
+ if($player !== null){
+ $this->facing = $player->getHorizontalFacing();
+ }
+ $this->lit = true;
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if(!$this->lit){
+ if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
+ $item->pop();
+ $this->ignite();
+ $this->position->getWorld()->addSound($this->position, new BlazeShootSound());
+ return true;
+ }elseif($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
+ if($item instanceof Durable){
+ $item->applyDamage(1);
+ }
+ $this->ignite();
+ return true;
+ }
+ }elseif($item instanceof Shovel){
+ $item->applyDamage(1);
+ $this->extinguish();
+ return true;
+ }
+
+ if($this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){
+ $ingredient = clone $item;
+ $ingredient->setCount(1);
+ if(count($this->inventory->addItem($ingredient)) === 0){
+ $item->pop();
+ $this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function onNearbyBlockChange() : void{
+ if($this->lit && $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER){
+ $this->extinguish();
+ //TODO: Waterlogging
+ }
+ }
+
+ public function onEntityInside(Entity $entity) : bool{
+ if(!$this->lit){
+ if($entity->isOnFire()){
+ $this->ignite();
+ return false;
+ }
+ }elseif($entity instanceof Living){
+ $entity->attack(new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, $this->getEntityCollisionDamage()));
+ }
+ return true;
+ }
+
+ public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
+ if($this->lit && $projectile instanceof SplashPotion && $projectile->getPotionType() === PotionType::WATER){
+ $this->extinguish();
+ }
+ }
+
+ public function onScheduledUpdate() : void{
+ if($this->lit){
+ $items = $this->inventory->getContents();
+ $furnaceType = $this->getFurnaceType();
+ $maxCookDuration = $furnaceType->getCookDurationTicks();
+ foreach($items as $slot => $item){
+ $this->setCookingTime($slot, min($maxCookDuration, $this->getCookingTime($slot) + self::UPDATE_INTERVAL_TICKS));
+ if($this->getCookingTime($slot) >= $maxCookDuration){
+ $result =
+ ($recipe = $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($furnaceType)->match($item)) instanceof FurnaceRecipe ?
+ $recipe->getResult() :
+ VanillaItems::AIR();
+
+ $ev = new CampfireCookEvent($this, $slot, $item, $result);
+ $ev->call();
+
+ if ($ev->isCancelled()){
+ continue;
+ }
+
+ $this->inventory->setItem($slot, VanillaItems::AIR());
+ $this->setCookingTime($slot, 0);
+ $this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult());
+ }
+ }
+ if(count($items) > 0){
+ $this->position->getWorld()->setBlock($this->position, $this);
+ }
+ if(mt_rand(1, 6) === 1){
+ $this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound());
+ }
+ $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS);
+ }
+ }
+
+ private function extinguish() : void{
+ $this->position->getWorld()->addSound($this->position, new FireExtinguishSound());
+ $this->position->getWorld()->setBlock($this->position, $this->setLit(false));
+ }
+
+ private function ignite() : void{
+ $this->position->getWorld()->addSound($this->position, new FlintSteelSound());
+ $this->position->getWorld()->setBlock($this->position, $this->setLit(true));
+ $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS);
+ }
+}
diff --git a/src/block/Candle.php b/src/block/Candle.php
index 5936a081205..7f22641e118 100644
--- a/src/block/Candle.php
+++ b/src/block/Candle.php
@@ -48,7 +48,7 @@ class Candle extends Transparent{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$this->encodeLitState($w);
- $w->boundedInt(2, self::MIN_COUNT, self::MAX_COUNT, $this->count);
+ $w->boundedIntAuto(self::MIN_COUNT, self::MAX_COUNT, $this->count);
}
public function getCount() : int{ return $this->count; }
@@ -91,7 +91,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
protected function getCandleIfCompatibleType(Block $block) : ?Candle{
@@ -104,8 +104,7 @@ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $fa
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $blockReplace->getSide(Facing::DOWN);
- if(!$down->getSupportType(Facing::UP)->hasCenterSupport()){
+ if(!$blockReplace->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport()){
return false;
}
$existing = $this->getCandleIfCompatibleType($blockReplace);
diff --git a/src/block/Carpet.php b/src/block/Carpet.php
index c979571ebfa..1ee7240c516 100644
--- a/src/block/Carpet.php
+++ b/src/block/Carpet.php
@@ -24,21 +24,13 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
-use pocketmine\item\Item;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
class Carpet extends Flowable{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
+ use StaticSupportTrait;
public function isSolid() : bool{
return true;
@@ -51,19 +43,8 @@ protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)];
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $this->getSide(Facing::DOWN);
- if($down->getTypeId() !== BlockTypeIds::AIR){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
- }
-
- public function onNearbyBlockChange() : void{
- if($this->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::AIR){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::AIR;
}
public function getFlameEncouragement() : int{
diff --git a/src/block/Carrot.php b/src/block/Carrot.php
index 895b0a37db1..7d8947afaf7 100644
--- a/src/block/Carrot.php
+++ b/src/block/Carrot.php
@@ -23,15 +23,15 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
class Carrot extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 4) : 1)
+ VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
];
}
diff --git a/src/block/CarvedPumpkin.php b/src/block/CarvedPumpkin.php
index 5fc73d0889d..98f3c2307ac 100644
--- a/src/block/CarvedPumpkin.php
+++ b/src/block/CarvedPumpkin.php
@@ -24,9 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
class CarvedPumpkin extends Opaque{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
}
diff --git a/src/block/Cauldron.php b/src/block/Cauldron.php
index da1a938b297..772583a5af9 100644
--- a/src/block/Cauldron.php
+++ b/src/block/Cauldron.php
@@ -61,7 +61,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return $facing === Facing::UP ? SupportType::EDGE() : SupportType::NONE();
+ return $facing === Facing::UP ? SupportType::EDGE : SupportType::NONE;
}
/**
@@ -83,7 +83,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
}elseif($item->getTypeId() === ItemTypeIds::POWDER_SNOW_BUCKET){
//TODO: powder snow cauldron
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
- if($item->getType()->equals(PotionType::WATER())){
+ if($item->getType() === PotionType::WATER){
$this->fill(WaterCauldron::WATER_BOTTLE_FILL_AMOUNT, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
}else{
$this->fill(PotionCauldron::POTION_FILL_AMOUNT, VanillaBlocks::POTION_CAULDRON()->setPotionItem($item), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
diff --git a/src/block/CaveVines.php b/src/block/CaveVines.php
index 55f73fb6554..daa973507f3 100644
--- a/src/block/CaveVines.php
+++ b/src/block/CaveVines.php
@@ -23,10 +23,12 @@
namespace pocketmine\block;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
-use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@@ -38,14 +40,16 @@
use function mt_rand;
class CaveVines extends Flowable{
+ use AgeableTrait;
+ use StaticSupportTrait;
+
public const MAX_AGE = 25;
- protected int $age = 0;
protected bool $berries = false;
protected bool $head = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(5, 0, self::MAX_AGE, $this->age);
+ $w->boundedIntAuto(0, self::MAX_AGE, $this->age);
$w->bool($this->berries);
$w->bool($this->head);
}
@@ -66,19 +70,6 @@ public function setHead(bool $head) : self{
return $this;
}
- public function getAge() : int{
- return $this->age;
- }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0-" . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
-
public function canClimb() : bool{
return true;
}
@@ -87,20 +78,12 @@ public function getLightLevel() : int{
return $this->berries ? 14 : 0;
}
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::DOWN)->equals(SupportType::FULL()) || $block->hasSameTypeId($this);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::UP))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::UP);
+ return $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || $supportBlock->hasSameTypeId($this);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP))){
- return false;
- }
$this->age = mt_rand(0, self::MAX_AGE);
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -114,16 +97,12 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return true;
}
if($item instanceof Fertilizer){
- $ev = new BlockGrowEvent($this, (clone $this)
+ $newState = (clone $this)
->setBerries(true)
- ->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this))
- );
- $ev->call();
- if($ev->isCancelled()){
- return false;
+ ->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this));
+ if(BlockEventHelper::grow($this, $newState, $player)){
+ $item->pop();
}
- $item->pop();
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
return true;
}
return false;
@@ -141,16 +120,10 @@ public function onRandomTick() : void{
if($world->isInWorld($growthPos->getFloorX(), $growthPos->getFloorY(), $growthPos->getFloorZ())){
$block = $world->getBlock($growthPos);
if($block->getTypeId() === BlockTypeIds::AIR){
- $ev = new BlockGrowEvent($block, VanillaBlocks::CAVE_VINES()
+ $newState = VanillaBlocks::CAVE_VINES()
->setAge($this->age + 1)
- ->setBerries(mt_rand(1, 9) === 1)
- );
-
- $ev->call();
-
- if(!$ev->isCancelled()){
- $world->setBlock($growthPos, $ev->getNewState());
- }
+ ->setBerries(mt_rand(1, 9) === 1);
+ BlockEventHelper::grow($block, $newState, null);
}
}
}
@@ -186,6 +159,6 @@ public function asItem() : Item{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/Chain.php b/src/block/Chain.php
index fa8ffd1e822..e9cc2c9be8d 100644
--- a/src/block/Chain.php
+++ b/src/block/Chain.php
@@ -33,7 +33,7 @@ final class Chain extends Transparent{
use PillarRotationTrait;
public function getSupportType(int $facing) : SupportType{
- return $this->axis === Axis::Y && Facing::axis($facing) === Axis::Y ? SupportType::CENTER() : SupportType::NONE();
+ return $this->axis === Axis::Y && Facing::axis($facing) === Axis::Y ? SupportType::CENTER : SupportType::NONE;
}
protected function recalculateCollisionBoxes() : array{
diff --git a/src/block/ChemistryTable.php b/src/block/ChemistryTable.php
index 27fb6367465..058e4028839 100644
--- a/src/block/ChemistryTable.php
+++ b/src/block/ChemistryTable.php
@@ -24,14 +24,12 @@
namespace pocketmine\block;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
final class ChemistryTable extends Opaque{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
//TODO
diff --git a/src/block/Chest.php b/src/block/Chest.php
index 45c19050585..dca21576aa9 100644
--- a/src/block/Chest.php
+++ b/src/block/Chest.php
@@ -25,7 +25,6 @@
use pocketmine\block\tile\Chest as TileChest;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\event\block\ChestPairEvent;
use pocketmine\item\Item;
@@ -36,7 +35,6 @@
class Chest extends Transparent{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
/**
* @return AxisAlignedBB[]
@@ -47,7 +45,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onPostPlace() : void{
diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php
new file mode 100644
index 00000000000..73c4861bf35
--- /dev/null
+++ b/src/block/ChiseledBookshelf.php
@@ -0,0 +1,174 @@
+
+ */
+ private array $slots = [];
+
+ private ?ChiseledBookshelfSlot $lastInteractedSlot = null;
+
+ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
+ $w->horizontalFacing($this->facing);
+ $w->enumSet($this->slots, ChiseledBookshelfSlot::cases());
+ }
+
+ public function readStateFromWorld() : Block{
+ $tile = $this->position->getWorld()->getTile($this->position);
+ if($tile instanceof TileChiseledBookshelf){
+ $this->lastInteractedSlot = $tile->getLastInteractedSlot();
+ }else{
+ $this->lastInteractedSlot = null;
+ }
+ return $this;
+ }
+
+ public function writeStateToWorld() : void{
+ parent::writeStateToWorld();
+
+ $tile = $this->position->getWorld()->getTile($this->position);
+ if($tile instanceof TileChiseledBookshelf){
+ $tile->setLastInteractedSlot($this->lastInteractedSlot);
+ }
+ }
+
+ /**
+ * Returns whether the given slot is displayed as occupied.
+ * This doesn't guarantee that there is or isn't a book in the bookshelf's inventory.
+ */
+ public function hasSlot(ChiseledBookshelfSlot $slot) : bool{
+ return isset($this->slots[spl_object_id($slot)]);
+ }
+
+ /**
+ * Sets whether the given slot is displayed as occupied.
+ *
+ * This doesn't modify the bookshelf's inventory, so you can use this to make invisible
+ * books or display books that aren't actually in the bookshelf.
+ *
+ * To modify the contents of the bookshelf inventory, access the tile inventory.
+ *
+ * @return $this
+ */
+ public function setSlot(ChiseledBookshelfSlot $slot, bool $occupied) : self{
+ if($occupied){
+ $this->slots[spl_object_id($slot)] = $slot;
+ }else{
+ unset($this->slots[spl_object_id($slot)]);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns which slots of the bookshelf are displayed as occupied.
+ * As above, these values do not necessarily reflect the contents of the bookshelf inventory,
+ * although they usually will unless modified by plugins.
+ *
+ * @return ChiseledBookshelfSlot[]
+ * @phpstan-return array
+ */
+ public function getSlots() : array{
+ return $this->slots;
+ }
+
+ /**
+ * Returns the last slot interacted by a player or null if no slot has been interacted with yet.
+ */
+ public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{
+ return $this->lastInteractedSlot;
+ }
+
+ /**
+ * Sets the last slot interacted by a player.
+ *
+ * @return $this
+ */
+ public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : self{
+ $this->lastInteractedSlot = $lastInteractedSlot;
+ return $this;
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($face !== $this->facing){
+ return false;
+ }
+
+ $x = Facing::axis($face) === Axis::X ? $clickVector->z : $clickVector->x;
+ $slot = ChiseledBookshelfSlot::fromBlockFaceCoordinates(
+ Facing::isPositive(Facing::rotateY($face, true)) ? 1 - $x : $x,
+ $clickVector->y
+ );
+ $tile = $this->position->getWorld()->getTile($this->position);
+ if(!$tile instanceof TileChiseledBookshelf){
+ return false;
+ }
+
+ $inventory = $tile->getInventory();
+ if(!$inventory->isSlotEmpty($slot->value)){
+ $returnedItems[] = $inventory->getItem($slot->value);
+ $inventory->clear($slot->value);
+ $this->setSlot($slot, false);
+ $this->lastInteractedSlot = $slot;
+ }elseif($item instanceof WritableBookBase || $item instanceof Book || $item instanceof EnchantedBook){
+ //TODO: type tags like blocks would be better for this
+ $inventory->setItem($slot->value, $item->pop());
+ $this->setSlot($slot, true);
+ $this->lastInteractedSlot = $slot;
+ }else{
+ return true;
+ }
+
+ $this->position->getWorld()->setBlock($this->position, $this);
+ return true;
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [];
+ }
+
+ public function isAffectedBySilkTouch() : bool{
+ return true;
+ }
+}
diff --git a/src/block/ChorusFlower.php b/src/block/ChorusFlower.php
index 5c5077f22ac..cc3c606d92d 100644
--- a/src/block/ChorusFlower.php
+++ b/src/block/ChorusFlower.php
@@ -23,52 +23,38 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\block\StructureGrowEvent;
-use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
-use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
-use pocketmine\world\Position;
use pocketmine\world\sound\ChorusFlowerDieSound;
use pocketmine\world\sound\ChorusFlowerGrowSound;
use pocketmine\world\World;
use function array_rand;
+use function min;
use function mt_rand;
final class ChorusFlower extends Flowable{
+ use AgeableTrait;
+ use StaticSupportTrait;
+
public const MIN_AGE = 0;
public const MAX_AGE = 5;
private const MAX_STEM_HEIGHT = 5;
- private int $age = self::MIN_AGE;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, self::MIN_AGE, self::MAX_AGE, $this->age);
- }
-
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < self::MIN_AGE || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in the range " . self::MIN_AGE . " ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
-
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()];
}
- private function canBeSupportedAt(Position $position) : bool{
+ private function canBeSupportedAt(Block $block) : bool{
+ $position = $block->position;
$world = $position->getWorld();
$down = $world->getBlock($position->down());
@@ -93,25 +79,10 @@ private function canBeSupportedAt(Position $position) : bool{
return $plantAdjacent;
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedAt($blockReplace->getPosition())){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedAt($this->position)){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
$this->position->getWorld()->useBreakOn($this->position);
}
- public function ticksRandomly() : bool{ return $this->age < self::MAX_AGE; }
-
/**
* @phpstan-return array{int, bool}
*/
@@ -181,11 +152,13 @@ private function grow(int $facing, int $ageChange, ?BlockTransaction $tx) : Bloc
if($tx === null){
$tx = new BlockTransaction($this->position->getWorld());
}
- $tx->addBlock($this->position->getSide($facing), (clone $this)->setAge($this->getAge() + $ageChange));
+ $tx->addBlock($this->position->getSide($facing), (clone $this)->setAge(min(self::MAX_AGE, $this->age + $ageChange)));
return $tx;
}
+ public function ticksRandomly() : bool{ return $this->age < self::MAX_AGE; }
+
public function onRandomTick() : void{
$world = $this->position->getWorld();
diff --git a/src/block/ChorusPlant.php b/src/block/ChorusPlant.php
index f7642bd0480..9013f682564 100644
--- a/src/block/ChorusPlant.php
+++ b/src/block/ChorusPlant.php
@@ -23,18 +23,16 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
-use pocketmine\world\Position;
use function mt_rand;
final class ChorusPlant extends Flowable{
+ use StaticSupportTrait;
protected function recalculateCollisionBoxes() : array{
$bb = AxisAlignedBB::one();
@@ -52,7 +50,8 @@ private function canBeSupportedBy(Block $block) : bool{
return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE;
}
- private function canStay(Position $position) : bool{
+ private function canBeSupportedAt(Block $block) : bool{
+ $position = $block->position;
$world = $position->getWorld();
$down = $world->getBlock($position->down());
@@ -72,24 +71,7 @@ private function canStay(Position $position) : bool{
}
}
- if($this->canBeSupportedBy($down)){
- return true;
- }
-
- return false;
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canStay($blockReplace->getPosition())){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canStay($this->position)){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ return $this->canBeSupportedBy($down);
}
public function getDropsForCompatibleTool(Item $item) : array{
diff --git a/src/block/CoalOre.php b/src/block/CoalOre.php
index 47a0e255d26..19032b011a8 100644
--- a/src/block/CoalOre.php
+++ b/src/block/CoalOre.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -31,7 +32,7 @@ class CoalOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::COAL()
+ VanillaItems::COAL()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}
diff --git a/src/block/CocoaBlock.php b/src/block/CocoaBlock.php
index aafce31693c..6d8ce1adc9c 100644
--- a/src/block/CocoaBlock.php
+++ b/src/block/CocoaBlock.php
@@ -23,11 +23,11 @@
namespace pocketmine\block;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\HorizontalFacingTrait;
-use pocketmine\block\utils\SupportType;
use pocketmine\block\utils\WoodType;
use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@@ -39,27 +39,15 @@
use pocketmine\world\BlockTransaction;
use function mt_rand;
-class CocoaBlock extends Transparent{
+class CocoaBlock extends Flowable{
use HorizontalFacingTrait;
+ use AgeableTrait;
public const MAX_AGE = 2;
- protected int $age = 0;
-
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
- $w->boundedInt(2, 0, self::MAX_AGE, $this->age);
- }
-
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
+ $w->boundedIntAuto(0, self::MAX_AGE, $this->age);
}
/**
@@ -76,12 +64,8 @@ protected function recalculateCollisionBoxes() : array{
];
}
- public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
- }
-
private function canAttachTo(Block $block) : bool{
- return $block instanceof Wood && $block->getWoodType()->equals(WoodType::JUNGLE());
+ return $block instanceof Wood && $block->getWoodType() === WoodType::JUNGLE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
@@ -110,7 +94,7 @@ public function onNearbyBlockChange() : void{
}
public function ticksRandomly() : bool{
- return true;
+ return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{
@@ -123,12 +107,7 @@ private function grow(?Player $player = null) : bool{
if($this->age < self::MAX_AGE){
$block = clone $this;
$block->age++;
- $ev = new BlockGrowEvent($this, $block, $player);
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
- return true;
- }
+ return BlockEventHelper::grow($this, $block, $player);
}
return false;
}
diff --git a/src/block/Concrete.php b/src/block/Concrete.php
index cb8ee3b5295..fae6f8e2f82 100644
--- a/src/block/Concrete.php
+++ b/src/block/Concrete.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
class Concrete extends Opaque{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/ConcretePowder.php b/src/block/ConcretePowder.php
index 02fa31adbe1..59f14bc7226 100644
--- a/src/block/ConcretePowder.php
+++ b/src/block/ConcretePowder.php
@@ -23,11 +23,10 @@
namespace pocketmine\block;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
-use pocketmine\event\block\BlockFormEvent;
use pocketmine\math\Facing;
class ConcretePowder extends Opaque implements Fallable{
@@ -36,18 +35,9 @@ class ConcretePowder extends Opaque implements Fallable{
onNearbyBlockChange as protected startFalling;
}
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
public function onNearbyBlockChange() : void{
if(($water = $this->getAdjacentWater()) !== null){
- $ev = new BlockFormEvent($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water);
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::form($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water);
}else{
$this->startFalling();
}
diff --git a/src/block/Copper.php b/src/block/Copper.php
index 1da253fa418..d285e6ec0ae 100644
--- a/src/block/Copper.php
+++ b/src/block/Copper.php
@@ -23,8 +23,9 @@
namespace pocketmine\block;
+use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
-class Copper extends Opaque{
+class Copper extends Opaque implements CopperMaterial{
use CopperTrait;
}
diff --git a/src/block/CopperBulb.php b/src/block/CopperBulb.php
new file mode 100644
index 00000000000..97fc209fee3
--- /dev/null
+++ b/src/block/CopperBulb.php
@@ -0,0 +1,69 @@
+encodeLitState($w);
+ $w->bool($this->powered);
+ }
+
+ /** @return $this */
+ public function togglePowered(bool $powered) : self{
+ if($powered === $this->powered){
+ return $this;
+ }
+ if ($powered) {
+ $this->setLit(!$this->lit);
+ }
+ $this->setPowered($powered);
+ return $this;
+ }
+
+ public function getLightLevel() : int{
+ if ($this->lit) {
+ return match($this->oxidation){
+ CopperOxidation::NONE => 15,
+ CopperOxidation::EXPOSED => 12,
+ CopperOxidation::WEATHERED => 8,
+ CopperOxidation::OXIDIZED => 4,
+ };
+ }
+
+ return 0;
+ }
+}
diff --git a/src/block/CopperDoor.php b/src/block/CopperDoor.php
new file mode 100644
index 00000000000..82a611206cf
--- /dev/null
+++ b/src/block/CopperDoor.php
@@ -0,0 +1,53 @@
+isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) {
+ //copy copper properties to other half
+ $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
+ $world = $this->position->getWorld();
+ if ($other instanceof CopperDoor) {
+ $other->setOxidation($this->oxidation);
+ $other->setWaxed($this->waxed);
+ $world->setBlock($other->position, $other);
+ }
+ return true;
+ }
+
+ return parent::onInteract($item, $face, $clickVector, $player, $returnedItems);
+ }
+}
diff --git a/src/block/CopperGrate.php b/src/block/CopperGrate.php
new file mode 100644
index 00000000000..d646d133342
--- /dev/null
+++ b/src/block/CopperGrate.php
@@ -0,0 +1,33 @@
+setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 5)),
+ ];
}
public function isAffectedBySilkTouch() : bool{ return true; }
diff --git a/src/block/CopperSlab.php b/src/block/CopperSlab.php
index 088ace11e1b..cc1838e2930 100644
--- a/src/block/CopperSlab.php
+++ b/src/block/CopperSlab.php
@@ -23,8 +23,9 @@
namespace pocketmine\block;
+use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
-class CopperSlab extends Slab{
+class CopperSlab extends Slab implements CopperMaterial{
use CopperTrait;
}
diff --git a/src/block/CopperStairs.php b/src/block/CopperStairs.php
index fe52616ce90..ecb22431959 100644
--- a/src/block/CopperStairs.php
+++ b/src/block/CopperStairs.php
@@ -23,14 +23,9 @@
namespace pocketmine\block;
-use pocketmine\block\utils\CopperOxidation;
+use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
-class CopperStairs extends Stair{
+class CopperStairs extends Stair implements CopperMaterial{
use CopperTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->oxidation = CopperOxidation::NONE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/CopperTrapdoor.php b/src/block/CopperTrapdoor.php
new file mode 100644
index 00000000000..e7d56fa0c42
--- /dev/null
+++ b/src/block/CopperTrapdoor.php
@@ -0,0 +1,44 @@
+isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) {
+ return true;
+ }
+
+ return parent::onInteract($item, $face, $clickVector, $player, $returnedItems);
+ }
+}
diff --git a/src/block/Coral.php b/src/block/Coral.php
index b621a3ab0d4..96c6d4fe0ee 100644
--- a/src/block/Coral.php
+++ b/src/block/Coral.php
@@ -23,31 +23,13 @@
namespace pocketmine\block;
-use pocketmine\item\Item;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
final class Coral extends BaseCoral{
+ use StaticSupportTrait;
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($tx->fetchBlock($blockReplace->getPosition()->down()))){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- $world = $this->position->getWorld();
- if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){
- $world->useBreakOn($this->position);
- }else{
- parent::onNearbyBlockChange();
- }
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::UP)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();
}
}
diff --git a/src/block/CoralBlock.php b/src/block/CoralBlock.php
index b902465617c..3e7ca8224f7 100644
--- a/src/block/CoralBlock.php
+++ b/src/block/CoralBlock.php
@@ -23,20 +23,14 @@
namespace pocketmine\block;
-use pocketmine\block\utils\CoralType;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralTypeTrait;
-use pocketmine\event\block\BlockDeathEvent;
use pocketmine\item\Item;
use function mt_rand;
final class CoralBlock extends Opaque{
use CoralTypeTrait;
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->coralType = CoralType::TUBE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
public function onNearbyBlockChange() : void{
if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));
@@ -55,17 +49,13 @@ public function onScheduledUpdate() : void{
}
}
if(!$hasWater){
- $ev = new BlockDeathEvent($this, $this->setDead(true));
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::die($this, (clone $this)->setDead(true));
}
}
}
public function getDropsForCompatibleTool(Item $item) : array{
- return [$this->setDead(true)->asItem()];
+ return [(clone $this)->setDead(true)->asItem()];
}
public function isAffectedBySilkTouch() : bool{
diff --git a/src/block/Crops.php b/src/block/Crops.php
index 8949e663b1b..e90ac623679 100644
--- a/src/block/Crops.php
+++ b/src/block/Crops.php
@@ -23,56 +23,36 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockGrowEvent;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\CropGrowthHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
use function mt_rand;
abstract class Crops extends Flowable{
- public const MAX_AGE = 7;
-
- protected int $age = 0;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, 0, self::MAX_AGE, $this->age);
- }
-
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
+ use AgeableTrait;
+ use StaticSupportTrait;
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($blockReplace->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
+ public const MAX_AGE = 7;
- return false;
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($this->age < self::MAX_AGE && $item instanceof Fertilizer){
$block = clone $this;
- $block->age += mt_rand(2, 5);
- if($block->age > self::MAX_AGE){
- $block->age = self::MAX_AGE;
+ $tempAge = $block->age + mt_rand(2, 5);
+ if($tempAge > self::MAX_AGE){
+ $tempAge = self::MAX_AGE;
}
-
- $ev = new BlockGrowEvent($this, $block, $player);
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
+ $block->age = $tempAge;
+ if(BlockEventHelper::grow($this, $block, $player)){
$item->pop();
}
@@ -82,25 +62,15 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return false;
}
- public function onNearbyBlockChange() : void{
- if($this->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::FARMLAND){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
public function ticksRandomly() : bool{
- return true;
+ return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{
- if($this->age < self::MAX_AGE && mt_rand(0, 2) === 1){
+ if($this->age < self::MAX_AGE && CropGrowthHelper::canGrow($this)){
$block = clone $this;
++$block->age;
- $ev = new BlockGrowEvent($this, $block);
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::grow($this, $block, null);
}
}
}
diff --git a/src/block/DaylightSensor.php b/src/block/DaylightSensor.php
index e1f78aef5b9..4141a2b7eda 100644
--- a/src/block/DaylightSensor.php
+++ b/src/block/DaylightSensor.php
@@ -42,7 +42,7 @@ class DaylightSensor extends Transparent{
protected bool $inverted = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, 0, 15, $this->signalStrength);
+ $w->boundedIntAuto(0, 15, $this->signalStrength);
$w->bool($this->inverted);
}
@@ -70,7 +70,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
diff --git a/src/block/DeadBush.php b/src/block/DeadBush.php
index 4565332a36d..0c089a6f382 100644
--- a/src/block/DeadBush.php
+++ b/src/block/DeadBush.php
@@ -23,29 +23,14 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
use function mt_rand;
class DeadBush extends Flowable{
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
+ use StaticSupportTrait;
public function getDropsForIncompatibleTool(Item $item) : array{
return [
@@ -65,14 +50,21 @@ public function getFlammability() : int{
return 100;
}
- private function canBeSupportedBy(Block $block) : bool{
- $blockId = $block->getTypeId();
- return $blockId === BlockTypeIds::SAND
- || $blockId === BlockTypeIds::RED_SAND
- || $blockId === BlockTypeIds::PODZOL
- || $blockId === BlockTypeIds::MYCELIUM
- || $blockId === BlockTypeIds::DIRT
- || $blockId === BlockTypeIds::HARDENED_CLAY
- || $blockId === BlockTypeIds::STAINED_CLAY;
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ return
+ $supportBlock->hasTypeTag(BlockTypeTags::SAND) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
+ match($supportBlock->getTypeId()){
+ //can't use DIRT tag here because it includes farmland
+ BlockTypeIds::PODZOL,
+ BlockTypeIds::MYCELIUM,
+ BlockTypeIds::DIRT,
+ BlockTypeIds::GRASS,
+ BlockTypeIds::HARDENED_CLAY,
+ BlockTypeIds::STAINED_CLAY => true,
+ //TODO: moss block
+ default => false,
+ };
}
}
diff --git a/src/block/DiamondOre.php b/src/block/DiamondOre.php
index 8407bdf15ea..b0486dcfb42 100644
--- a/src/block/DiamondOre.php
+++ b/src/block/DiamondOre.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -31,7 +32,7 @@ class DiamondOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::DIAMOND()
+ VanillaItems::DIAMOND()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}
diff --git a/src/block/Dirt.php b/src/block/Dirt.php
index 539454b4157..104080d318c 100644
--- a/src/block/Dirt.php
+++ b/src/block/Dirt.php
@@ -38,15 +38,10 @@
use pocketmine\world\sound\WaterSplashSound;
class Dirt extends Opaque{
- protected DirtType $dirtType;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->dirtType = DirtType::NORMAL();
- parent::__construct($idInfo, $name, $typeInfo);
- }
+ protected DirtType $dirtType = DirtType::NORMAL;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->dirtType($this->dirtType);
+ $w->enum($this->dirtType);
}
public function getDirtType() : DirtType{ return $this->dirtType; }
@@ -59,19 +54,24 @@ public function setDirtType(DirtType $dirtType) : self{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
$world = $this->position->getWorld();
- if($face === Facing::UP && $item instanceof Hoe){
+ if($face !== Facing::DOWN && $item instanceof Hoe){
+ $up = $this->getSide(Facing::UP);
+ if($up->getTypeId() !== BlockTypeIds::AIR){
+ return true;
+ }
+
$item->applyDamage(1);
- $newBlock = $this->dirtType->equals(DirtType::NORMAL()) ? VanillaBlocks::FARMLAND() : VanillaBlocks::DIRT();
+ $newBlock = $this->dirtType === DirtType::NORMAL ? VanillaBlocks::FARMLAND() : VanillaBlocks::DIRT();
$center = $this->position->add(0.5, 0.5, 0.5);
$world->addSound($center, new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
- if($this->dirtType->equals(DirtType::ROOTED())){
+ if($this->dirtType === DirtType::ROOTED){
$world->dropItem($center, VanillaBlocks::HANGING_ROOTS()->asItem());
}
return true;
- }elseif($this->dirtType->equals(DirtType::ROOTED()) && $item instanceof Fertilizer){
+ }elseif($this->dirtType === DirtType::ROOTED && $item instanceof Fertilizer){
$down = $this->getSide(Facing::DOWN);
if($down->getTypeId() !== BlockTypeIds::AIR){
return true;
@@ -80,7 +80,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
$item->pop();
$world->setBlock($down->position, VanillaBlocks::HANGING_ROOTS());
//TODO: bonemeal particles, growth sounds
- }elseif(($item instanceof Potion || $item instanceof SplashPotion) && $item->getType()->equals(PotionType::WATER())){
+ }elseif(($item instanceof Potion || $item instanceof SplashPotion) && $item->getType() === PotionType::WATER){
$item->pop();
$world->setBlock($this->position, VanillaBlocks::MUD());
$world->addSound($this->position, new WaterSplashSound(0.5));
diff --git a/src/block/Door.php b/src/block/Door.php
index 06da8e68bbc..82ddaab518b 100644
--- a/src/block/Door.php
+++ b/src/block/Door.php
@@ -51,6 +51,8 @@ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
+ $this->collisionBoxes = null;
+
//copy door properties from other half
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other instanceof Door && $other->hasSameTypeId($this)){
@@ -102,11 +104,11 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN)) && !$this->getSide(Facing::DOWN) instanceof Door){ //Replace with common break method
+ if(!$this->canBeSupportedAt($this) && !$this->getSide(Facing::DOWN) instanceof Door){ //Replace with common break method
$this->position->getWorld()->useBreakOn($this->position); //this will delete both halves if they exist
}
}
@@ -114,8 +116,7 @@ public function onNearbyBlockChange() : void{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::UP){
$blockUp = $this->getSide(Facing::UP);
- $blockDown = $this->getSide(Facing::DOWN);
- if(!$blockUp->canBeReplaced() || !$this->canBeSupportedBy($blockDown)){
+ if(!$blockUp->canBeReplaced() || !$this->canBeSupportedAt($blockReplace)){
return false;
}
@@ -172,7 +173,7 @@ public function getAffectedBlocks() : array{
return parent::getAffectedBlocks();
}
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::UP)->hasEdgeSupport();
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport();
}
}
diff --git a/src/block/DoublePitcherCrop.php b/src/block/DoublePitcherCrop.php
new file mode 100644
index 00000000000..1233ed05d71
--- /dev/null
+++ b/src/block/DoublePitcherCrop.php
@@ -0,0 +1,120 @@
+describeAge($w);
+ }
+
+ protected function recalculateCollisionBoxes() : array{
+ if($this->top){
+ return [];
+ }
+
+ //the pod exists only in the bottom half of the plant
+ return [
+ AxisAlignedBB::one()
+ ->trim(Facing::UP, 11 / 16)
+ ->squash(Axis::X, 3 / 16)
+ ->squash(Axis::Z, 3 / 16)
+ ->extend(Facing::DOWN, 1 / 16) //presumably this is to correct for farmland being 15/16 of a block tall
+ ];
+ }
+
+ private function grow(?Player $player) : bool{
+ if($this->age >= self::MAX_AGE){
+ return false;
+ }
+
+ $bottom = $this->top ? $this->getSide(Facing::DOWN) : $this;
+ $top = $this->top ? $this : $this->getSide(Facing::UP);
+ if($top->getTypeId() !== BlockTypeIds::AIR && !$top->hasSameTypeId($this)){
+ return false;
+ }
+
+ $newState = (clone $this)->setAge($this->age + 1);
+
+ $tx = new BlockTransaction($this->position->getWorld());
+ $tx->addBlock($bottom->position, (clone $newState)->setTop(false));
+ $tx->addBlock($top->position, (clone $newState)->setTop(true));
+
+ $ev = new StructureGrowEvent($bottom, $tx, $player);
+ $ev->call();
+
+ return !$ev->isCancelled() && $tx->apply();
+
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($item instanceof Fertilizer && $this->grow($player)){
+ $item->pop();
+ return true;
+ }
+
+ return false;
+ }
+
+ public function ticksRandomly() : bool{
+ return $this->age < self::MAX_AGE && !$this->top;
+ }
+
+ public function onRandomTick() : void{
+ //only the bottom half of the plant can grow randomly
+ if(CropGrowthHelper::canGrow($this) && !$this->top){
+ $this->grow(null);
+ }
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [
+ $this->age >= self::MAX_AGE ? VanillaBlocks::PITCHER_PLANT()->asItem() : VanillaItems::PITCHER_POD()
+ ];
+ }
+
+ public function asItem() : Item{
+ return VanillaItems::PITCHER_POD();
+ }
+}
diff --git a/src/block/DoubleTallGrass.php b/src/block/DoubleTallGrass.php
index fc37442f5ec..42a6fb4dcaa 100644
--- a/src/block/DoubleTallGrass.php
+++ b/src/block/DoubleTallGrass.php
@@ -23,19 +23,21 @@
namespace pocketmine\block;
+use pocketmine\block\utils\TallGrassTrait;
use pocketmine\item\Item;
-use pocketmine\item\VanillaItems;
-use function mt_rand;
class DoubleTallGrass extends DoublePlant{
+ use TallGrassTrait {
+ getDropsForIncompatibleTool as traitGetDropsForIncompatibleTool;
+ }
public function canBeReplaced() : bool{
return true;
}
public function getDropsForIncompatibleTool(Item $item) : array{
- if($this->top && mt_rand(0, 7) === 0){
- return [VanillaItems::WHEAT_SEEDS()];
+ if($this->top){
+ return $this->traitGetDropsForIncompatibleTool($item);
}
return [];
}
diff --git a/src/block/DragonEgg.php b/src/block/DragonEgg.php
index 98809152d68..10fec639464 100644
--- a/src/block/DragonEgg.php
+++ b/src/block/DragonEgg.php
@@ -50,7 +50,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
}
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
- if($player !== null && !$player->getGamemode()->equals(GameMode::CREATIVE())){
+ if($player !== null && $player->getGamemode() !== GameMode::CREATIVE){
$this->teleport();
return true;
}
@@ -82,6 +82,6 @@ public function teleport() : void{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/DyedCandle.php b/src/block/DyedCandle.php
index 55d76e4066a..a495e8d0033 100644
--- a/src/block/DyedCandle.php
+++ b/src/block/DyedCandle.php
@@ -24,19 +24,13 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
class DyedCandle extends Candle{
use ColoredTrait;
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
protected function getCandleIfCompatibleType(Block $block) : ?Candle{
$result = parent::getCandleIfCompatibleType($block);
//different coloured candles can't be combined in the same block
- return $result instanceof DyedCandle && $result->color->equals($this->color) ? $result : null;
+ return $result instanceof DyedCandle && $result->color === $this->color ? $result : null;
}
}
diff --git a/src/block/DyedShulkerBox.php b/src/block/DyedShulkerBox.php
index 196ee02822c..5eae9237e45 100644
--- a/src/block/DyedShulkerBox.php
+++ b/src/block/DyedShulkerBox.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
final class DyedShulkerBox extends ShulkerBox{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/EmeraldOre.php b/src/block/EmeraldOre.php
index 6e997223dbb..8e5d96191bc 100644
--- a/src/block/EmeraldOre.php
+++ b/src/block/EmeraldOre.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -31,7 +32,7 @@ class EmeraldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::EMERALD()
+ VanillaItems::EMERALD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}
diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php
index f80d52d124c..6a6c936b220 100644
--- a/src/block/EnchantingTable.php
+++ b/src/block/EnchantingTable.php
@@ -41,7 +41,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
diff --git a/src/block/EndPortalFrame.php b/src/block/EndPortalFrame.php
index 08c903f117e..612cf3723cf 100644
--- a/src/block/EndPortalFrame.php
+++ b/src/block/EndPortalFrame.php
@@ -24,14 +24,12 @@
namespace pocketmine\block;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
class EndPortalFrame extends Opaque{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
protected bool $eye = false;
diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php
index 68c2805f9ab..9004f7c79f9 100644
--- a/src/block/EnderChest.php
+++ b/src/block/EnderChest.php
@@ -26,7 +26,6 @@
use pocketmine\block\inventory\EnderChestInventory;
use pocketmine\block\tile\EnderChest as TileEnderChest;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
@@ -36,7 +35,6 @@
class EnderChest extends Transparent{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
public function getLightLevel() : int{
return 7;
@@ -51,7 +49,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
diff --git a/src/block/Farmland.php b/src/block/Farmland.php
index 6fcdcd8bc20..a17a220f0b5 100644
--- a/src/block/Farmland.php
+++ b/src/block/Farmland.php
@@ -26,19 +26,45 @@
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
+use pocketmine\event\block\FarmlandHydrationChangeEvent;
use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
+use function intdiv;
use function lcg_value;
class Farmland extends Transparent{
public const MAX_WETNESS = 7;
+ private const WATER_SEARCH_HORIZONTAL_LENGTH = 9;
+
+ private const WATER_SEARCH_VERTICAL_LENGTH = 2;
+
+ private const WATER_POSITION_INDEX_UNKNOWN = -1;
+ /** Total possible options for water X/Z indexes */
+ private const WATER_POSITION_INDICES_TOTAL = (self::WATER_SEARCH_HORIZONTAL_LENGTH ** 2) * 2;
+
protected int $wetness = 0; //"moisture" blockstate property in PC
+ /**
+ * Cached value indicating the relative coordinates of the most recently found water block.
+ *
+ * If this is set to a non-unknown value, the farmland block will check the relative coordinates indicated by
+ * this value for water, before searching the entire 9x2x9 grid around the farmland. This significantly benefits
+ * hydrating or fully hydrated farmland, avoiding the need for costly searches on every random tick.
+ *
+ * If the coordinates indicated don't contain water, the full 9x2x9 volume will be searched as before. A new index
+ * will be recorded if water is found, otherwise it will be set to unknown and future searches will search the full
+ * 9x2x9 volume again.
+ *
+ * This property is not exposed to the API or saved on disk. It is only used by PocketMine-MP at runtime as a cache.
+ */
+ private int $waterPositionIndex = self::WATER_POSITION_INDEX_UNKNOWN;
+
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, 0, self::MAX_WETNESS, $this->wetness);
+ $w->boundedIntAuto(0, self::MAX_WETNESS, $this->wetness);
+ $w->boundedIntAuto(-1, self::WATER_POSITION_INDICES_TOTAL - 1, $this->waterPositionIndex);
}
public function getWetness() : int{ return $this->wetness; }
@@ -52,6 +78,22 @@ public function setWetness(int $wetness) : self{
return $this;
}
+ /**
+ * @internal
+ */
+ public function getWaterPositionIndex() : int{ return $this->waterPositionIndex; }
+
+ /**
+ * @internal
+ */
+ public function setWaterPositionIndex(int $waterPositionIndex) : self{
+ if($waterPositionIndex < -1 || $waterPositionIndex >= self::WATER_POSITION_INDICES_TOTAL){
+ throw new \InvalidArgumentException("Water XZ index must be in range -1 ... " . (self::WATER_POSITION_INDICES_TOTAL - 1));
+ }
+ $this->waterPositionIndex = $waterPositionIndex;
+ return $this;
+ }
+
/**
* @return AxisAlignedBB[]
*/
@@ -71,15 +113,36 @@ public function ticksRandomly() : bool{
public function onRandomTick() : void{
$world = $this->position->getWorld();
+
+ //this property may be updated by canHydrate() - track this so we know if we need to set the block again
+ $oldWaterPositionIndex = $this->waterPositionIndex;
+ $changed = false;
+
if(!$this->canHydrate()){
if($this->wetness > 0){
- $this->wetness--;
- $world->setBlock($this->position, $this, false);
+ $event = new FarmlandHydrationChangeEvent($this, $this->wetness, $this->wetness - 1);
+ $event->call();
+ if(!$event->isCancelled()){
+ $this->wetness = $event->getNewHydration();
+ $world->setBlock($this->position, $this, false);
+ $changed = true;
+ }
}else{
$world->setBlock($this->position, VanillaBlocks::DIRT());
+ $changed = true;
}
}elseif($this->wetness < self::MAX_WETNESS){
- $this->wetness = self::MAX_WETNESS;
+ $event = new FarmlandHydrationChangeEvent($this, $this->wetness, self::MAX_WETNESS);
+ $event->call();
+ if(!$event->isCancelled()){
+ $this->wetness = $event->getNewHydration();
+ $world->setBlock($this->position, $this, false);
+ $changed = true;
+ }
+ }
+
+ if(!$changed && $oldWaterPositionIndex !== $this->waterPositionIndex){
+ //ensure the water square index is saved regardless of whether anything else happened
$world->setBlock($this->position, $this, false);
}
}
@@ -89,26 +152,46 @@ public function onEntityLand(Entity $entity) : ?float{
$ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call();
if(!$ev->isCancelled()){
- $this->getPosition()->getWorld()->setBlock($this->getPosition(), VanillaBlocks::DIRT());
+ $this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
}
}
return null;
}
protected function canHydrate() : bool{
- //TODO: check rain
- $start = $this->position->add(-4, 0, -4);
- $end = $this->position->add(4, 1, 4);
- for($y = $start->y; $y <= $end->y; ++$y){
- for($z = $start->z; $z <= $end->z; ++$z){
- for($x = $start->x; $x <= $end->x; ++$x){
- if($this->position->getWorld()->getBlockAt($x, $y, $z) instanceof Water){
+ $world = $this->position->getWorld();
+
+ $startX = $this->position->getFloorX() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
+ $startY = $this->position->getFloorY();
+ $startZ = $this->position->getFloorZ() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
+
+ if($this->waterPositionIndex !== self::WATER_POSITION_INDEX_UNKNOWN){
+ $raw = $this->waterPositionIndex;
+ $x = $raw % self::WATER_SEARCH_HORIZONTAL_LENGTH;
+ $raw = intdiv($raw, self::WATER_SEARCH_HORIZONTAL_LENGTH);
+ $z = $raw % self::WATER_SEARCH_HORIZONTAL_LENGTH;
+ $raw = intdiv($raw, self::WATER_SEARCH_HORIZONTAL_LENGTH);
+ $y = $raw % self::WATER_SEARCH_VERTICAL_LENGTH;
+
+ if($world->getBlockAt($startX + $x, $startY + $y, $startZ + $z) instanceof Water){
+ return true;
+ }
+ }
+
+ //no water found at cached position - search the whole area
+ //y will increment after x/z have been exhausted, as usually water will be at the same Y as the farmland
+ for($y = 0; $y < self::WATER_SEARCH_VERTICAL_LENGTH; $y++){
+ for($x = 0; $x < self::WATER_SEARCH_HORIZONTAL_LENGTH; $x++){
+ for($z = 0; $z < self::WATER_SEARCH_HORIZONTAL_LENGTH; $z++){
+ if($world->getBlockAt($startX + $x, $startY + $y, $startZ + $z) instanceof Water){
+ $this->waterPositionIndex = $x + ($z * self::WATER_SEARCH_HORIZONTAL_LENGTH) + ($y * self::WATER_SEARCH_HORIZONTAL_LENGTH ** 2);
return true;
}
}
}
}
+ $this->waterPositionIndex = self::WATER_POSITION_INDEX_UNKNOWN;
return false;
}
diff --git a/src/block/Fence.php b/src/block/Fence.php
index 7180b38ea2b..30caaa4cfce 100644
--- a/src/block/Fence.php
+++ b/src/block/Fence.php
@@ -40,9 +40,11 @@ public function getThickness() : float{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
+ $this->collisionBoxes = null;
+
foreach(Facing::HORIZONTAL as $facing){
$block = $this->getSide($facing);
- if($block instanceof static || $block instanceof FenceGate || ($block->isSolid() && !$block->isTransparent())){
+ if($block instanceof static || $block instanceof FenceGate || $block->getSupportType(Facing::opposite($facing)) === SupportType::FULL){
$this->connections[$facing] = true;
}else{
unset($this->connections[$facing]);
@@ -98,6 +100,6 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return Facing::axis($facing) === Axis::Y ? SupportType::CENTER() : SupportType::NONE();
+ return Facing::axis($facing) === Axis::Y ? SupportType::CENTER : SupportType::NONE;
}
}
diff --git a/src/block/FenceGate.php b/src/block/FenceGate.php
index c22edabc490..7354564495e 100644
--- a/src/block/FenceGate.php
+++ b/src/block/FenceGate.php
@@ -72,7 +72,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
private function checkInWall() : bool{
diff --git a/src/block/FillableCauldron.php b/src/block/FillableCauldron.php
index 84705ea9d56..ceef3529968 100644
--- a/src/block/FillableCauldron.php
+++ b/src/block/FillableCauldron.php
@@ -38,7 +38,7 @@ abstract class FillableCauldron extends Transparent{
private int $fillLevel = self::MIN_FILL_LEVEL;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, self::MIN_FILL_LEVEL, self::MAX_FILL_LEVEL, $this->fillLevel);
+ $w->boundedIntAuto(self::MIN_FILL_LEVEL, self::MAX_FILL_LEVEL, $this->fillLevel);
}
public function getFillLevel() : int{ return $this->fillLevel; }
@@ -64,7 +64,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return $facing === Facing::UP ? SupportType::EDGE() : SupportType::NONE();
+ return $facing === Facing::UP ? SupportType::EDGE : SupportType::NONE;
}
protected function withFillLevel(int $fillLevel) : Block{
diff --git a/src/block/Fire.php b/src/block/Fire.php
index 9e485fa6128..35a7a696c6c 100644
--- a/src/block/Fire.php
+++ b/src/block/Fire.php
@@ -23,9 +23,10 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\SupportType;
use pocketmine\event\block\BlockBurnEvent;
-use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\math\Facing;
use pocketmine\world\format\Chunk;
use pocketmine\world\World;
@@ -35,35 +36,24 @@
use function mt_rand;
class Fire extends BaseFire{
- public const MAX_AGE = 15;
-
- protected int $age = 0;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, 0, self::MAX_AGE, $this->age);
- }
+ use AgeableTrait;
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
+ public const MAX_AGE = 15;
protected function getFireDamage() : int{
return 1;
}
+ private function canBeSupportedBy(Block $block) : bool{
+ return $block->getSupportType(Facing::UP) === SupportType::FULL;
+ }
+
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
$down = $this->getSide(Facing::DOWN);
if(SoulFire::canBeSupportedBy($down)){
$world->setBlock($this->position, VanillaBlocks::SOUL_FIRE());
- }elseif($down->isTransparent() && !$this->hasAdjacentFlammableBlocks()){
+ }elseif(!$this->canBeSupportedBy($this->getSide(Facing::DOWN)) && !$this->hasAdjacentFlammableBlocks()){
$world->setBlock($this->position, VanillaBlocks::AIR());
}else{
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
@@ -140,13 +130,17 @@ private function burnBlocksAround() : void{
private function burnBlock(Block $block, int $chanceBound) : void{
if(mt_rand(0, $chanceBound) < $block->getFlammability()){
- $ev = new BlockBurnEvent($block, $this);
- $ev->call();
- if(!$ev->isCancelled()){
+ $cancelled = false;
+ if(BlockBurnEvent::hasHandlers()){
+ $ev = new BlockBurnEvent($block, $this);
+ $ev->call();
+ $cancelled = $ev->isCancelled();
+ }
+ if(!$cancelled){
$block->onIncinerate();
$world = $this->position->getWorld();
- if($world->getBlock($block->getPosition())->isSameState($block)){
+ if($world->getBlock($block->position)->isSameState($block)){
$spreadedFire = false;
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
$fire = clone $this;
@@ -220,13 +214,6 @@ private function spreadFire() : void{
}
private function spreadBlock(Block $block, Block $newState) : bool{
- $ev = new BlockSpreadEvent($block, $this, $newState);
- $ev->call();
- if(!$ev->isCancelled()){
- $block->position->getWorld()->setBlock($block->position, $ev->getNewState());
- return true;
- }
-
- return false;
+ return BlockEventHelper::spread($block, $newState, $this);
}
}
diff --git a/src/block/FloorCoralFan.php b/src/block/FloorCoralFan.php
index efa560467ef..5b74d08afff 100644
--- a/src/block/FloorCoralFan.php
+++ b/src/block/FloorCoralFan.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@@ -35,6 +36,8 @@
use function rad2deg;
final class FloorCoralFan extends BaseCoral{
+ use StaticSupportTrait;
+
private int $axis = Axis::X;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@@ -53,12 +56,9 @@ public function setAxis(int $axis) : self{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($tx->fetchBlock($blockReplace->getPosition()->down()))){
- return false;
- }
if($player !== null){
$playerBlockPos = $player->getPosition()->floor();
- $directionVector = $blockReplace->getPosition()->subtractVector($playerBlockPos)->normalize();
+ $directionVector = $blockReplace->position->subtractVector($playerBlockPos)->normalize();
$angle = rad2deg(atan2($directionVector->getZ(), $directionVector->getX()));
if($angle <= 45 || 315 <= $angle || (135 <= $angle && $angle <= 225)){
@@ -73,17 +73,8 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
- public function onNearbyBlockChange() : void{
- $world = $this->position->getWorld();
- if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){
- $world->useBreakOn($this->position);
- }else{
- parent::onNearbyBlockChange();
- }
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::UP)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();
}
public function asItem() : Item{
diff --git a/src/block/Flowable.php b/src/block/Flowable.php
index 2b4e8a02bd2..0328bcd745e 100644
--- a/src/block/Flowable.php
+++ b/src/block/Flowable.php
@@ -25,6 +25,7 @@
use pocketmine\block\utils\SupportType;
use pocketmine\math\AxisAlignedBB;
+use pocketmine\math\Vector3;
/**
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
@@ -40,6 +41,11 @@ public function isSolid() : bool{
return false;
}
+ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
+ return (!$this->canBeFlowedInto() || !$blockReplace instanceof Liquid) &&
+ parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
+ }
+
/**
* @return AxisAlignedBB[]
*/
@@ -48,6 +54,6 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/Flower.php b/src/block/Flower.php
index fca5dd98fd5..ef79813fe25 100644
--- a/src/block/Flower.php
+++ b/src/block/Flower.php
@@ -23,28 +23,15 @@
namespace pocketmine\block;
-use pocketmine\item\Item;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
class Flower extends Flowable{
+ use StaticSupportTrait;
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $this->getSide(Facing::DOWN);
- if($down->hasTypeTag(BlockTypeTags::DIRT) || $down->hasTypeTag(BlockTypeTags::MUD)){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
- }
-
- public function onNearbyBlockChange() : void{
- $down = $this->getSide(Facing::DOWN);
- if(!$down->hasTypeTag(BlockTypeTags::DIRT) && !$down->hasTypeTag(BlockTypeTags::MUD)){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
public function getFlameEncouragement() : int{
diff --git a/src/block/FlowerPot.php b/src/block/FlowerPot.php
index 1c85ea0d85a..fb3e78d82ad 100644
--- a/src/block/FlowerPot.php
+++ b/src/block/FlowerPot.php
@@ -24,15 +24,16 @@
namespace pocketmine\block;
use pocketmine\block\tile\FlowerPot as TileFlowerPot;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
use function assert;
class FlowerPot extends Flowable{
+ use StaticSupportTrait;
protected ?Block $plant = null;
@@ -89,22 +90,8 @@ protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->contract(3 / 16, 0, 3 / 16)->trim(Facing::UP, 5 / 8)];
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- return false;
- }
-
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::UP)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
diff --git a/src/block/Froglight.php b/src/block/Froglight.php
index f33fc82613f..562b33f174e 100644
--- a/src/block/Froglight.php
+++ b/src/block/Froglight.php
@@ -28,15 +28,10 @@
final class Froglight extends SimplePillar{
- private FroglightType $froglightType;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->froglightType = FroglightType::OCHRE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
+ private FroglightType $froglightType = FroglightType::OCHRE;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->froglightType($this->froglightType);
+ $w->enum($this->froglightType);
}
public function getFroglightType() : FroglightType{ return $this->froglightType; }
diff --git a/src/block/FrostedIce.php b/src/block/FrostedIce.php
index ba8c7b5361a..3e859230678 100644
--- a/src/block/FrostedIce.php
+++ b/src/block/FrostedIce.php
@@ -23,37 +23,17 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockMeltEvent;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
use function mt_rand;
class FrostedIce extends Ice{
- public const MAX_AGE = 3;
-
- protected int $age = 0;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(2, 0, self::MAX_AGE, $this->age);
- }
+ use AgeableTrait;
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
+ public const MAX_AGE = 3;
public function onNearbyBlockChange() : void{
- $world = $this->position->getWorld();
- if(!$this->checkAdjacentBlocks(2)){
- $world->useBreakOn($this->position);
- }else{
- $world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
- }
+ $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
}
public function onRandomTick() : void{
@@ -102,11 +82,7 @@ private function checkAdjacentBlocks(int $requirement) : bool{
private function tryMelt() : bool{
$world = $this->position->getWorld();
if($this->age >= self::MAX_AGE){
- $ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::melt($this, VanillaBlocks::WATER());
return true;
}
diff --git a/src/block/Furnace.php b/src/block/Furnace.php
index d943f8cc613..7a64e3cd3e5 100644
--- a/src/block/Furnace.php
+++ b/src/block/Furnace.php
@@ -25,7 +25,7 @@
use pocketmine\block\tile\Furnace as TileFurnace;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
+use pocketmine\block\utils\LightableTrait;
use pocketmine\crafting\FurnaceType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@@ -35,12 +35,10 @@
class Furnace extends Opaque{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
+ use LightableTrait;
protected FurnaceType $furnaceType;
- protected bool $lit = false;
-
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, FurnaceType $furnaceType){
$this->furnaceType = $furnaceType;
parent::__construct($idInfo, $name, $typeInfo);
@@ -59,18 +57,6 @@ public function getLightLevel() : int{
return $this->lit ? 13 : 0;
}
- public function isLit() : bool{
- return $this->lit;
- }
-
- /**
- * @return $this
- */
- public function setLit(bool $lit = true) : self{
- $this->lit = $lit;
- return $this;
- }
-
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($player instanceof Player){
$furnace = $this->position->getWorld()->getTile($this->position);
diff --git a/src/block/GildedBlackstone.php b/src/block/GildedBlackstone.php
index e01d6bfdc7b..75c822f8e07 100644
--- a/src/block/GildedBlackstone.php
+++ b/src/block/GildedBlackstone.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -30,7 +31,7 @@
final class GildedBlackstone extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
- if(mt_rand(1, 10) === 1){
+ if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){
return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 5))];
}
diff --git a/src/block/GlazedTerracotta.php b/src/block/GlazedTerracotta.php
index 5568703c239..15b3254e5d9 100644
--- a/src/block/GlazedTerracotta.php
+++ b/src/block/GlazedTerracotta.php
@@ -24,17 +24,9 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
class GlazedTerracotta extends Opaque{
use ColoredTrait;
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::BLACK();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php
index d1baaa7d242..d30e2539582 100644
--- a/src/block/GlowLichen.php
+++ b/src/block/GlowLichen.php
@@ -23,61 +23,21 @@
namespace pocketmine\block;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType;
-use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
-use function array_key_first;
use function count;
use function shuffle;
class GlowLichen extends Transparent{
-
- /** @var int[] */
- protected array $faces = [];
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->facingFlags($this->faces);
- }
-
- /** @return int[] */
- public function getFaces() : array{ return $this->faces; }
-
- public function hasFace(int $face) : bool{
- return isset($this->faces[$face]);
- }
-
- /**
- * @param int[] $faces
- * @return $this
- */
- public function setFaces(array $faces) : self{
- $uniqueFaces = [];
- foreach($faces as $face){
- Facing::validate($face);
- $uniqueFaces[$face] = $face;
- }
- $this->faces = $uniqueFaces;
- return $this;
- }
-
- /** @return $this */
- public function setFace(int $face, bool $value) : self{
- Facing::validate($face);
- if($value){
- $this->faces[$face] = $face;
- }else{
- unset($this->faces[$face]);
- }
- return $this;
- }
+ use MultiAnySupportTrait;
public function getLightLevel() : int{
return 7;
@@ -95,46 +55,18 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function canBeReplaced() : bool{
return true;
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $this->faces = $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
- $availableFaces = $this->getAvailableFaces();
-
- if(count($availableFaces) === 0){
- return false;
- }
-
- $opposite = Facing::opposite($face);
- $placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
- $this->faces[$placedFace] = $placedFace;
-
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- $changed = false;
-
- foreach($this->faces as $face){
- if(!$this->getSide($face)->getSupportType(Facing::opposite($face))->equals(SupportType::FULL())){
- unset($this->faces[$face]);
- $changed = true;
- }
- }
-
- if($changed){
- $world = $this->position->getWorld();
- if(count($this->faces) === 0){
- $world->useBreakOn($this->position);
- }else{
- $world->setBlock($this->position, $this);
- }
- }
+ /**
+ * @return int[]
+ */
+ protected function getInitialPlaceFaces(Block $blockReplace) : array{
+ return $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
}
private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
@@ -156,7 +88,7 @@ private function spread(World $world, Vector3 $replacePos, int $spreadFace) : bo
$supportBlock = $world->getBlock($replacePos->getSide($spreadFace));
$supportFace = Facing::opposite($spreadFace);
- if(!$supportBlock->getSupportType($supportFace)->equals(SupportType::FULL())){
+ if($supportBlock->getSupportType($supportFace) !== SupportType::FULL){
return false;
}
@@ -166,14 +98,7 @@ private function spread(World $world, Vector3 $replacePos, int $spreadFace) : bo
return false;
}
- $ev = new BlockSpreadEvent($replacedBlock, $this, $replacementBlock);
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($replacedBlock->getPosition(), $ev->getNewState());
- return true;
- }
-
- return false;
+ return BlockEventHelper::spread($replacedBlock, $replacementBlock, $this);
}
/**
@@ -268,17 +193,4 @@ public function getFlameEncouragement() : int{
public function getFlammability() : int{
return 100;
}
-
- /**
- * @return array $faces
- */
- private function getAvailableFaces() : array{
- $faces = [];
- foreach(Facing::ALL as $face){
- if(!$this->hasFace($face) && $this->getSide($face)->getSupportType(Facing::opposite($face))->equals(SupportType::FULL())){
- $faces[$face] = $face;
- }
- }
- return $faces;
- }
}
diff --git a/src/block/Glowstone.php b/src/block/Glowstone.php
index 3b567aa5b37..1fead9abef0 100644
--- a/src/block/Glowstone.php
+++ b/src/block/Glowstone.php
@@ -23,9 +23,10 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
+use function min;
class Glowstone extends Transparent{
@@ -35,7 +36,7 @@ public function getLightLevel() : int{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::GLOWSTONE_DUST()->setCount(mt_rand(2, 4))
+ VanillaItems::GLOWSTONE_DUST()->setCount(min(4, FortuneDropHelper::discrete($item, 2, 4)))
];
}
diff --git a/src/block/GoldOre.php b/src/block/GoldOre.php
index ae26d8b4e20..75374c1b05f 100644
--- a/src/block/GoldOre.php
+++ b/src/block/GoldOre.php
@@ -23,13 +23,14 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
final class GoldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
- return [VanillaItems::RAW_GOLD()];
+ return [VanillaItems::RAW_GOLD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))];
}
public function isAffectedBySilkTouch() : bool{ return true; }
diff --git a/src/block/Grass.php b/src/block/Grass.php
index 54975d04600..8a9fea8eac0 100644
--- a/src/block/Grass.php
+++ b/src/block/Grass.php
@@ -23,8 +23,8 @@
namespace pocketmine\block;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\DirtType;
-use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Hoe;
use pocketmine\item\Item;
@@ -58,11 +58,7 @@ public function onRandomTick() : void{
$lightAbove = $world->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
if($lightAbove < 4 && $world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
//grass dies
- $ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT());
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState(), false);
- }
+ BlockEventHelper::spread($this, VanillaBlocks::DIRT(), $this);
}elseif($lightAbove >= 9){
//try grass spread
for($i = 0; $i < 4; ++$i){
@@ -73,24 +69,20 @@ public function onRandomTick() : void{
$b = $world->getBlockAt($x, $y, $z);
if(
!($b instanceof Dirt) ||
- !$b->getDirtType()->equals(DirtType::NORMAL()) ||
+ $b->getDirtType() !== DirtType::NORMAL ||
$world->getFullLightAt($x, $y + 1, $z) < 4 ||
$world->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
){
continue;
}
- $ev = new BlockSpreadEvent($b, $this, VanillaBlocks::GRASS());
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($b->position, $ev->getNewState(), false);
- }
+ BlockEventHelper::spread($b, VanillaBlocks::GRASS(), $this);
}
}
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
- if($face !== Facing::UP){
+ if($this->getSide(Facing::UP)->getTypeId() !== BlockTypeIds::AIR){
return false;
}
$world = $this->position->getWorld();
@@ -99,20 +91,23 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
TallGrassObject::growGrass($world, $this->position, new Random(mt_rand()), 8, 2);
return true;
- }elseif($item instanceof Hoe){
- $item->applyDamage(1);
- $newBlock = VanillaBlocks::FARMLAND();
- $world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
- $world->setBlock($this->position, $newBlock);
-
- return true;
- }elseif($item instanceof Shovel && $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::AIR){
- $item->applyDamage(1);
- $newBlock = VanillaBlocks::GRASS_PATH();
- $world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
- $world->setBlock($this->position, $newBlock);
-
- return true;
+ }
+ if($face !== Facing::DOWN){
+ if($item instanceof Hoe){
+ $item->applyDamage(1);
+ $newBlock = VanillaBlocks::FARMLAND();
+ $world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
+ $world->setBlock($this->position, $newBlock);
+
+ return true;
+ }elseif($item instanceof Shovel){
+ $item->applyDamage(1);
+ $newBlock = VanillaBlocks::GRASS_PATH();
+ $world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
+ $world->setBlock($this->position, $newBlock);
+
+ return true;
+ }
}
return false;
diff --git a/src/block/Gravel.php b/src/block/Gravel.php
index 4bff2208ffe..6445ce30ebf 100644
--- a/src/block/Gravel.php
+++ b/src/block/Gravel.php
@@ -25,15 +25,15 @@
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
class Gravel extends Opaque implements Fallable{
use FallableTrait;
public function getDropsForCompatibleTool(Item $item) : array{
- if(mt_rand(1, 10) === 1){
+ if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){
return [
VanillaItems::FLINT()
];
diff --git a/src/block/HangingRoots.php b/src/block/HangingRoots.php
index cd85bcea799..9c7bfd09601 100644
--- a/src/block/HangingRoots.php
+++ b/src/block/HangingRoots.php
@@ -23,30 +23,16 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
final class HangingRoots extends Flowable{
+ use StaticSupportTrait;
- private function canBeSupportedBy(Block $block) : bool{
- return $block->isSolid(); //TODO: not sure if this is the correct logic
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$blockReplace->getSide(Facing::UP)->isSolid()){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::UP))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::UP)->hasCenterSupport(); //weird I know, but they can be placed on the bottom of fences
}
public function getDropsForIncompatibleTool(Item $item) : array{
diff --git a/src/block/Hopper.php b/src/block/Hopper.php
index ea000503cb0..0d823674b23 100644
--- a/src/block/Hopper.php
+++ b/src/block/Hopper.php
@@ -68,9 +68,9 @@ protected function recalculateCollisionBoxes() : array{
public function getSupportType(int $facing) : SupportType{
return match($facing){
- Facing::UP => SupportType::FULL(),
- Facing::DOWN => $this->facing === Facing::DOWN ? SupportType::CENTER() : SupportType::NONE(),
- default => SupportType::NONE()
+ Facing::UP => SupportType::FULL,
+ Facing::DOWN => $this->facing === Facing::DOWN ? SupportType::CENTER : SupportType::NONE,
+ default => SupportType::NONE
};
}
diff --git a/src/block/Ice.php b/src/block/Ice.php
index 41f60e3f9df..ca0302f9060 100644
--- a/src/block/Ice.php
+++ b/src/block/Ice.php
@@ -23,7 +23,7 @@
namespace pocketmine\block;
-use pocketmine\event\block\BlockMeltEvent;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\player\Player;
@@ -53,11 +53,7 @@ public function ticksRandomly() : bool{
public function onRandomTick() : void{
$world = $this->position->getWorld();
if($world->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
- $ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::melt($this, VanillaBlocks::WATER());
}
}
diff --git a/src/block/IronOre.php b/src/block/IronOre.php
index 8d7e9bd3f50..11c8cc2995a 100644
--- a/src/block/IronOre.php
+++ b/src/block/IronOre.php
@@ -23,13 +23,14 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
final class IronOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
- return [VanillaItems::RAW_IRON()];
+ return [VanillaItems::RAW_IRON()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))];
}
public function isAffectedBySilkTouch() : bool{ return true; }
diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php
index f14720fe42b..b5b6093c497 100644
--- a/src/block/ItemFrame.php
+++ b/src/block/ItemFrame.php
@@ -163,18 +163,18 @@ public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
return true;
}
- private function canBeSupportedBy(Block $block, int $face) : bool{
- return !$block->getSupportType($face)->equals(SupportType::NONE());
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType($face) !== SupportType::NONE;
}
public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)){
+ if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
+ if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
return false;
}
diff --git a/src/block/Jukebox.php b/src/block/Jukebox.php
index 20c3cab6131..a61dd06dbe5 100644
--- a/src/block/Jukebox.php
+++ b/src/block/Jukebox.php
@@ -61,7 +61,7 @@ public function getRecord() : ?Record{
public function ejectRecord() : void{
if($this->record !== null){
- $this->getPosition()->getWorld()->dropItem($this->getPosition()->add(0.5, 1, 0.5), $this->record);
+ $this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $this->record);
$this->record = null;
$this->stopSound();
}
@@ -76,12 +76,12 @@ public function insertRecord(Record $record) : void{
public function startSound() : void{
if($this->record !== null){
- $this->getPosition()->getWorld()->addSound($this->getPosition(), new RecordSound($this->record->getRecordType()));
+ $this->position->getWorld()->addSound($this->position, new RecordSound($this->record->getRecordType()));
}
}
public function stopSound() : void{
- $this->getPosition()->getWorld()->addSound($this->getPosition(), new RecordStopSound());
+ $this->position->getWorld()->addSound($this->position, new RecordStopSound());
}
public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{
diff --git a/src/block/Ladder.php b/src/block/Ladder.php
index 66e5f28cdd7..58f133f6ecf 100644
--- a/src/block/Ladder.php
+++ b/src/block/Ladder.php
@@ -66,11 +66,11 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face) && Facing::axis($face) !== Axis::Y){
+ if($this->canBeSupportedAt($blockReplace, Facing::opposite($face)) && Facing::axis($face) !== Axis::Y){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -79,12 +79,12 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
}
public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)){ //Replace with common break method
+ if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){ //Replace with common break method
$this->position->getWorld()->useBreakOn($this->position);
}
}
- private function canBeSupportedBy(Block $block, int $face) : bool{
- return $block->getSupportType($face)->equals(SupportType::FULL());
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType($face) === SupportType::FULL;
}
}
diff --git a/src/block/Lantern.php b/src/block/Lantern.php
index bc50c3cb622..e9cbcc3fe97 100644
--- a/src/block/Lantern.php
+++ b/src/block/Lantern.php
@@ -73,26 +73,27 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP), Facing::DOWN) && !$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN), Facing::UP)){
+ $downSupport = $this->canBeSupportedAt($blockReplace, Facing::DOWN);
+ if(!$downSupport && !$this->canBeSupportedAt($blockReplace, Facing::UP)){
return false;
}
- $this->hanging = ($face === Facing::DOWN || !$this->canBeSupportedBy($this->position->getWorld()->getBlock($blockReplace->getPosition()->down()), Facing::UP));
+ $this->hanging = $face === Facing::DOWN || !$downSupport;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$face = $this->hanging ? Facing::UP : Facing::DOWN;
- if(!$this->canBeSupportedBy($this->getSide($face), Facing::opposite($face))){
+ if(!$this->canBeSupportedAt($this, $face)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
- private function canBeSupportedBy(Block $block, int $face) : bool{
- return $block->getSupportType($face)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType($face)->hasCenterSupport();
}
}
diff --git a/src/block/LapisOre.php b/src/block/LapisOre.php
index 656b0a8951e..ce54321a48e 100644
--- a/src/block/LapisOre.php
+++ b/src/block/LapisOre.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -31,7 +32,7 @@ class LapisOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::LAPIS_LAZULI()->setCount(mt_rand(4, 8))
+ VanillaItems::LAPIS_LAZULI()->setCount(FortuneDropHelper::weighted($item, min: 4, maxBase: 9))
];
}
diff --git a/src/block/Leaves.php b/src/block/Leaves.php
index 62a30a3e830..7fe9eae74f2 100644
--- a/src/block/Leaves.php
+++ b/src/block/Leaves.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\block\utils\LeavesType;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
@@ -32,12 +33,13 @@
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
-use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
use function mt_rand;
class Leaves extends Transparent{
+ private const MAX_LOG_DISTANCE = 4;
+
protected LeavesType $leavesType; //immutable for now
protected bool $noDecay = false;
protected bool $checkDecay = false;
@@ -91,7 +93,7 @@ protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0
return true;
}
- if($block instanceof Leaves && $distance <= 4){
+ if($block instanceof Leaves && $distance <= self::MAX_LOG_DISTANCE){
foreach(Facing::ALL as $side){
if($this->findLog($pos->getSide($side), $visited, $distance + 1)){
return true;
@@ -110,15 +112,20 @@ public function onNearbyBlockChange() : void{
}
public function ticksRandomly() : bool{
- return true;
+ return !$this->noDecay && $this->checkDecay;
}
public function onRandomTick() : void{
if(!$this->noDecay && $this->checkDecay){
- $ev = new LeavesDecayEvent($this);
- $ev->call();
+ $cancelled = false;
+ if(LeavesDecayEvent::hasHandlers()){
+ $ev = new LeavesDecayEvent($this);
+ $ev->call();
+ $cancelled = $ev->isCancelled();
+ }
+
$world = $this->position->getWorld();
- if($ev->isCancelled() || $this->findLog($this->position)){
+ if($cancelled || $this->findLog($this->position)){
$this->checkDecay = false;
$world->setBlock($this->position, $this, false);
}else{
@@ -138,26 +145,30 @@ public function getDropsForCompatibleTool(Item $item) : array{
}
$drops = [];
- if(mt_rand(1, 20) === 1){ //Saplings
+ if(FortuneDropHelper::bonusChanceDivisor($item, 20, 4)){ //Saplings
+ // TODO: according to the wiki, the jungle saplings have a different drop rate
$sapling = (match($this->leavesType){
- LeavesType::ACACIA() => VanillaBlocks::ACACIA_SAPLING(),
- LeavesType::BIRCH() => VanillaBlocks::BIRCH_SAPLING(),
- LeavesType::DARK_OAK() => VanillaBlocks::DARK_OAK_SAPLING(),
- LeavesType::JUNGLE() => VanillaBlocks::JUNGLE_SAPLING(),
- LeavesType::OAK() => VanillaBlocks::OAK_SAPLING(),
- LeavesType::SPRUCE() => VanillaBlocks::SPRUCE_SAPLING(),
- LeavesType::MANGROVE(), //TODO: mangrove propagule
- LeavesType::AZALEA(), LeavesType::FLOWERING_AZALEA() => null, //TODO: azalea
- default => throw new AssumptionFailedError("Unreachable")
+ LeavesType::ACACIA => VanillaBlocks::ACACIA_SAPLING(),
+ LeavesType::BIRCH => VanillaBlocks::BIRCH_SAPLING(),
+ LeavesType::DARK_OAK => VanillaBlocks::DARK_OAK_SAPLING(),
+ LeavesType::JUNGLE => VanillaBlocks::JUNGLE_SAPLING(),
+ LeavesType::OAK => VanillaBlocks::OAK_SAPLING(),
+ LeavesType::SPRUCE => VanillaBlocks::SPRUCE_SAPLING(),
+ LeavesType::MANGROVE, //TODO: mangrove propagule
+ LeavesType::AZALEA, LeavesType::FLOWERING_AZALEA => null, //TODO: azalea
+ LeavesType::CHERRY => null, //TODO: cherry
})?->asItem();
if($sapling !== null){
$drops[] = $sapling;
}
}
- if(($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) && mt_rand(1, 200) === 1){ //Apples
+ if(
+ ($this->leavesType === LeavesType::OAK || $this->leavesType === LeavesType::DARK_OAK) &&
+ FortuneDropHelper::bonusChanceDivisor($item, 200, 20)
+ ){ //Apples
$drops[] = VanillaItems::APPLE();
}
- if(mt_rand(1, 50) === 1){
+ if(FortuneDropHelper::bonusChanceDivisor($item, 50, 5)){
$drops[] = VanillaItems::STICK()->setCount(mt_rand(1, 2));
}
@@ -177,6 +188,6 @@ public function getFlammability() : int{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/Lectern.php b/src/block/Lectern.php
index a80426acffa..03880b3c5f1 100644
--- a/src/block/Lectern.php
+++ b/src/block/Lectern.php
@@ -25,7 +25,6 @@
use pocketmine\block\tile\Lectern as TileLectern;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@@ -39,7 +38,6 @@
class Lectern extends Transparent{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
protected int $viewedPage = 0;
protected ?WritableBookBase $book = null;
@@ -89,7 +87,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function isProducingSignal() : bool{ return $this->producingSignal; }
diff --git a/src/block/Lever.php b/src/block/Lever.php
index 5d86ac7d2a2..d2b98efc34f 100644
--- a/src/block/Lever.php
+++ b/src/block/Lever.php
@@ -36,16 +36,11 @@
use pocketmine\world\sound\RedstonePowerOnSound;
class Lever extends Flowable{
- protected LeverFacing $facing;
+ protected LeverFacing $facing = LeverFacing::UP_AXIS_X;
protected bool $activated = false;
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->facing = LeverFacing::UP_AXIS_X();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->leverFacing($this->facing);
+ $w->enum($this->facing);
$w->bool($this->activated);
}
@@ -66,7 +61,7 @@ public function setActivated(bool $activated) : self{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
+ if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
return false;
}
@@ -77,12 +72,12 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
return $x;
};
$this->facing = match($face){
- Facing::DOWN => $selectUpDownPos(LeverFacing::DOWN_AXIS_X(), LeverFacing::DOWN_AXIS_Z()),
- Facing::UP => $selectUpDownPos(LeverFacing::UP_AXIS_X(), LeverFacing::UP_AXIS_Z()),
- Facing::NORTH => LeverFacing::NORTH(),
- Facing::SOUTH => LeverFacing::SOUTH(),
- Facing::WEST => LeverFacing::WEST(),
- Facing::EAST => LeverFacing::EAST(),
+ Facing::DOWN => $selectUpDownPos(LeverFacing::DOWN_AXIS_X, LeverFacing::DOWN_AXIS_Z),
+ Facing::UP => $selectUpDownPos(LeverFacing::UP_AXIS_X, LeverFacing::UP_AXIS_Z),
+ Facing::NORTH => LeverFacing::NORTH,
+ Facing::SOUTH => LeverFacing::SOUTH,
+ Facing::WEST => LeverFacing::WEST,
+ Facing::EAST => LeverFacing::EAST,
default => throw new AssumptionFailedError("Bad facing value"),
};
@@ -90,8 +85,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
}
public function onNearbyBlockChange() : void{
- $facing = $this->facing->getFacing();
- if(!$this->canBeSupportedBy($this->getSide(Facing::opposite($facing)), $facing)){
+ if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing->getFacing()))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
@@ -107,8 +101,8 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return true;
}
- private function canBeSupportedBy(Block $block, int $face) : bool{
- return $block->getSupportType($face)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType($face)->hasCenterSupport();
}
//TODO
diff --git a/src/block/Light.php b/src/block/Light.php
index 239e1c04855..29a3a8dfca7 100644
--- a/src/block/Light.php
+++ b/src/block/Light.php
@@ -35,7 +35,7 @@ final class Light extends Flowable{
private int $level = self::MAX_LIGHT_LEVEL;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, self::MIN_LIGHT_LEVEL, self::MAX_LIGHT_LEVEL, $this->level);
+ $w->boundedIntAuto(self::MIN_LIGHT_LEVEL, self::MAX_LIGHT_LEVEL, $this->level);
}
public function getLightLevel() : int{ return $this->level; }
diff --git a/src/block/Liquid.php b/src/block/Liquid.php
index 98f1d562790..6404cf9081f 100644
--- a/src/block/Liquid.php
+++ b/src/block/Liquid.php
@@ -23,11 +23,11 @@
namespace pocketmine\block;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\MinimumCostFlowCalculator;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
-use pocketmine\event\block\BlockFormEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
@@ -49,7 +49,7 @@ abstract class Liquid extends Transparent{
protected bool $still = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, 0, self::MAX_DECAY, $this->decay);
+ $w->boundedIntAuto(0, self::MAX_DECAY, $this->decay);
$w->bool($this->falling);
$w->bool($this->still);
}
@@ -97,7 +97,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function getDropsForCompatibleTool(Item $item) : array{
@@ -165,23 +165,22 @@ public function getFlowVector() : Vector3{
$vX = $vY = $vZ = 0;
+ $x = $this->position->getFloorX();
+ $y = $this->position->getFloorY();
+ $z = $this->position->getFloorZ();
+
$decay = $this->getEffectiveFlowDecay($this);
$world = $this->position->getWorld();
foreach(Facing::HORIZONTAL as $j){
- $x = $this->position->x;
- $y = $this->position->y;
- $z = $this->position->z;
-
- match($j){
- Facing::WEST => --$x,
- Facing::EAST => ++$x,
- Facing::NORTH => --$z,
- Facing::SOUTH => ++$z
- };
-
- $sideBlock = $world->getBlockAt($x, $y, $z);
+ [$dx, $dy, $dz] = Facing::OFFSET[$j];
+
+ $sideX = $x + $dx;
+ $sideY = $y + $dy;
+ $sideZ = $z + $dz;
+
+ $sideBlock = $world->getBlockAt($sideX, $sideY, $sideZ);
$blockDecay = $this->getEffectiveFlowDecay($sideBlock);
if($blockDecay < 0){
@@ -189,21 +188,21 @@ public function getFlowVector() : Vector3{
continue;
}
- $blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($x, $y - 1, $z));
+ $blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($sideX, $sideY - 1, $sideZ));
if($blockDecay >= 0){
$realDecay = $blockDecay - ($decay - 8);
- $vX += ($x - $this->position->x) * $realDecay;
- $vY += ($y - $this->position->y) * $realDecay;
- $vZ += ($z - $this->position->z) * $realDecay;
+ $vX += $dx * $realDecay;
+ $vY += $dy * $realDecay;
+ $vZ += $dz * $realDecay;
}
continue;
}else{
$realDecay = $blockDecay - $decay;
- $vX += ($x - $this->position->x) * $realDecay;
- $vY += ($y - $this->position->y) * $realDecay;
- $vZ += ($z - $this->position->z) * $realDecay;
+ $vX += $dx * $realDecay;
+ $vY += $dy * $realDecay;
+ $vZ += $dz * $realDecay;
}
}
@@ -211,10 +210,10 @@ public function getFlowVector() : Vector3{
if($this->falling){
foreach(Facing::HORIZONTAL as $facing){
- $pos = $this->position->getSide($facing);
+ [$dx, $dy, $dz] = Facing::OFFSET[$facing];
if(
- !$this->canFlowInto($world->getBlockAt($pos->x, $pos->y, $pos->z)) ||
- !$this->canFlowInto($world->getBlockAt($pos->x, $pos->y + 1, $pos->z))
+ !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy, $z + $dz)) ||
+ !$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy + 1, $z + $dz))
){
$vector = $vector->normalize()->add(0, -6, 0);
break;
@@ -260,13 +259,17 @@ public function onScheduledUpdate() : void{
$world = $this->position->getWorld();
+ $x = $this->position->getFloorX();
+ $y = $this->position->getFloorY();
+ $z = $this->position->getFloorZ();
+
if(!$this->isSource()){
$smallestFlowDecay = -100;
$this->adjacentSources = 0;
- $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x, $this->position->y, $this->position->z - 1), $smallestFlowDecay);
- $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x, $this->position->y, $this->position->z + 1), $smallestFlowDecay);
- $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x - 1, $this->position->y, $this->position->z), $smallestFlowDecay);
- $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x + 1, $this->position->y, $this->position->z), $smallestFlowDecay);
+ $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z - 1), $smallestFlowDecay);
+ $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z + 1), $smallestFlowDecay);
+ $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x - 1, $y, $z), $smallestFlowDecay);
+ $smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x + 1, $y, $z), $smallestFlowDecay);
$newDecay = $smallestFlowDecay + $multiplier;
$falling = false;
@@ -275,13 +278,13 @@ public function onScheduledUpdate() : void{
$newDecay = -1;
}
- if($this->getEffectiveFlowDecay($world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)) >= 0){
+ if($this->getEffectiveFlowDecay($world->getBlockAt($x, $y + 1, $z)) >= 0){
$falling = true;
}
$minAdjacentSources = $this->getMinAdjacentSourcesToFormSource();
if($minAdjacentSources !== null && $this->adjacentSources >= $minAdjacentSources){
- $bottomBlock = $world->getBlockAt($this->position->x, $this->position->y - 1, $this->position->z);
+ $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
if($bottomBlock->isSolid() || ($bottomBlock instanceof Liquid && $bottomBlock->hasSameTypeId($this) && $bottomBlock->isSource())){
$newDecay = 0;
$falling = false;
@@ -290,17 +293,17 @@ public function onScheduledUpdate() : void{
if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
if(!$falling && $newDecay < 0){
- $world->setBlock($this->position, VanillaBlocks::AIR());
+ $world->setBlockAt($x, $y, $z, VanillaBlocks::AIR());
return;
}
$this->falling = $falling;
$this->decay = $falling ? 0 : $newDecay;
- $world->setBlock($this->position, $this); //local block update will cause an update to be scheduled
+ $world->setBlockAt($x, $y, $z, $this); //local block update will cause an update to be scheduled
}
}
- $bottomBlock = $world->getBlockAt($this->position->x, $this->position->y - 1, $this->position->z);
+ $bottomBlock = $world->getBlockAt($x, $y - 1, $z);
$this->flowIntoBlock($bottomBlock, 0, true);
@@ -312,9 +315,10 @@ public function onScheduledUpdate() : void{
}
if($adjacentDecay <= self::MAX_DECAY){
- $calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto']));
- foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){
- $this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false);
+ $calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), $this->canFlowInto(...));
+ foreach($calculator->getOptimalFlowDirections($x, $y, $z) as $facing){
+ [$dx, $dy, $dz] = Facing::OFFSET[$facing];
+ $this->flowIntoBlock($world->getBlockAt($x + $dx, $y + $dy, $z + $dz), $adjacentDecay, false);
}
}
}
@@ -363,12 +367,8 @@ protected function checkForHarden() : bool{
}
protected function liquidCollide(Block $cause, Block $result) : bool{
- $ev = new BlockFormEvent($this, $result, $cause);
- $ev->call();
- if(!$ev->isCancelled()){
- $world = $this->position->getWorld();
- $world->setBlock($this->position, $ev->getNewState());
- $world->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
+ if(BlockEventHelper::form($this, $result, $cause)){
+ $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
}
return true;
}
diff --git a/src/block/Loom.php b/src/block/Loom.php
index a10b5772322..d3dd4f3c766 100644
--- a/src/block/Loom.php
+++ b/src/block/Loom.php
@@ -25,14 +25,12 @@
use pocketmine\block\inventory\LoomInventory;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
final class Loom extends Opaque{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($player !== null){
diff --git a/src/block/Melon.php b/src/block/Melon.php
index 42d54c0ab26..4a32332e119 100644
--- a/src/block/Melon.php
+++ b/src/block/Melon.php
@@ -23,15 +23,16 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
+use function min;
class Melon extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::MELON()->setCount(mt_rand(3, 7))
+ VanillaItems::MELON()->setCount(min(9, FortuneDropHelper::discrete($item, 3, 7)))
];
}
diff --git a/src/block/MobHead.php b/src/block/MobHead.php
index 1b3f2d88faa..f4e945841d2 100644
--- a/src/block/MobHead.php
+++ b/src/block/MobHead.php
@@ -39,18 +39,13 @@ class MobHead extends Flowable{
public const MIN_ROTATION = 0;
public const MAX_ROTATION = 15;
- protected MobHeadType $mobHeadType;
+ protected MobHeadType $mobHeadType = MobHeadType::SKELETON;
protected int $facing = Facing::NORTH;
protected int $rotation = self::MIN_ROTATION; //TODO: split this into floor skull and wall skull handling
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->mobHeadType = MobHeadType::SKELETON(); //TODO: this should be a parameter
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->mobHeadType($this->mobHeadType);
+ $w->enum($this->mobHeadType);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@@ -113,14 +108,15 @@ public function setRotation(int $rotation) : self{
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
- $collisionBox = AxisAlignedBB::one()->contract(0.25, 0, 0.25)->trim(Facing::UP, 0.5);
- return match($this->facing){
- Facing::NORTH => [$collisionBox->offset(0, 0.25, 0.25)],
- Facing::SOUTH => [$collisionBox->offset(0, 0.25, -0.25)],
- Facing::WEST => [$collisionBox->offset(0.25, 0.25, 0)],
- Facing::EAST => [$collisionBox->offset(-0.25, 0.25, 0)],
- default => [$collisionBox]
- };
+ $collisionBox = AxisAlignedBB::one()
+ ->contract(0.25, 0, 0.25)
+ ->trim(Facing::UP, 0.5);
+ if($this->facing !== Facing::UP){
+ $collisionBox = $collisionBox
+ ->offsetTowards(Facing::opposite($this->facing), 0.25)
+ ->offsetTowards(Facing::UP, 0.25);
+ }
+ return [$collisionBox];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
diff --git a/src/block/MonsterSpawner.php b/src/block/MonsterSpawner.php
index 129da33946a..5cbe80e0a81 100644
--- a/src/block/MonsterSpawner.php
+++ b/src/block/MonsterSpawner.php
@@ -42,6 +42,6 @@ public function onScheduledUpdate() : void{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/Mycelium.php b/src/block/Mycelium.php
index c87f893678d..05b63d9bff0 100644
--- a/src/block/Mycelium.php
+++ b/src/block/Mycelium.php
@@ -23,8 +23,8 @@
namespace pocketmine\block;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\DirtType;
-use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use function mt_rand;
@@ -52,13 +52,9 @@ public function onRandomTick() : void{
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
$world = $this->position->getWorld();
$block = $world->getBlockAt($x, $y, $z);
- if($block instanceof Dirt && $block->getDirtType()->equals(DirtType::NORMAL())){
+ if($block instanceof Dirt && $block->getDirtType() === DirtType::NORMAL){
if($block->getSide(Facing::UP) instanceof Transparent){
- $ev = new BlockSpreadEvent($block, $this, VanillaBlocks::MYCELIUM());
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($block->position, $ev->getNewState());
- }
+ BlockEventHelper::spread($block, VanillaBlocks::MYCELIUM(), $this);
}
}
}
diff --git a/src/block/NetherGoldOre.php b/src/block/NetherGoldOre.php
index 7782f4fc45a..fd6745df437 100644
--- a/src/block/NetherGoldOre.php
+++ b/src/block/NetherGoldOre.php
@@ -23,14 +23,14 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
final class NetherGoldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
- return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 6))];
+ return [VanillaItems::GOLD_NUGGET()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 6))];
}
public function isAffectedBySilkTouch() : bool{ return true; }
diff --git a/src/block/NetherPortal.php b/src/block/NetherPortal.php
index a2524914abd..6a45fb7a0ac 100644
--- a/src/block/NetherPortal.php
+++ b/src/block/NetherPortal.php
@@ -70,7 +70,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function getDrops(Item $item) : array{
diff --git a/src/block/NetherQuartzOre.php b/src/block/NetherQuartzOre.php
index c2ab20491b6..0981974ee7f 100644
--- a/src/block/NetherQuartzOre.php
+++ b/src/block/NetherQuartzOre.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -31,7 +32,7 @@ class NetherQuartzOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::NETHER_QUARTZ()
+ VanillaItems::NETHER_QUARTZ()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}
diff --git a/src/block/NetherRoots.php b/src/block/NetherRoots.php
new file mode 100644
index 00000000000..0b08d1b180a
--- /dev/null
+++ b/src/block/NetherRoots.php
@@ -0,0 +1,40 @@
+getSide(Facing::DOWN);
+ return
+ $supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
+ $supportBlock->getTypeId() === BlockTypeIds::SOUL_SOIL;
+ }
+}
diff --git a/src/block/NetherVines.php b/src/block/NetherVines.php
index adf61178584..e8729c00f05 100644
--- a/src/block/NetherVines.php
+++ b/src/block/NetherVines.php
@@ -23,8 +23,10 @@
namespace pocketmine\block;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\FortuneDropHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
-use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
@@ -40,60 +42,30 @@
* This class is used for Weeping & Twisting vines, because they have same behaviour
*/
class NetherVines extends Flowable{
+ use AgeableTrait;
+ use StaticSupportTrait;
+
public const MAX_AGE = 25;
/** Direction the vine grows towards. */
private int $growthFace;
- protected int $age = 0;
-
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, int $growthFace){
$this->growthFace = $growthFace;
parent::__construct($idInfo, $name, $typeInfo);
}
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(5, 0, self::MAX_AGE, $this->age);
- }
-
- public function getAge() : int{
- return $this->age;
- }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0-" . self::MAX_AGE);
- }
-
- $this->age = $age;
- return $this;
- }
-
public function isAffectedBySilkTouch() : bool{
return true;
}
- public function ticksRandomly() : bool{
- return true;
- }
-
public function canClimb() : bool{
return true;
}
- private function getSupportFace() : int{
- return Facing::opposite($this->growthFace);
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType($this->getSupportFace())->hasCenterSupport() || $block->hasSameTypeId($this);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide($this->getSupportFace()))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::opposite($this->growthFace));
+ return $supportBlock->getSupportType($this->growthFace)->hasCenterSupport() || $supportBlock->hasSameTypeId($this);
}
/**
@@ -108,9 +80,6 @@ private function seekToTip() : NetherVines{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide($this->getSupportFace()))){
- return false;
- }
$this->age = mt_rand(0, self::MAX_AGE - 1);
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -125,8 +94,12 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return false;
}
+ public function ticksRandomly() : bool{
+ return $this->age < self::MAX_AGE;
+ }
+
public function onRandomTick() : void{
- if(mt_rand(1, 10) === 1 && $this->age < self::MAX_AGE){
+ if($this->age < self::MAX_AGE && mt_rand(1, 10) === 1){
if($this->getSide($this->growthFace)->canBeReplaced()){
$this->grow(null);
}
@@ -135,8 +108,8 @@ public function onRandomTick() : void{
private function grow(?Player $player, int $growthAmount = 1) : bool{
$top = $this->seekToTip();
- $age = $top->getAge();
- $pos = $top->getPosition();
+ $age = $top->age;
+ $pos = $top->position;
$world = $pos->getWorld();
$changedBlocks = 0;
@@ -179,13 +152,13 @@ protected function recalculateCollisionBoxes() : array{
}
public function getDropsForCompatibleTool(Item $item) : array{
- if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || mt_rand(1, 3) === 1){
+ if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || FortuneDropHelper::bonusChanceFixed($item, 1 / 3, 2 / 9)){
return [$this->asItem()];
}
return [];
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/NetherWartPlant.php b/src/block/NetherWartPlant.php
index 76de2a4705f..34e6fd57ece 100644
--- a/src/block/NetherWartPlant.php
+++ b/src/block/NetherWartPlant.php
@@ -23,69 +23,39 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockGrowEvent;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\FortuneDropHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
use function mt_rand;
class NetherWartPlant extends Flowable{
- public const MAX_AGE = 3;
-
- protected int $age = 0;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(2, 0, self::MAX_AGE, $this->age);
- }
+ use AgeableTrait;
+ use StaticSupportTrait;
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ..." . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $this->getSide(Facing::DOWN);
- if($down->getTypeId() === BlockTypeIds::SOUL_SAND){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
- }
+ public const MAX_AGE = 3;
- public function onNearbyBlockChange() : void{
- if($this->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::SOUL_SAND){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::SOUL_SAND;
}
public function ticksRandomly() : bool{
- return true;
+ return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{
if($this->age < self::MAX_AGE && mt_rand(0, 10) === 0){ //Still growing
$block = clone $this;
$block->age++;
- $ev = new BlockGrowEvent($this, $block);
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::grow($this, $block, null);
}
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
- $this->asItem()->setCount($this->age === self::MAX_AGE ? mt_rand(2, 4) : 1)
+ $this->asItem()->setCount($this->age === self::MAX_AGE ? FortuneDropHelper::discrete($item, 2, 4) : 1)
];
}
}
diff --git a/src/block/PinkPetals.php b/src/block/PinkPetals.php
new file mode 100644
index 00000000000..17bc4c50a6b
--- /dev/null
+++ b/src/block/PinkPetals.php
@@ -0,0 +1,114 @@
+horizontalFacing($this->facing);
+ $w->boundedIntAuto(self::MIN_COUNT, self::MAX_COUNT, $this->count);
+ }
+
+ public function getCount() : int{
+ return $this->count;
+ }
+
+ /** @return $this */
+ public function setCount(int $count) : self{
+ if($count < self::MIN_COUNT || $count > self::MAX_COUNT){
+ throw new \InvalidArgumentException("Count must be in range " . self::MIN_COUNT . " ... " . self::MAX_COUNT);
+ }
+ $this->count = $count;
+ return $this;
+ }
+
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ //TODO: Moss block
+ return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
+ }
+
+ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
+ return ($blockReplace instanceof PinkPetals && $blockReplace->count < self::MAX_COUNT) || $this->supportedWhenPlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
+ }
+
+ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+ if($blockReplace instanceof PinkPetals && $blockReplace->count < self::MAX_COUNT){
+ $this->count = $blockReplace->count + 1;
+ $this->facing = $blockReplace->facing;
+ }elseif($player !== null){
+ $this->facing = Facing::opposite($player->getHorizontalFacing());
+ }
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($item instanceof Fertilizer){
+ $grew = false;
+ if($this->count < self::MAX_COUNT){
+ $grew = BlockEventHelper::grow($this, (clone $this)->setCount($this->count + 1), $player);
+ }else{
+ $this->position->getWorld()->dropItem($this->position->add(0, 0.5, 0), $this->asItem());
+ $grew = true;
+ }
+ if($grew){
+ $item->pop();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function getFlameEncouragement() : int{
+ return 60;
+ }
+
+ public function getFlammability() : int{
+ return 100;
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [$this->asItem()->setCount($this->count)];
+ }
+}
diff --git a/src/block/PitcherCrop.php b/src/block/PitcherCrop.php
new file mode 100644
index 00000000000..d41aed284dd
--- /dev/null
+++ b/src/block/PitcherCrop.php
@@ -0,0 +1,109 @@
+getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND;
+ }
+
+ protected function recalculateCollisionBoxes() : array{
+ $widthTrim = $this->age === 0 ? 5 : 3;
+ $heightTrim = $this->age === 0 ? 13 : 11;
+ return [
+ AxisAlignedBB::one()
+ ->trim(Facing::UP, $heightTrim / 16)
+ ->squash(Axis::X, $widthTrim / 16)
+ ->squash(Axis::Z, $widthTrim / 16)
+ ->extend(Facing::DOWN, 1 / 16) //presumably this is to correct for farmland being 15/16 of a block tall
+ ];
+ }
+
+ private function grow(?Player $player) : bool{
+ if($this->age > self::MAX_AGE){
+ return false;
+ }
+
+ if($this->age === self::MAX_AGE){
+ $up = $this->getSide(Facing::UP);
+ if($up->getTypeId() !== BlockTypeIds::AIR){
+ return false;
+ }
+
+ $tx = new BlockTransaction($this->position->getWorld());
+ $tx->addBlock($this->position, VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(false));
+ $tx->addBlock($this->position->up(), VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(true));
+
+ $ev = new StructureGrowEvent($this, $tx, $player);
+ $ev->call();
+
+ return !$ev->isCancelled() && $tx->apply();
+ }
+
+ return BlockEventHelper::grow($this, (clone $this)->setAge($this->age + 1), $player);
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($item instanceof Fertilizer && $this->grow($player)){
+ $item->pop();
+ return true;
+ }
+
+ return false;
+ }
+
+ public function ticksRandomly() : bool{
+ return true;
+ }
+
+ public function onRandomTick() : void{
+ if(CropGrowthHelper::canGrow($this)){
+ $this->grow(null);
+ }
+ }
+
+ public function asItem() : Item{
+ return VanillaItems::PITCHER_POD();
+ }
+}
diff --git a/src/block/Podzol.php b/src/block/Podzol.php
index 4eb720573d0..33285cc0ed4 100644
--- a/src/block/Podzol.php
+++ b/src/block/Podzol.php
@@ -27,6 +27,10 @@
class Podzol extends Opaque{
+ public function isAffectedBySilkTouch() : bool{
+ return true;
+ }
+
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaBlocks::DIRT()->asItem()
diff --git a/src/block/Potato.php b/src/block/Potato.php
index 47d39f61265..4d1bbf9790c 100644
--- a/src/block/Potato.php
+++ b/src/block/Potato.php
@@ -23,6 +23,7 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@@ -31,7 +32,8 @@ class Potato extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
$result = [
- VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 5) : 1)
+ //min/max would be 2-5 in Java
+ VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
];
if($this->age >= self::MAX_AGE && mt_rand(0, 49) === 0){
$result[] = VanillaItems::POISONOUS_POTATO();
diff --git a/src/block/PotionCauldron.php b/src/block/PotionCauldron.php
index b8259fbc351..ca91664e4e7 100644
--- a/src/block/PotionCauldron.php
+++ b/src/block/PotionCauldron.php
@@ -63,6 +63,14 @@ public function getPotionItem() : ?Item{ return $this->potionItem === null ? nul
/** @return $this */
public function setPotionItem(?Item $potionItem) : self{
+ if($potionItem !== null && !match($potionItem->getTypeId()){
+ ItemTypeIds::POTION,
+ ItemTypeIds::SPLASH_POTION,
+ ItemTypeIds::LINGERING_POTION => true,
+ default => false,
+ }){
+ throw new \InvalidArgumentException("Item must be a POTION, SPLASH_POTION or LINGERING_POTION");
+ }
$this->potionItem = $potionItem !== null ? (clone $potionItem)->setCount(1) : null;
return $this;
}
diff --git a/src/block/PressurePlate.php b/src/block/PressurePlate.php
index 7f9403b7423..1dd4b50d97d 100644
--- a/src/block/PressurePlate.php
+++ b/src/block/PressurePlate.php
@@ -23,14 +23,31 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
-use pocketmine\item\Item;
+use pocketmine\entity\Entity;
+use pocketmine\event\block\PressurePlateUpdateEvent;
+use pocketmine\math\Axis;
+use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
+use pocketmine\world\sound\PressurePlateActivateSound;
+use pocketmine\world\sound\PressurePlateDeactivateSound;
+use function count;
abstract class PressurePlate extends Transparent{
+ use StaticSupportTrait;
+
+ private readonly int $deactivationDelayTicks;
+
+ public function __construct(
+ BlockIdentifier $idInfo,
+ string $name,
+ BlockTypeInfo $typeInfo,
+ int $deactivationDelayTicks = 20 //TODO: make this mandatory in PM6
+ ){
+ parent::__construct($idInfo, $name, $typeInfo);
+ $this->deactivationDelayTicks = $deactivationDelayTicks;
+ }
public function isSolid() : bool{
return false;
@@ -41,25 +58,96 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
+ }
+
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ public function hasEntityCollision() : bool{
+ return true;
+ }
+
+ public function onEntityInside(Entity $entity) : bool{
+ if(!$this->hasOutputSignal()){
+ $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 0);
}
+ return true;
+ }
+
+ /**
+ * Returns the AABB that entities must intersect to activate the pressure plate.
+ * Note that this is not the same as the collision box (pressure plate doesn't have one), nor the visual bounding
+ * box. The activation area has a height of 0.25 blocks.
+ */
+ protected function getActivationBox() : AxisAlignedBB{
+ return AxisAlignedBB::one()
+ ->squash(Axis::X, 1 / 8)
+ ->squash(Axis::Z, 1 / 8)
+ ->trim(Facing::UP, 3 / 4)
+ ->offset($this->position->x, $this->position->y, $this->position->z);
+ }
+
+ /**
+ * TODO: make this abstract in PM6
+ */
+ protected function hasOutputSignal() : bool{
return false;
}
- private function canBeSupportedBy(Block $block) : bool{
- return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
+ /**
+ * TODO: make this abstract in PM6
+ *
+ * @param Entity[] $entities
+ *
+ * @return mixed[]
+ * @phpstan-return array{Block, ?bool}
+ */
+ protected function calculatePlateState(array $entities) : array{
+ return [$this, null];
}
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ /**
+ * Filters entities which don't affect the pressure plate state from the given list.
+ *
+ * @param Entity[] $entities
+ * @return Entity[]
+ */
+ protected function filterIrrelevantEntities(array $entities) : array{
+ return $entities;
}
- //TODO
+ public function onScheduledUpdate() : void{
+ $world = $this->position->getWorld();
+
+ $intersectionAABB = $this->getActivationBox();
+ $activatingEntities = $this->filterIrrelevantEntities($world->getNearbyEntities($intersectionAABB));
+
+ //if an irrelevant entity is inside the full cube space of the pressure plate but not activating the plate,
+ //it will cause scheduled updates on the plate every tick. We don't want to fire events in this case if the
+ //plate is already deactivated.
+ if(count($activatingEntities) > 0 || $this->hasOutputSignal()){
+ [$newState, $pressedChange] = $this->calculatePlateState($activatingEntities);
+
+ //always call this, in case there are new entities on the plate
+ if(PressurePlateUpdateEvent::hasHandlers()){
+ $ev = new PressurePlateUpdateEvent($this, $newState, $activatingEntities);
+ $ev->call();
+ $newState = $ev->isCancelled() ? null : $ev->getNewState();
+ }
+ if($newState !== null){
+ $world->setBlock($this->position, $newState);
+ if($pressedChange !== null){
+ $world->addSound($this->position, $pressedChange ?
+ new PressurePlateActivateSound($this) :
+ new PressurePlateDeactivateSound($this)
+ );
+ }
+ }
+ if($pressedChange ?? $this->hasOutputSignal()){
+ $world->scheduleDelayedBlockUpdate($this->position, $this->deactivationDelayTicks);
+ }
+ }
+ }
}
diff --git a/src/block/RedMushroom.php b/src/block/RedMushroom.php
index 553957fe6c0..81ab940f53d 100644
--- a/src/block/RedMushroom.php
+++ b/src/block/RedMushroom.php
@@ -43,7 +43,7 @@ public function onNearbyBlockChange() : void{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
- $position = $this->getPosition();
+ $position = $this->position;
$lightLevel = $position->getWorld()->getFullLightAt($position->x, $position->y, $position->z);
$downId = $down->getTypeId();
//TODO: nylium support
diff --git a/src/block/RedMushroomBlock.php b/src/block/RedMushroomBlock.php
index ecac38e18c7..0a7cb30f253 100644
--- a/src/block/RedMushroomBlock.php
+++ b/src/block/RedMushroomBlock.php
@@ -29,17 +29,12 @@
use function mt_rand;
class RedMushroomBlock extends Opaque{
- protected MushroomBlockType $mushroomBlockType;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->mushroomBlockType = MushroomBlockType::ALL_CAP();
- parent::__construct($idInfo, $name, $typeInfo);
- }
+ protected MushroomBlockType $mushroomBlockType = MushroomBlockType::ALL_CAP;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
//these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative),
//so this information needs to be kept in the type info
- $w->mushroomBlockType($this->mushroomBlockType);
+ $w->enum($this->mushroomBlockType);
}
public function getMushroomBlockType() : MushroomBlockType{ return $this->mushroomBlockType; }
@@ -61,10 +56,10 @@ public function isAffectedBySilkTouch() : bool{
}
public function getSilkTouchDrops(Item $item) : array{
- return [(clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP())->asItem()];
+ return [(clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP)->asItem()];
}
public function getPickedItem(bool $addUserData = false) : Item{
- return (clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP())->asItem();
+ return (clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP)->asItem();
}
}
diff --git a/src/block/RedstoneComparator.php b/src/block/RedstoneComparator.php
index 2158f1a8407..ee63a77a99d 100644
--- a/src/block/RedstoneComparator.php
+++ b/src/block/RedstoneComparator.php
@@ -27,6 +27,7 @@
use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\PoweredByRedstoneTrait;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@@ -41,6 +42,7 @@ class RedstoneComparator extends Flowable{
use HorizontalFacingTrait;
use AnalogRedstoneSignalEmitterTrait;
use PoweredByRedstoneTrait;
+ use StaticSupportTrait;
protected bool $isSubtractMode = false;
@@ -85,14 +87,10 @@ protected function recalculateCollisionBoxes() : array{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
- if($player !== null){
- $this->facing = Facing::opposite($player->getHorizontalFacing());
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ if($player !== null){
+ $this->facing = Facing::opposite($player->getHorizontalFacing());
}
-
- return false;
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@@ -101,14 +99,8 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return true;
}
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
//TODO: redstone functionality
diff --git a/src/block/RedstoneOre.php b/src/block/RedstoneOre.php
index 74708c2bd54..10e701a6f4c 100644
--- a/src/block/RedstoneOre.php
+++ b/src/block/RedstoneOre.php
@@ -23,7 +23,8 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
+use pocketmine\block\utils\FortuneDropHelper;
+use pocketmine\block\utils\LightableTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
@@ -31,23 +32,7 @@
use function mt_rand;
class RedstoneOre extends Opaque{
- protected bool $lit = false;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->bool($this->lit);
- }
-
- public function isLit() : bool{
- return $this->lit;
- }
-
- /**
- * @return $this
- */
- public function setLit(bool $lit = true) : self{
- $this->lit = $lit;
- return $this;
- }
+ use LightableTrait;
public function getLightLevel() : int{
return $this->lit ? 9 : 0;
@@ -69,7 +54,7 @@ public function onNearbyBlockChange() : void{
}
public function ticksRandomly() : bool{
- return true;
+ return $this->lit;
}
public function onRandomTick() : void{
@@ -81,7 +66,7 @@ public function onRandomTick() : void{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::REDSTONE_DUST()->setCount(mt_rand(4, 5))
+ VanillaItems::REDSTONE_DUST()->setCount(FortuneDropHelper::discrete($item, 4, 5))
];
}
diff --git a/src/block/RedstoneRepeater.php b/src/block/RedstoneRepeater.php
index d4f14523842..7e6e73da846 100644
--- a/src/block/RedstoneRepeater.php
+++ b/src/block/RedstoneRepeater.php
@@ -25,6 +25,7 @@
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\PoweredByRedstoneTrait;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@@ -37,6 +38,7 @@
class RedstoneRepeater extends Flowable{
use HorizontalFacingTrait;
use PoweredByRedstoneTrait;
+ use StaticSupportTrait;
public const MIN_DELAY = 1;
public const MAX_DELAY = 4;
@@ -45,7 +47,7 @@ class RedstoneRepeater extends Flowable{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
- $w->boundedInt(2, self::MIN_DELAY, self::MAX_DELAY, $this->delay);
+ $w->boundedIntAuto(self::MIN_DELAY, self::MAX_DELAY, $this->delay);
$w->bool($this->powered);
}
@@ -68,15 +70,11 @@ protected function recalculateCollisionBoxes() : array{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
- if($player !== null){
- $this->facing = Facing::opposite($player->getHorizontalFacing());
- }
-
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ if($player !== null){
+ $this->facing = Facing::opposite($player->getHorizontalFacing());
}
- return false;
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@@ -87,14 +85,8 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return true;
}
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
//TODO: redstone functionality
diff --git a/src/block/RedstoneTorch.php b/src/block/RedstoneTorch.php
index b30c011d4d8..26c86038b95 100644
--- a/src/block/RedstoneTorch.php
+++ b/src/block/RedstoneTorch.php
@@ -23,28 +23,22 @@
namespace pocketmine\block;
+use pocketmine\block\utils\LightableTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
class RedstoneTorch extends Torch{
- protected bool $lit = true;
+ use LightableTrait;
+
+ public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
+ $this->lit = true;
+ parent::__construct($idInfo, $name, $typeInfo);
+ }
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
parent::describeBlockOnlyState($w);
$w->bool($this->lit);
}
- public function isLit() : bool{
- return $this->lit;
- }
-
- /**
- * @return $this
- */
- public function setLit(bool $lit = true) : self{
- $this->lit = $lit;
- return $this;
- }
-
public function getLightLevel() : int{
return $this->lit ? 7 : 0;
}
diff --git a/src/block/RedstoneWire.php b/src/block/RedstoneWire.php
index 022672b5da2..a2d293fca0d 100644
--- a/src/block/RedstoneWire.php
+++ b/src/block/RedstoneWire.php
@@ -24,22 +24,14 @@
namespace pocketmine\block;
use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
class RedstoneWire extends Flowable{
use AnalogRedstoneSignalEmitterTrait;
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
- return false;
- }
+ use StaticSupportTrait;
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
@@ -48,14 +40,8 @@ public function readStateFromWorld() : Block{
return $this;
}
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::UP)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();
}
public function asItem() : Item{
diff --git a/src/block/Sapling.php b/src/block/Sapling.php
index b1f255f0d7e..b3fdf59af63 100644
--- a/src/block/Sapling.php
+++ b/src/block/Sapling.php
@@ -24,6 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\SaplingType;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
@@ -32,11 +33,12 @@
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Random;
-use pocketmine\world\BlockTransaction;
use pocketmine\world\generator\object\TreeFactory;
use function mt_rand;
class Sapling extends Flowable{
+ use StaticSupportTrait;
+
protected bool $ready = false;
private SaplingType $saplingType;
@@ -58,13 +60,9 @@ public function setReady(bool $ready) : self{
return $this;
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $this->getSide(Facing::DOWN);
- if($down->hasTypeTag(BlockTypeTags::DIRT) || $down->hasTypeTag(BlockTypeTags::MUD)){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@@ -77,13 +75,6 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return false;
}
- public function onNearbyBlockChange() : void{
- $down = $this->getSide(Facing::DOWN);
- if(!$down->hasTypeTag(BlockTypeTags::DIRT) && !$down->hasTypeTag(BlockTypeTags::MUD)){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
public function ticksRandomly() : bool{
return true;
}
diff --git a/src/block/SeaLantern.php b/src/block/SeaLantern.php
index 27ed73f0df0..ff0c97d7333 100644
--- a/src/block/SeaLantern.php
+++ b/src/block/SeaLantern.php
@@ -23,8 +23,10 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
+use function min;
class SeaLantern extends Transparent{
@@ -34,7 +36,7 @@ public function getLightLevel() : int{
public function getDropsForCompatibleTool(Item $item) : array{
return [
- VanillaItems::PRISMARINE_CRYSTALS()->setCount(3)
+ VanillaItems::PRISMARINE_CRYSTALS()->setCount(min(5, FortuneDropHelper::discrete($item, 2, 3)))
];
}
diff --git a/src/block/SeaPickle.php b/src/block/SeaPickle.php
index 4e667038e0c..627af9bacc6 100644
--- a/src/block/SeaPickle.php
+++ b/src/block/SeaPickle.php
@@ -39,7 +39,7 @@ class SeaPickle extends Transparent{
protected bool $underwater = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(2, self::MIN_COUNT, self::MAX_COUNT, $this->count);
+ $w->boundedIntAuto(self::MIN_COUNT, self::MAX_COUNT, $this->count);
$w->bool($this->underwater);
}
@@ -78,7 +78,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php
index b2f53e0a7dc..d557401eec3 100644
--- a/src/block/ShulkerBox.php
+++ b/src/block/ShulkerBox.php
@@ -25,6 +25,7 @@
use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
use pocketmine\block\utils\AnyFacingTrait;
+use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@@ -110,4 +111,8 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return true;
}
+
+ public function getSupportType(int $facing) : SupportType{
+ return SupportType::NONE;
+ }
}
diff --git a/src/block/SimplePressurePlate.php b/src/block/SimplePressurePlate.php
index e4278410d53..3429b9b5da6 100644
--- a/src/block/SimplePressurePlate.php
+++ b/src/block/SimplePressurePlate.php
@@ -24,6 +24,7 @@
namespace pocketmine\block;
use pocketmine\data\runtime\RuntimeDataDescriber;
+use function count;
abstract class SimplePressurePlate extends PressurePlate{
protected bool $pressed = false;
@@ -39,4 +40,19 @@ public function setPressed(bool $pressed) : self{
$this->pressed = $pressed;
return $this;
}
+
+ protected function hasOutputSignal() : bool{
+ return $this->pressed;
+ }
+
+ protected function calculatePlateState(array $entities) : array{
+ $newPressed = count($entities) > 0;
+ if($newPressed === $this->pressed){
+ return [$this, null];
+ }
+ return [
+ (clone $this)->setPressed($newPressed),
+ $newPressed
+ ];
+ }
}
diff --git a/src/block/Slab.php b/src/block/Slab.php
index cbf5dce189a..6000bec39e0 100644
--- a/src/block/Slab.php
+++ b/src/block/Slab.php
@@ -34,19 +34,18 @@
use pocketmine\world\BlockTransaction;
class Slab extends Transparent{
- protected SlabType $slabType;
+ protected SlabType $slabType = SlabType::BOTTOM;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->slabType = SlabType::BOTTOM();
parent::__construct($idInfo, $name . " Slab", $typeInfo);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->slabType($this->slabType);
+ $w->enum($this->slabType);
}
public function isTransparent() : bool{
- return !$this->slabType->equals(SlabType::DOUBLE());
+ return $this->slabType !== SlabType::DOUBLE;
}
/**
@@ -69,8 +68,8 @@ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $fa
return true;
}
- if($blockReplace instanceof Slab && !$blockReplace->slabType->equals(SlabType::DOUBLE()) && $blockReplace->hasSameTypeId($this)){
- if($blockReplace->slabType->equals(SlabType::TOP())){ //Trying to combine with top slab
+ if($blockReplace instanceof Slab && $blockReplace->slabType !== SlabType::DOUBLE && $blockReplace->hasSameTypeId($this)){
+ if($blockReplace->slabType === SlabType::TOP){ //Trying to combine with top slab
return $clickVector->y <= 0.5 || (!$isClickedBlock && $face === Facing::UP);
}else{
return $clickVector->y >= 0.5 || (!$isClickedBlock && $face === Facing::DOWN);
@@ -81,14 +80,14 @@ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $fa
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($blockReplace instanceof Slab && !$blockReplace->slabType->equals(SlabType::DOUBLE()) && $blockReplace->hasSameTypeId($this) && (
- ($blockReplace->slabType->equals(SlabType::TOP()) && ($clickVector->y <= 0.5 || $face === Facing::UP)) ||
- ($blockReplace->slabType->equals(SlabType::BOTTOM()) && ($clickVector->y >= 0.5 || $face === Facing::DOWN))
+ if($blockReplace instanceof Slab && $blockReplace->slabType !== SlabType::DOUBLE && $blockReplace->hasSameTypeId($this) && (
+ ($blockReplace->slabType === SlabType::TOP && ($clickVector->y <= 0.5 || $face === Facing::UP)) ||
+ ($blockReplace->slabType === SlabType::BOTTOM && ($clickVector->y >= 0.5 || $face === Facing::DOWN))
)){
//Clicked in empty half of existing slab
- $this->slabType = SlabType::DOUBLE();
+ $this->slabType = SlabType::DOUBLE;
}else{
- $this->slabType = (($face !== Facing::UP && $clickVector->y > 0.5) || $face === Facing::DOWN) ? SlabType::TOP() : SlabType::BOTTOM();
+ $this->slabType = (($face !== Facing::UP && $clickVector->y > 0.5) || $face === Facing::DOWN) ? SlabType::TOP : SlabType::BOTTOM;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
@@ -98,22 +97,22 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
- if($this->slabType->equals(SlabType::DOUBLE())){
+ if($this->slabType === SlabType::DOUBLE){
return [AxisAlignedBB::one()];
}
- return [AxisAlignedBB::one()->trim($this->slabType->equals(SlabType::TOP()) ? Facing::DOWN : Facing::UP, 0.5)];
+ return [AxisAlignedBB::one()->trim($this->slabType === SlabType::TOP ? Facing::DOWN : Facing::UP, 0.5)];
}
public function getSupportType(int $facing) : SupportType{
- if($this->slabType->equals(SlabType::DOUBLE())){
- return SupportType::FULL();
- }elseif(($facing === Facing::UP && $this->slabType->equals(SlabType::TOP())) || ($facing === Facing::DOWN && $this->slabType->equals(SlabType::BOTTOM()))){
- return SupportType::FULL();
+ if($this->slabType === SlabType::DOUBLE){
+ return SupportType::FULL;
+ }elseif(($facing === Facing::UP && $this->slabType === SlabType::TOP) || ($facing === Facing::DOWN && $this->slabType === SlabType::BOTTOM)){
+ return SupportType::FULL;
}
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function getDropsForCompatibleTool(Item $item) : array{
- return [$this->asItem()->setCount($this->slabType->equals(SlabType::DOUBLE()) ? 2 : 1)];
+ return [$this->asItem()->setCount($this->slabType === SlabType::DOUBLE ? 2 : 1)];
}
}
diff --git a/src/block/SmallDripleaf.php b/src/block/SmallDripleaf.php
new file mode 100644
index 00000000000..d192e43db70
--- /dev/null
+++ b/src/block/SmallDripleaf.php
@@ -0,0 +1,170 @@
+horizontalFacing($this->facing);
+ $w->bool($this->top);
+ }
+
+ public function isTop() : bool{
+ return $this->top;
+ }
+
+ /** @return $this */
+ public function setTop(bool $top) : self{
+ $this->top = $top;
+ return $this;
+ }
+
+ private function canBeSupportedBy(Block $block) : bool{
+ //TODO: Moss
+ //TODO: Small Dripleaf also can be placed on dirt, coarse dirt, farmland, grass blocks,
+ // podzol, rooted dirt, mycelium, and mud if these blocks are underwater (needs waterlogging)
+ return $block->getTypeId() === BlockTypeIds::CLAY;
+ }
+
+ public function onNearbyBlockChange() : void{
+ if(!$this->top && !$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
+ $this->position->getWorld()->useBreakOn($this->position);
+ return;
+ }
+ $face = $this->top ? Facing::DOWN : Facing::UP;
+ if(!$this->getSide($face)->hasSameTypeId($this)){
+ $this->position->getWorld()->useBreakOn($this->position);
+ }
+ }
+
+ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+ $block = $blockReplace->getSide(Facing::UP);
+ if($block->getTypeId() !== BlockTypeIds::AIR || !$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
+ return false;
+ }
+ if($player !== null){
+ $this->facing = Facing::opposite($player->getHorizontalFacing());
+ }
+
+ $tx->addBlock($block->position, VanillaBlocks::SMALL_DRIPLEAF()
+ ->setFacing($this->facing)
+ ->setTop(true)
+ );
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($item instanceof Fertilizer && $this->grow($player)){
+ $item->pop();
+ return true;
+ }
+ return false;
+ }
+
+ private function canGrowTo(Position $pos) : bool{
+ $world = $pos->getWorld();
+ if(!$world->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ())){
+ return false;
+ }
+ $block = $world->getBlock($pos);
+ return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::AIR;
+ }
+
+ private function grow(?Player $player) : bool{
+ $bottomBlock = $this->top ? $this->getSide(Facing::DOWN) : $this;
+ if(!$this->hasSameTypeId($bottomBlock)){
+ return false;
+ }
+ $world = $this->position->getWorld();
+ $tx = new BlockTransaction($world);
+ $height = mt_rand(2, 5);
+ $grown = 0;
+ for($i = 0; $i < $height; $i++){
+ $pos = $bottomBlock->getSide(Facing::UP, $i)->position;
+ if(!$this->canGrowTo($pos)){
+ break;
+ }
+ $block = ++$grown < $height && $this->canGrowTo($pos->getSide(Facing::UP)) ?
+ VanillaBlocks::BIG_DRIPLEAF_STEM() :
+ VanillaBlocks::BIG_DRIPLEAF_HEAD();
+ $tx->addBlock($pos, $block->setFacing($this->facing));
+ }
+ if($grown > 1){
+ $ev = new StructureGrowEvent($bottomBlock, $tx, $player);
+ $ev->call();
+ if(!$ev->isCancelled()){
+ return $tx->apply();
+ }
+ }
+
+ return false;
+ }
+
+ public function getAffectedBlocks() : array{
+ $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
+ if($other->hasSameTypeId($this)){
+ return [$this, $other];
+ }
+ return parent::getAffectedBlocks();
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ if(!$this->top){
+ return [$this->asItem()];
+ }
+ return [];
+ }
+
+ public function getFlameEncouragement() : int{
+ return 15;
+ }
+
+ public function getFlammability() : int{
+ return 100;
+ }
+
+ public function getSupportType(int $facing) : SupportType{
+ return SupportType::NONE;
+ }
+
+ protected function recalculateCollisionBoxes() : array{
+ return [];
+ }
+}
diff --git a/src/block/SnowLayer.php b/src/block/SnowLayer.php
index f2425455ce6..cca8424a983 100644
--- a/src/block/SnowLayer.php
+++ b/src/block/SnowLayer.php
@@ -23,11 +23,11 @@
namespace pocketmine\block;
+use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockMeltEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
@@ -47,7 +47,7 @@ class SnowLayer extends Flowable implements Fallable{
protected int $layers = self::MIN_LAYERS;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, self::MIN_LAYERS, self::MAX_LAYERS, $this->layers);
+ $w->boundedIntAuto(self::MIN_LAYERS, self::MAX_LAYERS, $this->layers);
}
public function getLayers() : int{ return $this->layers; }
@@ -75,13 +75,13 @@ protected function recalculateCollisionBoxes() : array{
public function getSupportType(int $facing) : SupportType{
if(!$this->canBeReplaced()){
- return SupportType::FULL();
+ return SupportType::FULL;
}
- return SupportType::NONE();
+ return SupportType::NONE;
}
- private function canBeSupportedBy(Block $b) : bool{
- return $b->getSupportType(Facing::UP)->equals(SupportType::FULL());
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::DOWN) === SupportType::FULL;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
@@ -91,7 +91,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
}
$this->layers = $blockReplace->layers + 1;
}
- if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
+ if($this->canBeSupportedAt($blockReplace)){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -105,11 +105,7 @@ public function ticksRandomly() : bool{
public function onRandomTick() : void{
$world = $this->position->getWorld();
if($world->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
- $ev = new BlockMeltEvent($this, VanillaBlocks::AIR());
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::melt($this, VanillaBlocks::AIR());
}
}
diff --git a/src/block/SoulCampfire.php b/src/block/SoulCampfire.php
new file mode 100644
index 00000000000..a9c8fc918f1
--- /dev/null
+++ b/src/block/SoulCampfire.php
@@ -0,0 +1,48 @@
+lit ? 10 : 0;
+ }
+
+ public function getDropsForCompatibleTool(Item $item) : array{
+ return [
+ VanillaBlocks::SOUL_SOIL()->asItem()
+ ];
+ }
+
+ protected function getEntityCollisionDamage() : int{
+ return 2;
+ }
+
+ protected function getFurnaceType() : FurnaceType{
+ return FurnaceType::SOUL_CAMPFIRE;
+ }
+}
diff --git a/src/block/SporeBlossom.php b/src/block/SporeBlossom.php
index 73e31edf20f..3ca5fa1f206 100644
--- a/src/block/SporeBlossom.php
+++ b/src/block/SporeBlossom.php
@@ -23,30 +23,14 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
-use pocketmine\item\Item;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
final class SporeBlossom extends Flowable{
+ use StaticSupportTrait;
- private function canBeSupportedBy(Block $block) : bool{
- return $block->getSupportType(Facing::DOWN)->equals(SupportType::FULL());
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP))){
- return false;
- }
-
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::UP))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getAdjacentSupportType(Facing::UP) === SupportType::FULL;
}
}
diff --git a/src/block/StainedGlass.php b/src/block/StainedGlass.php
index 5b4b6a883c3..bc0d8487700 100644
--- a/src/block/StainedGlass.php
+++ b/src/block/StainedGlass.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
final class StainedGlass extends Glass{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/StainedGlassPane.php b/src/block/StainedGlassPane.php
index 2a592395dde..18ecfdee0e6 100644
--- a/src/block/StainedGlassPane.php
+++ b/src/block/StainedGlassPane.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
final class StainedGlassPane extends GlassPane{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/StainedHardenedClay.php b/src/block/StainedHardenedClay.php
index 1a9d68737be..2c2c01ba3ab 100644
--- a/src/block/StainedHardenedClay.php
+++ b/src/block/StainedHardenedClay.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
final class StainedHardenedClay extends HardenedClay{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/StainedHardenedGlass.php b/src/block/StainedHardenedGlass.php
index 85d5fcec0f7..cc609a49a9b 100644
--- a/src/block/StainedHardenedGlass.php
+++ b/src/block/StainedHardenedGlass.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
final class StainedHardenedGlass extends HardenedGlass{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/StainedHardenedGlassPane.php b/src/block/StainedHardenedGlassPane.php
index 4a61f9778bc..63dbe1f776e 100644
--- a/src/block/StainedHardenedGlassPane.php
+++ b/src/block/StainedHardenedGlassPane.php
@@ -24,13 +24,7 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
final class StainedHardenedGlassPane extends HardenedGlassPane{
use ColoredTrait;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
}
diff --git a/src/block/Stair.php b/src/block/Stair.php
index a2074672170..d66a9ce5cfc 100644
--- a/src/block/Stair.php
+++ b/src/block/Stair.php
@@ -39,12 +39,7 @@ class Stair extends Transparent{
use HorizontalFacingTrait;
protected bool $upsideDown = false;
- protected StairShape $shape;
-
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->shape = StairShape::STRAIGHT();
- parent::__construct($idInfo, $name, $typeInfo);
- }
+ protected StairShape $shape = StairShape::STRAIGHT;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
@@ -54,13 +49,15 @@ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
+ $this->collisionBoxes = null;
+
$clockwise = Facing::rotateY($this->facing, true);
if(($backFacing = $this->getPossibleCornerFacing(false)) !== null){
- $this->shape = $backFacing === $clockwise ? StairShape::OUTER_RIGHT() : StairShape::OUTER_LEFT();
+ $this->shape = $backFacing === $clockwise ? StairShape::OUTER_RIGHT : StairShape::OUTER_LEFT;
}elseif(($frontFacing = $this->getPossibleCornerFacing(true)) !== null){
- $this->shape = $frontFacing === $clockwise ? StairShape::INNER_RIGHT() : StairShape::INNER_LEFT();
+ $this->shape = $frontFacing === $clockwise ? StairShape::INNER_RIGHT : StairShape::INNER_LEFT;
}else{
- $this->shape = StairShape::STRAIGHT();
+ $this->shape = StairShape::STRAIGHT;
}
return $this;
@@ -92,14 +89,14 @@ protected function recalculateCollisionBoxes() : array{
->trim(Facing::opposite($topStepFace), 0.5)
->trim(Facing::opposite($this->facing), 0.5);
- if($this->shape->equals(StairShape::OUTER_LEFT()) || $this->shape->equals(StairShape::OUTER_RIGHT())){
- $topStep->trim(Facing::rotateY($this->facing, $this->shape->equals(StairShape::OUTER_LEFT())), 0.5);
- }elseif($this->shape->equals(StairShape::INNER_LEFT()) || $this->shape->equals(StairShape::INNER_RIGHT())){
+ if($this->shape === StairShape::OUTER_LEFT || $this->shape === StairShape::OUTER_RIGHT){
+ $topStep->trim(Facing::rotateY($this->facing, $this->shape === StairShape::OUTER_LEFT), 0.5);
+ }elseif($this->shape === StairShape::INNER_LEFT || $this->shape === StairShape::INNER_RIGHT){
//add an extra cube
$bbs[] = AxisAlignedBB::one()
->trim(Facing::opposite($topStepFace), 0.5)
->trim($this->facing, 0.5) //avoid overlapping with main step
- ->trim(Facing::rotateY($this->facing, $this->shape->equals(StairShape::INNER_LEFT())), 0.5);
+ ->trim(Facing::rotateY($this->facing, $this->shape === StairShape::INNER_LEFT), 0.5);
}
$bbs[] = $topStep;
@@ -109,15 +106,15 @@ protected function recalculateCollisionBoxes() : array{
public function getSupportType(int $facing) : SupportType{
if(
- $facing === Facing::UP && $this->isUpsideDown() ||
- $facing === Facing::DOWN && !$this->isUpsideDown() ||
- ($facing === $this->facing && !$this->shape->equals(StairShape::OUTER_LEFT()) && !$this->shape->equals(StairShape::OUTER_RIGHT())) ||
- ($facing === Facing::rotate($this->facing, Axis::Y, false) && $this->shape->equals(StairShape::INNER_LEFT())) ||
- ($facing === Facing::rotate($this->facing, Axis::Y, true) && $this->shape->equals(StairShape::INNER_RIGHT()))
+ $facing === Facing::UP && $this->upsideDown ||
+ $facing === Facing::DOWN && !$this->upsideDown ||
+ ($facing === $this->facing && $this->shape !== StairShape::OUTER_LEFT && $this->shape !== StairShape::OUTER_RIGHT) ||
+ ($facing === Facing::rotate($this->facing, Axis::Y, false) && $this->shape === StairShape::INNER_LEFT) ||
+ ($facing === Facing::rotate($this->facing, Axis::Y, true) && $this->shape === StairShape::INNER_RIGHT)
){
- return SupportType::FULL();
+ return SupportType::FULL;
}
- return SupportType::NONE();
+ return SupportType::NONE;
}
private function getPossibleCornerFacing(bool $oppositeFacing) : ?int{
diff --git a/src/block/Stem.php b/src/block/Stem.php
index 5f06a46cb12..2ac95aa3f11 100644
--- a/src/block/Stem.php
+++ b/src/block/Stem.php
@@ -23,27 +23,53 @@
namespace pocketmine\block;
-use pocketmine\event\block\BlockGrowEvent;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\CropGrowthHelper;
+use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use function array_rand;
use function mt_rand;
abstract class Stem extends Crops{
+ protected int $facing = Facing::UP;
+
+ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
+ parent::describeBlockOnlyState($w);
+ $w->facingExcept($this->facing, Facing::DOWN);
+ }
+
+ public function getFacing() : int{ return $this->facing; }
+
+ /** @return $this */
+ public function setFacing(int $facing) : self{
+ if($facing === Facing::DOWN){
+ throw new \InvalidArgumentException("DOWN is not a valid facing for this block");
+ }
+ $this->facing = $facing;
+ return $this;
+ }
abstract protected function getPlant() : Block;
+ public function onNearbyBlockChange() : void{
+ if($this->facing !== Facing::UP && !$this->getSide($this->facing)->hasSameTypeId($this->getPlant())){
+ $this->position->getWorld()->setBlock($this->position, $this->setFacing(Facing::UP));
+ }
+ parent::onNearbyBlockChange();
+ }
+
+ public function ticksRandomly() : bool{
+ return $this->age < self::MAX_AGE || $this->facing === Facing::UP;
+ }
+
public function onRandomTick() : void{
- if(mt_rand(0, 2) === 1){
+ if($this->facing === Facing::UP && CropGrowthHelper::canGrow($this)){
$world = $this->position->getWorld();
if($this->age < self::MAX_AGE){
$block = clone $this;
++$block->age;
- $ev = new BlockGrowEvent($this, $block);
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::grow($this, $block, null);
}else{
$grow = $this->getPlant();
foreach(Facing::HORIZONTAL as $side){
@@ -52,12 +78,11 @@ public function onRandomTick() : void{
}
}
- $side = $this->getSide(Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]);
+ $facing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)];
+ $side = $this->getSide($facing);
if($side->getTypeId() === BlockTypeIds::AIR && $side->getSide(Facing::DOWN)->hasTypeTag(BlockTypeTags::DIRT)){
- $ev = new BlockGrowEvent($side, $grow);
- $ev->call();
- if(!$ev->isCancelled()){
- $world->setBlock($side->position, $ev->getNewState());
+ if(BlockEventHelper::grow($side, $grow, null)){
+ $this->position->getWorld()->setBlock($this->position, $this->setFacing($facing));
}
}
}
diff --git a/src/block/StonePressurePlate.php b/src/block/StonePressurePlate.php
index 626e6d8851b..5ddc5a59934 100644
--- a/src/block/StonePressurePlate.php
+++ b/src/block/StonePressurePlate.php
@@ -23,6 +23,13 @@
namespace pocketmine\block;
+use pocketmine\entity\Entity;
+use pocketmine\entity\Living;
+use function array_filter;
+
class StonePressurePlate extends SimplePressurePlate{
+ protected function filterIrrelevantEntities(array $entities) : array{
+ return array_filter($entities, fn(Entity $e) => $e instanceof Living); //TODO: armor stands should activate stone plates too
+ }
}
diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php
index 7736381e43b..30c19d25dcb 100644
--- a/src/block/Stonecutter.php
+++ b/src/block/Stonecutter.php
@@ -25,7 +25,6 @@
use pocketmine\block\inventory\StonecutterInventory;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
-use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
@@ -35,7 +34,6 @@
class Stonecutter extends Transparent{
use FacesOppositePlacingPlayerTrait;
- use HorizontalFacingTrait;
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($player !== null){
@@ -49,6 +47,6 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php
index 4cc5989a798..2da2dc9b971 100644
--- a/src/block/Sugarcane.php
+++ b/src/block/Sugarcane.php
@@ -23,8 +23,9 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
-use pocketmine\event\block\BlockGrowEvent;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
@@ -34,14 +35,13 @@
use pocketmine\world\Position;
class Sugarcane extends Flowable{
- public const MAX_AGE = 15;
-
- protected int $age = 0;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, 0, self::MAX_AGE, $this->age);
+ use AgeableTrait;
+ use StaticSupportTrait {
+ onNearbyBlockChange as onSupportBlockChange;
}
+ public const MAX_AGE = 15;
+
private function seekToBottom() : Position{
$world = $this->position->getWorld();
$bottom = $this->position;
@@ -60,13 +60,11 @@ private function grow(Position $pos, ?Player $player = null) : bool{
}
$b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z);
if($b->getTypeId() === BlockTypeIds::AIR){
- $ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE(), $player);
- $ev->call();
- if($ev->isCancelled()){
+ if(BlockEventHelper::grow($b, VanillaBlocks::SUGARCANE(), $player)){
+ $grew = true;
+ }else{
break;
}
- $world->setBlock($b->position, $ev->getNewState());
- $grew = true;
}elseif(!$b->hasSameTypeId($this)){
break;
}
@@ -76,17 +74,6 @@ private function grow(Position $pos, ?Player $player = null) : bool{
return $grew;
}
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < 0 || $age > self::MAX_AGE){
- throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
- }
- $this->age = $age;
- return $this;
- }
-
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($item instanceof Fertilizer){
if($this->grow($this->seekToBottom(), $player)){
@@ -99,18 +86,12 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
return false;
}
- private function canBeSupportedBy(Block $block) : bool{
- return
- $block->hasTypeTag(BlockTypeTags::MUD) ||
- $block->hasTypeTag(BlockTypeTags::DIRT) ||
- $block->hasTypeTag(BlockTypeTags::SAND);
- }
-
- public function onNearbyBlockChange() : void{
- $down = $this->getSide(Facing::DOWN);
- if(!$down->hasSameTypeId($this) && !$this->canBeSupportedBy($down)){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ return $supportBlock->hasSameTypeId($this) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::SAND);
}
public function ticksRandomly() : bool{
@@ -118,7 +99,13 @@ public function ticksRandomly() : bool{
}
public function onRandomTick() : void{
- if(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)){
+ $down = $this->getSide(Facing::DOWN);
+ if(!$down->hasSameTypeId($this)){
+ if(!$this->hasNearbyWater($down)){
+ $this->position->getWorld()->useBreakOn($this->position, createParticles: true);
+ return;
+ }
+
if($this->age === self::MAX_AGE){
$this->grow($this->position);
}else{
@@ -129,18 +116,38 @@ public function onRandomTick() : void{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- $down = $this->getSide(Facing::DOWN);
+ $down = $blockReplace->getSide(Facing::DOWN);
if($down->hasSameTypeId($this)){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }elseif($this->canBeSupportedBy($down)){
- foreach(Facing::HORIZONTAL as $side){
- $sideBlock = $down->getSide($side);
- if($sideBlock instanceof Water || $sideBlock instanceof FrostedIce){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
+ }
+
+ //support criteria are checked by FixedSupportTrait, but this part applies to placement only
+ foreach(Facing::HORIZONTAL as $side){
+ $sideBlock = $down->getSide($side);
+ if($sideBlock instanceof Water || $sideBlock instanceof FrostedIce){
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
}
return false;
}
+
+ private function hasNearbyWater(Block $down) : bool{
+ foreach($down->getHorizontalSides() as $sideBlock){
+ $blockId = $sideBlock->getTypeId();
+ if($blockId === BlockTypeIds::WATER || $blockId === BlockTypeIds::FROSTED_ICE){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function onNearbyBlockChange() : void{
+ $down = $this->getSide(Facing::DOWN);
+ if(!$down->hasSameTypeId($this) && !$this->hasNearbyWater($down)){
+ $this->position->getWorld()->useBreakOn($this->position, createParticles: true);
+ }else{
+ $this->onSupportBlockChange();
+ }
+ }
}
diff --git a/src/block/SweetBerryBush.php b/src/block/SweetBerryBush.php
index b75a343ec53..eabdde118aa 100644
--- a/src/block/SweetBerryBush.php
+++ b/src/block/SweetBerryBush.php
@@ -23,10 +23,12 @@
namespace pocketmine\block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
+use pocketmine\block\utils\AgeableTrait;
+use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\FortuneDropHelper;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
-use pocketmine\event\block\BlockGrowEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
@@ -34,31 +36,18 @@
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
+use pocketmine\world\sound\SweetBerriesPickSound;
use function mt_rand;
class SweetBerryBush extends Flowable{
+ use AgeableTrait;
+ use StaticSupportTrait;
+
public const STAGE_SAPLING = 0;
public const STAGE_BUSH_NO_BERRIES = 1;
public const STAGE_BUSH_SOME_BERRIES = 2;
public const STAGE_MATURE = 3;
-
- protected int $age = self::STAGE_SAPLING;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(3, self::STAGE_SAPLING, self::STAGE_MATURE, $this->age);
- }
-
- public function getAge() : int{ return $this->age; }
-
- /** @return $this */
- public function setAge(int $age) : self{
- if($age < self::STAGE_SAPLING || $age > self::STAGE_MATURE){
- throw new \InvalidArgumentException("Age must be in range 0-3");
- }
- $this->age = $age;
- return $this;
- }
+ public const MAX_AGE = self::STAGE_MATURE;
public function getBerryDropAmount() : int{
if($this->age === self::STAGE_MATURE){
@@ -69,16 +58,17 @@ public function getBerryDropAmount() : int{
return 0;
}
+ /**
+ * @deprecated
+ */
protected function canBeSupportedBy(Block $block) : bool{
return $block->getTypeId() !== BlockTypeIds::FARMLAND && //bedrock-specific thing (bug?)
($block->hasTypeTag(BlockTypeTags::DIRT) || $block->hasTypeTag(BlockTypeTags::MUD));
}
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ return $this->canBeSupportedBy($supportBlock);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@@ -86,18 +76,13 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
if($this->age < self::STAGE_MATURE && $item instanceof Fertilizer){
$block = clone $this;
$block->age++;
-
- $ev = new BlockGrowEvent($this, $block, $player);
- $ev->call();
-
- if(!$ev->isCancelled()){
- $world->setBlock($this->position, $ev->getNewState());
+ if(BlockEventHelper::grow($this, $block, $player)){
$item->pop();
}
-
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$world->dropItem($this->position, $this->asItem()->setCount($dropAmount));
+ $world->addSound($this->position, new SweetBerriesPickSound());
}
return true;
@@ -108,34 +93,25 @@ public function asItem() : Item{
}
public function getDropsForCompatibleTool(Item $item) : array{
- if(($dropAmount = $this->getBerryDropAmount()) > 0){
- return [
- $this->asItem()->setCount($dropAmount)
- ];
- }
-
- return [];
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ $count = match($this->age){
+ self::STAGE_MATURE => FortuneDropHelper::discrete($item, 2, 3),
+ self::STAGE_BUSH_SOME_BERRIES => FortuneDropHelper::discrete($item, 1, 2),
+ default => 0
+ };
+ return [
+ $this->asItem()->setCount($count)
+ ];
}
public function ticksRandomly() : bool{
- return true;
+ return $this->age < self::STAGE_MATURE;
}
public function onRandomTick() : void{
if($this->age < self::STAGE_MATURE && mt_rand(0, 2) === 1){
$block = clone $this;
++$block->age;
- $ev = new BlockGrowEvent($this, $block);
- $ev->call();
- if(!$ev->isCancelled()){
- $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
- }
+ BlockEventHelper::grow($this, $block, null);
}
}
diff --git a/src/block/TallGrass.php b/src/block/TallGrass.php
index e8f533325a3..459751c4e9c 100644
--- a/src/block/TallGrass.php
+++ b/src/block/TallGrass.php
@@ -23,53 +23,16 @@
namespace pocketmine\block;
-use pocketmine\item\Item;
-use pocketmine\item\VanillaItems;
+use pocketmine\block\utils\StaticSupportTrait;
+use pocketmine\block\utils\TallGrassTrait;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
-use function mt_rand;
class TallGrass extends Flowable{
+ use TallGrassTrait;
+ use StaticSupportTrait;
- public function canBeReplaced() : bool{
- return true;
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block->hasTypeTag(BlockTypeTags::DIRT) || $block->hasTypeTag(BlockTypeTags::MUD);
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
- return false;
- }
-
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ //Replace with common break method
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
- public function getDropsForIncompatibleTool(Item $item) : array{
- if(mt_rand(0, 15) === 0){
- return [
- VanillaItems::WHEAT_SEEDS()
- ];
- }
-
- return [];
- }
-
- public function getFlameEncouragement() : int{
- return 60;
- }
-
- public function getFlammability() : int{
- return 100;
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
+ return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
}
diff --git a/src/block/Thin.php b/src/block/Thin.php
index 8de39f4ea85..dde2d7d8478 100644
--- a/src/block/Thin.php
+++ b/src/block/Thin.php
@@ -39,9 +39,11 @@ class Thin extends Transparent{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
+ $this->collisionBoxes = null;
+
foreach(Facing::HORIZONTAL as $facing){
$side = $this->getSide($facing);
- if($side instanceof Thin || $side instanceof Wall || $side->isFullCube()){
+ if($side instanceof Thin || $side instanceof Wall || $side->getSupportType(Facing::opposite($facing)) === SupportType::FULL){
$this->connections[$facing] = true;
}else{
unset($this->connections[$facing]);
@@ -90,6 +92,6 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
}
diff --git a/src/block/Torch.php b/src/block/Torch.php
index 163c0d115c9..aee4da32aa4 100644
--- a/src/block/Torch.php
+++ b/src/block/Torch.php
@@ -26,7 +26,6 @@
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
-use pocketmine\math\Axis;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
@@ -56,15 +55,13 @@ public function getLightLevel() : int{
}
public function onNearbyBlockChange() : void{
- $face = Facing::opposite($this->facing);
-
- if(!$this->canBeSupportedBy($this->getSide($face), $this->facing)){
+ if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if($face !== Facing::DOWN && $this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
+ if($face !== Facing::DOWN && $this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}else{
@@ -75,8 +72,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
Facing::EAST,
Facing::DOWN
] as $side){
- $block = $this->getSide($side);
- if($this->canBeSupportedBy($block, Facing::opposite($side))){
+ if($this->canBeSupportedAt($blockReplace, $side)){
$this->facing = Facing::opposite($side);
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@@ -85,8 +81,9 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
return false;
}
- private function canBeSupportedBy(Block $support, int $face) : bool{
- return ($face === Facing::UP && $support->getSupportType($face)->hasCenterSupport()) ||
- (Facing::axis($face) !== Axis::Y && $support->getSupportType($face)->equals(SupportType::FULL()));
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $face === Facing::DOWN ?
+ $block->getAdjacentSupportType($face)->hasCenterSupport() :
+ $block->getAdjacentSupportType($face) === SupportType::FULL;
}
}
diff --git a/src/block/TorchflowerCrop.php b/src/block/TorchflowerCrop.php
new file mode 100644
index 00000000000..033b08552e1
--- /dev/null
+++ b/src/block/TorchflowerCrop.php
@@ -0,0 +1,90 @@
+bool($this->ready);
+ }
+
+ public function isReady() : bool{ return $this->ready; }
+
+ public function setReady(bool $ready) : self{
+ $this->ready = $ready;
+ return $this;
+ }
+
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND;
+ }
+
+ private function getNextState() : Block{
+ if($this->ready){
+ return VanillaBlocks::TORCHFLOWER();
+ }else{
+ return VanillaBlocks::TORCHFLOWER_CROP()->setReady(true);
+ }
+ }
+
+ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
+ if($item instanceof Fertilizer){
+ if(BlockEventHelper::grow($this, $this->getNextState(), $player)){
+ $item->pop();
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public function ticksRandomly() : bool{
+ return true;
+ }
+
+ public function onRandomTick() : void{
+ if(CropGrowthHelper::canGrow($this)){
+ BlockEventHelper::grow($this, $this->getNextState(), null);
+ }
+ }
+
+ public function asItem() : Item{
+ return VanillaItems::TORCHFLOWER_SEEDS();
+ }
+}
diff --git a/src/block/Trapdoor.php b/src/block/Trapdoor.php
index d12a922df62..20b6af2abdc 100644
--- a/src/block/Trapdoor.php
+++ b/src/block/Trapdoor.php
@@ -70,7 +70,7 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return SupportType::NONE();
+ return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php
index 7b8b7c52a1e..54cf90a0c3b 100644
--- a/src/block/VanillaBlocks.php
+++ b/src/block/VanillaBlocks.php
@@ -26,7 +26,6 @@
use pocketmine\block\BlockBreakInfo as BreakInfo;
use pocketmine\block\BlockIdentifier as BID;
use pocketmine\block\BlockToolType as ToolType;
-use pocketmine\block\BlockTypeIds as Ids;
use pocketmine\block\BlockTypeInfo as Info;
use pocketmine\block\BlockTypeTags as Tags;
use pocketmine\block\tile\Banner as TileBanner;
@@ -36,8 +35,10 @@
use pocketmine\block\tile\Bell as TileBell;
use pocketmine\block\tile\BlastFurnace as TileBlastFurnace;
use pocketmine\block\tile\BrewingStand as TileBrewingStand;
+use pocketmine\block\tile\Campfire as TileCampfire;
use pocketmine\block\tile\Cauldron as TileCauldron;
use pocketmine\block\tile\Chest as TileChest;
+use pocketmine\block\tile\ChiseledBookshelf as TileChiseledBookshelf;
use pocketmine\block\tile\Comparator as TileComparator;
use pocketmine\block\tile\DaylightSensor as TileDaylightSensor;
use pocketmine\block\tile\EnchantTable as TileEnchantingTable;
@@ -54,19 +55,22 @@
use pocketmine\block\tile\Note as TileNote;
use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
use pocketmine\block\tile\Smoker as TileSmoker;
+use pocketmine\block\tile\Tile;
+use pocketmine\block\utils\AmethystTrait;
use pocketmine\block\utils\LeavesType;
use pocketmine\block\utils\SaplingType;
use pocketmine\block\utils\WoodType;
use pocketmine\crafting\FurnaceType;
-use pocketmine\entity\projectile\Projectile;
+use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\Item;
use pocketmine\item\ToolTier;
+use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
-use pocketmine\math\RayTraceResult;
use pocketmine\utils\CloningRegistryTrait;
-use pocketmine\world\sound\AmethystBlockChimeSound;
-use pocketmine\world\sound\BlockPunchSound;
+use function is_int;
use function mb_strtolower;
+use function mb_strtoupper;
+use function strtolower;
/**
* This doc-block is generated automatically, do not modify it manually.
@@ -94,6 +98,7 @@
* @method static Flower ALLIUM()
* @method static MushroomStem ALL_SIDED_MUSHROOM_STEM()
* @method static Opaque AMETHYST()
+ * @method static AmethystCluster AMETHYST_CLUSTER()
* @method static Opaque ANCIENT_DEBRIS()
* @method static Opaque ANDESITE()
* @method static Slab ANDESITE_SLAB()
@@ -113,6 +118,8 @@
* @method static Bedrock BEDROCK()
* @method static Beetroot BEETROOTS()
* @method static Bell BELL()
+ * @method static BigDripleafHead BIG_DRIPLEAF_HEAD()
+ * @method static BigDripleafStem BIG_DRIPLEAF_STEM()
* @method static WoodenButton BIRCH_BUTTON()
* @method static WoodenDoor BIRCH_DOOR()
* @method static WoodenFence BIRCH_FENCE()
@@ -145,11 +152,13 @@
* @method static Wall BRICK_WALL()
* @method static BrownMushroom BROWN_MUSHROOM()
* @method static BrownMushroomBlock BROWN_MUSHROOM_BLOCK()
+ * @method static BuddingAmethyst BUDDING_AMETHYST()
* @method static Cactus CACTUS()
* @method static Cake CAKE()
* @method static CakeWithCandle CAKE_WITH_CANDLE()
* @method static CakeWithDyedCandle CAKE_WITH_DYED_CANDLE()
* @method static Opaque CALCITE()
+ * @method static Campfire CAMPFIRE()
* @method static Candle CANDLE()
* @method static Carpet CARPET()
* @method static Carrot CARROTS()
@@ -159,7 +168,23 @@
* @method static CaveVines CAVE_VINES()
* @method static Chain CHAIN()
* @method static ChemicalHeat CHEMICAL_HEAT()
+ * @method static WoodenButton CHERRY_BUTTON()
+ * @method static WoodenDoor CHERRY_DOOR()
+ * @method static WoodenFence CHERRY_FENCE()
+ * @method static FenceGate CHERRY_FENCE_GATE()
+ * @method static Leaves CHERRY_LEAVES()
+ * @method static Wood CHERRY_LOG()
+ * @method static Planks CHERRY_PLANKS()
+ * @method static WoodenPressurePlate CHERRY_PRESSURE_PLATE()
+ * @method static FloorSign CHERRY_SIGN()
+ * @method static WoodenSlab CHERRY_SLAB()
+ * @method static WoodenStairs CHERRY_STAIRS()
+ * @method static WoodenTrapdoor CHERRY_TRAPDOOR()
+ * @method static WallSign CHERRY_WALL_SIGN()
+ * @method static Wood CHERRY_WOOD()
* @method static Chest CHEST()
+ * @method static ChiseledBookshelf CHISELED_BOOKSHELF()
+ * @method static Copper CHISELED_COPPER()
* @method static Opaque CHISELED_DEEPSLATE()
* @method static Opaque CHISELED_NETHER_BRICKS()
* @method static Opaque CHISELED_POLISHED_BLACKSTONE()
@@ -167,6 +192,8 @@
* @method static Opaque CHISELED_RED_SANDSTONE()
* @method static Opaque CHISELED_SANDSTONE()
* @method static Opaque CHISELED_STONE_BRICKS()
+ * @method static Opaque CHISELED_TUFF()
+ * @method static Opaque CHISELED_TUFF_BRICKS()
* @method static ChorusFlower CHORUS_FLOWER()
* @method static ChorusPlant CHORUS_PLANT()
* @method static Clay CLAY()
@@ -186,7 +213,11 @@
* @method static Concrete CONCRETE()
* @method static ConcretePowder CONCRETE_POWDER()
* @method static Copper COPPER()
+ * @method static CopperBulb COPPER_BULB()
+ * @method static CopperDoor COPPER_DOOR()
+ * @method static CopperGrate COPPER_GRATE()
* @method static CopperOre COPPER_ORE()
+ * @method static CopperTrapdoor COPPER_TRAPDOOR()
* @method static Coral CORAL()
* @method static CoralBlock CORAL_BLOCK()
* @method static FloorCoralFan CORAL_FAN()
@@ -204,6 +235,7 @@
* @method static Wood CRIMSON_HYPHAE()
* @method static Planks CRIMSON_PLANKS()
* @method static WoodenPressurePlate CRIMSON_PRESSURE_PLATE()
+ * @method static NetherRoots CRIMSON_ROOTS()
* @method static FloorSign CRIMSON_SIGN()
* @method static WoodenSlab CRIMSON_SLAB()
* @method static WoodenStairs CRIMSON_STAIRS()
@@ -264,6 +296,7 @@
* @method static Stair DIORITE_STAIRS()
* @method static Wall DIORITE_WALL()
* @method static Dirt DIRT()
+ * @method static DoublePitcherCrop DOUBLE_PITCHER_CROP()
* @method static DoubleTallGrass DOUBLE_TALLGRASS()
* @method static DragonEgg DRAGON_EGG()
* @method static DriedKelp DRIED_KELP()
@@ -557,7 +590,10 @@
* @method static PackedIce PACKED_ICE()
* @method static Opaque PACKED_MUD()
* @method static DoublePlant PEONY()
+ * @method static PinkPetals PINK_PETALS()
* @method static Flower PINK_TULIP()
+ * @method static PitcherCrop PITCHER_CROP()
+ * @method static DoublePlant PITCHER_PLANT()
* @method static Podzol PODZOL()
* @method static Opaque POLISHED_ANDESITE()
* @method static Slab POLISHED_ANDESITE_SLAB()
@@ -583,6 +619,10 @@
* @method static Opaque POLISHED_GRANITE()
* @method static Slab POLISHED_GRANITE_SLAB()
* @method static Stair POLISHED_GRANITE_STAIRS()
+ * @method static Opaque POLISHED_TUFF()
+ * @method static Slab POLISHED_TUFF_SLAB()
+ * @method static Stair POLISHED_TUFF_STAIRS()
+ * @method static Wall POLISHED_TUFF_WALL()
* @method static Flower POPPY()
* @method static Potato POTATOES()
* @method static PotionCauldron POTION_CAULDRON()
@@ -644,6 +684,7 @@
* @method static Opaque SHROOMLIGHT()
* @method static ShulkerBox SHULKER_BOX()
* @method static Slime SLIME()
+ * @method static SmallDripleaf SMALL_DRIPLEAF()
* @method static SmithingTable SMITHING_TABLE()
* @method static Furnace SMOKER()
* @method static Opaque SMOOTH_BASALT()
@@ -660,6 +701,7 @@
* @method static Slab SMOOTH_STONE_SLAB()
* @method static Snow SNOW()
* @method static SnowLayer SNOW_LAYER()
+ * @method static SoulCampfire SOUL_CAMPFIRE()
* @method static SoulFire SOUL_FIRE()
* @method static Lantern SOUL_LANTERN()
* @method static SoulSand SOUL_SAND()
@@ -704,10 +746,19 @@
* @method static TintedGlass TINTED_GLASS()
* @method static TNT TNT()
* @method static Torch TORCH()
+ * @method static Flower TORCHFLOWER()
+ * @method static TorchflowerCrop TORCHFLOWER_CROP()
* @method static TrappedChest TRAPPED_CHEST()
* @method static Tripwire TRIPWIRE()
* @method static TripwireHook TRIPWIRE_HOOK()
* @method static Opaque TUFF()
+ * @method static Opaque TUFF_BRICKS()
+ * @method static Slab TUFF_BRICK_SLAB()
+ * @method static Stair TUFF_BRICK_STAIRS()
+ * @method static Wall TUFF_BRICK_WALL()
+ * @method static Slab TUFF_SLAB()
+ * @method static Stair TUFF_STAIRS()
+ * @method static Wall TUFF_WALL()
* @method static NetherVines TWISTING_VINES()
* @method static UnderwaterTorch UNDERWATER_TORCH()
* @method static Vine VINES()
@@ -720,6 +771,7 @@
* @method static Wood WARPED_HYPHAE()
* @method static Planks WARPED_PLANKS()
* @method static WoodenPressurePlate WARPED_PRESSURE_PLATE()
+ * @method static NetherRoots WARPED_ROOTS()
* @method static FloorSign WARPED_SIGN()
* @method static WoodenSlab WARPED_SLAB()
* @method static WoodenStairs WARPED_STAIRS()
@@ -744,8 +796,28 @@ private function __construct(){
//NOOP
}
- protected static function register(string $name, Block $block) : void{
+ /**
+ * @phpstan-template TBlock of Block
+ * @phpstan-param \Closure(BID) : TBlock $createBlock
+ * @phpstan-param class-string $tileClass
+ * @phpstan-return TBlock
+ */
+ protected static function register(string $name, \Closure $createBlock, ?string $tileClass = null) : Block{
+ //this sketchy hack allows us to avoid manually writing the constants inline
+ //since type IDs are generated from this class anyway, I'm OK with this hack
+ //nonetheless, we should try to get rid of it in a future major version (e.g by using string type IDs)
+ $reflect = new \ReflectionClass(BlockTypeIds::class);
+ $typeId = $reflect->getConstant(mb_strtoupper($name));
+ if(!is_int($typeId)){
+ //this allows registering new stuff without adding new type ID constants
+ //this reduces the number of mandatory steps to test new features in local development
+ \GlobalLogger::get()->error(self::class . ": No constant type ID found for $name, generating a new one");
+ $typeId = BlockTypeIds::newId();
+ }
+ $block = $createBlock(new BID($typeId, $tileClass));
self::_registryRegister($name, $block);
+
+ return $block;
}
/**
@@ -760,11 +832,12 @@ public static function getAll() : array{
}
protected static function setup() : void{
- $railBreakInfo = new Info(new BlockBreakInfo(0.7));
- self::register("activator_rail", new ActivatorRail(new BID(Ids::ACTIVATOR_RAIL), "Activator Rail", $railBreakInfo));
- self::register("air", new Air(new BID(Ids::AIR), "Air", new Info(BreakInfo::indestructible(-1.0))));
- self::register("anvil", new Anvil(new BID(Ids::ANVIL), "Anvil", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD(), 6000.0))));
- self::register("bamboo", new Bamboo(new BID(Ids::BAMBOO), "Bamboo", new Info(new class(2.0 /* 1.0 in PC */, ToolType::AXE) extends BreakInfo{
+ self::register("air", fn(BID $id) => new Air($id, "Air", new Info(BreakInfo::indestructible(-1.0))));
+
+ $railBreakInfo = new Info(new BreakInfo(0.7));
+ self::register("activator_rail", fn(BID $id) => new ActivatorRail($id, "Activator Rail", $railBreakInfo));
+ self::register("anvil", fn(BID $id) => new Anvil($id, "Anvil", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))));
+ self::register("bamboo", fn(BID $id) => new Bamboo($id, "Bamboo", new Info(new class(2.0 /* 1.0 in PC */, ToolType::AXE) extends BreakInfo{
public function getBreakTime(Item $item) : float{
if($item->getBlockToolType() === ToolType::SWORD){
return 0.0;
@@ -772,230 +845,242 @@ public function getBreakTime(Item $item) : float{
return parent::getBreakTime($item);
}
}, [Tags::POTTABLE_PLANTS])));
- self::register("bamboo_sapling", new BambooSapling(new BID(Ids::BAMBOO_SAPLING), "Bamboo Sapling", new Info(BreakInfo::instant())));
+ self::register("bamboo_sapling", fn(BID $id) => new BambooSapling($id, "Bamboo Sapling", new Info(BreakInfo::instant())));
$bannerBreakInfo = new Info(BreakInfo::axe(1.0));
- self::register("banner", new FloorBanner(new BID(Ids::BANNER, TileBanner::class), "Banner", $bannerBreakInfo));
- self::register("wall_banner", new WallBanner(new BID(Ids::WALL_BANNER, TileBanner::class), "Wall Banner", $bannerBreakInfo));
- self::register("barrel", new Barrel(new BID(Ids::BARREL, TileBarrel::class), "Barrel", new Info(BreakInfo::axe(2.5))));
- self::register("barrier", new Transparent(new BID(Ids::BARRIER), "Barrier", new Info(BreakInfo::indestructible())));
- self::register("beacon", new Beacon(new BID(Ids::BEACON, TileBeacon::class), "Beacon", new Info(new BreakInfo(3.0))));
- self::register("bed", new Bed(new BID(Ids::BED, TileBed::class), "Bed Block", new Info(new BreakInfo(0.2))));
- self::register("bedrock", new Bedrock(new BID(Ids::BEDROCK), "Bedrock", new Info(BreakInfo::indestructible())));
-
- self::register("beetroots", new Beetroot(new BID(Ids::BEETROOTS), "Beetroot Block", new Info(BreakInfo::instant())));
- self::register("bell", new Bell(new BID(Ids::BELL, TileBell::class), "Bell", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD()))));
- self::register("blue_ice", new BlueIce(new BID(Ids::BLUE_ICE), "Blue Ice", new Info(BreakInfo::pickaxe(2.8))));
- self::register("bone_block", new BoneBlock(new BID(Ids::BONE_BLOCK), "Bone Block", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD()))));
- self::register("bookshelf", new Bookshelf(new BID(Ids::BOOKSHELF), "Bookshelf", new Info(BreakInfo::axe(1.5))));
- self::register("brewing_stand", new BrewingStand(new BID(Ids::BREWING_STAND, TileBrewingStand::class), "Brewing Stand", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()))));
-
- $bricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
- self::register("brick_stairs", new Stair(new BID(Ids::BRICK_STAIRS), "Brick Stairs", $bricksBreakInfo));
- self::register("bricks", new Opaque(new BID(Ids::BRICKS), "Bricks", $bricksBreakInfo));
-
- self::register("brown_mushroom", new BrownMushroom(new BID(Ids::BROWN_MUSHROOM), "Brown Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
- self::register("cactus", new Cactus(new BID(Ids::CACTUS), "Cactus", new Info(new BreakInfo(0.4), [Tags::POTTABLE_PLANTS])));
- self::register("cake", new Cake(new BID(Ids::CAKE), "Cake", new Info(new BreakInfo(0.5))));
- self::register("carrots", new Carrot(new BID(Ids::CARROTS), "Carrot Block", new Info(BreakInfo::instant())));
+ self::register("banner", fn(BID $id) => new FloorBanner($id, "Banner", $bannerBreakInfo), TileBanner::class);
+ self::register("wall_banner", fn(BID $id) => new WallBanner($id, "Wall Banner", $bannerBreakInfo), TileBanner::class);
+ self::register("barrel", fn(BID $id) => new Barrel($id, "Barrel", new Info(BreakInfo::axe(2.5))), TileBarrel::class);
+ self::register("barrier", fn(BID $id) => new Transparent($id, "Barrier", new Info(BreakInfo::indestructible())));
+ self::register("beacon", fn(BID $id) => new Beacon($id, "Beacon", new Info(new BreakInfo(3.0))), TileBeacon::class);
+ self::register("bed", fn(BID $id) => new Bed($id, "Bed Block", new Info(new BreakInfo(0.2))), TileBed::class);
+ self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible())));
+
+ self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant())));
+ self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))), TileBell::class);
+ self::register("blue_ice", fn(BID $id) => new BlueIce($id, "Blue Ice", new Info(BreakInfo::pickaxe(2.8))));
+ self::register("bone_block", fn(BID $id) => new BoneBlock($id, "Bone Block", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD))));
+ self::register("bookshelf", fn(BID $id) => new Bookshelf($id, "Bookshelf", new Info(BreakInfo::axe(1.5))));
+ self::register("chiseled_bookshelf", fn(BID $id) => new ChiseledBookshelf($id, "Chiseled Bookshelf", new Info(BreakInfo::axe(1.5))), TileChiseledBookshelf::class);
+ self::register("brewing_stand", fn(BID $id) => new BrewingStand($id, "Brewing Stand", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))), TileBrewingStand::class);
+
+ $bricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
+ self::register("brick_stairs", fn(BID $id) => new Stair($id, "Brick Stairs", $bricksBreakInfo));
+ self::register("bricks", fn(BID $id) => new Opaque($id, "Bricks", $bricksBreakInfo));
+
+ self::register("brown_mushroom", fn(BID $id) => new BrownMushroom($id, "Brown Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
+ self::register("cactus", fn(BID $id) => new Cactus($id, "Cactus", new Info(new BreakInfo(0.4), [Tags::POTTABLE_PLANTS])));
+ self::register("cake", fn(BID $id) => new Cake($id, "Cake", new Info(new BreakInfo(0.5))));
+
+ $campfireBreakInfo = new Info(BreakInfo::axe(2.0));
+ self::register("campfire", fn(BID $id) => new Campfire($id, "Campfire", $campfireBreakInfo), TileCampfire::class);
+ self::register("soul_campfire", fn(BID $id) => new SoulCampfire($id, "Soul Campfire", $campfireBreakInfo), TileCampfire::class);
+
+ self::register("carrots", fn(BID $id) => new Carrot($id, "Carrot Block", new Info(BreakInfo::instant())));
$chestBreakInfo = new Info(BreakInfo::axe(2.5));
- self::register("chest", new Chest(new BID(Ids::CHEST, TileChest::class), "Chest", $chestBreakInfo));
- self::register("clay", new Clay(new BID(Ids::CLAY), "Clay Block", new Info(BreakInfo::shovel(0.6))));
- self::register("coal", new Coal(new BID(Ids::COAL), "Coal Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD(), 30.0))));
-
- $cobblestoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
- self::register("cobblestone", $cobblestone = new Opaque(new BID(Ids::COBBLESTONE), "Cobblestone", $cobblestoneBreakInfo));
- self::register("mossy_cobblestone", new Opaque(new BID(Ids::MOSSY_COBBLESTONE), "Mossy Cobblestone", $cobblestoneBreakInfo));
- self::register("cobblestone_stairs", new Stair(new BID(Ids::COBBLESTONE_STAIRS), "Cobblestone Stairs", $cobblestoneBreakInfo));
- self::register("mossy_cobblestone_stairs", new Stair(new BID(Ids::MOSSY_COBBLESTONE_STAIRS), "Mossy Cobblestone Stairs", $cobblestoneBreakInfo));
-
- self::register("cobweb", new Cobweb(new BID(Ids::COBWEB), "Cobweb", new Info(new BreakInfo(4.0, ToolType::SWORD | ToolType::SHEARS, 1))));
- self::register("cocoa_pod", new CocoaBlock(new BID(Ids::COCOA_POD), "Cocoa Block", new Info(BreakInfo::axe(0.2, null, 15.0))));
- self::register("coral_block", new CoralBlock(new BID(Ids::CORAL_BLOCK), "Coral Block", new Info(BreakInfo::pickaxe(7.0, ToolTier::WOOD()))));
- self::register("daylight_sensor", new DaylightSensor(new BID(Ids::DAYLIGHT_SENSOR, TileDaylightSensor::class), "Daylight Sensor", new Info(BreakInfo::axe(0.2))));
- self::register("dead_bush", new DeadBush(new BID(Ids::DEAD_BUSH), "Dead Bush", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
- self::register("detector_rail", new DetectorRail(new BID(Ids::DETECTOR_RAIL), "Detector Rail", $railBreakInfo));
-
- self::register("diamond", new Opaque(new BID(Ids::DIAMOND), "Diamond Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON(), 30.0))));
- self::register("dirt", new Dirt(new BID(Ids::DIRT), "Dirt", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
- self::register("sunflower", new DoublePlant(new BID(Ids::SUNFLOWER), "Sunflower", new Info(BreakInfo::instant())));
- self::register("lilac", new DoublePlant(new BID(Ids::LILAC), "Lilac", new Info(BreakInfo::instant())));
- self::register("rose_bush", new DoublePlant(new BID(Ids::ROSE_BUSH), "Rose Bush", new Info(BreakInfo::instant())));
- self::register("peony", new DoublePlant(new BID(Ids::PEONY), "Peony", new Info(BreakInfo::instant())));
- self::register("double_tallgrass", new DoubleTallGrass(new BID(Ids::DOUBLE_TALLGRASS), "Double Tallgrass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
- self::register("large_fern", new DoubleTallGrass(new BID(Ids::LARGE_FERN), "Large Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
- self::register("dragon_egg", new DragonEgg(new BID(Ids::DRAGON_EGG), "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD()))));
- self::register("dried_kelp", new DriedKelp(new BID(Ids::DRIED_KELP), "Dried Kelp Block", new Info(new BreakInfo(0.5, ToolType::NONE, 0, 12.5))));
- self::register("emerald", new Opaque(new BID(Ids::EMERALD), "Emerald Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON(), 30.0))));
- self::register("enchanting_table", new EnchantingTable(new BID(Ids::ENCHANTING_TABLE, TileEnchantingTable::class), "Enchanting Table", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD(), 6000.0))));
- self::register("end_portal_frame", new EndPortalFrame(new BID(Ids::END_PORTAL_FRAME), "End Portal Frame", new Info(BreakInfo::indestructible())));
- self::register("end_rod", new EndRod(new BID(Ids::END_ROD), "End Rod", new Info(BreakInfo::instant())));
- self::register("end_stone", new Opaque(new BID(Ids::END_STONE), "End Stone", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD(), 45.0))));
-
- $endBrickBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD(), 4.0));
- self::register("end_stone_bricks", new Opaque(new BID(Ids::END_STONE_BRICKS), "End Stone Bricks", $endBrickBreakInfo));
- self::register("end_stone_brick_stairs", new Stair(new BID(Ids::END_STONE_BRICK_STAIRS), "End Stone Brick Stairs", $endBrickBreakInfo));
-
- self::register("ender_chest", new EnderChest(new BID(Ids::ENDER_CHEST, TileEnderChest::class), "Ender Chest", new Info(BreakInfo::pickaxe(22.5, ToolTier::WOOD(), 3000.0))));
- self::register("farmland", new Farmland(new BID(Ids::FARMLAND), "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
- self::register("fire", new Fire(new BID(Ids::FIRE), "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE])));
+ self::register("chest", fn(BID $id) => new Chest($id, "Chest", $chestBreakInfo), TileChest::class);
+ self::register("clay", fn(BID $id) => new Clay($id, "Clay Block", new Info(BreakInfo::shovel(0.6))));
+ self::register("coal", fn(BID $id) => new Coal($id, "Coal Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
+
+ $cobblestoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
+ $cobblestone = self::register("cobblestone", fn(BID $id) => new Opaque($id, "Cobblestone", $cobblestoneBreakInfo));
+ self::register("mossy_cobblestone", fn(BID $id) => new Opaque($id, "Mossy Cobblestone", $cobblestoneBreakInfo));
+ self::register("cobblestone_stairs", fn(BID $id) => new Stair($id, "Cobblestone Stairs", $cobblestoneBreakInfo));
+ self::register("mossy_cobblestone_stairs", fn(BID $id) => new Stair($id, "Mossy Cobblestone Stairs", $cobblestoneBreakInfo));
+
+ self::register("cobweb", fn(BID $id) => new Cobweb($id, "Cobweb", new Info(new BreakInfo(4.0, ToolType::SWORD | ToolType::SHEARS, 1))));
+ self::register("cocoa_pod", fn(BID $id) => new CocoaBlock($id, "Cocoa Block", new Info(BreakInfo::axe(0.2, null, 15.0))));
+ self::register("coral_block", fn(BID $id) => new CoralBlock($id, "Coral Block", new Info(BreakInfo::pickaxe(7.0, ToolTier::WOOD))));
+ self::register("daylight_sensor", fn(BID $id) => new DaylightSensor($id, "Daylight Sensor", new Info(BreakInfo::axe(0.2))), TileDaylightSensor::class);
+ self::register("dead_bush", fn(BID $id) => new DeadBush($id, "Dead Bush", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
+ self::register("detector_rail", fn(BID $id) => new DetectorRail($id, "Detector Rail", $railBreakInfo));
+
+ self::register("diamond", fn(BID $id) => new Opaque($id, "Diamond Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
+ self::register("dirt", fn(BID $id) => new Dirt($id, "Dirt", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
+ self::register("sunflower", fn(BID $id) => new DoublePlant($id, "Sunflower", new Info(BreakInfo::instant())));
+ self::register("lilac", fn(BID $id) => new DoublePlant($id, "Lilac", new Info(BreakInfo::instant())));
+ self::register("rose_bush", fn(BID $id) => new DoublePlant($id, "Rose Bush", new Info(BreakInfo::instant())));
+ self::register("peony", fn(BID $id) => new DoublePlant($id, "Peony", new Info(BreakInfo::instant())));
+ self::register("pink_petals", fn(BID $id) => new PinkPetals($id, "Pink Petals", new Info(BreakInfo::instant())));
+ self::register("double_tallgrass", fn(BID $id) => new DoubleTallGrass($id, "Double Tallgrass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+ self::register("large_fern", fn(BID $id) => new DoubleTallGrass($id, "Large Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+ self::register("pitcher_plant", fn(BID $id) => new DoublePlant($id, "Pitcher Plant", new Info(BreakInfo::instant())));
+ self::register("pitcher_crop", fn(BID $id) => new PitcherCrop($id, "Pitcher Crop", new Info(BreakInfo::instant())));
+ self::register("double_pitcher_crop", fn(BID $id) => new DoublePitcherCrop($id, "Double Pitcher Crop", new Info(BreakInfo::instant())));
+ self::register("dragon_egg", fn(BID $id) => new DragonEgg($id, "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
+ self::register("dried_kelp", fn(BID $id) => new DriedKelp($id, "Dried Kelp Block", new Info(new BreakInfo(0.5, ToolType::NONE, 0, 12.5))));
+ self::register("emerald", fn(BID $id) => new Opaque($id, "Emerald Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
+ self::register("enchanting_table", fn(BID $id) => new EnchantingTable($id, "Enchanting Table", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))), TileEnchantingTable::class);
+ self::register("end_portal_frame", fn(BID $id) => new EndPortalFrame($id, "End Portal Frame", new Info(BreakInfo::indestructible())));
+ self::register("end_rod", fn(BID $id) => new EndRod($id, "End Rod", new Info(BreakInfo::instant())));
+ self::register("end_stone", fn(BID $id) => new Opaque($id, "End Stone", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0))));
+
+ $endBrickBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD, 4.0));
+ self::register("end_stone_bricks", fn(BID $id) => new Opaque($id, "End Stone Bricks", $endBrickBreakInfo));
+ self::register("end_stone_brick_stairs", fn(BID $id) => new Stair($id, "End Stone Brick Stairs", $endBrickBreakInfo));
+
+ self::register("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, ToolTier::WOOD, 3000.0))), TileEnderChest::class);
+ self::register("farmland", fn(BID $id) => new Farmland($id, "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
+ self::register("fire", fn(BID $id) => new Fire($id, "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE])));
$flowerTypeInfo = new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]);
- self::register("dandelion", new Flower(new BID(Ids::DANDELION), "Dandelion", $flowerTypeInfo));
- self::register("poppy", new Flower(new BID(Ids::POPPY), "Poppy", $flowerTypeInfo));
- self::register("allium", new Flower(new BID(Ids::ALLIUM), "Allium", $flowerTypeInfo));
- self::register("azure_bluet", new Flower(new BID(Ids::AZURE_BLUET), "Azure Bluet", $flowerTypeInfo));
- self::register("blue_orchid", new Flower(new BID(Ids::BLUE_ORCHID), "Blue Orchid", $flowerTypeInfo));
- self::register("cornflower", new Flower(new BID(Ids::CORNFLOWER), "Cornflower", $flowerTypeInfo));
- self::register("lily_of_the_valley", new Flower(new BID(Ids::LILY_OF_THE_VALLEY), "Lily of the Valley", $flowerTypeInfo));
- self::register("orange_tulip", new Flower(new BID(Ids::ORANGE_TULIP), "Orange Tulip", $flowerTypeInfo));
- self::register("oxeye_daisy", new Flower(new BID(Ids::OXEYE_DAISY), "Oxeye Daisy", $flowerTypeInfo));
- self::register("pink_tulip", new Flower(new BID(Ids::PINK_TULIP), "Pink Tulip", $flowerTypeInfo));
- self::register("red_tulip", new Flower(new BID(Ids::RED_TULIP), "Red Tulip", $flowerTypeInfo));
- self::register("white_tulip", new Flower(new BID(Ids::WHITE_TULIP), "White Tulip", $flowerTypeInfo));
- self::register("flower_pot", new FlowerPot(new BID(Ids::FLOWER_POT, TileFlowerPot::class), "Flower Pot", new Info(BreakInfo::instant())));
- self::register("frosted_ice", new FrostedIce(new BID(Ids::FROSTED_ICE), "Frosted Ice", new Info(BreakInfo::pickaxe(2.5))));
- self::register("furnace", new Furnace(new BID(Ids::FURNACE, TileNormalFurnace::class), "Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD())), FurnaceType::FURNACE()));
- self::register("blast_furnace", new Furnace(new BID(Ids::BLAST_FURNACE, TileBlastFurnace::class), "Blast Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD())), FurnaceType::BLAST_FURNACE()));
- self::register("smoker", new Furnace(new BID(Ids::SMOKER, TileSmoker::class), "Smoker", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD())), FurnaceType::SMOKER()));
+ self::register("dandelion", fn(BID $id) => new Flower($id, "Dandelion", $flowerTypeInfo));
+ self::register("poppy", fn(BID $id) => new Flower($id, "Poppy", $flowerTypeInfo));
+ self::register("allium", fn(BID $id) => new Flower($id, "Allium", $flowerTypeInfo));
+ self::register("azure_bluet", fn(BID $id) => new Flower($id, "Azure Bluet", $flowerTypeInfo));
+ self::register("blue_orchid", fn(BID $id) => new Flower($id, "Blue Orchid", $flowerTypeInfo));
+ self::register("cornflower", fn(BID $id) => new Flower($id, "Cornflower", $flowerTypeInfo));
+ self::register("lily_of_the_valley", fn(BID $id) => new Flower($id, "Lily of the Valley", $flowerTypeInfo));
+ self::register("orange_tulip", fn(BID $id) => new Flower($id, "Orange Tulip", $flowerTypeInfo));
+ self::register("oxeye_daisy", fn(BID $id) => new Flower($id, "Oxeye Daisy", $flowerTypeInfo));
+ self::register("pink_tulip", fn(BID $id) => new Flower($id, "Pink Tulip", $flowerTypeInfo));
+ self::register("red_tulip", fn(BID $id) => new Flower($id, "Red Tulip", $flowerTypeInfo));
+ self::register("white_tulip", fn(BID $id) => new Flower($id, "White Tulip", $flowerTypeInfo));
+ self::register("torchflower", fn(BID $id) => new Flower($id, "Torchflower", $flowerTypeInfo));
+ self::register("torchflower_crop", fn(BID $id) => new TorchflowerCrop($id, "Torchflower Crop", new Info(BreakInfo::instant())));
+ self::register("flower_pot", fn(BID $id) => new FlowerPot($id, "Flower Pot", new Info(BreakInfo::instant())), TileFlowerPot::class);
+ self::register("frosted_ice", fn(BID $id) => new FrostedIce($id, "Frosted Ice", new Info(BreakInfo::pickaxe(2.5))));
+ self::register("furnace", fn(BID $id) => new Furnace($id, "Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::FURNACE), TileNormalFurnace::class);
+ self::register("blast_furnace", fn(BID $id) => new Furnace($id, "Blast Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::BLAST_FURNACE), TileBlastFurnace::class);
+ self::register("smoker", fn(BID $id) => new Furnace($id, "Smoker", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::SMOKER), TileSmoker::class);
$glassBreakInfo = new Info(new BreakInfo(0.3));
- self::register("glass", new Glass(new BID(Ids::GLASS), "Glass", $glassBreakInfo));
- self::register("glass_pane", new GlassPane(new BID(Ids::GLASS_PANE), "Glass Pane", $glassBreakInfo));
- self::register("glowing_obsidian", new GlowingObsidian(new BID(Ids::GLOWING_OBSIDIAN), "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND(), 50.0))));
- self::register("glowstone", new Glowstone(new BID(Ids::GLOWSTONE), "Glowstone", new Info(BreakInfo::pickaxe(0.3))));
- self::register("glow_lichen", new GlowLichen(new BID(Ids::GLOW_LICHEN), "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2))));
- self::register("gold", new Opaque(new BID(Ids::GOLD), "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON(), 30.0))));
+ self::register("glass", fn(BID $id) => new Glass($id, "Glass", $glassBreakInfo));
+ self::register("glass_pane", fn(BID $id) => new GlassPane($id, "Glass Pane", $glassBreakInfo));
+ self::register("glowing_obsidian", fn(BID $id) => new GlowingObsidian($id, "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND, 50.0))));
+ self::register("glowstone", fn(BID $id) => new Glowstone($id, "Glowstone", new Info(BreakInfo::pickaxe(0.3))));
+ self::register("glow_lichen", fn(BID $id) => new GlowLichen($id, "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2))));
+ self::register("gold", fn(BID $id) => new Opaque($id, "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON, 30.0))));
$grassBreakInfo = BreakInfo::shovel(0.6);
- self::register("grass", new Grass(new BID(Ids::GRASS), "Grass", new Info($grassBreakInfo, [Tags::DIRT])));
- self::register("grass_path", new GrassPath(new BID(Ids::GRASS_PATH), "Grass Path", new Info($grassBreakInfo)));
- self::register("gravel", new Gravel(new BID(Ids::GRAVEL), "Gravel", new Info(BreakInfo::shovel(0.6))));
+ self::register("grass", fn(BID $id) => new Grass($id, "Grass", new Info($grassBreakInfo, [Tags::DIRT])));
+ self::register("grass_path", fn(BID $id) => new GrassPath($id, "Grass Path", new Info($grassBreakInfo)));
+ self::register("gravel", fn(BID $id) => new Gravel($id, "Gravel", new Info(BreakInfo::shovel(0.6))));
- $hardenedClayBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD(), 21.0));
- self::register("hardened_clay", new HardenedClay(new BID(Ids::HARDENED_CLAY), "Hardened Clay", $hardenedClayBreakInfo));
+ $hardenedClayBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0));
+ self::register("hardened_clay", fn(BID $id) => new HardenedClay($id, "Hardened Clay", $hardenedClayBreakInfo));
$hardenedGlassBreakInfo = new Info(new BreakInfo(10.0));
- self::register("hardened_glass", new HardenedGlass(new BID(Ids::HARDENED_GLASS), "Hardened Glass", $hardenedGlassBreakInfo));
- self::register("hardened_glass_pane", new HardenedGlassPane(new BID(Ids::HARDENED_GLASS_PANE), "Hardened Glass Pane", $hardenedGlassBreakInfo));
- self::register("hay_bale", new HayBale(new BID(Ids::HAY_BALE), "Hay Bale", new Info(new BreakInfo(0.5))));
- self::register("hopper", new Hopper(new BID(Ids::HOPPER, TileHopper::class), "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD(), 15.0))));
- self::register("ice", new Ice(new BID(Ids::ICE), "Ice", new Info(BreakInfo::pickaxe(0.5))));
+ self::register("hardened_glass", fn(BID $id) => new HardenedGlass($id, "Hardened Glass", $hardenedGlassBreakInfo));
+ self::register("hardened_glass_pane", fn(BID $id) => new HardenedGlassPane($id, "Hardened Glass Pane", $hardenedGlassBreakInfo));
+ self::register("hay_bale", fn(BID $id) => new HayBale($id, "Hay Bale", new Info(new BreakInfo(0.5))));
+ self::register("hopper", fn(BID $id) => new Hopper($id, "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 15.0))), TileHopper::class);
+ self::register("ice", fn(BID $id) => new Ice($id, "Ice", new Info(BreakInfo::pickaxe(0.5))));
$updateBlockBreakInfo = new Info(new BreakInfo(1.0));
- self::register("info_update", new Opaque(new BID(Ids::INFO_UPDATE), "update!", $updateBlockBreakInfo));
- self::register("info_update2", new Opaque(new BID(Ids::INFO_UPDATE2), "ate!upd", $updateBlockBreakInfo));
- self::register("invisible_bedrock", new Transparent(new BID(Ids::INVISIBLE_BEDROCK), "Invisible Bedrock", new Info(BreakInfo::indestructible())));
+ self::register("info_update", fn(BID $id) => new Opaque($id, "update!", $updateBlockBreakInfo));
+ self::register("info_update2", fn(BID $id) => new Opaque($id, "ate!upd", $updateBlockBreakInfo));
+ self::register("invisible_bedrock", fn(BID $id) => new Transparent($id, "Invisible Bedrock", new Info(BreakInfo::indestructible())));
- $ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE(), 30.0));
- self::register("iron", new Opaque(new BID(Ids::IRON), "Iron Block", $ironBreakInfo));
- self::register("iron_bars", new Thin(new BID(Ids::IRON_BARS), "Iron Bars", $ironBreakInfo));
- $ironDoorBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD(), 25.0));
- self::register("iron_door", new Door(new BID(Ids::IRON_DOOR), "Iron Door", $ironDoorBreakInfo));
- self::register("iron_trapdoor", new Trapdoor(new BID(Ids::IRON_TRAPDOOR), "Iron Trapdoor", $ironDoorBreakInfo));
+ $ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE, 30.0));
+ self::register("iron", fn(BID $id) => new Opaque($id, "Iron Block", $ironBreakInfo));
+ self::register("iron_bars", fn(BID $id) => new Thin($id, "Iron Bars", $ironBreakInfo));
+ $ironDoorBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 25.0));
+ self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", $ironDoorBreakInfo));
+ self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", $ironDoorBreakInfo));
$itemFrameInfo = new Info(new BreakInfo(0.25));
- self::register("item_frame", new ItemFrame(new BID(Ids::ITEM_FRAME, TileItemFrame::class), "Item Frame", $itemFrameInfo));
- self::register("glowing_item_frame", new ItemFrame(new BID(Ids::GLOWING_ITEM_FRAME, TileGlowingItemFrame::class), "Glow Item Frame", $itemFrameInfo));
-
- self::register("jukebox", new Jukebox(new BID(Ids::JUKEBOX, TileJukebox::class), "Jukebox", new Info(BreakInfo::axe(0.8)))); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not
- self::register("ladder", new Ladder(new BID(Ids::LADDER), "Ladder", new Info(BreakInfo::axe(0.4))));
-
- $lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD()));
- self::register("lantern", new Lantern(new BID(Ids::LANTERN), "Lantern", $lanternBreakInfo, 15));
- self::register("soul_lantern", new Lantern(new BID(Ids::SOUL_LANTERN), "Soul Lantern", $lanternBreakInfo, 10));
-
- self::register("lapis_lazuli", new Opaque(new BID(Ids::LAPIS_LAZULI), "Lapis Lazuli Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE()))));
- self::register("lava", new Lava(new BID(Ids::LAVA), "Lava", new Info(BreakInfo::indestructible(500.0))));
- self::register("lectern", new Lectern(new BID(Ids::LECTERN, TileLectern::class), "Lectern", new Info(BreakInfo::axe(2.0))));
- self::register("lever", new Lever(new BID(Ids::LEVER), "Lever", new Info(new BreakInfo(0.5))));
- self::register("magma", new Magma(new BID(Ids::MAGMA), "Magma Block", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()))));
- self::register("melon", new Melon(new BID(Ids::MELON), "Melon Block", new Info(BreakInfo::axe(1.0))));
- self::register("melon_stem", new MelonStem(new BID(Ids::MELON_STEM), "Melon Stem", new Info(BreakInfo::instant())));
- self::register("monster_spawner", new MonsterSpawner(new BID(Ids::MONSTER_SPAWNER, TileMonsterSpawner::class), "Monster Spawner", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD()))));
- self::register("mycelium", new Mycelium(new BID(Ids::MYCELIUM), "Mycelium", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
-
- $netherBrickBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
- self::register("nether_bricks", new Opaque(new BID(Ids::NETHER_BRICKS), "Nether Bricks", $netherBrickBreakInfo));
- self::register("red_nether_bricks", new Opaque(new BID(Ids::RED_NETHER_BRICKS), "Red Nether Bricks", $netherBrickBreakInfo));
- self::register("nether_brick_fence", new Fence(new BID(Ids::NETHER_BRICK_FENCE), "Nether Brick Fence", $netherBrickBreakInfo));
- self::register("nether_brick_stairs", new Stair(new BID(Ids::NETHER_BRICK_STAIRS), "Nether Brick Stairs", $netherBrickBreakInfo));
- self::register("red_nether_brick_stairs", new Stair(new BID(Ids::RED_NETHER_BRICK_STAIRS), "Red Nether Brick Stairs", $netherBrickBreakInfo));
- self::register("chiseled_nether_bricks", new Opaque(new BID(Ids::CHISELED_NETHER_BRICKS), "Chiseled Nether Bricks", $netherBrickBreakInfo));
- self::register("cracked_nether_bricks", new Opaque(new BID(Ids::CRACKED_NETHER_BRICKS), "Cracked Nether Bricks", $netherBrickBreakInfo));
-
- self::register("nether_portal", new NetherPortal(new BID(Ids::NETHER_PORTAL), "Nether Portal", new Info(BreakInfo::indestructible(0.0))));
- self::register("nether_reactor_core", new NetherReactor(new BID(Ids::NETHER_REACTOR_CORE), "Nether Reactor Core", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD()))));
- self::register("nether_wart_block", new Opaque(new BID(Ids::NETHER_WART_BLOCK), "Nether Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
- self::register("nether_wart", new NetherWartPlant(new BID(Ids::NETHER_WART), "Nether Wart", new Info(BreakInfo::instant())));
- self::register("netherrack", new Netherrack(new BID(Ids::NETHERRACK), "Netherrack", new Info(BreakInfo::pickaxe(0.4, ToolTier::WOOD()))));
- self::register("note_block", new Note(new BID(Ids::NOTE_BLOCK, TileNote::class), "Note Block", new Info(BreakInfo::axe(0.8))));
- self::register("obsidian", new Opaque(new BID(Ids::OBSIDIAN), "Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in PC */, ToolTier::DIAMOND(), 6000.0))));
- self::register("packed_ice", new PackedIce(new BID(Ids::PACKED_ICE), "Packed Ice", new Info(BreakInfo::pickaxe(0.5))));
- self::register("podzol", new Podzol(new BID(Ids::PODZOL), "Podzol", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
- self::register("potatoes", new Potato(new BID(Ids::POTATOES), "Potato Block", new Info(BreakInfo::instant())));
- self::register("powered_rail", new PoweredRail(new BID(Ids::POWERED_RAIL), "Powered Rail", $railBreakInfo));
-
- $prismarineBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD(), 30.0));
- self::register("prismarine", new Opaque(new BID(Ids::PRISMARINE), "Prismarine", $prismarineBreakInfo));
- self::register("dark_prismarine", new Opaque(new BID(Ids::DARK_PRISMARINE), "Dark Prismarine", $prismarineBreakInfo));
- self::register("prismarine_bricks", new Opaque(new BID(Ids::PRISMARINE_BRICKS), "Prismarine Bricks", $prismarineBreakInfo));
- self::register("prismarine_bricks_stairs", new Stair(new BID(Ids::PRISMARINE_BRICKS_STAIRS), "Prismarine Bricks Stairs", $prismarineBreakInfo));
- self::register("dark_prismarine_stairs", new Stair(new BID(Ids::DARK_PRISMARINE_STAIRS), "Dark Prismarine Stairs", $prismarineBreakInfo));
- self::register("prismarine_stairs", new Stair(new BID(Ids::PRISMARINE_STAIRS), "Prismarine Stairs", $prismarineBreakInfo));
+ self::register("item_frame", fn(BID $id) => new ItemFrame($id, "Item Frame", $itemFrameInfo), TileItemFrame::class);
+ self::register("glowing_item_frame", fn(BID $id) => new ItemFrame($id, "Glow Item Frame", $itemFrameInfo), TileGlowingItemFrame::class);
+
+ self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(0.8))), TileJukebox::class); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not
+ self::register("ladder", fn(BID $id) => new Ladder($id, "Ladder", new Info(BreakInfo::axe(0.4))));
+
+ $lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD));
+ self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15));
+ self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10));
+
+ self::register("lapis_lazuli", fn(BID $id) => new Opaque($id, "Lapis Lazuli Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE))));
+ self::register("lava", fn(BID $id) => new Lava($id, "Lava", new Info(BreakInfo::indestructible(500.0))));
+ self::register("lectern", fn(BID $id) => new Lectern($id, "Lectern", new Info(BreakInfo::axe(2.0))), TileLectern::class);
+ self::register("lever", fn(BID $id) => new Lever($id, "Lever", new Info(new BreakInfo(0.5))));
+ self::register("magma", fn(BID $id) => new Magma($id, "Magma Block", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
+ self::register("melon", fn(BID $id) => new Melon($id, "Melon Block", new Info(BreakInfo::axe(1.0))));
+ self::register("melon_stem", fn(BID $id) => new MelonStem($id, "Melon Stem", new Info(BreakInfo::instant())));
+ self::register("monster_spawner", fn(BID $id) => new MonsterSpawner($id, "Monster Spawner", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))), TileMonsterSpawner::class);
+ self::register("mycelium", fn(BID $id) => new Mycelium($id, "Mycelium", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
+
+ $netherBrickBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
+ self::register("nether_bricks", fn(BID $id) => new Opaque($id, "Nether Bricks", $netherBrickBreakInfo));
+ self::register("red_nether_bricks", fn(BID $id) => new Opaque($id, "Red Nether Bricks", $netherBrickBreakInfo));
+ self::register("nether_brick_fence", fn(BID $id) => new Fence($id, "Nether Brick Fence", $netherBrickBreakInfo));
+ self::register("nether_brick_stairs", fn(BID $id) => new Stair($id, "Nether Brick Stairs", $netherBrickBreakInfo));
+ self::register("red_nether_brick_stairs", fn(BID $id) => new Stair($id, "Red Nether Brick Stairs", $netherBrickBreakInfo));
+ self::register("chiseled_nether_bricks", fn(BID $id) => new Opaque($id, "Chiseled Nether Bricks", $netherBrickBreakInfo));
+ self::register("cracked_nether_bricks", fn(BID $id) => new Opaque($id, "Cracked Nether Bricks", $netherBrickBreakInfo));
+
+ self::register("nether_portal", fn(BID $id) => new NetherPortal($id, "Nether Portal", new Info(BreakInfo::indestructible(0.0))));
+ self::register("nether_reactor_core", fn(BID $id) => new NetherReactor($id, "Nether Reactor Core", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
+ self::register("nether_wart_block", fn(BID $id) => new Opaque($id, "Nether Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
+ self::register("nether_wart", fn(BID $id) => new NetherWartPlant($id, "Nether Wart", new Info(BreakInfo::instant())));
+ self::register("netherrack", fn(BID $id) => new Netherrack($id, "Netherrack", new Info(BreakInfo::pickaxe(0.4, ToolTier::WOOD))));
+ self::register("note_block", fn(BID $id) => new Note($id, "Note Block", new Info(BreakInfo::axe(0.8))), TileNote::class);
+ self::register("obsidian", fn(BID $id) => new Opaque($id, "Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in PC */, ToolTier::DIAMOND, 6000.0))));
+ self::register("packed_ice", fn(BID $id) => new PackedIce($id, "Packed Ice", new Info(BreakInfo::pickaxe(0.5))));
+ self::register("podzol", fn(BID $id) => new Podzol($id, "Podzol", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
+ self::register("potatoes", fn(BID $id) => new Potato($id, "Potato Block", new Info(BreakInfo::instant())));
+ self::register("powered_rail", fn(BID $id) => new PoweredRail($id, "Powered Rail", $railBreakInfo));
+
+ $prismarineBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
+ self::register("prismarine", fn(BID $id) => new Opaque($id, "Prismarine", $prismarineBreakInfo));
+ self::register("dark_prismarine", fn(BID $id) => new Opaque($id, "Dark Prismarine", $prismarineBreakInfo));
+ self::register("prismarine_bricks", fn(BID $id) => new Opaque($id, "Prismarine Bricks", $prismarineBreakInfo));
+ self::register("prismarine_bricks_stairs", fn(BID $id) => new Stair($id, "Prismarine Bricks Stairs", $prismarineBreakInfo));
+ self::register("dark_prismarine_stairs", fn(BID $id) => new Stair($id, "Dark Prismarine Stairs", $prismarineBreakInfo));
+ self::register("prismarine_stairs", fn(BID $id) => new Stair($id, "Prismarine Stairs", $prismarineBreakInfo));
$pumpkinBreakInfo = new Info(BreakInfo::axe(1.0));
- self::register("pumpkin", new Pumpkin(new BID(Ids::PUMPKIN), "Pumpkin", $pumpkinBreakInfo));
- self::register("carved_pumpkin", new CarvedPumpkin(new BID(Ids::CARVED_PUMPKIN), "Carved Pumpkin", $pumpkinBreakInfo));
- self::register("lit_pumpkin", new LitPumpkin(new BID(Ids::LIT_PUMPKIN), "Jack o'Lantern", $pumpkinBreakInfo));
-
- self::register("pumpkin_stem", new PumpkinStem(new BID(Ids::PUMPKIN_STEM), "Pumpkin Stem", new Info(BreakInfo::instant())));
-
- $purpurBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD(), 30.0));
- self::register("purpur", new Opaque(new BID(Ids::PURPUR), "Purpur Block", $purpurBreakInfo));
- self::register("purpur_pillar", new SimplePillar(new BID(Ids::PURPUR_PILLAR), "Purpur Pillar", $purpurBreakInfo));
- self::register("purpur_stairs", new Stair(new BID(Ids::PURPUR_STAIRS), "Purpur Stairs", $purpurBreakInfo));
-
- $quartzBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD()));
- self::register("quartz", new Opaque(new BID(Ids::QUARTZ), "Quartz Block", $quartzBreakInfo));
- self::register("chiseled_quartz", new SimplePillar(new BID(Ids::CHISELED_QUARTZ), "Chiseled Quartz Block", $quartzBreakInfo));
- self::register("quartz_pillar", new SimplePillar(new BID(Ids::QUARTZ_PILLAR), "Quartz Pillar", $quartzBreakInfo));
- self::register("smooth_quartz", new Opaque(new BID(Ids::SMOOTH_QUARTZ), "Smooth Quartz Block", $quartzBreakInfo));
- self::register("quartz_bricks", new Opaque(new BID(Ids::QUARTZ_BRICKS), "Quartz Bricks", $quartzBreakInfo));
-
- self::register("quartz_stairs", new Stair(new BID(Ids::QUARTZ_STAIRS), "Quartz Stairs", $quartzBreakInfo));
- self::register("smooth_quartz_stairs", new Stair(new BID(Ids::SMOOTH_QUARTZ_STAIRS), "Smooth Quartz Stairs", $quartzBreakInfo));
-
- self::register("rail", new Rail(new BID(Ids::RAIL), "Rail", $railBreakInfo));
- self::register("red_mushroom", new RedMushroom(new BID(Ids::RED_MUSHROOM), "Red Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
- self::register("redstone", new Redstone(new BID(Ids::REDSTONE), "Redstone Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD(), 30.0))));
- self::register("redstone_comparator", new RedstoneComparator(new BID(Ids::REDSTONE_COMPARATOR, TileComparator::class), "Redstone Comparator", new Info(BreakInfo::instant())));
- self::register("redstone_lamp", new RedstoneLamp(new BID(Ids::REDSTONE_LAMP), "Redstone Lamp", new Info(new BreakInfo(0.3))));
- self::register("redstone_repeater", new RedstoneRepeater(new BID(Ids::REDSTONE_REPEATER), "Redstone Repeater", new Info(BreakInfo::instant())));
- self::register("redstone_torch", new RedstoneTorch(new BID(Ids::REDSTONE_TORCH), "Redstone Torch", new Info(BreakInfo::instant())));
- self::register("redstone_wire", new RedstoneWire(new BID(Ids::REDSTONE_WIRE), "Redstone", new Info(BreakInfo::instant())));
- self::register("reserved6", new Reserved6(new BID(Ids::RESERVED6), "reserved6", new Info(BreakInfo::instant())));
+ self::register("pumpkin", fn(BID $id) => new Pumpkin($id, "Pumpkin", $pumpkinBreakInfo));
+ self::register("carved_pumpkin", fn(BID $id) => new CarvedPumpkin($id, "Carved Pumpkin", new Info(BreakInfo::axe(1.0), enchantmentTags: [EnchantmentTags::MASK])));
+ self::register("lit_pumpkin", fn(BID $id) => new LitPumpkin($id, "Jack o'Lantern", $pumpkinBreakInfo));
+
+ self::register("pumpkin_stem", fn(BID $id) => new PumpkinStem($id, "Pumpkin Stem", new Info(BreakInfo::instant())));
+
+ $purpurBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
+ self::register("purpur", fn(BID $id) => new Opaque($id, "Purpur Block", $purpurBreakInfo));
+ self::register("purpur_pillar", fn(BID $id) => new SimplePillar($id, "Purpur Pillar", $purpurBreakInfo));
+ self::register("purpur_stairs", fn(BID $id) => new Stair($id, "Purpur Stairs", $purpurBreakInfo));
+
+ $quartzBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
+ self::register("quartz", fn(BID $id) => new Opaque($id, "Quartz Block", $quartzBreakInfo));
+ self::register("chiseled_quartz", fn(BID $id) => new SimplePillar($id, "Chiseled Quartz Block", $quartzBreakInfo));
+ self::register("quartz_pillar", fn(BID $id) => new SimplePillar($id, "Quartz Pillar", $quartzBreakInfo));
+ self::register("smooth_quartz", fn(BID $id) => new Opaque($id, "Smooth Quartz Block", $quartzBreakInfo));
+ self::register("quartz_bricks", fn(BID $id) => new Opaque($id, "Quartz Bricks", $quartzBreakInfo));
+
+ self::register("quartz_stairs", fn(BID $id) => new Stair($id, "Quartz Stairs", $quartzBreakInfo));
+ self::register("smooth_quartz_stairs", fn(BID $id) => new Stair($id, "Smooth Quartz Stairs", $quartzBreakInfo));
+
+ self::register("rail", fn(BID $id) => new Rail($id, "Rail", $railBreakInfo));
+ self::register("red_mushroom", fn(BID $id) => new RedMushroom($id, "Red Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
+ self::register("redstone", fn(BID $id) => new Redstone($id, "Redstone Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
+ self::register("redstone_comparator", fn(BID $id) => new RedstoneComparator($id, "Redstone Comparator", new Info(BreakInfo::instant())), TileComparator::class);
+ self::register("redstone_lamp", fn(BID $id) => new RedstoneLamp($id, "Redstone Lamp", new Info(new BreakInfo(0.3))));
+ self::register("redstone_repeater", fn(BID $id) => new RedstoneRepeater($id, "Redstone Repeater", new Info(BreakInfo::instant())));
+ self::register("redstone_torch", fn(BID $id) => new RedstoneTorch($id, "Redstone Torch", new Info(BreakInfo::instant())));
+ self::register("redstone_wire", fn(BID $id) => new RedstoneWire($id, "Redstone", new Info(BreakInfo::instant())));
+ self::register("reserved6", fn(BID $id) => new Reserved6($id, "reserved6", new Info(BreakInfo::instant())));
$sandTypeInfo = new Info(BreakInfo::shovel(0.5), [Tags::SAND]);
- self::register("sand", new Sand(new BID(Ids::SAND), "Sand", $sandTypeInfo));
- self::register("red_sand", new Sand(new BID(Ids::RED_SAND), "Red Sand", $sandTypeInfo));
-
- self::register("sea_lantern", new SeaLantern(new BID(Ids::SEA_LANTERN), "Sea Lantern", new Info(new BreakInfo(0.3))));
- self::register("sea_pickle", new SeaPickle(new BID(Ids::SEA_PICKLE), "Sea Pickle", new Info(BreakInfo::instant())));
- self::register("mob_head", new MobHead(new BID(Ids::MOB_HEAD, TileMobHead::class), "Mob Head", new Info(new BreakInfo(1.0))));
- self::register("slime", new Slime(new BID(Ids::SLIME), "Slime Block", new Info(BreakInfo::instant())));
- self::register("snow", new Snow(new BID(Ids::SNOW), "Snow Block", new Info(BreakInfo::shovel(0.2, ToolTier::WOOD()))));
- self::register("snow_layer", new SnowLayer(new BID(Ids::SNOW_LAYER), "Snow Layer", new Info(BreakInfo::shovel(0.1, ToolTier::WOOD()))));
- self::register("soul_sand", new SoulSand(new BID(Ids::SOUL_SAND), "Soul Sand", new Info(BreakInfo::shovel(0.5))));
- self::register("sponge", new Sponge(new BID(Ids::SPONGE), "Sponge", new Info(new BreakInfo(0.6, ToolType::HOE))));
+ self::register("sand", fn(BID $id) => new Sand($id, "Sand", $sandTypeInfo));
+ self::register("red_sand", fn(BID $id) => new Sand($id, "Red Sand", $sandTypeInfo));
+
+ self::register("sea_lantern", fn(BID $id) => new SeaLantern($id, "Sea Lantern", new Info(new BreakInfo(0.3))));
+ self::register("sea_pickle", fn(BID $id) => new SeaPickle($id, "Sea Pickle", new Info(BreakInfo::instant())));
+ self::register("mob_head", fn(BID $id) => new MobHead($id, "Mob Head", new Info(new BreakInfo(1.0), enchantmentTags: [EnchantmentTags::MASK])), TileMobHead::class);
+ self::register("slime", fn(BID $id) => new Slime($id, "Slime Block", new Info(BreakInfo::instant())));
+ self::register("snow", fn(BID $id) => new Snow($id, "Snow Block", new Info(BreakInfo::shovel(0.2, ToolTier::WOOD))));
+ self::register("snow_layer", fn(BID $id) => new SnowLayer($id, "Snow Layer", new Info(BreakInfo::shovel(0.1, ToolTier::WOOD))));
+ self::register("soul_sand", fn(BID $id) => new SoulSand($id, "Soul Sand", new Info(BreakInfo::shovel(0.5))));
+ self::register("sponge", fn(BID $id) => new Sponge($id, "Sponge", new Info(new BreakInfo(0.6, ToolType::HOE))));
$shulkerBoxBreakInfo = new Info(BreakInfo::pickaxe(2));
- self::register("shulker_box", new ShulkerBox(new BID(Ids::SHULKER_BOX, TileShulkerBox::class), "Shulker Box", $shulkerBoxBreakInfo));
+ self::register("shulker_box", fn(BID $id) => new ShulkerBox($id, "Shulker Box", $shulkerBoxBreakInfo), TileShulkerBox::class);
- $stoneBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD(), 30.0));
- self::register(
+ $stoneBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
+ $stone = self::register(
"stone",
- $stone = new class(new BID(Ids::STONE), "Stone", $stoneBreakInfo) extends Opaque{
+ fn(BID $id) => new class($id, "Stone", $stoneBreakInfo) extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [VanillaBlocks::COBBLESTONE()->asItem()];
}
@@ -1005,98 +1090,110 @@ public function isAffectedBySilkTouch() : bool{
}
}
);
- self::register("andesite", new Opaque(new BID(Ids::ANDESITE), "Andesite", $stoneBreakInfo));
- self::register("diorite", new Opaque(new BID(Ids::DIORITE), "Diorite", $stoneBreakInfo));
- self::register("granite", new Opaque(new BID(Ids::GRANITE), "Granite", $stoneBreakInfo));
- self::register("polished_andesite", new Opaque(new BID(Ids::POLISHED_ANDESITE), "Polished Andesite", $stoneBreakInfo));
- self::register("polished_diorite", new Opaque(new BID(Ids::POLISHED_DIORITE), "Polished Diorite", $stoneBreakInfo));
- self::register("polished_granite", new Opaque(new BID(Ids::POLISHED_GRANITE), "Polished Granite", $stoneBreakInfo));
-
- self::register("stone_bricks", $stoneBrick = new Opaque(new BID(Ids::STONE_BRICKS), "Stone Bricks", $stoneBreakInfo));
- self::register("mossy_stone_bricks", $mossyStoneBrick = new Opaque(new BID(Ids::MOSSY_STONE_BRICKS), "Mossy Stone Bricks", $stoneBreakInfo));
- self::register("cracked_stone_bricks", $crackedStoneBrick = new Opaque(new BID(Ids::CRACKED_STONE_BRICKS), "Cracked Stone Bricks", $stoneBreakInfo));
- self::register("chiseled_stone_bricks", $chiseledStoneBrick = new Opaque(new BID(Ids::CHISELED_STONE_BRICKS), "Chiseled Stone Bricks", $stoneBreakInfo));
+ self::register("andesite", fn(BID $id) => new Opaque($id, "Andesite", $stoneBreakInfo));
+ self::register("diorite", fn(BID $id) => new Opaque($id, "Diorite", $stoneBreakInfo));
+ self::register("granite", fn(BID $id) => new Opaque($id, "Granite", $stoneBreakInfo));
+ self::register("polished_andesite", fn(BID $id) => new Opaque($id, "Polished Andesite", $stoneBreakInfo));
+ self::register("polished_diorite", fn(BID $id) => new Opaque($id, "Polished Diorite", $stoneBreakInfo));
+ self::register("polished_granite", fn(BID $id) => new Opaque($id, "Polished Granite", $stoneBreakInfo));
+
+ $stoneBrick = self::register("stone_bricks", fn(BID $id) => new Opaque($id, "Stone Bricks", $stoneBreakInfo));
+ $mossyStoneBrick = self::register("mossy_stone_bricks", fn(BID $id) => new Opaque($id, "Mossy Stone Bricks", $stoneBreakInfo));
+ $crackedStoneBrick = self::register("cracked_stone_bricks", fn(BID $id) => new Opaque($id, "Cracked Stone Bricks", $stoneBreakInfo));
+ $chiseledStoneBrick = self::register("chiseled_stone_bricks", fn(BID $id) => new Opaque($id, "Chiseled Stone Bricks", $stoneBreakInfo));
$infestedStoneBreakInfo = new Info(BreakInfo::pickaxe(0.75));
- self::register("infested_stone", new InfestedStone(new BID(Ids::INFESTED_STONE), "Infested Stone", $infestedStoneBreakInfo, $stone));
- self::register("infested_stone_brick", new InfestedStone(new BID(Ids::INFESTED_STONE_BRICK), "Infested Stone Brick", $infestedStoneBreakInfo, $stoneBrick));
- self::register("infested_cobblestone", new InfestedStone(new BID(Ids::INFESTED_COBBLESTONE), "Infested Cobblestone", $infestedStoneBreakInfo, $cobblestone));
- self::register("infested_mossy_stone_brick", new InfestedStone(new BID(Ids::INFESTED_MOSSY_STONE_BRICK), "Infested Mossy Stone Brick", $infestedStoneBreakInfo, $mossyStoneBrick));
- self::register("infested_cracked_stone_brick", new InfestedStone(new BID(Ids::INFESTED_CRACKED_STONE_BRICK), "Infested Cracked Stone Brick", $infestedStoneBreakInfo, $crackedStoneBrick));
- self::register("infested_chiseled_stone_brick", new InfestedStone(new BID(Ids::INFESTED_CHISELED_STONE_BRICK), "Infested Chiseled Stone Brick", $infestedStoneBreakInfo, $chiseledStoneBrick));
-
- self::register("stone_stairs", new Stair(new BID(Ids::STONE_STAIRS), "Stone Stairs", $stoneBreakInfo));
- self::register("smooth_stone", new Opaque(new BID(Ids::SMOOTH_STONE), "Smooth Stone", $stoneBreakInfo));
- self::register("andesite_stairs", new Stair(new BID(Ids::ANDESITE_STAIRS), "Andesite Stairs", $stoneBreakInfo));
- self::register("diorite_stairs", new Stair(new BID(Ids::DIORITE_STAIRS), "Diorite Stairs", $stoneBreakInfo));
- self::register("granite_stairs", new Stair(new BID(Ids::GRANITE_STAIRS), "Granite Stairs", $stoneBreakInfo));
- self::register("polished_andesite_stairs", new Stair(new BID(Ids::POLISHED_ANDESITE_STAIRS), "Polished Andesite Stairs", $stoneBreakInfo));
- self::register("polished_diorite_stairs", new Stair(new BID(Ids::POLISHED_DIORITE_STAIRS), "Polished Diorite Stairs", $stoneBreakInfo));
- self::register("polished_granite_stairs", new Stair(new BID(Ids::POLISHED_GRANITE_STAIRS), "Polished Granite Stairs", $stoneBreakInfo));
- self::register("stone_brick_stairs", new Stair(new BID(Ids::STONE_BRICK_STAIRS), "Stone Brick Stairs", $stoneBreakInfo));
- self::register("mossy_stone_brick_stairs", new Stair(new BID(Ids::MOSSY_STONE_BRICK_STAIRS), "Mossy Stone Brick Stairs", $stoneBreakInfo));
- self::register("stone_button", new StoneButton(new BID(Ids::STONE_BUTTON), "Stone Button", new Info(BreakInfo::pickaxe(0.5))));
- self::register("stonecutter", new Stonecutter(new BID(Ids::STONECUTTER), "Stonecutter", new Info(BreakInfo::pickaxe(3.5))));
- self::register("stone_pressure_plate", new StonePressurePlate(new BID(Ids::STONE_PRESSURE_PLATE), "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()))));
+ self::register("infested_stone", fn(BID $id) => new InfestedStone($id, "Infested Stone", $infestedStoneBreakInfo, $stone));
+ self::register("infested_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Stone Brick", $infestedStoneBreakInfo, $stoneBrick));
+ self::register("infested_cobblestone", fn(BID $id) => new InfestedStone($id, "Infested Cobblestone", $infestedStoneBreakInfo, $cobblestone));
+ self::register("infested_mossy_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Mossy Stone Brick", $infestedStoneBreakInfo, $mossyStoneBrick));
+ self::register("infested_cracked_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Cracked Stone Brick", $infestedStoneBreakInfo, $crackedStoneBrick));
+ self::register("infested_chiseled_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Chiseled Stone Brick", $infestedStoneBreakInfo, $chiseledStoneBrick));
+
+ self::register("stone_stairs", fn(BID $id) => new Stair($id, "Stone Stairs", $stoneBreakInfo));
+ self::register("smooth_stone", fn(BID $id) => new Opaque($id, "Smooth Stone", $stoneBreakInfo));
+ self::register("andesite_stairs", fn(BID $id) => new Stair($id, "Andesite Stairs", $stoneBreakInfo));
+ self::register("diorite_stairs", fn(BID $id) => new Stair($id, "Diorite Stairs", $stoneBreakInfo));
+ self::register("granite_stairs", fn(BID $id) => new Stair($id, "Granite Stairs", $stoneBreakInfo));
+ self::register("polished_andesite_stairs", fn(BID $id) => new Stair($id, "Polished Andesite Stairs", $stoneBreakInfo));
+ self::register("polished_diorite_stairs", fn(BID $id) => new Stair($id, "Polished Diorite Stairs", $stoneBreakInfo));
+ self::register("polished_granite_stairs", fn(BID $id) => new Stair($id, "Polished Granite Stairs", $stoneBreakInfo));
+ self::register("stone_brick_stairs", fn(BID $id) => new Stair($id, "Stone Brick Stairs", $stoneBreakInfo));
+ self::register("mossy_stone_brick_stairs", fn(BID $id) => new Stair($id, "Mossy Stone Brick Stairs", $stoneBreakInfo));
+ self::register("stone_button", fn(BID $id) => new StoneButton($id, "Stone Button", new Info(BreakInfo::pickaxe(0.5))));
+ self::register("stonecutter", fn(BID $id) => new Stonecutter($id, "Stonecutter", new Info(BreakInfo::pickaxe(3.5))));
+ self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
//TODO: in the future this won't be the same for all the types
- $stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
-
- self::register("brick_slab", new Slab(new BID(Ids::BRICK_SLAB), "Brick", $stoneSlabBreakInfo));
- self::register("cobblestone_slab", new Slab(new BID(Ids::COBBLESTONE_SLAB), "Cobblestone", $stoneSlabBreakInfo));
- self::register("fake_wooden_slab", new Slab(new BID(Ids::FAKE_WOODEN_SLAB), "Fake Wooden", $stoneSlabBreakInfo));
- self::register("nether_brick_slab", new Slab(new BID(Ids::NETHER_BRICK_SLAB), "Nether Brick", $stoneSlabBreakInfo));
- self::register("quartz_slab", new Slab(new BID(Ids::QUARTZ_SLAB), "Quartz", $stoneSlabBreakInfo));
- self::register("sandstone_slab", new Slab(new BID(Ids::SANDSTONE_SLAB), "Sandstone", $stoneSlabBreakInfo));
- self::register("smooth_stone_slab", new Slab(new BID(Ids::SMOOTH_STONE_SLAB), "Smooth Stone", $stoneSlabBreakInfo));
- self::register("stone_brick_slab", new Slab(new BID(Ids::STONE_BRICK_SLAB), "Stone Brick", $stoneSlabBreakInfo));
- self::register("dark_prismarine_slab", new Slab(new BID(Ids::DARK_PRISMARINE_SLAB), "Dark Prismarine", $stoneSlabBreakInfo));
- self::register("mossy_cobblestone_slab", new Slab(new BID(Ids::MOSSY_COBBLESTONE_SLAB), "Mossy Cobblestone", $stoneSlabBreakInfo));
- self::register("prismarine_slab", new Slab(new BID(Ids::PRISMARINE_SLAB), "Prismarine", $stoneSlabBreakInfo));
- self::register("prismarine_bricks_slab", new Slab(new BID(Ids::PRISMARINE_BRICKS_SLAB), "Prismarine Bricks", $stoneSlabBreakInfo));
- self::register("purpur_slab", new Slab(new BID(Ids::PURPUR_SLAB), "Purpur", $stoneSlabBreakInfo));
- self::register("red_nether_brick_slab", new Slab(new BID(Ids::RED_NETHER_BRICK_SLAB), "Red Nether Brick", $stoneSlabBreakInfo));
- self::register("red_sandstone_slab", new Slab(new BID(Ids::RED_SANDSTONE_SLAB), "Red Sandstone", $stoneSlabBreakInfo));
- self::register("smooth_sandstone_slab", new Slab(new BID(Ids::SMOOTH_SANDSTONE_SLAB), "Smooth Sandstone", $stoneSlabBreakInfo));
- self::register("andesite_slab", new Slab(new BID(Ids::ANDESITE_SLAB), "Andesite", $stoneSlabBreakInfo));
- self::register("diorite_slab", new Slab(new BID(Ids::DIORITE_SLAB), "Diorite", $stoneSlabBreakInfo));
- self::register("end_stone_brick_slab", new Slab(new BID(Ids::END_STONE_BRICK_SLAB), "End Stone Brick", $stoneSlabBreakInfo));
- self::register("granite_slab", new Slab(new BID(Ids::GRANITE_SLAB), "Granite", $stoneSlabBreakInfo));
- self::register("polished_andesite_slab", new Slab(new BID(Ids::POLISHED_ANDESITE_SLAB), "Polished Andesite", $stoneSlabBreakInfo));
- self::register("polished_diorite_slab", new Slab(new BID(Ids::POLISHED_DIORITE_SLAB), "Polished Diorite", $stoneSlabBreakInfo));
- self::register("polished_granite_slab", new Slab(new BID(Ids::POLISHED_GRANITE_SLAB), "Polished Granite", $stoneSlabBreakInfo));
- self::register("smooth_red_sandstone_slab", new Slab(new BID(Ids::SMOOTH_RED_SANDSTONE_SLAB), "Smooth Red Sandstone", $stoneSlabBreakInfo));
- self::register("cut_red_sandstone_slab", new Slab(new BID(Ids::CUT_RED_SANDSTONE_SLAB), "Cut Red Sandstone", $stoneSlabBreakInfo));
- self::register("cut_sandstone_slab", new Slab(new BID(Ids::CUT_SANDSTONE_SLAB), "Cut Sandstone", $stoneSlabBreakInfo));
- self::register("mossy_stone_brick_slab", new Slab(new BID(Ids::MOSSY_STONE_BRICK_SLAB), "Mossy Stone Brick", $stoneSlabBreakInfo));
- self::register("smooth_quartz_slab", new Slab(new BID(Ids::SMOOTH_QUARTZ_SLAB), "Smooth Quartz", $stoneSlabBreakInfo));
- self::register("stone_slab", new Slab(new BID(Ids::STONE_SLAB), "Stone", $stoneSlabBreakInfo));
-
- self::register("legacy_stonecutter", new Opaque(new BID(Ids::LEGACY_STONECUTTER), "Legacy Stonecutter", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD()))));
- self::register("sugarcane", new Sugarcane(new BID(Ids::SUGARCANE), "Sugarcane", new Info(BreakInfo::instant())));
- self::register("sweet_berry_bush", new SweetBerryBush(new BID(Ids::SWEET_BERRY_BUSH), "Sweet Berry Bush", new Info(BreakInfo::instant())));
- self::register("tnt", new TNT(new BID(Ids::TNT), "TNT", new Info(BreakInfo::instant())));
- self::register("fern", new TallGrass(new BID(Ids::FERN), "Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
- self::register("tall_grass", new TallGrass(new BID(Ids::TALL_GRASS), "Tall Grass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
-
- self::register("blue_torch", new Torch(new BID(Ids::BLUE_TORCH), "Blue Torch", new Info(BreakInfo::instant())));
- self::register("purple_torch", new Torch(new BID(Ids::PURPLE_TORCH), "Purple Torch", new Info(BreakInfo::instant())));
- self::register("red_torch", new Torch(new BID(Ids::RED_TORCH), "Red Torch", new Info(BreakInfo::instant())));
- self::register("green_torch", new Torch(new BID(Ids::GREEN_TORCH), "Green Torch", new Info(BreakInfo::instant())));
- self::register("torch", new Torch(new BID(Ids::TORCH), "Torch", new Info(BreakInfo::instant())));
-
- self::register("trapped_chest", new TrappedChest(new BID(Ids::TRAPPED_CHEST, TileChest::class), "Trapped Chest", $chestBreakInfo));
- self::register("tripwire", new Tripwire(new BID(Ids::TRIPWIRE), "Tripwire", new Info(BreakInfo::instant())));
- self::register("tripwire_hook", new TripwireHook(new BID(Ids::TRIPWIRE_HOOK), "Tripwire Hook", new Info(BreakInfo::instant())));
- self::register("underwater_torch", new UnderwaterTorch(new BID(Ids::UNDERWATER_TORCH), "Underwater Torch", new Info(BreakInfo::instant())));
- self::register("vines", new Vine(new BID(Ids::VINES), "Vines", new Info(BreakInfo::axe(0.2))));
- self::register("water", new Water(new BID(Ids::WATER), "Water", new Info(BreakInfo::indestructible(500.0))));
- self::register("lily_pad", new WaterLily(new BID(Ids::LILY_PAD), "Lily Pad", new Info(BreakInfo::instant())));
-
- $weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()));
- self::register("weighted_pressure_plate_heavy", new WeightedPressurePlateHeavy(new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY), "Weighted Pressure Plate Heavy", $weightedPressurePlateBreakInfo));
- self::register("weighted_pressure_plate_light", new WeightedPressurePlateLight(new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT), "Weighted Pressure Plate Light", $weightedPressurePlateBreakInfo));
- self::register("wheat", new Wheat(new BID(Ids::WHEAT), "Wheat Block", new Info(BreakInfo::instant())));
+ $stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
+
+ self::register("brick_slab", fn(BID $id) => new Slab($id, "Brick", $stoneSlabBreakInfo));
+ self::register("cobblestone_slab", fn(BID $id) => new Slab($id, "Cobblestone", $stoneSlabBreakInfo));
+ self::register("fake_wooden_slab", fn(BID $id) => new Slab($id, "Fake Wooden", $stoneSlabBreakInfo));
+ self::register("nether_brick_slab", fn(BID $id) => new Slab($id, "Nether Brick", $stoneSlabBreakInfo));
+ self::register("quartz_slab", fn(BID $id) => new Slab($id, "Quartz", $stoneSlabBreakInfo));
+ self::register("sandstone_slab", fn(BID $id) => new Slab($id, "Sandstone", $stoneSlabBreakInfo));
+ self::register("smooth_stone_slab", fn(BID $id) => new Slab($id, "Smooth Stone", $stoneSlabBreakInfo));
+ self::register("stone_brick_slab", fn(BID $id) => new Slab($id, "Stone Brick", $stoneSlabBreakInfo));
+ self::register("dark_prismarine_slab", fn(BID $id) => new Slab($id, "Dark Prismarine", $stoneSlabBreakInfo));
+ self::register("mossy_cobblestone_slab", fn(BID $id) => new Slab($id, "Mossy Cobblestone", $stoneSlabBreakInfo));
+ self::register("prismarine_slab", fn(BID $id) => new Slab($id, "Prismarine", $stoneSlabBreakInfo));
+ self::register("prismarine_bricks_slab", fn(BID $id) => new Slab($id, "Prismarine Bricks", $stoneSlabBreakInfo));
+ self::register("purpur_slab", fn(BID $id) => new Slab($id, "Purpur", $stoneSlabBreakInfo));
+ self::register("red_nether_brick_slab", fn(BID $id) => new Slab($id, "Red Nether Brick", $stoneSlabBreakInfo));
+ self::register("red_sandstone_slab", fn(BID $id) => new Slab($id, "Red Sandstone", $stoneSlabBreakInfo));
+ self::register("smooth_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Sandstone", $stoneSlabBreakInfo));
+ self::register("andesite_slab", fn(BID $id) => new Slab($id, "Andesite", $stoneSlabBreakInfo));
+ self::register("diorite_slab", fn(BID $id) => new Slab($id, "Diorite", $stoneSlabBreakInfo));
+ self::register("end_stone_brick_slab", fn(BID $id) => new Slab($id, "End Stone Brick", $stoneSlabBreakInfo));
+ self::register("granite_slab", fn(BID $id) => new Slab($id, "Granite", $stoneSlabBreakInfo));
+ self::register("polished_andesite_slab", fn(BID $id) => new Slab($id, "Polished Andesite", $stoneSlabBreakInfo));
+ self::register("polished_diorite_slab", fn(BID $id) => new Slab($id, "Polished Diorite", $stoneSlabBreakInfo));
+ self::register("polished_granite_slab", fn(BID $id) => new Slab($id, "Polished Granite", $stoneSlabBreakInfo));
+ self::register("smooth_red_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Red Sandstone", $stoneSlabBreakInfo));
+ self::register("cut_red_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Red Sandstone", $stoneSlabBreakInfo));
+ self::register("cut_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Sandstone", $stoneSlabBreakInfo));
+ self::register("mossy_stone_brick_slab", fn(BID $id) => new Slab($id, "Mossy Stone Brick", $stoneSlabBreakInfo));
+ self::register("smooth_quartz_slab", fn(BID $id) => new Slab($id, "Smooth Quartz", $stoneSlabBreakInfo));
+ self::register("stone_slab", fn(BID $id) => new Slab($id, "Stone", $stoneSlabBreakInfo));
+
+ self::register("legacy_stonecutter", fn(BID $id) => new Opaque($id, "Legacy Stonecutter", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD))));
+ self::register("sugarcane", fn(BID $id) => new Sugarcane($id, "Sugarcane", new Info(BreakInfo::instant())));
+ self::register("sweet_berry_bush", fn(BID $id) => new SweetBerryBush($id, "Sweet Berry Bush", new Info(BreakInfo::instant())));
+ self::register("tnt", fn(BID $id) => new TNT($id, "TNT", new Info(BreakInfo::instant())));
+ self::register("fern", fn(BID $id) => new TallGrass($id, "Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
+ self::register("tall_grass", fn(BID $id) => new TallGrass($id, "Tall Grass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+
+ self::register("blue_torch", fn(BID $id) => new Torch($id, "Blue Torch", new Info(BreakInfo::instant())));
+ self::register("purple_torch", fn(BID $id) => new Torch($id, "Purple Torch", new Info(BreakInfo::instant())));
+ self::register("red_torch", fn(BID $id) => new Torch($id, "Red Torch", new Info(BreakInfo::instant())));
+ self::register("green_torch", fn(BID $id) => new Torch($id, "Green Torch", new Info(BreakInfo::instant())));
+ self::register("torch", fn(BID $id) => new Torch($id, "Torch", new Info(BreakInfo::instant())));
+
+ self::register("trapped_chest", fn(BID $id) => new TrappedChest($id, "Trapped Chest", $chestBreakInfo), TileChest::class);
+ self::register("tripwire", fn(BID $id) => new Tripwire($id, "Tripwire", new Info(BreakInfo::instant())));
+ self::register("tripwire_hook", fn(BID $id) => new TripwireHook($id, "Tripwire Hook", new Info(BreakInfo::instant())));
+ self::register("underwater_torch", fn(BID $id) => new UnderwaterTorch($id, "Underwater Torch", new Info(BreakInfo::instant())));
+ self::register("vines", fn(BID $id) => new Vine($id, "Vines", new Info(BreakInfo::axe(0.2))));
+ self::register("water", fn(BID $id) => new Water($id, "Water", new Info(BreakInfo::indestructible(500.0))));
+ self::register("lily_pad", fn(BID $id) => new WaterLily($id, "Lily Pad", new Info(BreakInfo::instant())));
+
+ $weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD));
+ self::register("weighted_pressure_plate_heavy", fn(BID $id) => new WeightedPressurePlateHeavy(
+ $id,
+ "Weighted Pressure Plate Heavy",
+ $weightedPressurePlateBreakInfo,
+ deactivationDelayTicks: 10,
+ signalStrengthFactor: 0.1
+ ));
+ self::register("weighted_pressure_plate_light", fn(BID $id) => new WeightedPressurePlateLight(
+ $id,
+ "Weighted Pressure Plate Light",
+ $weightedPressurePlateBreakInfo,
+ deactivationDelayTicks: 10,
+ signalStrengthFactor: 1.0
+ ));
+ self::register("wheat", fn(BID $id) => new Wheat($id, "Wheat Block", new Info(BreakInfo::instant())));
$leavesBreakInfo = new Info(new class(0.2, ToolType::HOE) extends BreakInfo{
public function getBreakTime(Item $item) : float{
@@ -1108,41 +1205,41 @@ public function getBreakTime(Item $item) : float{
});
$saplingTypeInfo = new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]);
- foreach(SaplingType::getAll() as $saplingType){
+ foreach(SaplingType::cases() as $saplingType){
$name = $saplingType->getDisplayName();
- self::register($saplingType->name() . "_sapling", new Sapling(WoodLikeBlockIdHelper::getSaplingIdentifier($saplingType), $name . " Sapling", $saplingTypeInfo, $saplingType));
+ self::register(strtolower($saplingType->name) . "_sapling", fn(BID $id) => new Sapling($id, $name . " Sapling", $saplingTypeInfo, $saplingType));
}
- foreach(LeavesType::getAll() as $leavesType){
+ foreach(LeavesType::cases() as $leavesType){
$name = $leavesType->getDisplayName();
- self::register($leavesType->name() . "_leaves", new Leaves(WoodLikeBlockIdHelper::getLeavesIdentifier($leavesType), $name . " Leaves", $leavesBreakInfo, $leavesType));
+ self::register(strtolower($leavesType->name) . "_leaves", fn(BID $id) => new Leaves($id, $name . " Leaves", $leavesBreakInfo, $leavesType));
}
- $sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD()));
- self::register("red_sandstone_stairs", new Stair(new BID(Ids::RED_SANDSTONE_STAIRS), "Red Sandstone Stairs", $sandstoneBreakInfo));
- self::register("smooth_red_sandstone_stairs", new Stair(new BID(Ids::SMOOTH_RED_SANDSTONE_STAIRS), "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
- self::register("red_sandstone", new Opaque(new BID(Ids::RED_SANDSTONE), "Red Sandstone", $sandstoneBreakInfo));
- self::register("chiseled_red_sandstone", new Opaque(new BID(Ids::CHISELED_RED_SANDSTONE), "Chiseled Red Sandstone", $sandstoneBreakInfo));
- self::register("cut_red_sandstone", new Opaque(new BID(Ids::CUT_RED_SANDSTONE), "Cut Red Sandstone", $sandstoneBreakInfo));
- self::register("smooth_red_sandstone", new Opaque(new BID(Ids::SMOOTH_RED_SANDSTONE), "Smooth Red Sandstone", $sandstoneBreakInfo));
-
- self::register("sandstone_stairs", new Stair(new BID(Ids::SANDSTONE_STAIRS), "Sandstone Stairs", $sandstoneBreakInfo));
- self::register("smooth_sandstone_stairs", new Stair(new BID(Ids::SMOOTH_SANDSTONE_STAIRS), "Smooth Sandstone Stairs", $sandstoneBreakInfo));
- self::register("sandstone", new Opaque(new BID(Ids::SANDSTONE), "Sandstone", $sandstoneBreakInfo));
- self::register("chiseled_sandstone", new Opaque(new BID(Ids::CHISELED_SANDSTONE), "Chiseled Sandstone", $sandstoneBreakInfo));
- self::register("cut_sandstone", new Opaque(new BID(Ids::CUT_SANDSTONE), "Cut Sandstone", $sandstoneBreakInfo));
- self::register("smooth_sandstone", new Opaque(new BID(Ids::SMOOTH_SANDSTONE), "Smooth Sandstone", $sandstoneBreakInfo));
-
- self::register("glazed_terracotta", new GlazedTerracotta(new BID(Ids::GLAZED_TERRACOTTA), "Glazed Terracotta", new Info(BreakInfo::pickaxe(1.4, ToolTier::WOOD()))));
- self::register("dyed_shulker_box", new DyedShulkerBox(new BID(Ids::DYED_SHULKER_BOX, TileShulkerBox::class), "Dyed Shulker Box", $shulkerBoxBreakInfo));
- self::register("stained_glass", new StainedGlass(new BID(Ids::STAINED_GLASS), "Stained Glass", $glassBreakInfo));
- self::register("stained_glass_pane", new StainedGlassPane(new BID(Ids::STAINED_GLASS_PANE), "Stained Glass Pane", $glassBreakInfo));
- self::register("stained_clay", new StainedHardenedClay(new BID(Ids::STAINED_CLAY), "Stained Clay", $hardenedClayBreakInfo));
- self::register("stained_hardened_glass", new StainedHardenedGlass(new BID(Ids::STAINED_HARDENED_GLASS), "Stained Hardened Glass", $hardenedGlassBreakInfo));
- self::register("stained_hardened_glass_pane", new StainedHardenedGlassPane(new BID(Ids::STAINED_HARDENED_GLASS_PANE), "Stained Hardened Glass Pane", $hardenedGlassBreakInfo));
- self::register("carpet", new Carpet(new BID(Ids::CARPET), "Carpet", new Info(new BreakInfo(0.1))));
- self::register("concrete", new Concrete(new BID(Ids::CONCRETE), "Concrete", new Info(BreakInfo::pickaxe(1.8, ToolTier::WOOD()))));
- self::register("concrete_powder", new ConcretePowder(new BID(Ids::CONCRETE_POWDER), "Concrete Powder", new Info(BreakInfo::shovel(0.5))));
- self::register("wool", new Wool(new BID(Ids::WOOL), "Wool", new Info(new class(0.8, ToolType::SHEARS) extends BreakInfo{
+ $sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
+ self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo));
+ self::register("smooth_red_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
+ self::register("red_sandstone", fn(BID $id) => new Opaque($id, "Red Sandstone", $sandstoneBreakInfo));
+ self::register("chiseled_red_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Red Sandstone", $sandstoneBreakInfo));
+ self::register("cut_red_sandstone", fn(BID $id) => new Opaque($id, "Cut Red Sandstone", $sandstoneBreakInfo));
+ self::register("smooth_red_sandstone", fn(BID $id) => new Opaque($id, "Smooth Red Sandstone", $sandstoneBreakInfo));
+
+ self::register("sandstone_stairs", fn(BID $id) => new Stair($id, "Sandstone Stairs", $sandstoneBreakInfo));
+ self::register("smooth_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Sandstone Stairs", $sandstoneBreakInfo));
+ self::register("sandstone", fn(BID $id) => new Opaque($id, "Sandstone", $sandstoneBreakInfo));
+ self::register("chiseled_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Sandstone", $sandstoneBreakInfo));
+ self::register("cut_sandstone", fn(BID $id) => new Opaque($id, "Cut Sandstone", $sandstoneBreakInfo));
+ self::register("smooth_sandstone", fn(BID $id) => new Opaque($id, "Smooth Sandstone", $sandstoneBreakInfo));
+
+ self::register("glazed_terracotta", fn(BID $id) => new GlazedTerracotta($id, "Glazed Terracotta", new Info(BreakInfo::pickaxe(1.4, ToolTier::WOOD))));
+ self::register("dyed_shulker_box", fn(BID $id) => new DyedShulkerBox($id, "Dyed Shulker Box", $shulkerBoxBreakInfo), TileShulkerBox::class);
+ self::register("stained_glass", fn(BID $id) => new StainedGlass($id, "Stained Glass", $glassBreakInfo));
+ self::register("stained_glass_pane", fn(BID $id) => new StainedGlassPane($id, "Stained Glass Pane", $glassBreakInfo));
+ self::register("stained_clay", fn(BID $id) => new StainedHardenedClay($id, "Stained Clay", $hardenedClayBreakInfo));
+ self::register("stained_hardened_glass", fn(BID $id) => new StainedHardenedGlass($id, "Stained Hardened Glass", $hardenedGlassBreakInfo));
+ self::register("stained_hardened_glass_pane", fn(BID $id) => new StainedHardenedGlassPane($id, "Stained Hardened Glass Pane", $hardenedGlassBreakInfo));
+ self::register("carpet", fn(BID $id) => new Carpet($id, "Carpet", new Info(new BreakInfo(0.1))));
+ self::register("concrete", fn(BID $id) => new Concrete($id, "Concrete", new Info(BreakInfo::pickaxe(1.8, ToolTier::WOOD))));
+ self::register("concrete_powder", fn(BID $id) => new ConcretePowder($id, "Concrete Powder", new Info(BreakInfo::shovel(0.5))));
+ self::register("wool", fn(BID $id) => new Wool($id, "Wool", new Info(new class(0.8, ToolType::SHEARS) extends BreakInfo{
public function getBreakTime(Item $item) : float{
$time = parent::getBreakTime($item);
if($item->getBlockToolType() === ToolType::SHEARS){
@@ -1154,55 +1251,55 @@ public function getBreakTime(Item $item) : float{
})));
//TODO: in the future these won't all have the same hardness; they only do now because of the old metadata crap
- $wallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
- self::register("cobblestone_wall", new Wall(new BID(Ids::COBBLESTONE_WALL), "Cobblestone Wall", $wallBreakInfo));
- self::register("andesite_wall", new Wall(new BID(Ids::ANDESITE_WALL), "Andesite Wall", $wallBreakInfo));
- self::register("brick_wall", new Wall(new BID(Ids::BRICK_WALL), "Brick Wall", $wallBreakInfo));
- self::register("diorite_wall", new Wall(new BID(Ids::DIORITE_WALL), "Diorite Wall", $wallBreakInfo));
- self::register("end_stone_brick_wall", new Wall(new BID(Ids::END_STONE_BRICK_WALL), "End Stone Brick Wall", $wallBreakInfo));
- self::register("granite_wall", new Wall(new BID(Ids::GRANITE_WALL), "Granite Wall", $wallBreakInfo));
- self::register("mossy_stone_brick_wall", new Wall(new BID(Ids::MOSSY_STONE_BRICK_WALL), "Mossy Stone Brick Wall", $wallBreakInfo));
- self::register("mossy_cobblestone_wall", new Wall(new BID(Ids::MOSSY_COBBLESTONE_WALL), "Mossy Cobblestone Wall", $wallBreakInfo));
- self::register("nether_brick_wall", new Wall(new BID(Ids::NETHER_BRICK_WALL), "Nether Brick Wall", $wallBreakInfo));
- self::register("prismarine_wall", new Wall(new BID(Ids::PRISMARINE_WALL), "Prismarine Wall", $wallBreakInfo));
- self::register("red_nether_brick_wall", new Wall(new BID(Ids::RED_NETHER_BRICK_WALL), "Red Nether Brick Wall", $wallBreakInfo));
- self::register("red_sandstone_wall", new Wall(new BID(Ids::RED_SANDSTONE_WALL), "Red Sandstone Wall", $wallBreakInfo));
- self::register("sandstone_wall", new Wall(new BID(Ids::SANDSTONE_WALL), "Sandstone Wall", $wallBreakInfo));
- self::register("stone_brick_wall", new Wall(new BID(Ids::STONE_BRICK_WALL), "Stone Brick Wall", $wallBreakInfo));
+ $wallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
+ self::register("cobblestone_wall", fn(BID $id) => new Wall($id, "Cobblestone Wall", $wallBreakInfo));
+ self::register("andesite_wall", fn(BID $id) => new Wall($id, "Andesite Wall", $wallBreakInfo));
+ self::register("brick_wall", fn(BID $id) => new Wall($id, "Brick Wall", $wallBreakInfo));
+ self::register("diorite_wall", fn(BID $id) => new Wall($id, "Diorite Wall", $wallBreakInfo));
+ self::register("end_stone_brick_wall", fn(BID $id) => new Wall($id, "End Stone Brick Wall", $wallBreakInfo));
+ self::register("granite_wall", fn(BID $id) => new Wall($id, "Granite Wall", $wallBreakInfo));
+ self::register("mossy_stone_brick_wall", fn(BID $id) => new Wall($id, "Mossy Stone Brick Wall", $wallBreakInfo));
+ self::register("mossy_cobblestone_wall", fn(BID $id) => new Wall($id, "Mossy Cobblestone Wall", $wallBreakInfo));
+ self::register("nether_brick_wall", fn(BID $id) => new Wall($id, "Nether Brick Wall", $wallBreakInfo));
+ self::register("prismarine_wall", fn(BID $id) => new Wall($id, "Prismarine Wall", $wallBreakInfo));
+ self::register("red_nether_brick_wall", fn(BID $id) => new Wall($id, "Red Nether Brick Wall", $wallBreakInfo));
+ self::register("red_sandstone_wall", fn(BID $id) => new Wall($id, "Red Sandstone Wall", $wallBreakInfo));
+ self::register("sandstone_wall", fn(BID $id) => new Wall($id, "Sandstone Wall", $wallBreakInfo));
+ self::register("stone_brick_wall", fn(BID $id) => new Wall($id, "Stone Brick Wall", $wallBreakInfo));
self::registerElements();
- $chemistryTableBreakInfo = new Info(BreakInfo::pickaxe(2.5, ToolTier::WOOD()));
- self::register("compound_creator", new ChemistryTable(new BID(Ids::COMPOUND_CREATOR), "Compound Creator", $chemistryTableBreakInfo));
- self::register("element_constructor", new ChemistryTable(new BID(Ids::ELEMENT_CONSTRUCTOR), "Element Constructor", $chemistryTableBreakInfo));
- self::register("lab_table", new ChemistryTable(new BID(Ids::LAB_TABLE), "Lab Table", $chemistryTableBreakInfo));
- self::register("material_reducer", new ChemistryTable(new BID(Ids::MATERIAL_REDUCER), "Material Reducer", $chemistryTableBreakInfo));
+ $chemistryTableBreakInfo = new Info(BreakInfo::pickaxe(2.5, ToolTier::WOOD));
+ self::register("compound_creator", fn(BID $id) => new ChemistryTable($id, "Compound Creator", $chemistryTableBreakInfo));
+ self::register("element_constructor", fn(BID $id) => new ChemistryTable($id, "Element Constructor", $chemistryTableBreakInfo));
+ self::register("lab_table", fn(BID $id) => new ChemistryTable($id, "Lab Table", $chemistryTableBreakInfo));
+ self::register("material_reducer", fn(BID $id) => new ChemistryTable($id, "Material Reducer", $chemistryTableBreakInfo));
- self::register("chemical_heat", new ChemicalHeat(new BID(Ids::CHEMICAL_HEAT), "Heat Block", $chemistryTableBreakInfo));
+ self::register("chemical_heat", fn(BID $id) => new ChemicalHeat($id, "Heat Block", $chemistryTableBreakInfo));
self::registerMushroomBlocks();
- self::register("coral", new Coral(
- new BID(Ids::CORAL),
+ self::register("coral", fn(BID $id) => new Coral(
+ $id,
"Coral",
new Info(BreakInfo::instant()),
));
- self::register("coral_fan", new FloorCoralFan(
- new BID(Ids::CORAL_FAN),
+ self::register("coral_fan", fn(BID $id) => new FloorCoralFan(
+ $id,
"Coral Fan",
new Info(BreakInfo::instant()),
));
- self::register("wall_coral_fan", new WallCoralFan(
- new BID(Ids::WALL_CORAL_FAN),
+ self::register("wall_coral_fan", fn(BID $id) => new WallCoralFan(
+ $id,
"Wall Coral Fan",
new Info(BreakInfo::instant()),
));
- self::register("mangrove_roots", new MangroveRoots(new BID(Ids::MANGROVE_ROOTS), "Mangrove Roots", new Info(BreakInfo::axe(0.7))));
- self::register("muddy_mangrove_roots", new SimplePillar(new BID(Ids::MUDDY_MANGROVE_ROOTS), "Muddy Mangrove Roots", new Info(BreakInfo::shovel(0.7), [Tags::MUD])));
- self::register("froglight", new Froglight(new BID(Ids::FROGLIGHT), "Froglight", new Info(new BreakInfo(0.3))));
- self::register("sculk", new Sculk(new BID(Ids::SCULK), "Sculk", new Info(new BreakInfo(0.6, ToolType::HOE))));
- self::register("reinforced_deepslate", new class(new BID(Ids::REINFORCED_DEEPSLATE), "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 3600.0))) extends Opaque{
+ self::register("mangrove_roots", fn(BID $id) => new MangroveRoots($id, "Mangrove Roots", new Info(BreakInfo::axe(0.7))));
+ self::register("muddy_mangrove_roots", fn(BID $id) => new SimplePillar($id, "Muddy Mangrove Roots", new Info(BreakInfo::shovel(0.7), [Tags::MUD])));
+ self::register("froglight", fn(BID $id) => new Froglight($id, "Froglight", new Info(new BreakInfo(0.3))));
+ self::register("sculk", fn(BID $id) => new Sculk($id, "Sculk", new Info(new BreakInfo(0.6, ToolType::HOE))));
+ self::register("reinforced_deepslate", fn(BID $id) => new class($id, "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 3600.0))) extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
@@ -1214,6 +1311,7 @@ public function getDropsForCompatibleTool(Item $item) : array{
self::registerBlocksR17();
self::registerBlocksR18();
self::registerMudBlocks();
+ self::registerTuffBlocks();
self::registerCraftingTables();
self::registerChorusBlocks();
@@ -1230,299 +1328,315 @@ private static function registerWoodenBlocks() : void{
$woodenButtonBreakInfo = new Info(BreakInfo::axe(0.5));
$woodenPressurePlateBreakInfo = new Info(BreakInfo::axe(0.5));
- foreach(WoodType::getAll() as $woodType){
+ foreach(WoodType::cases() as $woodType){
$name = $woodType->getDisplayName();
- $idName = fn(string $suffix) => $woodType->name() . "_" . $suffix;
-
- self::register($idName(mb_strtolower($woodType->getStandardLogSuffix() ?? "log", 'US-ASCII')), new Wood(WoodLikeBlockIdHelper::getLogIdentifier($woodType), $name . " " . ($woodType->getStandardLogSuffix() ?? "Log"), $logBreakInfo, $woodType));
- self::register($idName(mb_strtolower($woodType->getAllSidedLogSuffix() ?? "wood", 'US-ASCII')), new Wood(WoodLikeBlockIdHelper::getAllSidedLogIdentifier($woodType), $name . " " . ($woodType->getAllSidedLogSuffix() ?? "Wood"), $logBreakInfo, $woodType));
-
- self::register($idName("planks"), new Planks(WoodLikeBlockIdHelper::getPlanksIdentifier($woodType), $name . " Planks", $planksBreakInfo, $woodType));
- self::register($idName("fence"), new WoodenFence(WoodLikeBlockIdHelper::getFenceIdentifier($woodType), $name . " Fence", $planksBreakInfo, $woodType));
- self::register($idName("slab"), new WoodenSlab(WoodLikeBlockIdHelper::getSlabIdentifier($woodType), $name, $planksBreakInfo, $woodType));
-
- self::register($idName("fence_gate"), new FenceGate(WoodLikeBlockIdHelper::getFenceGateIdentifier($woodType), $name . " Fence Gate", $planksBreakInfo, $woodType));
- self::register($idName("stairs"), new WoodenStairs(WoodLikeBlockIdHelper::getStairsIdentifier($woodType), $name . " Stairs", $planksBreakInfo, $woodType));
- self::register($idName("door"), new WoodenDoor(WoodLikeBlockIdHelper::getDoorIdentifier($woodType), $name . " Door", $woodenDoorBreakInfo, $woodType));
-
- self::register($idName("button"), new WoodenButton(WoodLikeBlockIdHelper::getButtonIdentifier($woodType), $name . " Button", $woodenButtonBreakInfo, $woodType));
- self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType));
- self::register($idName("trapdoor"), new WoodenTrapdoor(WoodLikeBlockIdHelper::getTrapdoorIdentifier($woodType), $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
-
- [$floorSignId, $wallSignId, $signAsItem] = WoodLikeBlockIdHelper::getSignInfo($woodType);
- self::register($idName("sign"), new FloorSign($floorSignId, $name . " Sign", $signBreakInfo, $woodType, $signAsItem));
- self::register($idName("wall_sign"), new WallSign($wallSignId, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem));
+ $idName = fn(string $suffix) => strtolower($woodType->name) . "_" . $suffix;
+
+ self::register($idName(mb_strtolower($woodType->getStandardLogSuffix() ?? "log", 'US-ASCII')), fn(BID $id) => new Wood($id, $name . " " . ($woodType->getStandardLogSuffix() ?? "Log"), $logBreakInfo, $woodType));
+ self::register($idName(mb_strtolower($woodType->getAllSidedLogSuffix() ?? "wood", 'US-ASCII')), fn(BID $id) => new Wood($id, $name . " " . ($woodType->getAllSidedLogSuffix() ?? "Wood"), $logBreakInfo, $woodType));
+
+ self::register($idName("planks"), fn(BID $id) => new Planks($id, $name . " Planks", $planksBreakInfo, $woodType));
+ self::register($idName("fence"), fn(BID $id) => new WoodenFence($id, $name . " Fence", $planksBreakInfo, $woodType));
+ self::register($idName("slab"), fn(BID $id) => new WoodenSlab($id, $name, $planksBreakInfo, $woodType));
+
+ self::register($idName("fence_gate"), fn(BID $id) => new FenceGate($id, $name . " Fence Gate", $planksBreakInfo, $woodType));
+ self::register($idName("stairs"), fn(BID $id) => new WoodenStairs($id, $name . " Stairs", $planksBreakInfo, $woodType));
+ self::register($idName("door"), fn(BID $id) => new WoodenDoor($id, $name . " Door", $woodenDoorBreakInfo, $woodType));
+
+ self::register($idName("button"), fn(BID $id) => new WoodenButton($id, $name . " Button", $woodenButtonBreakInfo, $woodType));
+ self::register($idName("pressure_plate"), fn(BID $id) => new WoodenPressurePlate($id, $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
+ self::register($idName("trapdoor"), fn(BID $id) => new WoodenTrapdoor($id, $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
+
+ $signAsItem = match($woodType){
+ WoodType::OAK => VanillaItems::OAK_SIGN(...),
+ WoodType::SPRUCE => VanillaItems::SPRUCE_SIGN(...),
+ WoodType::BIRCH => VanillaItems::BIRCH_SIGN(...),
+ WoodType::JUNGLE => VanillaItems::JUNGLE_SIGN(...),
+ WoodType::ACACIA => VanillaItems::ACACIA_SIGN(...),
+ WoodType::DARK_OAK => VanillaItems::DARK_OAK_SIGN(...),
+ WoodType::MANGROVE => VanillaItems::MANGROVE_SIGN(...),
+ WoodType::CRIMSON => VanillaItems::CRIMSON_SIGN(...),
+ 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));
}
}
private static function registerMushroomBlocks() : void{
$mushroomBlockBreakInfo = new Info(BreakInfo::axe(0.2));
- self::register("brown_mushroom_block", new BrownMushroomBlock(new BID(Ids::BROWN_MUSHROOM_BLOCK), "Brown Mushroom Block", $mushroomBlockBreakInfo));
- self::register("red_mushroom_block", new RedMushroomBlock(new BID(Ids::RED_MUSHROOM_BLOCK), "Red Mushroom Block", $mushroomBlockBreakInfo));
+ self::register("brown_mushroom_block", fn(BID $id) => new BrownMushroomBlock($id, "Brown Mushroom Block", $mushroomBlockBreakInfo));
+ self::register("red_mushroom_block", fn(BID $id) => new RedMushroomBlock($id, "Red Mushroom Block", $mushroomBlockBreakInfo));
//finally, the stems
- self::register("mushroom_stem", new MushroomStem(new BID(Ids::MUSHROOM_STEM), "Mushroom Stem", $mushroomBlockBreakInfo));
- self::register("all_sided_mushroom_stem", new MushroomStem(new BID(Ids::ALL_SIDED_MUSHROOM_STEM), "All Sided Mushroom Stem", $mushroomBlockBreakInfo));
+ self::register("mushroom_stem", fn(BID $id) => new MushroomStem($id, "Mushroom Stem", $mushroomBlockBreakInfo));
+ self::register("all_sided_mushroom_stem", fn(BID $id) => new MushroomStem($id, "All Sided Mushroom Stem", $mushroomBlockBreakInfo));
}
private static function registerElements() : void{
$instaBreak = new Info(BreakInfo::instant());
- self::register("element_zero", new Opaque(new BID(Ids::ELEMENT_ZERO), "???", $instaBreak));
-
- $register = fn(string $name, int $id, string $displayName, string $symbol, int $atomicWeight, int $group) =>
- self::register("element_$name", new Element(new BID($id), $displayName, $instaBreak, $symbol, $atomicWeight, $group));
-
- $register("hydrogen", Ids::ELEMENT_HYDROGEN, "Hydrogen", "h", 1, 5);
- $register("helium", Ids::ELEMENT_HELIUM, "Helium", "he", 2, 7);
- $register("lithium", Ids::ELEMENT_LITHIUM, "Lithium", "li", 3, 0);
- $register("beryllium", Ids::ELEMENT_BERYLLIUM, "Beryllium", "be", 4, 1);
- $register("boron", Ids::ELEMENT_BORON, "Boron", "b", 5, 4);
- $register("carbon", Ids::ELEMENT_CARBON, "Carbon", "c", 6, 5);
- $register("nitrogen", Ids::ELEMENT_NITROGEN, "Nitrogen", "n", 7, 5);
- $register("oxygen", Ids::ELEMENT_OXYGEN, "Oxygen", "o", 8, 5);
- $register("fluorine", Ids::ELEMENT_FLUORINE, "Fluorine", "f", 9, 6);
- $register("neon", Ids::ELEMENT_NEON, "Neon", "ne", 10, 7);
- $register("sodium", Ids::ELEMENT_SODIUM, "Sodium", "na", 11, 0);
- $register("magnesium", Ids::ELEMENT_MAGNESIUM, "Magnesium", "mg", 12, 1);
- $register("aluminum", Ids::ELEMENT_ALUMINUM, "Aluminum", "al", 13, 3);
- $register("silicon", Ids::ELEMENT_SILICON, "Silicon", "si", 14, 4);
- $register("phosphorus", Ids::ELEMENT_PHOSPHORUS, "Phosphorus", "p", 15, 5);
- $register("sulfur", Ids::ELEMENT_SULFUR, "Sulfur", "s", 16, 5);
- $register("chlorine", Ids::ELEMENT_CHLORINE, "Chlorine", "cl", 17, 6);
- $register("argon", Ids::ELEMENT_ARGON, "Argon", "ar", 18, 7);
- $register("potassium", Ids::ELEMENT_POTASSIUM, "Potassium", "k", 19, 0);
- $register("calcium", Ids::ELEMENT_CALCIUM, "Calcium", "ca", 20, 1);
- $register("scandium", Ids::ELEMENT_SCANDIUM, "Scandium", "sc", 21, 2);
- $register("titanium", Ids::ELEMENT_TITANIUM, "Titanium", "ti", 22, 2);
- $register("vanadium", Ids::ELEMENT_VANADIUM, "Vanadium", "v", 23, 2);
- $register("chromium", Ids::ELEMENT_CHROMIUM, "Chromium", "cr", 24, 2);
- $register("manganese", Ids::ELEMENT_MANGANESE, "Manganese", "mn", 25, 2);
- $register("iron", Ids::ELEMENT_IRON, "Iron", "fe", 26, 2);
- $register("cobalt", Ids::ELEMENT_COBALT, "Cobalt", "co", 27, 2);
- $register("nickel", Ids::ELEMENT_NICKEL, "Nickel", "ni", 28, 2);
- $register("copper", Ids::ELEMENT_COPPER, "Copper", "cu", 29, 2);
- $register("zinc", Ids::ELEMENT_ZINC, "Zinc", "zn", 30, 2);
- $register("gallium", Ids::ELEMENT_GALLIUM, "Gallium", "ga", 31, 3);
- $register("germanium", Ids::ELEMENT_GERMANIUM, "Germanium", "ge", 32, 4);
- $register("arsenic", Ids::ELEMENT_ARSENIC, "Arsenic", "as", 33, 4);
- $register("selenium", Ids::ELEMENT_SELENIUM, "Selenium", "se", 34, 5);
- $register("bromine", Ids::ELEMENT_BROMINE, "Bromine", "br", 35, 6);
- $register("krypton", Ids::ELEMENT_KRYPTON, "Krypton", "kr", 36, 7);
- $register("rubidium", Ids::ELEMENT_RUBIDIUM, "Rubidium", "rb", 37, 0);
- $register("strontium", Ids::ELEMENT_STRONTIUM, "Strontium", "sr", 38, 1);
- $register("yttrium", Ids::ELEMENT_YTTRIUM, "Yttrium", "y", 39, 2);
- $register("zirconium", Ids::ELEMENT_ZIRCONIUM, "Zirconium", "zr", 40, 2);
- $register("niobium", Ids::ELEMENT_NIOBIUM, "Niobium", "nb", 41, 2);
- $register("molybdenum", Ids::ELEMENT_MOLYBDENUM, "Molybdenum", "mo", 42, 2);
- $register("technetium", Ids::ELEMENT_TECHNETIUM, "Technetium", "tc", 43, 2);
- $register("ruthenium", Ids::ELEMENT_RUTHENIUM, "Ruthenium", "ru", 44, 2);
- $register("rhodium", Ids::ELEMENT_RHODIUM, "Rhodium", "rh", 45, 2);
- $register("palladium", Ids::ELEMENT_PALLADIUM, "Palladium", "pd", 46, 2);
- $register("silver", Ids::ELEMENT_SILVER, "Silver", "ag", 47, 2);
- $register("cadmium", Ids::ELEMENT_CADMIUM, "Cadmium", "cd", 48, 2);
- $register("indium", Ids::ELEMENT_INDIUM, "Indium", "in", 49, 3);
- $register("tin", Ids::ELEMENT_TIN, "Tin", "sn", 50, 3);
- $register("antimony", Ids::ELEMENT_ANTIMONY, "Antimony", "sb", 51, 4);
- $register("tellurium", Ids::ELEMENT_TELLURIUM, "Tellurium", "te", 52, 4);
- $register("iodine", Ids::ELEMENT_IODINE, "Iodine", "i", 53, 6);
- $register("xenon", Ids::ELEMENT_XENON, "Xenon", "xe", 54, 7);
- $register("cesium", Ids::ELEMENT_CESIUM, "Cesium", "cs", 55, 0);
- $register("barium", Ids::ELEMENT_BARIUM, "Barium", "ba", 56, 1);
- $register("lanthanum", Ids::ELEMENT_LANTHANUM, "Lanthanum", "la", 57, 8);
- $register("cerium", Ids::ELEMENT_CERIUM, "Cerium", "ce", 58, 8);
- $register("praseodymium", Ids::ELEMENT_PRASEODYMIUM, "Praseodymium", "pr", 59, 8);
- $register("neodymium", Ids::ELEMENT_NEODYMIUM, "Neodymium", "nd", 60, 8);
- $register("promethium", Ids::ELEMENT_PROMETHIUM, "Promethium", "pm", 61, 8);
- $register("samarium", Ids::ELEMENT_SAMARIUM, "Samarium", "sm", 62, 8);
- $register("europium", Ids::ELEMENT_EUROPIUM, "Europium", "eu", 63, 8);
- $register("gadolinium", Ids::ELEMENT_GADOLINIUM, "Gadolinium", "gd", 64, 8);
- $register("terbium", Ids::ELEMENT_TERBIUM, "Terbium", "tb", 65, 8);
- $register("dysprosium", Ids::ELEMENT_DYSPROSIUM, "Dysprosium", "dy", 66, 8);
- $register("holmium", Ids::ELEMENT_HOLMIUM, "Holmium", "ho", 67, 8);
- $register("erbium", Ids::ELEMENT_ERBIUM, "Erbium", "er", 68, 8);
- $register("thulium", Ids::ELEMENT_THULIUM, "Thulium", "tm", 69, 8);
- $register("ytterbium", Ids::ELEMENT_YTTERBIUM, "Ytterbium", "yb", 70, 8);
- $register("lutetium", Ids::ELEMENT_LUTETIUM, "Lutetium", "lu", 71, 8);
- $register("hafnium", Ids::ELEMENT_HAFNIUM, "Hafnium", "hf", 72, 2);
- $register("tantalum", Ids::ELEMENT_TANTALUM, "Tantalum", "ta", 73, 2);
- $register("tungsten", Ids::ELEMENT_TUNGSTEN, "Tungsten", "w", 74, 2);
- $register("rhenium", Ids::ELEMENT_RHENIUM, "Rhenium", "re", 75, 2);
- $register("osmium", Ids::ELEMENT_OSMIUM, "Osmium", "os", 76, 2);
- $register("iridium", Ids::ELEMENT_IRIDIUM, "Iridium", "ir", 77, 2);
- $register("platinum", Ids::ELEMENT_PLATINUM, "Platinum", "pt", 78, 2);
- $register("gold", Ids::ELEMENT_GOLD, "Gold", "au", 79, 2);
- $register("mercury", Ids::ELEMENT_MERCURY, "Mercury", "hg", 80, 2);
- $register("thallium", Ids::ELEMENT_THALLIUM, "Thallium", "tl", 81, 3);
- $register("lead", Ids::ELEMENT_LEAD, "Lead", "pb", 82, 3);
- $register("bismuth", Ids::ELEMENT_BISMUTH, "Bismuth", "bi", 83, 3);
- $register("polonium", Ids::ELEMENT_POLONIUM, "Polonium", "po", 84, 4);
- $register("astatine", Ids::ELEMENT_ASTATINE, "Astatine", "at", 85, 6);
- $register("radon", Ids::ELEMENT_RADON, "Radon", "rn", 86, 7);
- $register("francium", Ids::ELEMENT_FRANCIUM, "Francium", "fr", 87, 0);
- $register("radium", Ids::ELEMENT_RADIUM, "Radium", "ra", 88, 1);
- $register("actinium", Ids::ELEMENT_ACTINIUM, "Actinium", "ac", 89, 9);
- $register("thorium", Ids::ELEMENT_THORIUM, "Thorium", "th", 90, 9);
- $register("protactinium", Ids::ELEMENT_PROTACTINIUM, "Protactinium", "pa", 91, 9);
- $register("uranium", Ids::ELEMENT_URANIUM, "Uranium", "u", 92, 9);
- $register("neptunium", Ids::ELEMENT_NEPTUNIUM, "Neptunium", "np", 93, 9);
- $register("plutonium", Ids::ELEMENT_PLUTONIUM, "Plutonium", "pu", 94, 9);
- $register("americium", Ids::ELEMENT_AMERICIUM, "Americium", "am", 95, 9);
- $register("curium", Ids::ELEMENT_CURIUM, "Curium", "cm", 96, 9);
- $register("berkelium", Ids::ELEMENT_BERKELIUM, "Berkelium", "bk", 97, 9);
- $register("californium", Ids::ELEMENT_CALIFORNIUM, "Californium", "cf", 98, 9);
- $register("einsteinium", Ids::ELEMENT_EINSTEINIUM, "Einsteinium", "es", 99, 9);
- $register("fermium", Ids::ELEMENT_FERMIUM, "Fermium", "fm", 100, 9);
- $register("mendelevium", Ids::ELEMENT_MENDELEVIUM, "Mendelevium", "md", 101, 9);
- $register("nobelium", Ids::ELEMENT_NOBELIUM, "Nobelium", "no", 102, 9);
- $register("lawrencium", Ids::ELEMENT_LAWRENCIUM, "Lawrencium", "lr", 103, 9);
- $register("rutherfordium", Ids::ELEMENT_RUTHERFORDIUM, "Rutherfordium", "rf", 104, 2);
- $register("dubnium", Ids::ELEMENT_DUBNIUM, "Dubnium", "db", 105, 2);
- $register("seaborgium", Ids::ELEMENT_SEABORGIUM, "Seaborgium", "sg", 106, 2);
- $register("bohrium", Ids::ELEMENT_BOHRIUM, "Bohrium", "bh", 107, 2);
- $register("hassium", Ids::ELEMENT_HASSIUM, "Hassium", "hs", 108, 2);
- $register("meitnerium", Ids::ELEMENT_MEITNERIUM, "Meitnerium", "mt", 109, 2);
- $register("darmstadtium", Ids::ELEMENT_DARMSTADTIUM, "Darmstadtium", "ds", 110, 2);
- $register("roentgenium", Ids::ELEMENT_ROENTGENIUM, "Roentgenium", "rg", 111, 2);
- $register("copernicium", Ids::ELEMENT_COPERNICIUM, "Copernicium", "cn", 112, 2);
- $register("nihonium", Ids::ELEMENT_NIHONIUM, "Nihonium", "nh", 113, 3);
- $register("flerovium", Ids::ELEMENT_FLEROVIUM, "Flerovium", "fl", 114, 3);
- $register("moscovium", Ids::ELEMENT_MOSCOVIUM, "Moscovium", "mc", 115, 3);
- $register("livermorium", Ids::ELEMENT_LIVERMORIUM, "Livermorium", "lv", 116, 3);
- $register("tennessine", Ids::ELEMENT_TENNESSINE, "Tennessine", "ts", 117, 6);
- $register("oganesson", Ids::ELEMENT_OGANESSON, "Oganesson", "og", 118, 7);
+ self::register("element_zero", fn(BID $id) => new Opaque($id, "???", $instaBreak));
+
+ $register = fn(string $name, string $displayName, string $symbol, int $atomicWeight, int $group) =>
+ self::register("element_$name", fn(BID $id) => new Element($id, $displayName, $instaBreak, $symbol, $atomicWeight, $group));
+
+ $register("hydrogen", "Hydrogen", "h", 1, 5);
+ $register("helium", "Helium", "he", 2, 7);
+ $register("lithium", "Lithium", "li", 3, 0);
+ $register("beryllium", "Beryllium", "be", 4, 1);
+ $register("boron", "Boron", "b", 5, 4);
+ $register("carbon", "Carbon", "c", 6, 5);
+ $register("nitrogen", "Nitrogen", "n", 7, 5);
+ $register("oxygen", "Oxygen", "o", 8, 5);
+ $register("fluorine", "Fluorine", "f", 9, 6);
+ $register("neon", "Neon", "ne", 10, 7);
+ $register("sodium", "Sodium", "na", 11, 0);
+ $register("magnesium", "Magnesium", "mg", 12, 1);
+ $register("aluminum", "Aluminum", "al", 13, 3);
+ $register("silicon", "Silicon", "si", 14, 4);
+ $register("phosphorus", "Phosphorus", "p", 15, 5);
+ $register("sulfur", "Sulfur", "s", 16, 5);
+ $register("chlorine", "Chlorine", "cl", 17, 6);
+ $register("argon", "Argon", "ar", 18, 7);
+ $register("potassium", "Potassium", "k", 19, 0);
+ $register("calcium", "Calcium", "ca", 20, 1);
+ $register("scandium", "Scandium", "sc", 21, 2);
+ $register("titanium", "Titanium", "ti", 22, 2);
+ $register("vanadium", "Vanadium", "v", 23, 2);
+ $register("chromium", "Chromium", "cr", 24, 2);
+ $register("manganese", "Manganese", "mn", 25, 2);
+ $register("iron", "Iron", "fe", 26, 2);
+ $register("cobalt", "Cobalt", "co", 27, 2);
+ $register("nickel", "Nickel", "ni", 28, 2);
+ $register("copper", "Copper", "cu", 29, 2);
+ $register("zinc", "Zinc", "zn", 30, 2);
+ $register("gallium", "Gallium", "ga", 31, 3);
+ $register("germanium", "Germanium", "ge", 32, 4);
+ $register("arsenic", "Arsenic", "as", 33, 4);
+ $register("selenium", "Selenium", "se", 34, 5);
+ $register("bromine", "Bromine", "br", 35, 6);
+ $register("krypton", "Krypton", "kr", 36, 7);
+ $register("rubidium", "Rubidium", "rb", 37, 0);
+ $register("strontium", "Strontium", "sr", 38, 1);
+ $register("yttrium", "Yttrium", "y", 39, 2);
+ $register("zirconium", "Zirconium", "zr", 40, 2);
+ $register("niobium", "Niobium", "nb", 41, 2);
+ $register("molybdenum", "Molybdenum", "mo", 42, 2);
+ $register("technetium", "Technetium", "tc", 43, 2);
+ $register("ruthenium", "Ruthenium", "ru", 44, 2);
+ $register("rhodium", "Rhodium", "rh", 45, 2);
+ $register("palladium", "Palladium", "pd", 46, 2);
+ $register("silver", "Silver", "ag", 47, 2);
+ $register("cadmium", "Cadmium", "cd", 48, 2);
+ $register("indium", "Indium", "in", 49, 3);
+ $register("tin", "Tin", "sn", 50, 3);
+ $register("antimony", "Antimony", "sb", 51, 4);
+ $register("tellurium", "Tellurium", "te", 52, 4);
+ $register("iodine", "Iodine", "i", 53, 6);
+ $register("xenon", "Xenon", "xe", 54, 7);
+ $register("cesium", "Cesium", "cs", 55, 0);
+ $register("barium", "Barium", "ba", 56, 1);
+ $register("lanthanum", "Lanthanum", "la", 57, 8);
+ $register("cerium", "Cerium", "ce", 58, 8);
+ $register("praseodymium", "Praseodymium", "pr", 59, 8);
+ $register("neodymium", "Neodymium", "nd", 60, 8);
+ $register("promethium", "Promethium", "pm", 61, 8);
+ $register("samarium", "Samarium", "sm", 62, 8);
+ $register("europium", "Europium", "eu", 63, 8);
+ $register("gadolinium", "Gadolinium", "gd", 64, 8);
+ $register("terbium", "Terbium", "tb", 65, 8);
+ $register("dysprosium", "Dysprosium", "dy", 66, 8);
+ $register("holmium", "Holmium", "ho", 67, 8);
+ $register("erbium", "Erbium", "er", 68, 8);
+ $register("thulium", "Thulium", "tm", 69, 8);
+ $register("ytterbium", "Ytterbium", "yb", 70, 8);
+ $register("lutetium", "Lutetium", "lu", 71, 8);
+ $register("hafnium", "Hafnium", "hf", 72, 2);
+ $register("tantalum", "Tantalum", "ta", 73, 2);
+ $register("tungsten", "Tungsten", "w", 74, 2);
+ $register("rhenium", "Rhenium", "re", 75, 2);
+ $register("osmium", "Osmium", "os", 76, 2);
+ $register("iridium", "Iridium", "ir", 77, 2);
+ $register("platinum", "Platinum", "pt", 78, 2);
+ $register("gold", "Gold", "au", 79, 2);
+ $register("mercury", "Mercury", "hg", 80, 2);
+ $register("thallium", "Thallium", "tl", 81, 3);
+ $register("lead", "Lead", "pb", 82, 3);
+ $register("bismuth", "Bismuth", "bi", 83, 3);
+ $register("polonium", "Polonium", "po", 84, 4);
+ $register("astatine", "Astatine", "at", 85, 6);
+ $register("radon", "Radon", "rn", 86, 7);
+ $register("francium", "Francium", "fr", 87, 0);
+ $register("radium", "Radium", "ra", 88, 1);
+ $register("actinium", "Actinium", "ac", 89, 9);
+ $register("thorium", "Thorium", "th", 90, 9);
+ $register("protactinium", "Protactinium", "pa", 91, 9);
+ $register("uranium", "Uranium", "u", 92, 9);
+ $register("neptunium", "Neptunium", "np", 93, 9);
+ $register("plutonium", "Plutonium", "pu", 94, 9);
+ $register("americium", "Americium", "am", 95, 9);
+ $register("curium", "Curium", "cm", 96, 9);
+ $register("berkelium", "Berkelium", "bk", 97, 9);
+ $register("californium", "Californium", "cf", 98, 9);
+ $register("einsteinium", "Einsteinium", "es", 99, 9);
+ $register("fermium", "Fermium", "fm", 100, 9);
+ $register("mendelevium", "Mendelevium", "md", 101, 9);
+ $register("nobelium", "Nobelium", "no", 102, 9);
+ $register("lawrencium", "Lawrencium", "lr", 103, 9);
+ $register("rutherfordium", "Rutherfordium", "rf", 104, 2);
+ $register("dubnium", "Dubnium", "db", 105, 2);
+ $register("seaborgium", "Seaborgium", "sg", 106, 2);
+ $register("bohrium", "Bohrium", "bh", 107, 2);
+ $register("hassium", "Hassium", "hs", 108, 2);
+ $register("meitnerium", "Meitnerium", "mt", 109, 2);
+ $register("darmstadtium", "Darmstadtium", "ds", 110, 2);
+ $register("roentgenium", "Roentgenium", "rg", 111, 2);
+ $register("copernicium", "Copernicium", "cn", 112, 2);
+ $register("nihonium", "Nihonium", "nh", 113, 3);
+ $register("flerovium", "Flerovium", "fl", 114, 3);
+ $register("moscovium", "Moscovium", "mc", 115, 3);
+ $register("livermorium", "Livermorium", "lv", 116, 3);
+ $register("tennessine", "Tennessine", "ts", 117, 6);
+ $register("oganesson", "Oganesson", "og", 118, 7);
}
private static function registerOres() : void{
$stoneOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(3.0, $toolTier));
- self::register("coal_ore", new CoalOre(new BID(Ids::COAL_ORE), "Coal Ore", $stoneOreBreakInfo(ToolTier::WOOD())));
- self::register("copper_ore", new CopperOre(new BID(Ids::COPPER_ORE), "Copper Ore", $stoneOreBreakInfo(ToolTier::STONE())));
- self::register("diamond_ore", new DiamondOre(new BID(Ids::DIAMOND_ORE), "Diamond Ore", $stoneOreBreakInfo(ToolTier::IRON())));
- self::register("emerald_ore", new EmeraldOre(new BID(Ids::EMERALD_ORE), "Emerald Ore", $stoneOreBreakInfo(ToolTier::IRON())));
- self::register("gold_ore", new GoldOre(new BID(Ids::GOLD_ORE), "Gold Ore", $stoneOreBreakInfo(ToolTier::IRON())));
- self::register("iron_ore", new IronOre(new BID(Ids::IRON_ORE), "Iron Ore", $stoneOreBreakInfo(ToolTier::STONE())));
- self::register("lapis_lazuli_ore", new LapisOre(new BID(Ids::LAPIS_LAZULI_ORE), "Lapis Lazuli Ore", $stoneOreBreakInfo(ToolTier::STONE())));
- self::register("redstone_ore", new RedstoneOre(new BID(Ids::REDSTONE_ORE), "Redstone Ore", $stoneOreBreakInfo(ToolTier::IRON())));
+ self::register("coal_ore", fn(BID $id) => new CoalOre($id, "Coal Ore", $stoneOreBreakInfo(ToolTier::WOOD)));
+ self::register("copper_ore", fn(BID $id) => new CopperOre($id, "Copper Ore", $stoneOreBreakInfo(ToolTier::STONE)));
+ self::register("diamond_ore", fn(BID $id) => new DiamondOre($id, "Diamond Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+ self::register("emerald_ore", fn(BID $id) => new EmeraldOre($id, "Emerald Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+ self::register("gold_ore", fn(BID $id) => new GoldOre($id, "Gold Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+ self::register("iron_ore", fn(BID $id) => new IronOre($id, "Iron Ore", $stoneOreBreakInfo(ToolTier::STONE)));
+ self::register("lapis_lazuli_ore", fn(BID $id) => new LapisOre($id, "Lapis Lazuli Ore", $stoneOreBreakInfo(ToolTier::STONE)));
+ self::register("redstone_ore", fn(BID $id) => new RedstoneOre($id, "Redstone Ore", $stoneOreBreakInfo(ToolTier::IRON)));
$deepslateOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(4.5, $toolTier));
- self::register("deepslate_coal_ore", new CoalOre(new BID(Ids::DEEPSLATE_COAL_ORE), "Deepslate Coal Ore", $deepslateOreBreakInfo(ToolTier::WOOD())));
- self::register("deepslate_copper_ore", new CopperOre(new BID(Ids::DEEPSLATE_COPPER_ORE), "Deepslate Copper Ore", $deepslateOreBreakInfo(ToolTier::STONE())));
- self::register("deepslate_diamond_ore", new DiamondOre(new BID(Ids::DEEPSLATE_DIAMOND_ORE), "Deepslate Diamond Ore", $deepslateOreBreakInfo(ToolTier::IRON())));
- self::register("deepslate_emerald_ore", new EmeraldOre(new BID(Ids::DEEPSLATE_EMERALD_ORE), "Deepslate Emerald Ore", $deepslateOreBreakInfo(ToolTier::IRON())));
- self::register("deepslate_gold_ore", new GoldOre(new BID(Ids::DEEPSLATE_GOLD_ORE), "Deepslate Gold Ore", $deepslateOreBreakInfo(ToolTier::IRON())));
- self::register("deepslate_iron_ore", new IronOre(new BID(Ids::DEEPSLATE_IRON_ORE), "Deepslate Iron Ore", $deepslateOreBreakInfo(ToolTier::STONE())));
- self::register("deepslate_lapis_lazuli_ore", new LapisOre(new BID(Ids::DEEPSLATE_LAPIS_LAZULI_ORE), "Deepslate Lapis Lazuli Ore", $deepslateOreBreakInfo(ToolTier::STONE())));
- self::register("deepslate_redstone_ore", new RedstoneOre(new BID(Ids::DEEPSLATE_REDSTONE_ORE), "Deepslate Redstone Ore", $deepslateOreBreakInfo(ToolTier::IRON())));
-
- $netherrackOreBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD()));
- self::register("nether_quartz_ore", new NetherQuartzOre(new BID(Ids::NETHER_QUARTZ_ORE), "Nether Quartz Ore", $netherrackOreBreakInfo));
- self::register("nether_gold_ore", new NetherGoldOre(new BID(Ids::NETHER_GOLD_ORE), "Nether Gold Ore", $netherrackOreBreakInfo));
+ self::register("deepslate_coal_ore", fn(BID $id) => new CoalOre($id, "Deepslate Coal Ore", $deepslateOreBreakInfo(ToolTier::WOOD)));
+ self::register("deepslate_copper_ore", fn(BID $id) => new CopperOre($id, "Deepslate Copper Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
+ self::register("deepslate_diamond_ore", fn(BID $id) => new DiamondOre($id, "Deepslate Diamond Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+ self::register("deepslate_emerald_ore", fn(BID $id) => new EmeraldOre($id, "Deepslate Emerald Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+ self::register("deepslate_gold_ore", fn(BID $id) => new GoldOre($id, "Deepslate Gold Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+ self::register("deepslate_iron_ore", fn(BID $id) => new IronOre($id, "Deepslate Iron Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
+ self::register("deepslate_lapis_lazuli_ore", fn(BID $id) => new LapisOre($id, "Deepslate Lapis Lazuli Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
+ self::register("deepslate_redstone_ore", fn(BID $id) => new RedstoneOre($id, "Deepslate Redstone Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+
+ $netherrackOreBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD));
+ self::register("nether_quartz_ore", fn(BID $id) => new NetherQuartzOre($id, "Nether Quartz Ore", $netherrackOreBreakInfo));
+ self::register("nether_gold_ore", fn(BID $id) => new NetherGoldOre($id, "Nether Gold Ore", $netherrackOreBreakInfo));
}
private static function registerCraftingTables() : void{
//TODO: this is the same for all wooden crafting blocks
$craftingBlockBreakInfo = new Info(BreakInfo::axe(2.5));
- self::register("cartography_table", new CartographyTable(new BID(Ids::CARTOGRAPHY_TABLE), "Cartography Table", $craftingBlockBreakInfo));
- self::register("crafting_table", new CraftingTable(new BID(Ids::CRAFTING_TABLE), "Crafting Table", $craftingBlockBreakInfo));
- self::register("fletching_table", new FletchingTable(new BID(Ids::FLETCHING_TABLE), "Fletching Table", $craftingBlockBreakInfo));
- self::register("loom", new Loom(new BID(Ids::LOOM), "Loom", $craftingBlockBreakInfo));
- self::register("smithing_table", new SmithingTable(new BID(Ids::SMITHING_TABLE), "Smithing Table", $craftingBlockBreakInfo));
+ self::register("cartography_table", fn(BID $id) => new CartographyTable($id, "Cartography Table", $craftingBlockBreakInfo));
+ self::register("crafting_table", fn(BID $id) => new CraftingTable($id, "Crafting Table", $craftingBlockBreakInfo));
+ self::register("fletching_table", fn(BID $id) => new FletchingTable($id, "Fletching Table", $craftingBlockBreakInfo));
+ self::register("loom", fn(BID $id) => new Loom($id, "Loom", $craftingBlockBreakInfo));
+ self::register("smithing_table", fn(BID $id) => new SmithingTable($id, "Smithing Table", $craftingBlockBreakInfo));
}
private static function registerChorusBlocks() : void{
$chorusBlockBreakInfo = new Info(BreakInfo::axe(0.4));
- self::register("chorus_plant", new ChorusPlant(new BID(Ids::CHORUS_PLANT), "Chorus Plant", $chorusBlockBreakInfo));
- self::register("chorus_flower", new ChorusFlower(new BID(Ids::CHORUS_FLOWER), "Chorus Flower", $chorusBlockBreakInfo));
+ self::register("chorus_plant", fn(BID $id) => new ChorusPlant($id, "Chorus Plant", $chorusBlockBreakInfo));
+ self::register("chorus_flower", fn(BID $id) => new ChorusFlower($id, "Chorus Flower", $chorusBlockBreakInfo));
}
private static function registerBlocksR13() : void{
- self::register("light", new Light(new BID(Ids::LIGHT), "Light Block", new Info(BreakInfo::indestructible())));
- self::register("wither_rose", new WitherRose(new BID(Ids::WITHER_ROSE), "Wither Rose", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
+ self::register("light", fn(BID $id) => new Light($id, "Light Block", new Info(BreakInfo::indestructible())));
+ self::register("wither_rose", fn(BID $id) => new WitherRose($id, "Wither Rose", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
}
private static function registerBlocksR14() : void{
- self::register("honeycomb", new Opaque(new BID(Ids::HONEYCOMB), "Honeycomb Block", new Info(new BreakInfo(0.6))));
+ self::register("honeycomb", fn(BID $id) => new Opaque($id, "Honeycomb Block", new Info(new BreakInfo(0.6))));
}
private static function registerBlocksR16() : void{
//for some reason, slabs have weird hardness like the legacy ones
- $slabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
+ $slabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
- self::register("ancient_debris", new Opaque(new BID(Ids::ANCIENT_DEBRIS), "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND(), 3600.0))));
- $netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND(), 3600.0));
- self::register("netherite", new class(new BID(Ids::NETHERITE), "Netherite Block", $netheriteBreakInfo) extends Opaque{
+ self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{
+ public function isFireProofAsItem() : bool{ return true; }
+ });
+ $netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 3600.0));
+ self::register("netherite", fn(BID $id) => new class($id, "Netherite Block", $netheriteBreakInfo) extends Opaque{
public function isFireProofAsItem() : bool{ return true; }
});
- $basaltBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD(), 21.0));
- self::register("basalt", new SimplePillar(new BID(Ids::BASALT), "Basalt", $basaltBreakInfo));
- self::register("polished_basalt", new SimplePillar(new BID(Ids::POLISHED_BASALT), "Polished Basalt", $basaltBreakInfo));
- self::register("smooth_basalt", new Opaque(new BID(Ids::SMOOTH_BASALT), "Smooth Basalt", $basaltBreakInfo));
+ $basaltBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0));
+ self::register("basalt", fn(BID $id) => new SimplePillar($id, "Basalt", $basaltBreakInfo));
+ self::register("polished_basalt", fn(BID $id) => new SimplePillar($id, "Polished Basalt", $basaltBreakInfo));
+ self::register("smooth_basalt", fn(BID $id) => new Opaque($id, "Smooth Basalt", $basaltBreakInfo));
- $blackstoneBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD(), 30.0));
- self::register("blackstone", new Opaque(new BID(Ids::BLACKSTONE), "Blackstone", $blackstoneBreakInfo));
- self::register("blackstone_slab", new Slab(new BID(Ids::BLACKSTONE_SLAB), "Blackstone", $slabBreakInfo));
- self::register("blackstone_stairs", new Stair(new BID(Ids::BLACKSTONE_STAIRS), "Blackstone Stairs", $blackstoneBreakInfo));
- self::register("blackstone_wall", new Wall(new BID(Ids::BLACKSTONE_WALL), "Blackstone Wall", $blackstoneBreakInfo));
+ $blackstoneBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
+ self::register("blackstone", fn(BID $id) => new Opaque($id, "Blackstone", $blackstoneBreakInfo));
+ self::register("blackstone_slab", fn(BID $id) => new Slab($id, "Blackstone", $slabBreakInfo));
+ self::register("blackstone_stairs", fn(BID $id) => new Stair($id, "Blackstone Stairs", $blackstoneBreakInfo));
+ self::register("blackstone_wall", fn(BID $id) => new Wall($id, "Blackstone Wall", $blackstoneBreakInfo));
- self::register("gilded_blackstone", new GildedBlackstone(new BID(Ids::GILDED_BLACKSTONE), "Gilded Blackstone", $blackstoneBreakInfo));
+ self::register("gilded_blackstone", fn(BID $id) => new GildedBlackstone($id, "Gilded Blackstone", $blackstoneBreakInfo));
//TODO: polished blackstone ought to have 2.0 hardness (as per java) but it's 1.5 in Bedrock (probably parity bug)
$prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : "");
- self::register("polished_blackstone", new Opaque(new BID(Ids::POLISHED_BLACKSTONE), $prefix(""), $blackstoneBreakInfo));
- self::register("polished_blackstone_button", new StoneButton(new BID(Ids::POLISHED_BLACKSTONE_BUTTON), $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
- self::register("polished_blackstone_pressure_plate", new StonePressurePlate(new BID(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE), $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()))));
- self::register("polished_blackstone_slab", new Slab(new BID(Ids::POLISHED_BLACKSTONE_SLAB), $prefix(""), $slabBreakInfo));
- self::register("polished_blackstone_stairs", new Stair(new BID(Ids::POLISHED_BLACKSTONE_STAIRS), $prefix("Stairs"), $blackstoneBreakInfo));
- self::register("polished_blackstone_wall", new Wall(new BID(Ids::POLISHED_BLACKSTONE_WALL), $prefix("Wall"), $blackstoneBreakInfo));
- self::register("chiseled_polished_blackstone", new Opaque(new BID(Ids::CHISELED_POLISHED_BLACKSTONE), "Chiseled Polished Blackstone", $blackstoneBreakInfo));
+ self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $blackstoneBreakInfo));
+ self::register("polished_blackstone_button", fn(BID $id) => new StoneButton($id, $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
+ self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)), 20));
+ self::register("polished_blackstone_slab", fn(BID $id) => new Slab($id, $prefix(""), $slabBreakInfo));
+ self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo));
+ self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo));
+ self::register("chiseled_polished_blackstone", fn(BID $id) => new Opaque($id, "Chiseled Polished Blackstone", $blackstoneBreakInfo));
$prefix = fn(string $thing) => "Polished Blackstone Brick" . ($thing !== "" ? " $thing" : "");
- self::register("polished_blackstone_bricks", new Opaque(new BID(Ids::POLISHED_BLACKSTONE_BRICKS), "Polished Blackstone Bricks", $blackstoneBreakInfo));
- self::register("polished_blackstone_brick_slab", new Slab(new BID(Ids::POLISHED_BLACKSTONE_BRICK_SLAB), "Polished Blackstone Brick", $slabBreakInfo));
- self::register("polished_blackstone_brick_stairs", new Stair(new BID(Ids::POLISHED_BLACKSTONE_BRICK_STAIRS), $prefix("Stairs"), $blackstoneBreakInfo));
- self::register("polished_blackstone_brick_wall", new Wall(new BID(Ids::POLISHED_BLACKSTONE_BRICK_WALL), $prefix("Wall"), $blackstoneBreakInfo));
- self::register("cracked_polished_blackstone_bricks", new Opaque(new BID(Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS), "Cracked Polished Blackstone Bricks", $blackstoneBreakInfo));
+ self::register("polished_blackstone_bricks", fn(BID $id) => new Opaque($id, "Polished Blackstone Bricks", $blackstoneBreakInfo));
+ self::register("polished_blackstone_brick_slab", fn(BID $id) => new Slab($id, "Polished Blackstone Brick", $slabBreakInfo));
+ self::register("polished_blackstone_brick_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo));
+ self::register("polished_blackstone_brick_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo));
+ self::register("cracked_polished_blackstone_bricks", fn(BID $id) => new Opaque($id, "Cracked Polished Blackstone Bricks", $blackstoneBreakInfo));
- self::register("soul_torch", new Torch(new BID(Ids::SOUL_TORCH), "Soul Torch", new Info(BreakInfo::instant())));
- self::register("soul_fire", new SoulFire(new BID(Ids::SOUL_FIRE), "Soul Fire", new Info(BreakInfo::instant(), [Tags::FIRE])));
+ self::register("soul_torch", fn(BID $id) => new Torch($id, "Soul Torch", new Info(BreakInfo::instant())));
+ self::register("soul_fire", fn(BID $id) => new SoulFire($id, "Soul Fire", new Info(BreakInfo::instant(), [Tags::FIRE])));
//TODO: soul soul ought to have 0.5 hardness (as per java) but it's 1.0 in Bedrock (probably parity bug)
- self::register("soul_soil", new Opaque(new BID(Ids::SOUL_SOIL), "Soul Soil", new Info(BreakInfo::shovel(1.0))));
+ self::register("soul_soil", fn(BID $id) => new Opaque($id, "Soul Soil", new Info(BreakInfo::shovel(1.0))));
- self::register("shroomlight", new class(new BID(Ids::SHROOMLIGHT), "Shroomlight", new Info(new BreakInfo(1.0, ToolType::HOE))) extends Opaque{
+ self::register("shroomlight", fn(BID $id) => new class($id, "Shroomlight", new Info(new BreakInfo(1.0, ToolType::HOE))) extends Opaque{
public function getLightLevel() : int{ return 15; }
});
- self::register("warped_wart_block", new Opaque(new BID(Ids::WARPED_WART_BLOCK), "Warped Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
- self::register("crying_obsidian", new class(new BID(Ids::CRYING_OBSIDIAN), "Crying Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in Java */, ToolTier::DIAMOND(), 6000.0))) extends Opaque{
+ self::register("warped_wart_block", fn(BID $id) => new Opaque($id, "Warped Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
+ self::register("crying_obsidian", fn(BID $id) => new class($id, "Crying Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in Java */, ToolTier::DIAMOND, 6000.0))) extends Opaque{
public function getLightLevel() : int{ return 10;}
});
- self::register("twisting_vines", new NetherVines(new BID(Ids::TWISTING_VINES), "Twisting Vines", new Info(BreakInfo::instant()), Facing::UP));
- self::register("weeping_vines", new NetherVines(new BID(Ids::WEEPING_VINES), "Weeping Vines", new Info(BreakInfo::instant()), Facing::DOWN));
+ self::register("twisting_vines", fn(BID $id) => new NetherVines($id, "Twisting Vines", new Info(BreakInfo::instant()), Facing::UP));
+ self::register("weeping_vines", fn(BID $id) => new NetherVines($id, "Weeping Vines", new Info(BreakInfo::instant()), Facing::DOWN));
+
+ $netherRootsInfo = new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]);
+ self::register("crimson_roots", fn(BID $id) => new NetherRoots($id, "Crimson Roots", $netherRootsInfo));
+ self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo));
- self::register("chain", new Chain(new BID(Ids::CHAIN), "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD()))));
+ self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
}
private static function registerBlocksR17() : void{
//in java this can be acquired using any tool - seems to be a parity issue in bedrock
- self::register("amethyst", new class(new BID(Ids::AMETHYST), "Amethyst", new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD()))) extends Opaque{
- public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
- $this->position->getWorld()->addSound($this->position, new AmethystBlockChimeSound());
- $this->position->getWorld()->addSound($this->position, new BlockPunchSound($this));
- }
+ $amethystInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD));
+ self::register("amethyst", fn(BID $id) => new class($id, "Amethyst", $amethystInfo) extends Opaque{
+ use AmethystTrait;
});
+ self::register("budding_amethyst", fn(BID $id) => new BuddingAmethyst($id, "Budding Amethyst", $amethystInfo));
+ self::register("amethyst_cluster", fn(BID $id) => new AmethystCluster($id, "Amethyst Cluster", $amethystInfo));
- self::register("calcite", new Opaque(new BID(Ids::CALCITE), "Calcite", new Info(BreakInfo::pickaxe(0.75, ToolTier::WOOD()))));
- self::register("tuff", new Opaque(new BID(Ids::TUFF), "Tuff", new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD(), 30.0))));
+ self::register("calcite", fn(BID $id) => new Opaque($id, "Calcite", new Info(BreakInfo::pickaxe(0.75, ToolTier::WOOD))));
- self::register("raw_copper", new Opaque(new BID(Ids::RAW_COPPER), "Raw Copper Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE(), 30.0))));
- self::register("raw_gold", new Opaque(new BID(Ids::RAW_GOLD), "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON(), 30.0))));
- self::register("raw_iron", new Opaque(new BID(Ids::RAW_IRON), "Raw Iron Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE(), 30.0))));
+ self::register("raw_copper", fn(BID $id) => new Opaque($id, "Raw Copper Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
+ self::register("raw_gold", fn(BID $id) => new Opaque($id, "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON, 30.0))));
+ self::register("raw_iron", fn(BID $id) => new Opaque($id, "Raw Iron Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
- $deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD(), 18.0));
- self::register("deepslate", new class(new BID(Ids::DEEPSLATE), "Deepslate", $deepslateBreakInfo) extends SimplePillar{
+ $deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD, 18.0));
+ self::register("deepslate", fn(BID $id) => new class($id, "Deepslate", $deepslateBreakInfo) extends SimplePillar{
public function getDropsForCompatibleTool(Item $item) : array{
return [VanillaBlocks::COBBLED_DEEPSLATE()->asItem()];
}
@@ -1533,81 +1647,113 @@ public function isAffectedBySilkTouch() : bool{
});
//TODO: parity issue here - in Java this has a hardness of 3.0, but in bedrock it's 3.5
- self::register("chiseled_deepslate", new Opaque(new BID(Ids::CHISELED_DEEPSLATE), "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD(), 18.0))));
-
- $deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD(), 18.0));
- self::register("deepslate_bricks", new Opaque(new BID(Ids::DEEPSLATE_BRICKS), "Deepslate Bricks", $deepslateBrickBreakInfo));
- self::register("deepslate_brick_slab", new Slab(new BID(Ids::DEEPSLATE_BRICK_SLAB), "Deepslate Brick", $deepslateBrickBreakInfo));
- self::register("deepslate_brick_stairs", new Stair(new BID(Ids::DEEPSLATE_BRICK_STAIRS), "Deepslate Brick Stairs", $deepslateBrickBreakInfo));
- self::register("deepslate_brick_wall", new Wall(new BID(Ids::DEEPSLATE_BRICK_WALL), "Deepslate Brick Wall", $deepslateBrickBreakInfo));
- self::register("cracked_deepslate_bricks", new Opaque(new BID(Ids::CRACKED_DEEPSLATE_BRICKS), "Cracked Deepslate Bricks", $deepslateBrickBreakInfo));
-
- $deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD(), 18.0));
- self::register("deepslate_tiles", new Opaque(new BID(Ids::DEEPSLATE_TILES), "Deepslate Tiles", $deepslateTilesBreakInfo));
- self::register("deepslate_tile_slab", new Slab(new BID(Ids::DEEPSLATE_TILE_SLAB), "Deepslate Tile", $deepslateTilesBreakInfo));
- self::register("deepslate_tile_stairs", new Stair(new BID(Ids::DEEPSLATE_TILE_STAIRS), "Deepslate Tile Stairs", $deepslateTilesBreakInfo));
- self::register("deepslate_tile_wall", new Wall(new BID(Ids::DEEPSLATE_TILE_WALL), "Deepslate Tile Wall", $deepslateTilesBreakInfo));
- self::register("cracked_deepslate_tiles", new Opaque(new BID(Ids::CRACKED_DEEPSLATE_TILES), "Cracked Deepslate Tiles", $deepslateTilesBreakInfo));
-
- $cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD(), 18.0));
- self::register("cobbled_deepslate", new Opaque(new BID(Ids::COBBLED_DEEPSLATE), "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
- self::register("cobbled_deepslate_slab", new Slab(new BID(Ids::COBBLED_DEEPSLATE_SLAB), "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
- self::register("cobbled_deepslate_stairs", new Stair(new BID(Ids::COBBLED_DEEPSLATE_STAIRS), "Cobbled Deepslate Stairs", $cobbledDeepslateBreakInfo));
- self::register("cobbled_deepslate_wall", new Wall(new BID(Ids::COBBLED_DEEPSLATE_WALL), "Cobbled Deepslate Wall", $cobbledDeepslateBreakInfo));
-
- $polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD(), 18.0));
- self::register("polished_deepslate", new Opaque(new BID(Ids::POLISHED_DEEPSLATE), "Polished Deepslate", $polishedDeepslateBreakInfo));
- self::register("polished_deepslate_slab", new Slab(new BID(Ids::POLISHED_DEEPSLATE_SLAB), "Polished Deepslate", $polishedDeepslateBreakInfo));
- self::register("polished_deepslate_stairs", new Stair(new BID(Ids::POLISHED_DEEPSLATE_STAIRS), "Polished Deepslate Stairs", $polishedDeepslateBreakInfo));
- self::register("polished_deepslate_wall", new Wall(new BID(Ids::POLISHED_DEEPSLATE_WALL), "Polished Deepslate Wall", $polishedDeepslateBreakInfo));
-
- self::register("tinted_glass", new TintedGlass(new BID(Ids::TINTED_GLASS), "Tinted Glass", new Info(new BreakInfo(0.3))));
+ self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0))));
+
+ $deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
+ self::register("deepslate_bricks", fn(BID $id) => new Opaque($id, "Deepslate Bricks", $deepslateBrickBreakInfo));
+ self::register("deepslate_brick_slab", fn(BID $id) => new Slab($id, "Deepslate Brick", $deepslateBrickBreakInfo));
+ self::register("deepslate_brick_stairs", fn(BID $id) => new Stair($id, "Deepslate Brick Stairs", $deepslateBrickBreakInfo));
+ self::register("deepslate_brick_wall", fn(BID $id) => new Wall($id, "Deepslate Brick Wall", $deepslateBrickBreakInfo));
+ self::register("cracked_deepslate_bricks", fn(BID $id) => new Opaque($id, "Cracked Deepslate Bricks", $deepslateBrickBreakInfo));
+
+ $deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
+ self::register("deepslate_tiles", fn(BID $id) => new Opaque($id, "Deepslate Tiles", $deepslateTilesBreakInfo));
+ self::register("deepslate_tile_slab", fn(BID $id) => new Slab($id, "Deepslate Tile", $deepslateTilesBreakInfo));
+ self::register("deepslate_tile_stairs", fn(BID $id) => new Stair($id, "Deepslate Tile Stairs", $deepslateTilesBreakInfo));
+ self::register("deepslate_tile_wall", fn(BID $id) => new Wall($id, "Deepslate Tile Wall", $deepslateTilesBreakInfo));
+ self::register("cracked_deepslate_tiles", fn(BID $id) => new Opaque($id, "Cracked Deepslate Tiles", $deepslateTilesBreakInfo));
+
+ $cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
+ self::register("cobbled_deepslate", fn(BID $id) => new Opaque($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
+ self::register("cobbled_deepslate_slab", fn(BID $id) => new Slab($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
+ self::register("cobbled_deepslate_stairs", fn(BID $id) => new Stair($id, "Cobbled Deepslate Stairs", $cobbledDeepslateBreakInfo));
+ self::register("cobbled_deepslate_wall", fn(BID $id) => new Wall($id, "Cobbled Deepslate Wall", $cobbledDeepslateBreakInfo));
+
+ $polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
+ self::register("polished_deepslate", fn(BID $id) => new Opaque($id, "Polished Deepslate", $polishedDeepslateBreakInfo));
+ self::register("polished_deepslate_slab", fn(BID $id) => new Slab($id, "Polished Deepslate", $polishedDeepslateBreakInfo));
+ self::register("polished_deepslate_stairs", fn(BID $id) => new Stair($id, "Polished Deepslate Stairs", $polishedDeepslateBreakInfo));
+ self::register("polished_deepslate_wall", fn(BID $id) => new Wall($id, "Polished Deepslate Wall", $polishedDeepslateBreakInfo));
+
+ self::register("tinted_glass", fn(BID $id) => new TintedGlass($id, "Tinted Glass", new Info(new BreakInfo(0.3))));
//blast resistance should be 30 if we were matched with java :(
- $copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE(), 18.0));
- self::register("lightning_rod", new LightningRod(new BID(Ids::LIGHTNING_ROD), "Lightning Rod", $copperBreakInfo));
+ $copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 18.0));
+ self::register("lightning_rod", fn(BID $id) => new LightningRod($id, "Lightning Rod", $copperBreakInfo));
+
+ self::register("copper", fn(BID $id) => new Copper($id, "Copper Block", $copperBreakInfo));
+ self::register("chiseled_copper", fn(BID $id) => new Copper($id, "Chiseled Copper", $copperBreakInfo));
+ self::register("copper_grate", fn(BID $id) => new CopperGrate($id, "Copper Grate", $copperBreakInfo));
+ self::register("cut_copper", fn(BID $id) => new Copper($id, "Cut Copper Block", $copperBreakInfo));
+ self::register("cut_copper_slab", fn(BID $id) => new CopperSlab($id, "Cut Copper Slab", $copperBreakInfo));
+ self::register("cut_copper_stairs", fn(BID $id) => new CopperStairs($id, "Cut Copper Stairs", $copperBreakInfo));
+ self::register("copper_bulb", fn(BID $id) => new CopperBulb($id, "Copper Bulb", $copperBreakInfo));
- self::register("copper", new Copper(new BID(Ids::COPPER), "Copper Block", $copperBreakInfo));
- self::register("cut_copper", new Copper(new BID(Ids::CUT_COPPER), "Cut Copper Block", $copperBreakInfo));
- self::register("cut_copper_slab", new CopperSlab(new BID(Ids::CUT_COPPER_SLAB), "Cut Copper Slab", $copperBreakInfo));
- self::register("cut_copper_stairs", new CopperStairs(new BID(Ids::CUT_COPPER_STAIRS), "Cut Copper Stairs", $copperBreakInfo));
+ $copperDoorBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0));
+ self::register("copper_door", fn(BID $id) => new CopperDoor($id, "Copper Door", $copperDoorBreakInfo));
+ self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", $copperDoorBreakInfo));
$candleBreakInfo = new Info(new BreakInfo(0.1));
- self::register("candle", new Candle(new BID(Ids::CANDLE), "Candle", $candleBreakInfo));
- self::register("dyed_candle", new DyedCandle(new BID(Ids::DYED_CANDLE), "Dyed Candle", $candleBreakInfo));
+ self::register("candle", fn(BID $id) => new Candle($id, "Candle", $candleBreakInfo));
+ self::register("dyed_candle", fn(BID $id) => new DyedCandle($id, "Dyed Candle", $candleBreakInfo));
//TODO: duplicated break info :(
$cakeBreakInfo = new Info(new BreakInfo(0.5));
- self::register("cake_with_candle", new CakeWithCandle(new BID(Ids::CAKE_WITH_CANDLE), "Cake With Candle", $cakeBreakInfo));
- self::register("cake_with_dyed_candle", new CakeWithDyedCandle(new BID(Ids::CAKE_WITH_DYED_CANDLE), "Cake With Dyed Candle", $cakeBreakInfo));
+ self::register("cake_with_candle", fn(BID $id) => new CakeWithCandle($id, "Cake With Candle", $cakeBreakInfo));
+ self::register("cake_with_dyed_candle", fn(BID $id) => new CakeWithDyedCandle($id, "Cake With Dyed Candle", $cakeBreakInfo));
+
+ self::register("hanging_roots", fn(BID $id) => new HangingRoots($id, "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
- self::register("hanging_roots", new HangingRoots(new BID(Ids::HANGING_ROOTS), "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+ self::register("cave_vines", fn(BID $id) => new CaveVines($id, "Cave Vines", new Info(BreakInfo::instant())));
- self::register("cave_vines", new CaveVines(new BID(Ids::CAVE_VINES), "Cave Vines", new Info(BreakInfo::instant())));
+ self::register("small_dripleaf", fn(BID $id) => new SmallDripleaf($id, "Small Dripleaf", new Info(BreakInfo::instant(ToolType::SHEARS, toolHarvestLevel: 1))));
+ self::register("big_dripleaf_head", fn(BID $id) => new BigDripleafHead($id, "Big Dripleaf", new Info(BreakInfo::instant())));
+ self::register("big_dripleaf_stem", fn(BID $id) => new BigDripleafStem($id, "Big Dripleaf Stem", new Info(BreakInfo::instant())));
}
private static function registerBlocksR18() : void{
- self::register("spore_blossom", new SporeBlossom(new BID(Ids::SPORE_BLOSSOM), "Spore Blossom", new Info(BreakInfo::instant())));
+ self::register("spore_blossom", fn(BID $id) => new SporeBlossom($id, "Spore Blossom", new Info(BreakInfo::instant())));
}
private static function registerMudBlocks() : void{
- self::register("mud", new Opaque(new BID(Ids::MUD), "Mud", new Info(BreakInfo::shovel(0.5), [Tags::MUD])));
- self::register("packed_mud", new Opaque(new BID(Ids::PACKED_MUD), "Packed Mud", new Info(BreakInfo::pickaxe(1.0, null, 15.0))));
+ self::register("mud", fn(BID $id) => new Opaque($id, "Mud", new Info(BreakInfo::shovel(0.5), [Tags::MUD])));
+ self::register("packed_mud", fn(BID $id) => new Opaque($id, "Packed Mud", new Info(BreakInfo::pickaxe(1.0, null, 15.0))));
- $mudBricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD(), 30.0));
+ $mudBricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
+
+ self::register("mud_bricks", fn(BID $id) => new Opaque($id, "Mud Bricks", $mudBricksBreakInfo));
+ self::register("mud_brick_slab", fn(BID $id) => new Slab($id, "Mud Brick", $mudBricksBreakInfo));
+ self::register("mud_brick_stairs", fn(BID $id) => new Stair($id, "Mud Brick Stairs", $mudBricksBreakInfo));
+ self::register("mud_brick_wall", fn(BID $id) => new Wall($id, "Mud Brick Wall", $mudBricksBreakInfo));
+ }
- self::register("mud_bricks", new Opaque(new BID(Ids::MUD_BRICKS), "Mud Bricks", $mudBricksBreakInfo));
- self::register("mud_brick_slab", new Slab(new BID(Ids::MUD_BRICK_SLAB), "Mud Brick", $mudBricksBreakInfo));
- self::register("mud_brick_stairs", new Stair(new BID(Ids::MUD_BRICK_STAIRS), "Mud Brick Stairs", $mudBricksBreakInfo));
- self::register("mud_brick_wall", new Wall(new BID(Ids::MUD_BRICK_WALL), "Mud Brick Wall", $mudBricksBreakInfo));
+ private static function registerTuffBlocks() : void{
+ $tuffBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
+
+ self::register("tuff", fn(BID $id) => new Opaque($id, "Tuff", $tuffBreakInfo));
+ self::register("tuff_slab", fn(BID $id) => new Slab($id, "Tuff", $tuffBreakInfo));
+ self::register("tuff_stairs", fn(BID $id) => new Stair($id, "Tuff Stairs", $tuffBreakInfo));
+ self::register("tuff_wall", fn(BID $id) => new Wall($id, "Tuff Wall", $tuffBreakInfo));
+ self::register("chiseled_tuff", fn(BID $id) => new Opaque($id, "Chiseled Tuff", $tuffBreakInfo));
+
+ self::register("tuff_bricks", fn(BID $id) => new Opaque($id, "Tuff Bricks", $tuffBreakInfo));
+ self::register("tuff_brick_slab", fn(BID $id) => new Slab($id, "Tuff Brick", $tuffBreakInfo));
+ self::register("tuff_brick_stairs", fn(BID $id) => new Stair($id, "Tuff Brick Stairs", $tuffBreakInfo));
+ self::register("tuff_brick_wall", fn(BID $id) => new Wall($id, "Tuff Brick Wall", $tuffBreakInfo));
+ self::register("chiseled_tuff_bricks", fn(BID $id) => new Opaque($id, "Chiseled Tuff Bricks", $tuffBreakInfo));
+
+ self::register("polished_tuff", fn(BID $id) => new Opaque($id, "Polished Tuff", $tuffBreakInfo));
+ self::register("polished_tuff_slab", fn(BID $id) => new Slab($id, "Polished Tuff", $tuffBreakInfo));
+ self::register("polished_tuff_stairs", fn(BID $id) => new Stair($id, "Polished Tuff Stairs", $tuffBreakInfo));
+ self::register("polished_tuff_wall", fn(BID $id) => new Wall($id, "Polished Tuff Wall", $tuffBreakInfo));
}
private static function registerCauldronBlocks() : void{
- $cauldronBreakInfo = new Info(BreakInfo::pickaxe(2, ToolTier::WOOD()));
+ $cauldronBreakInfo = new Info(BreakInfo::pickaxe(2, ToolTier::WOOD));
- self::register("cauldron", new Cauldron(new BID(Ids::CAULDRON, TileCauldron::class), "Cauldron", $cauldronBreakInfo));
- self::register("water_cauldron", new WaterCauldron(new BID(Ids::WATER_CAULDRON, TileCauldron::class), "Water Cauldron", $cauldronBreakInfo));
- self::register("lava_cauldron", new LavaCauldron(new BID(Ids::LAVA_CAULDRON, TileCauldron::class), "Lava Cauldron", $cauldronBreakInfo));
- self::register("potion_cauldron", new PotionCauldron(new BID(Ids::POTION_CAULDRON, TileCauldron::class), "Potion Cauldron", $cauldronBreakInfo));
+ self::register("cauldron", fn(BID $id) => new Cauldron($id, "Cauldron", $cauldronBreakInfo), TileCauldron::class);
+ self::register("water_cauldron", fn(BID $id) => new WaterCauldron($id, "Water Cauldron", $cauldronBreakInfo), TileCauldron::class);
+ self::register("lava_cauldron", fn(BID $id) => new LavaCauldron($id, "Lava Cauldron", $cauldronBreakInfo), TileCauldron::class);
+ self::register("potion_cauldron", fn(BID $id) => new PotionCauldron($id, "Potion Cauldron", $cauldronBreakInfo), TileCauldron::class);
}
}
diff --git a/src/block/Wall.php b/src/block/Wall.php
index 30584c92bef..520ced8eb00 100644
--- a/src/block/Wall.php
+++ b/src/block/Wall.php
@@ -101,9 +101,9 @@ protected function recalculateConnections() : bool{
foreach(Facing::HORIZONTAL as $facing){
$block = $this->getSide($facing);
- if($block instanceof static || $block instanceof FenceGate || $block instanceof Thin || ($block->isSolid() && !$block->isTransparent())){
+ if($block instanceof static || $block instanceof FenceGate || $block instanceof Thin || $block->getSupportType(Facing::opposite($facing)) === SupportType::FULL){
if(!isset($this->connections[$facing])){
- $this->connections[$facing] = WallConnectionType::SHORT();
+ $this->connections[$facing] = WallConnectionType::SHORT;
$changed++;
}
}elseif(isset($this->connections[$facing])){
@@ -152,6 +152,6 @@ protected function recalculateCollisionBoxes() : array{
}
public function getSupportType(int $facing) : SupportType{
- return Facing::axis($facing) === Axis::Y ? SupportType::CENTER() : SupportType::NONE();
+ return Facing::axis($facing) === Axis::Y ? SupportType::CENTER : SupportType::NONE;
}
}
diff --git a/src/block/WallCoralFan.php b/src/block/WallCoralFan.php
index 432dd5ddba3..f9dece1cd65 100644
--- a/src/block/WallCoralFan.php
+++ b/src/block/WallCoralFan.php
@@ -42,7 +42,7 @@ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$axis = Facing::axis($face);
- if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
+ if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
return false;
}
$this->facing = $face;
@@ -54,15 +54,15 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
- if(!$this->canBeSupportedBy($world->getBlock($this->position->getSide(Facing::opposite($this->facing))), $this->facing)){
+ if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){
$world->useBreakOn($this->position);
}else{
parent::onNearbyBlockChange();
}
}
- private function canBeSupportedBy(Block $block, int $face) : bool{
- return $block->getSupportType($face)->hasCenterSupport();
+ private function canBeSupportedAt(Block $block, int $face) : bool{
+ return $block->getAdjacentSupportType($face)->hasCenterSupport();
}
public function asItem() : Item{
diff --git a/src/block/WaterCauldron.php b/src/block/WaterCauldron.php
index 6a3c95048fa..e470aa6cb85 100644
--- a/src/block/WaterCauldron.php
+++ b/src/block/WaterCauldron.php
@@ -110,10 +110,10 @@ public function getEmptySound() : Sound{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
$world = $this->position->getWorld();
if(($dyeColor = match($item->getTypeId()){
- ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE(),
- ItemTypeIds::INK_SAC => DyeColor::BLACK(),
- ItemTypeIds::COCOA_BEANS => DyeColor::BROWN(),
- ItemTypeIds::BONE_MEAL => DyeColor::WHITE(),
+ ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
+ ItemTypeIds::INK_SAC => DyeColor::BLACK,
+ ItemTypeIds::COCOA_BEANS => DyeColor::BROWN,
+ ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
ItemTypeIds::DYE => $item instanceof Dye ? $item->getColor() : null,
default => null
}) !== null && ($newColor = $dyeColor->getRgbValue())->toRGBA() !== $this->customWaterColor?->toRGBA()
@@ -123,7 +123,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
$item->pop();
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
- if($item->getType()->equals(PotionType::WATER())){
+ if($item->getType() === PotionType::WATER){
$this->setCustomWaterColor(null)->addFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
}else{
$this->mix($item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
@@ -170,7 +170,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
match($item->getTypeId()){
ItemTypeIds::WATER_BUCKET => $this->setCustomWaterColor(null)->addFillLevels(self::MAX_FILL_LEVEL, $item, VanillaItems::BUCKET(), $returnedItems),
ItemTypeIds::BUCKET => $this->removeFillLevels(self::MAX_FILL_LEVEL, $item, VanillaItems::WATER_BUCKET(), $returnedItems),
- ItemTypeIds::GLASS_BOTTLE => $this->removeFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::POTION()->setType(PotionType::WATER()), $returnedItems),
+ ItemTypeIds::GLASS_BOTTLE => $this->removeFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::POTION()->setType(PotionType::WATER), $returnedItems),
ItemTypeIds::LAVA_BUCKET, ItemTypeIds::POWDER_SNOW_BUCKET => $this->mix($item, VanillaItems::BUCKET(), $returnedItems),
default => null
};
diff --git a/src/block/WaterLily.php b/src/block/WaterLily.php
index 8263330f64b..5dfb0d74a1f 100644
--- a/src/block/WaterLily.php
+++ b/src/block/WaterLily.php
@@ -23,14 +23,15 @@
namespace pocketmine\block;
-use pocketmine\item\Item;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
class WaterLily extends Flowable{
+ use StaticSupportTrait {
+ canBePlacedAt as supportedWhenPlacedAt;
+ }
/**
* @return AxisAlignedBB[]
@@ -40,23 +41,10 @@ protected function recalculateCollisionBoxes() : array{
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
- return !$blockReplace instanceof Water && parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
- }
-
- private function canBeSupportedBy(Block $block) : bool{
- return $block instanceof Water;
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ return !$blockReplace instanceof Water && $this->supportedWhenPlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
+ private function canBeSupportedAt(Block $block) : bool{
+ return $block->getSide(Facing::DOWN) instanceof Water;
}
}
diff --git a/src/block/WeightedPressurePlate.php b/src/block/WeightedPressurePlate.php
index bdfae50824a..726b31f6ba2 100644
--- a/src/block/WeightedPressurePlate.php
+++ b/src/block/WeightedPressurePlate.php
@@ -24,7 +24,40 @@
namespace pocketmine\block;
use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait;
+use function ceil;
+use function count;
+use function max;
+use function min;
-abstract class WeightedPressurePlate extends PressurePlate{
+class WeightedPressurePlate extends PressurePlate{
use AnalogRedstoneSignalEmitterTrait;
+
+ private readonly float $signalStrengthFactor;
+
+ /**
+ * @param float $signalStrengthFactor Number of entities on the plate is divided by this value to get signal strength
+ */
+ public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, int $deactivationDelayTicks, float $signalStrengthFactor = 1.0){
+ parent::__construct($idInfo, $name, $typeInfo, $deactivationDelayTicks);
+ $this->signalStrengthFactor = $signalStrengthFactor;
+ }
+
+ protected function hasOutputSignal() : bool{
+ return $this->signalStrength > 0;
+ }
+
+ protected function calculatePlateState(array $entities) : array{
+ $newSignalStrength = min(15, max(0,
+ (int) ceil(count($entities) * $this->signalStrengthFactor)
+ ));
+ if($newSignalStrength === $this->signalStrength){
+ return [$this, null];
+ }
+ $wasActive = $this->signalStrength !== 0;
+ $isActive = $newSignalStrength !== 0;
+ return [
+ (clone $this)->setOutputSignalStrength($newSignalStrength),
+ $wasActive !== $isActive ? $isActive : null
+ ];
+ }
}
diff --git a/src/block/WeightedPressurePlateHeavy.php b/src/block/WeightedPressurePlateHeavy.php
index 390297436f7..9a8d1c31b9e 100644
--- a/src/block/WeightedPressurePlateHeavy.php
+++ b/src/block/WeightedPressurePlateHeavy.php
@@ -23,6 +23,9 @@
namespace pocketmine\block;
+/**
+ * @deprecated
+ */
class WeightedPressurePlateHeavy extends WeightedPressurePlate{
}
diff --git a/src/block/WeightedPressurePlateLight.php b/src/block/WeightedPressurePlateLight.php
index 458c07e1a0d..85c13d438c0 100644
--- a/src/block/WeightedPressurePlateLight.php
+++ b/src/block/WeightedPressurePlateLight.php
@@ -23,6 +23,9 @@
namespace pocketmine\block;
+/**
+ * @deprecated
+ */
class WeightedPressurePlateLight extends WeightedPressurePlate{
}
diff --git a/src/block/Wheat.php b/src/block/Wheat.php
index 15701c97607..92f155f7664 100644
--- a/src/block/Wheat.php
+++ b/src/block/Wheat.php
@@ -23,9 +23,9 @@
namespace pocketmine\block;
+use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
-use function mt_rand;
class Wheat extends Crops{
@@ -33,7 +33,7 @@ public function getDropsForCompatibleTool(Item $item) : array{
if($this->age >= self::MAX_AGE){
return [
VanillaItems::WHEAT(),
- VanillaItems::WHEAT_SEEDS()->setCount(mt_rand(0, 3))
+ VanillaItems::WHEAT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0))
];
}else{
return [
diff --git a/src/block/WitherRose.php b/src/block/WitherRose.php
index 696b2611507..8c0977b3753 100644
--- a/src/block/WitherRose.php
+++ b/src/block/WitherRose.php
@@ -23,23 +23,22 @@
namespace pocketmine\block;
+use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
-use pocketmine\item\Item;
use pocketmine\math\Facing;
-use pocketmine\math\Vector3;
-use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
class WitherRose extends Flowable{
+ use StaticSupportTrait;
- private function canBeSupportedBy(Block $block) : bool{
+ private function canBeSupportedAt(Block $block) : bool{
+ $supportBlock = $block->getSide(Facing::DOWN);
return
- $block->hasTypeTag(BlockTypeTags::DIRT) ||
- $block->hasTypeTag(BlockTypeTags::MUD) ||
- match($block->getTypeId()){
+ $supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
+ $supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
+ match($supportBlock->getTypeId()){
BlockTypeIds::NETHERRACK,
BlockTypeIds::SOUL_SAND,
BlockTypeIds::SOUL_SOIL => true,
@@ -47,19 +46,6 @@ private function canBeSupportedBy(Block $block) : bool{
};
}
- public function onNearbyBlockChange() : void{
- if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
- $this->position->getWorld()->useBreakOn($this->position);
- }
- }
-
- public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
- if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
- return false;
- }
- return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
- }
-
public function hasEntityCollision() : bool{ return true; }
public function onEntityInside(Entity $entity) : bool{
diff --git a/src/block/WoodLikeBlockIdHelper.php b/src/block/WoodLikeBlockIdHelper.php
deleted file mode 100644
index a4134669ad7..00000000000
--- a/src/block/WoodLikeBlockIdHelper.php
+++ /dev/null
@@ -1,306 +0,0 @@
-id()){
- WoodType::OAK()->id() => Ids::OAK_PLANKS,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_PLANKS,
- WoodType::BIRCH()->id() => Ids::BIRCH_PLANKS,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_PLANKS,
- WoodType::ACACIA()->id() => Ids::ACACIA_PLANKS,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_PLANKS,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_PLANKS,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_PLANKS,
- WoodType::WARPED()->id() => Ids::WARPED_PLANKS,
- default => throw new AssumptionFailedError("All tree types should be covered")
- });
- }
-
- public static function getFenceIdentifier(WoodType $type) : BID{
- return new BID(match($type->id()){
- WoodType::OAK()->id() => Ids::OAK_FENCE,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_FENCE,
- WoodType::BIRCH()->id() => Ids::BIRCH_FENCE,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_FENCE,
- WoodType::ACACIA()->id() => Ids::ACACIA_FENCE,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_FENCE,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_FENCE,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_FENCE,
- WoodType::WARPED()->id() => Ids::WARPED_FENCE,
- default => throw new AssumptionFailedError("All tree types should be covered")
- });
- }
-
- public static function getSlabIdentifier(WoodType $type) : BID{
- return new BID(match($type->id()){
- WoodType::OAK()->id() => Ids::OAK_SLAB,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_SLAB,
- WoodType::BIRCH()->id() => Ids::BIRCH_SLAB,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_SLAB,
- WoodType::ACACIA()->id() => Ids::ACACIA_SLAB,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_SLAB,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_SLAB,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_SLAB,
- WoodType::WARPED()->id() => Ids::WARPED_SLAB,
- default => throw new AssumptionFailedError("All tree types should be covered")
- });
- }
-
- public static function getLogIdentifier(WoodType $treeType) : BID{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_LOG,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_LOG,
- WoodType::BIRCH()->id() => Ids::BIRCH_LOG,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_LOG,
- WoodType::ACACIA()->id() => Ids::ACACIA_LOG,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_LOG,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_LOG,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_STEM,
- WoodType::WARPED()->id() => Ids::WARPED_STEM,
- default => throw new AssumptionFailedError("All tree types should be covered")
- });
- }
-
- public static function getAllSidedLogIdentifier(WoodType $treeType) : BID{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_WOOD,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_WOOD,
- WoodType::BIRCH()->id() => Ids::BIRCH_WOOD,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_WOOD,
- WoodType::ACACIA()->id() => Ids::ACACIA_WOOD,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_WOOD,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_WOOD,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_HYPHAE,
- WoodType::WARPED()->id() => Ids::WARPED_HYPHAE,
- default => throw new AssumptionFailedError("All tree types should be covered")
- });
- }
-
- public static function getLeavesIdentifier(LeavesType $leavesType) : BID{
- return new BID(match($leavesType->id()){
- LeavesType::OAK()->id() => Ids::OAK_LEAVES,
- LeavesType::SPRUCE()->id() => Ids::SPRUCE_LEAVES,
- LeavesType::BIRCH()->id() => Ids::BIRCH_LEAVES,
- LeavesType::JUNGLE()->id() => Ids::JUNGLE_LEAVES,
- LeavesType::ACACIA()->id() => Ids::ACACIA_LEAVES,
- LeavesType::DARK_OAK()->id() => Ids::DARK_OAK_LEAVES,
- LeavesType::MANGROVE()->id() => Ids::MANGROVE_LEAVES,
- LeavesType::AZALEA()->id() => Ids::AZALEA_LEAVES,
- LeavesType::FLOWERING_AZALEA()->id() => Ids::FLOWERING_AZALEA_LEAVES,
- default => throw new AssumptionFailedError("All leaves types should be covered")
- });
- }
-
- public static function getSaplingIdentifier(SaplingType $treeType) : BID{
- return new BID(match($treeType->id()){
- SaplingType::OAK()->id() => Ids::OAK_SAPLING,
- SaplingType::SPRUCE()->id() => Ids::SPRUCE_SAPLING,
- SaplingType::BIRCH()->id() => Ids::BIRCH_SAPLING,
- SaplingType::JUNGLE()->id() => Ids::JUNGLE_SAPLING,
- SaplingType::ACACIA()->id() => Ids::ACACIA_SAPLING,
- SaplingType::DARK_OAK()->id() => Ids::DARK_OAK_SAPLING,
- default => throw new AssumptionFailedError("All tree types should be covered")
- });
- }
-
- /**
- * @return BID[]|\Closure[]
- * @phpstan-return array{BID, BID, \Closure() : \pocketmine\item\Item}
- */
- public static function getSignInfo(WoodType $treeType) : array{
- switch($treeType->id()){
- case WoodType::OAK()->id():
- return [
- new BID(Ids::OAK_SIGN, TileSign::class),
- new BID(Ids::OAK_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::OAK_SIGN()
- ];
- case WoodType::SPRUCE()->id():
- return [
- new BID(Ids::SPRUCE_SIGN, TileSign::class),
- new BID(Ids::SPRUCE_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::SPRUCE_SIGN()
- ];
- case WoodType::BIRCH()->id():
- return [
- new BID(Ids::BIRCH_SIGN, TileSign::class),
- new BID(Ids::BIRCH_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::BIRCH_SIGN()
- ];
- case WoodType::JUNGLE()->id():
- return [
- new BID(Ids::JUNGLE_SIGN, TileSign::class),
- new BID(Ids::JUNGLE_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::JUNGLE_SIGN()
- ];
- case WoodType::ACACIA()->id():
- return [
- new BID(Ids::ACACIA_SIGN, TileSign::class),
- new BID(Ids::ACACIA_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::ACACIA_SIGN()
- ];
- case WoodType::DARK_OAK()->id():
- return [
- new BID(Ids::DARK_OAK_SIGN, TileSign::class),
- new BID(Ids::DARK_OAK_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::DARK_OAK_SIGN()
- ];
- case WoodType::MANGROVE()->id():
- return [
- new BID(Ids::MANGROVE_SIGN, TileSign::class),
- new BID(Ids::MANGROVE_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::MANGROVE_SIGN()
- ];
- case WoodType::CRIMSON()->id():
- return [
- new BID(Ids::CRIMSON_SIGN, TileSign::class),
- new BID(Ids::CRIMSON_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::CRIMSON_SIGN()
- ];
- case WoodType::WARPED()->id():
- return [
- new BID(Ids::WARPED_SIGN, TileSign::class),
- new BID(Ids::WARPED_WALL_SIGN, TileSign::class),
- fn() => VanillaItems::WARPED_SIGN()
- ];
-
- }
- throw new AssumptionFailedError("Switch should cover all wood types");
- }
-
- public static function getTrapdoorIdentifier(WoodType $treeType) : BlockIdentifier{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_TRAPDOOR,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_TRAPDOOR,
- WoodType::BIRCH()->id() => Ids::BIRCH_TRAPDOOR,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_TRAPDOOR,
- WoodType::ACACIA()->id() => Ids::ACACIA_TRAPDOOR,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_TRAPDOOR,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_TRAPDOOR,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_TRAPDOOR,
- WoodType::WARPED()->id() => Ids::WARPED_TRAPDOOR,
- default => throw new AssumptionFailedError("All wood types should be covered")
- });
- }
-
- public static function getButtonIdentifier(WoodType $treeType) : BlockIdentifier{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_BUTTON,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_BUTTON,
- WoodType::BIRCH()->id() => Ids::BIRCH_BUTTON,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_BUTTON,
- WoodType::ACACIA()->id() => Ids::ACACIA_BUTTON,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_BUTTON,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_BUTTON,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_BUTTON,
- WoodType::WARPED()->id() => Ids::WARPED_BUTTON,
- default => throw new AssumptionFailedError("All wood types should be covered")
- });
- }
-
- public static function getPressurePlateIdentifier(WoodType $treeType) : BlockIdentifier{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_PRESSURE_PLATE,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_PRESSURE_PLATE,
- WoodType::BIRCH()->id() => Ids::BIRCH_PRESSURE_PLATE,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_PRESSURE_PLATE,
- WoodType::ACACIA()->id() => Ids::ACACIA_PRESSURE_PLATE,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_PRESSURE_PLATE,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_PRESSURE_PLATE,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_PRESSURE_PLATE,
- WoodType::WARPED()->id() => Ids::WARPED_PRESSURE_PLATE,
- default => throw new AssumptionFailedError("All wood types should be covered")
- });
- }
-
- public static function getDoorIdentifier(WoodType $treeType) : BlockIdentifier{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_DOOR,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_DOOR,
- WoodType::BIRCH()->id() => Ids::BIRCH_DOOR,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_DOOR,
- WoodType::ACACIA()->id() => Ids::ACACIA_DOOR,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_DOOR,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_DOOR,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_DOOR,
- WoodType::WARPED()->id() => Ids::WARPED_DOOR,
- default => throw new AssumptionFailedError("All wood types should be covered")
- });
- }
-
- public static function getFenceGateIdentifier(WoodType $treeType) : BlockIdentifier{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_FENCE_GATE,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_FENCE_GATE,
- WoodType::BIRCH()->id() => Ids::BIRCH_FENCE_GATE,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_FENCE_GATE,
- WoodType::ACACIA()->id() => Ids::ACACIA_FENCE_GATE,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_FENCE_GATE,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_FENCE_GATE,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_FENCE_GATE,
- WoodType::WARPED()->id() => Ids::WARPED_FENCE_GATE,
- default => throw new AssumptionFailedError("All wood types should be covered")
- });
- }
-
- public static function getStairsIdentifier(WoodType $treeType) : BlockIdentifier{
- return new BID(match($treeType->id()){
- WoodType::OAK()->id() => Ids::OAK_STAIRS,
- WoodType::SPRUCE()->id() => Ids::SPRUCE_STAIRS,
- WoodType::BIRCH()->id() => Ids::BIRCH_STAIRS,
- WoodType::JUNGLE()->id() => Ids::JUNGLE_STAIRS,
- WoodType::ACACIA()->id() => Ids::ACACIA_STAIRS,
- WoodType::DARK_OAK()->id() => Ids::DARK_OAK_STAIRS,
- WoodType::MANGROVE()->id() => Ids::MANGROVE_STAIRS,
- WoodType::CRIMSON()->id() => Ids::CRIMSON_STAIRS,
- WoodType::WARPED()->id() => Ids::WARPED_STAIRS,
- default => throw new AssumptionFailedError("All wood types should be covered")
- });
- }
-}
diff --git a/src/block/WoodenButton.php b/src/block/WoodenButton.php
index d1123ec2d6b..7ba8a7af0d1 100644
--- a/src/block/WoodenButton.php
+++ b/src/block/WoodenButton.php
@@ -35,4 +35,8 @@ protected function getActivationTime() : int{
public function hasEntityCollision() : bool{
return false; //TODO: arrows activate wooden buttons
}
+
+ public function getFuelTime() : int{
+ return $this->woodType->isFlammable() ? 100 : 0;
+ }
}
diff --git a/src/block/WoodenDoor.php b/src/block/WoodenDoor.php
index e398812cd0e..96f349e4969 100644
--- a/src/block/WoodenDoor.php
+++ b/src/block/WoodenDoor.php
@@ -27,4 +27,8 @@
class WoodenDoor extends Door{
use WoodTypeTrait;
+
+ public function getFuelTime() : int{
+ return $this->woodType->isFlammable() ? 200 : 0;
+ }
}
diff --git a/src/block/WoodenPressurePlate.php b/src/block/WoodenPressurePlate.php
index baaf44c2c94..a629c2f1c77 100644
--- a/src/block/WoodenPressurePlate.php
+++ b/src/block/WoodenPressurePlate.php
@@ -23,11 +23,23 @@
namespace pocketmine\block;
+use pocketmine\block\utils\WoodType;
use pocketmine\block\utils\WoodTypeTrait;
class WoodenPressurePlate extends SimplePressurePlate{
use WoodTypeTrait;
+ public function __construct(
+ BlockIdentifier $idInfo,
+ string $name,
+ BlockTypeInfo $typeInfo,
+ WoodType $woodType,
+ int $deactivationDelayTicks = 20 //TODO: make this mandatory in PM6
+ ){
+ $this->woodType = $woodType;
+ parent::__construct($idInfo, $name, $typeInfo, $deactivationDelayTicks);
+ }
+
public function getFuelTime() : int{
return 300;
}
diff --git a/src/block/WoodenStairs.php b/src/block/WoodenStairs.php
index 30fa7e60266..0d9ba62cebf 100644
--- a/src/block/WoodenStairs.php
+++ b/src/block/WoodenStairs.php
@@ -28,6 +28,10 @@
class WoodenStairs extends Stair{
use WoodTypeTrait;
+ public function getFuelTime() : int{
+ return $this->woodType->isFlammable() ? 300 : 0;
+ }
+
public function getFlameEncouragement() : int{
return 5;
}
diff --git a/src/block/Wool.php b/src/block/Wool.php
index 2cc2b75358c..0b008ac042f 100644
--- a/src/block/Wool.php
+++ b/src/block/Wool.php
@@ -24,16 +24,10 @@
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
-use pocketmine\block\utils\DyeColor;
class Wool extends Opaque{
use ColoredTrait;
- public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
- $this->color = DyeColor::WHITE();
- parent::__construct($idInfo, $name, $typeInfo);
- }
-
public function getFlameEncouragement() : int{
return 30;
}
diff --git a/src/block/inventory/AnimatedBlockInventoryTrait.php b/src/block/inventory/AnimatedBlockInventoryTrait.php
index a9965190cd4..8720c985b1e 100644
--- a/src/block/inventory/AnimatedBlockInventoryTrait.php
+++ b/src/block/inventory/AnimatedBlockInventoryTrait.php
@@ -47,20 +47,20 @@ abstract protected function getCloseSound() : Sound;
public function onOpen(Player $who) : void{
parent::onOpen($who);
- if($this->getHolder()->isValid() && $this->getViewerCount() === 1){
+ if($this->holder->isValid() && $this->getViewerCount() === 1){
//TODO: this crap really shouldn't be managed by the inventory
$this->animateBlock(true);
- $this->getHolder()->getWorld()->addSound($this->getHolder()->add(0.5, 0.5, 0.5), $this->getOpenSound());
+ $this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getOpenSound());
}
}
abstract protected function animateBlock(bool $isOpen) : void;
public function onClose(Player $who) : void{
- if($this->getHolder()->isValid() && $this->getViewerCount() === 1){
+ if($this->holder->isValid() && $this->getViewerCount() === 1){
//TODO: this crap really shouldn't be managed by the inventory
$this->animateBlock(false);
- $this->getHolder()->getWorld()->addSound($this->getHolder()->add(0.5, 0.5, 0.5), $this->getCloseSound());
+ $this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getCloseSound());
}
parent::onClose($who);
}
diff --git a/src/block/inventory/CampfireInventory.php b/src/block/inventory/CampfireInventory.php
new file mode 100644
index 00000000000..ae762473e23
--- /dev/null
+++ b/src/block/inventory/CampfireInventory.php
@@ -0,0 +1,40 @@
+holder = $holder;
+ parent::__construct(4);
+ }
+
+ public function getMaxStackSize() : int{
+ return 1;
+ }
+}
diff --git a/src/block/inventory/EnchantInventory.php b/src/block/inventory/EnchantInventory.php
index 2c682d134bf..b726dbedf32 100644
--- a/src/block/inventory/EnchantInventory.php
+++ b/src/block/inventory/EnchantInventory.php
@@ -23,9 +23,15 @@
namespace pocketmine\block\inventory;
+use pocketmine\event\player\PlayerEnchantingOptionsRequestEvent;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
+use pocketmine\item\enchantment\EnchantingHelper as Helper;
+use pocketmine\item\enchantment\EnchantingOption;
+use pocketmine\item\Item;
use pocketmine\world\Position;
+use function array_values;
+use function count;
class EnchantInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
@@ -33,8 +39,47 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
+ /** @var EnchantingOption[] $options */
+ private array $options = [];
+
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
}
+
+ protected function onSlotChange(int $index, Item $before) : void{
+ if($index === self::SLOT_INPUT){
+ foreach($this->viewers as $viewer){
+ $this->options = [];
+ $item = $this->getInput();
+ $options = Helper::generateOptions($this->holder, $item, $viewer->getEnchantmentSeed());
+
+ $event = new PlayerEnchantingOptionsRequestEvent($viewer, $this, $options);
+ $event->call();
+ if(!$event->isCancelled() && count($event->getOptions()) > 0){
+ $this->options = array_values($event->getOptions());
+ $viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options);
+ }
+ }
+ }
+
+ parent::onSlotChange($index, $before);
+ }
+
+ public function getInput() : Item{
+ return $this->getItem(self::SLOT_INPUT);
+ }
+
+ public function getLapis() : Item{
+ return $this->getItem(self::SLOT_LAPIS);
+ }
+
+ public function getOutput(int $optionId) : ?Item{
+ $option = $this->getOption($optionId);
+ return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments());
+ }
+
+ public function getOption(int $optionId) : ?EnchantingOption{
+ return $this->options[$optionId] ?? null;
+ }
}
diff --git a/src/block/inventory/SmithingTableInventory.php b/src/block/inventory/SmithingTableInventory.php
index a01b80cad0a..2f67ac9d2dd 100644
--- a/src/block/inventory/SmithingTableInventory.php
+++ b/src/block/inventory/SmithingTableInventory.php
@@ -32,6 +32,6 @@ final class SmithingTableInventory extends SimpleInventory implements BlockInven
public function __construct(Position $holder){
$this->holder = $holder;
- parent::__construct(2);
+ parent::__construct(3);
}
}
diff --git a/src/block/tile/Banner.php b/src/block/tile/Banner.php
index 191d4c8a522..08a560707b2 100644
--- a/src/block/tile/Banner.php
+++ b/src/block/tile/Banner.php
@@ -27,11 +27,9 @@
use pocketmine\block\utils\DyeColor;
use pocketmine\data\bedrock\BannerPatternTypeIdMap;
use pocketmine\data\bedrock\DyeColorIdMap;
-use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
-use pocketmine\world\World;
/**
* @deprecated
@@ -44,7 +42,7 @@ class Banner extends Spawnable{
public const TAG_PATTERN_COLOR = "Color";
public const TAG_PATTERN_NAME = "Pattern";
- private DyeColor $baseColor;
+ private DyeColor $baseColor = DyeColor::BLACK;
/**
* @var BannerPatternLayer[]
@@ -52,11 +50,6 @@ class Banner extends Spawnable{
*/
private array $patterns = [];
- public function __construct(World $world, Vector3 $pos){
- $this->baseColor = DyeColor::BLACK();
- parent::__construct($world, $pos);
- }
-
public function readSaveData(CompoundTag $nbt) : void{
$colorIdMap = DyeColorIdMap::getInstance();
if(
@@ -65,7 +58,7 @@ public function readSaveData(CompoundTag $nbt) : void{
){
$this->baseColor = $baseColor;
}else{
- $this->baseColor = DyeColor::BLACK(); //TODO: this should be an error
+ $this->baseColor = DyeColor::BLACK; //TODO: this should be an error
}
$patternTypeIdMap = BannerPatternTypeIdMap::getInstance();
@@ -74,7 +67,7 @@ public function readSaveData(CompoundTag $nbt) : void{
if($patterns !== null){
/** @var CompoundTag $pattern */
foreach($patterns as $pattern){
- $patternColor = $colorIdMap->fromInvertedId($pattern->getInt(self::TAG_PATTERN_COLOR)) ?? DyeColor::BLACK(); //TODO: missing pattern colour should be an error
+ $patternColor = $colorIdMap->fromInvertedId($pattern->getInt(self::TAG_PATTERN_COLOR)) ?? DyeColor::BLACK; //TODO: missing pattern colour should be an error
$patternType = $patternTypeIdMap->fromId($pattern->getString(self::TAG_PATTERN_NAME));
if($patternType === null){
continue; //TODO: this should be an error, but right now we don't have the setup to deal with it
diff --git a/src/block/tile/Bed.php b/src/block/tile/Bed.php
index 847bcca1fdf..17a0c8d9d33 100644
--- a/src/block/tile/Bed.php
+++ b/src/block/tile/Bed.php
@@ -25,20 +25,13 @@
use pocketmine\block\utils\DyeColor;
use pocketmine\data\bedrock\DyeColorIdMap;
-use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
-use pocketmine\world\World;
class Bed extends Spawnable{
public const TAG_COLOR = "color";
- private DyeColor $color;
-
- public function __construct(World $world, Vector3 $pos){
- $this->color = DyeColor::RED();
- parent::__construct($world, $pos);
- }
+ private DyeColor $color = DyeColor::RED;
public function getColor() : DyeColor{
return $this->color;
@@ -55,7 +48,7 @@ public function readSaveData(CompoundTag $nbt) : void{
){
$this->color = $color;
}else{
- $this->color = DyeColor::RED(); //TODO: this should be an error, but we don't have the systems to handle it yet
+ $this->color = DyeColor::RED; //TODO: this should be an error, but we don't have the systems to handle it yet
}
}
diff --git a/src/block/tile/BlastFurnace.php b/src/block/tile/BlastFurnace.php
index e6e23c88c3b..1356e32bf4f 100644
--- a/src/block/tile/BlastFurnace.php
+++ b/src/block/tile/BlastFurnace.php
@@ -27,6 +27,6 @@
class BlastFurnace extends Furnace{
public function getFurnaceType() : FurnaceType{
- return FurnaceType::BLAST_FURNACE();
+ return FurnaceType::BLAST_FURNACE;
}
}
diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php
new file mode 100644
index 00000000000..ad4a193d7b8
--- /dev/null
+++ b/src/block/tile/Campfire.php
@@ -0,0 +1,143 @@
+ */
+ private array $cookingTimes = [];
+
+ public function __construct(World $world, Vector3 $pos){
+ parent::__construct($world, $pos);
+ $this->inventory = new CampfireInventory($this->position);
+ $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange(
+ static function(Inventory $unused) use ($world, $pos) : void{
+ $block = $world->getBlock($pos);
+ if($block instanceof BlockCampfire){
+ $world->setBlock($pos, $block);
+ }
+ })
+ );
+ }
+
+ public function getInventory() : CampfireInventory{
+ return $this->inventory;
+ }
+
+ public function getRealInventory() : CampfireInventory{
+ return $this->inventory;
+ }
+
+ /**
+ * @return int[]
+ * @phpstan-return array
+ */
+ public function getCookingTimes() : array{
+ return $this->cookingTimes;
+ }
+
+ /**
+ * @param int[] $cookingTimes
+ * @phpstan-param array $cookingTimes
+ */
+ public function setCookingTimes(array $cookingTimes) : void{
+ $this->cookingTimes = $cookingTimes;
+ }
+
+ public function readSaveData(CompoundTag $nbt) : void{
+ $items = [];
+ $listeners = $this->inventory->getListeners()->toArray();
+ $this->inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
+
+ foreach([
+ [0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME],
+ [1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME],
+ [2, self::TAG_THIRD_INPUT_ITEM, self::TAG_THIRD_COOKING_TIME],
+ [3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME],
+ ] as [$slot, $itemTag, $cookingTimeTag]){
+ if(($tag = $nbt->getTag($itemTag)) instanceof CompoundTag){
+ $items[$slot] = Item::nbtDeserialize($tag);
+ }
+ if(($tag = $nbt->getTag($cookingTimeTag)) instanceof IntTag){
+ $this->cookingTimes[$slot] = $tag->getValue();
+ }
+ }
+ $this->inventory->setContents($items);
+ $this->inventory->getListeners()->add(...$listeners);
+ }
+
+ protected function writeSaveData(CompoundTag $nbt) : void{
+ foreach([
+ [0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME],
+ [1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME],
+ [2, self::TAG_THIRD_INPUT_ITEM, self::TAG_THIRD_COOKING_TIME],
+ [3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME],
+ ] as [$slot, $itemTag, $cookingTimeTag]){
+ $item = $this->inventory->getItem($slot);
+ if(!$item->isNull()){
+ $nbt->setTag($itemTag, $item->nbtSerialize());
+ if(isset($this->cookingTimes[$slot])){
+ $nbt->setInt($cookingTimeTag, $this->cookingTimes[$slot]);
+ }
+ }
+ }
+ }
+
+ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
+ foreach([
+ 0 => self::TAG_FIRST_INPUT_ITEM,
+ 1 => self::TAG_SECOND_INPUT_ITEM,
+ 2 => self::TAG_THIRD_INPUT_ITEM,
+ 3 => self::TAG_FOURTH_INPUT_ITEM
+ ] as $slot => $tag){
+ $item = $this->inventory->getItem($slot);
+ if(!$item->isNull()){
+ $nbt->setTag($tag, TypeConverter::getInstance()->getItemTranslator()->toNetworkNbt($item));
+ }
+ }
+ }
+}
diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php
index 46d97191b33..4f97eed234a 100644
--- a/src/block/tile/Chest.php
+++ b/src/block/tile/Chest.php
@@ -139,7 +139,7 @@ protected function checkPairing() : void{
if($pair->doubleInventory !== null){
$this->doubleInventory = $pair->doubleInventory;
}else{
- if(($pair->getPosition()->x + ($pair->getPosition()->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly
+ if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair->inventory, $this->inventory);
}else{
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this->inventory, $pair->inventory);
diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php
new file mode 100644
index 00000000000..06175e27f4d
--- /dev/null
+++ b/src/block/tile/ChiseledBookshelf.php
@@ -0,0 +1,139 @@
+inventory = new SimpleInventory(count(ChiseledBookshelfSlot::cases()));
+ }
+
+ public function getInventory() : SimpleInventory{
+ return $this->inventory;
+ }
+
+ public function getRealInventory() : SimpleInventory{
+ return $this->inventory;
+ }
+
+ public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{
+ return $this->lastInteractedSlot;
+ }
+
+ public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : void{
+ $this->lastInteractedSlot = $lastInteractedSlot;
+ }
+
+ public function readSaveData(CompoundTag $nbt) : void{
+ $this->loadItems($nbt);
+
+ $lastInteractedSlot = $nbt->getInt(self::TAG_LAST_INTERACTED_SLOT, 0);
+ if($lastInteractedSlot !== 0){
+ $this->lastInteractedSlot = ChiseledBookshelfSlot::tryFrom($lastInteractedSlot - 1);
+ }
+ }
+
+ protected function writeSaveData(CompoundTag $nbt) : void{
+ $this->saveItems($nbt);
+
+ $nbt->setInt(self::TAG_LAST_INTERACTED_SLOT, $this->lastInteractedSlot !== null ?
+ $this->lastInteractedSlot->value + 1 :
+ 0
+ );
+ }
+
+ protected function loadItems(CompoundTag $tag) : void{
+ if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){
+ $inventory = $this->getRealInventory();
+ $listeners = $inventory->getListeners()->toArray();
+ $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
+
+ $newContents = [];
+ /** @var CompoundTag $itemNBT */
+ foreach($inventoryTag as $slot => $itemNBT){
+ try{
+ $count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT);
+ if($count === 0){
+ continue;
+ }
+ $newContents[$slot] = Item::nbtDeserialize($itemNBT);
+ }catch(SavedDataLoadingException $e){
+ //TODO: not the best solution
+ \GlobalLogger::get()->logException($e);
+ continue;
+ }
+ }
+ $inventory->setContents($newContents);
+
+ $inventory->getListeners()->add(...$listeners);
+ }
+
+ if(($lockTag = $tag->getTag(Container::TAG_LOCK)) instanceof StringTag){
+ $this->lock = $lockTag->getValue();
+ }
+ }
+
+ protected function saveItems(CompoundTag $tag) : void{
+ $items = [];
+ foreach($this->getRealInventory()->getContents(true) as $slot => $item){
+ if($item->isNull()){
+ $items[$slot] = CompoundTag::create()
+ ->setByte(SavedItemStackData::TAG_COUNT, 0)
+ ->setShort(SavedItemData::TAG_DAMAGE, 0)
+ ->setString(SavedItemData::TAG_NAME, "")
+ ->setByte(SavedItemStackData::TAG_WAS_PICKED_UP, 0);
+ }else{
+ $items[$slot] = $item->nbtSerialize();
+ }
+ }
+
+ $tag->setTag(Container::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound));
+
+ if($this->lock !== null){
+ $tag->setString(Container::TAG_LOCK, $this->lock);
+ }
+ }
+}
diff --git a/src/block/tile/ItemFrame.php b/src/block/tile/ItemFrame.php
index faf0ddbb690..7d003770e69 100644
--- a/src/block/tile/ItemFrame.php
+++ b/src/block/tile/ItemFrame.php
@@ -27,6 +27,7 @@
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
+use pocketmine\nbt\tag\FloatTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\world\World;
@@ -52,13 +53,17 @@ public function readSaveData(CompoundTag $nbt) : void{
if(($itemTag = $nbt->getCompoundTag(self::TAG_ITEM)) !== null){
$this->item = Item::nbtDeserialize($itemTag);
}
- $this->itemRotation = $nbt->getByte(self::TAG_ITEM_ROTATION, $this->itemRotation);
+ if($nbt->getTag(self::TAG_ITEM_ROTATION) instanceof FloatTag){
+ $this->itemRotation = (int) ($nbt->getFloat(self::TAG_ITEM_ROTATION, $this->itemRotation * 45) / 45);
+ } else {
+ $this->itemRotation = $nbt->getByte(self::TAG_ITEM_ROTATION, $this->itemRotation);
+ }
$this->itemDropChance = $nbt->getFloat(self::TAG_ITEM_DROP_CHANCE, $this->itemDropChance);
}
protected function writeSaveData(CompoundTag $nbt) : void{
$nbt->setFloat(self::TAG_ITEM_DROP_CHANCE, $this->itemDropChance);
- $nbt->setByte(self::TAG_ITEM_ROTATION, $this->itemRotation);
+ $nbt->setFloat(self::TAG_ITEM_ROTATION, $this->itemRotation * 45);
if(!$this->item->isNull()){
$nbt->setTag(self::TAG_ITEM, $this->item->nbtSerialize());
}
@@ -98,7 +103,7 @@ public function setItemDropChance(float $chance) : void{
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
$nbt->setFloat(self::TAG_ITEM_DROP_CHANCE, $this->itemDropChance);
- $nbt->setByte(self::TAG_ITEM_ROTATION, $this->itemRotation);
+ $nbt->setFloat(self::TAG_ITEM_ROTATION, $this->itemRotation * 45);
if(!$this->item->isNull()){
$nbt->setTag(self::TAG_ITEM, TypeConverter::getInstance()->getItemTranslator()->toNetworkNbt($this->item));
}
diff --git a/src/block/tile/MobHead.php b/src/block/tile/MobHead.php
index 70a199bf67b..be7c0ac2bc2 100644
--- a/src/block/tile/MobHead.php
+++ b/src/block/tile/MobHead.php
@@ -26,10 +26,8 @@
use pocketmine\block\utils\MobHeadType;
use pocketmine\data\bedrock\MobHeadTypeIdMap;
use pocketmine\data\SavedDataLoadingException;
-use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
-use pocketmine\world\World;
/**
* @deprecated
@@ -42,14 +40,9 @@ class MobHead extends Spawnable{
private const TAG_MOUTH_MOVING = "MouthMoving"; //TAG_Byte
private const TAG_MOUTH_TICK_COUNT = "MouthTickCount"; //TAG_Int
- private MobHeadType $mobHeadType;
+ private MobHeadType $mobHeadType = MobHeadType::SKELETON;
private int $rotation = 0;
- public function __construct(World $world, Vector3 $pos){
- $this->mobHeadType = MobHeadType::SKELETON();
- parent::__construct($world, $pos);
- }
-
public function readSaveData(CompoundTag $nbt) : void{
if(($skullTypeTag = $nbt->getTag(self::TAG_SKULL_TYPE)) instanceof ByteTag){
$mobHeadType = MobHeadTypeIdMap::getInstance()->fromId($skullTypeTag->getValue());
diff --git a/src/block/tile/NormalFurnace.php b/src/block/tile/NormalFurnace.php
index 9580920e201..6aa61b8ce72 100644
--- a/src/block/tile/NormalFurnace.php
+++ b/src/block/tile/NormalFurnace.php
@@ -27,6 +27,6 @@
class NormalFurnace extends Furnace{
public function getFurnaceType() : FurnaceType{
- return FurnaceType::FURNACE();
+ return FurnaceType::FURNACE;
}
}
diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php
index 047c5470346..2ced414ff92 100644
--- a/src/block/tile/Sign.php
+++ b/src/block/tile/Sign.php
@@ -68,6 +68,8 @@ public static function fixTextBlob(string $blob) : array{
}
protected SignText $text;
+ private bool $waxed = false;
+
protected ?int $editorEntityRuntimeId = null;
public function __construct(World $world, Vector3 $pos){
@@ -75,22 +77,30 @@ public function __construct(World $world, Vector3 $pos){
parent::__construct($world, $pos);
}
+ private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : void{
+ $baseColor = new Color(0, 0, 0);
+ $glowingText = false;
+ if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){
+ $baseColor = Color::fromARGB(Binary::unsignInt($baseColorTag->getValue()));
+ }
+ if($lightingBugResolved && ($glowingTextTag = $nbt->getTag(self::TAG_GLOWING_TEXT)) instanceof ByteTag){
+ //both of these must be 1 - if only one is set, it's a leftover from 1.16.210 experimental features
+ //see https://bugs.mojang.com/browse/MCPE-117835
+ $glowingText = $glowingTextTag->getValue() !== 0;
+ }
+ $this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText);
+ }
+
public function readSaveData(CompoundTag $nbt) : void{
- if(($textBlobTag = $nbt->getTag(self::TAG_TEXT_BLOB)) instanceof StringTag){ //MCPE 1.2 save format
- $baseColor = new Color(0, 0, 0);
- $glowingText = false;
- if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){
- $baseColor = Color::fromARGB(Binary::unsignInt($baseColorTag->getValue()));
- }
- if(
- ($glowingTextTag = $nbt->getTag(self::TAG_GLOWING_TEXT)) instanceof ByteTag &&
- ($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag
- ){
- //both of these must be 1 - if only one is set, it's a leftover from 1.16.210 experimental features
- //see https://bugs.mojang.com/browse/MCPE-117835
- $glowingText = $glowingTextTag->getValue() !== 0 && $lightingBugResolvedTag->getValue() !== 0;
+ $frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT);
+ if($frontTextTag instanceof CompoundTag){
+ $this->readTextTag($frontTextTag, true);
+ }elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format
+ $lightingBugResolved = false;
+ if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){
+ $lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0;
}
- $this->text = SignText::fromBlob(mb_scrub($textBlobTag->getValue(), 'UTF-8'), $baseColor, $glowingText);
+ $this->readTextTag($nbt, $lightingBugResolved);
}else{
$text = [];
for($i = 0; $i < SignText::LINE_COUNT; ++$i){
@@ -101,18 +111,24 @@ public function readSaveData(CompoundTag $nbt) : void{
}
$this->text = new SignText($text);
}
+ $this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0;
}
protected function writeSaveData(CompoundTag $nbt) : void{
- $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()));
+ $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
+ ->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()))
+ ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()))
+ ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0)
+ ->setByte(self::TAG_PERSIST_FORMATTING, 1)
+ );
+ $nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create()
+ ->setString(self::TAG_TEXT_BLOB, "")
+ ->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00))
+ ->setByte(self::TAG_GLOWING_TEXT, 0)
+ ->setByte(self::TAG_PERSIST_FORMATTING, 1)
+ );
- for($i = 0; $i < SignText::LINE_COUNT; ++$i){ //Backwards-compatibility
- $textKey = sprintf(self::TAG_TEXT_LINE, $i + 1);
- $nbt->setString($textKey, $this->text->getLine($i));
- }
- $nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()));
- $nbt->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0);
- $nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1);
+ $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
}
public function getText() : SignText{
@@ -123,6 +139,10 @@ public function setText(SignText $text) : void{
$this->text = $text;
}
+ public function isWaxed() : bool{ return $this->waxed; }
+
+ public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; }
+
/**
* Returns the entity runtime ID of the player who placed this sign. Only the player whose entity ID matches this
* one may edit the sign text.
@@ -153,7 +173,7 @@ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
->setByte(self::TAG_GLOWING_TEXT, 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1)
);
- $nbt->setByte(self::TAG_WAXED, 0);
+ $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
}
}
diff --git a/src/block/tile/Smoker.php b/src/block/tile/Smoker.php
index 4b5a017336b..824eeedd28c 100644
--- a/src/block/tile/Smoker.php
+++ b/src/block/tile/Smoker.php
@@ -27,6 +27,6 @@
class Smoker extends Furnace{
public function getFurnaceType() : FurnaceType{
- return FurnaceType::SMOKER();
+ return FurnaceType::SMOKER;
}
}
diff --git a/src/block/tile/TileFactory.php b/src/block/tile/TileFactory.php
index b3abb1315a6..515dd8c63c6 100644
--- a/src/block/tile/TileFactory.php
+++ b/src/block/tile/TileFactory.php
@@ -57,8 +57,10 @@ public function __construct(){
$this->register(Bell::class, ["Bell", "minecraft:bell"]);
$this->register(BlastFurnace::class, ["BlastFurnace", "minecraft:blast_furnace"]);
$this->register(BrewingStand::class, ["BrewingStand", "minecraft:brewing_stand"]);
+ $this->register(Campfire::class, ["Campfire", "minecraft:campfire"]);
$this->register(Cauldron::class, ["Cauldron", "minecraft:cauldron"]);
$this->register(Chest::class, ["Chest", "minecraft:chest"]);
+ $this->register(ChiseledBookshelf::class, ["ChiseledBookshelf", "minecraft:chiseled_bookshelf"]);
$this->register(Comparator::class, ["Comparator", "minecraft:comparator"]);
$this->register(DaylightSensor::class, ["DaylightDetector", "minecraft:daylight_detector"]);
$this->register(EnchantTable::class, ["EnchantTable", "minecraft:enchanting_table"]);
@@ -78,7 +80,6 @@ public function __construct(){
$this->register(MobHead::class, ["Skull", "minecraft:skull"]);
$this->register(GlowingItemFrame::class, ["GlowItemFrame"]);
- //TODO: Campfire
//TODO: ChalkboardBlock
//TODO: ChemistryTable
//TODO: CommandBlock
diff --git a/src/block/utils/AgeableTrait.php b/src/block/utils/AgeableTrait.php
new file mode 100644
index 00000000000..dc1369c8760
--- /dev/null
+++ b/src/block/utils/AgeableTrait.php
@@ -0,0 +1,51 @@
+boundedIntAuto(0, self::MAX_AGE, $this->age);
+ }
+
+ public function getAge() : int{ return $this->age; }
+
+ /**
+ * @return $this
+ */
+ public function setAge(int $age) : self{
+ if($age < 0 || $age > self::MAX_AGE){
+ throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
+ }
+ $this->age = $age;
+ return $this;
+ }
+}
diff --git a/src/block/utils/AmethystTrait.php b/src/block/utils/AmethystTrait.php
new file mode 100644
index 00000000000..e581d3517dc
--- /dev/null
+++ b/src/block/utils/AmethystTrait.php
@@ -0,0 +1,40 @@
+position->getWorld()->addSound($this->position, new AmethystBlockChimeSound());
+ $this->position->getWorld()->addSound($this->position, new BlockPunchSound($this));
+ }
+}
diff --git a/src/block/utils/AnalogRedstoneSignalEmitterTrait.php b/src/block/utils/AnalogRedstoneSignalEmitterTrait.php
index fe61f652c7a..4b869464360 100644
--- a/src/block/utils/AnalogRedstoneSignalEmitterTrait.php
+++ b/src/block/utils/AnalogRedstoneSignalEmitterTrait.php
@@ -29,7 +29,7 @@ trait AnalogRedstoneSignalEmitterTrait{
protected int $signalStrength = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, 0, 15, $this->signalStrength);
+ $w->boundedIntAuto(0, 15, $this->signalStrength);
}
public function getOutputSignalStrength() : int{ return $this->signalStrength; }
diff --git a/src/block/utils/BannerPatternType.php b/src/block/utils/BannerPatternType.php
index 85b06405919..9b196360331 100644
--- a/src/block/utils/BannerPatternType.php
+++ b/src/block/utils/BannerPatternType.php
@@ -23,13 +23,11 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static BannerPatternType BORDER()
* @method static BannerPatternType BRICKS()
@@ -70,49 +68,49 @@
* @method static BannerPatternType TRIANGLE_BOTTOM()
* @method static BannerPatternType TRIANGLE_TOP()
*/
-final class BannerPatternType{
- use EnumTrait;
+enum BannerPatternType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("border"),
- new self("bricks"),
- new self("circle"),
- new self("creeper"),
- new self("cross"),
- new self("curly_border"),
- new self("diagonal_left"),
- new self("diagonal_right"),
- new self("diagonal_up_left"),
- new self("diagonal_up_right"),
- new self("flower"),
- new self("gradient"),
- new self("gradient_up"),
- new self("half_horizontal"),
- new self("half_horizontal_bottom"),
- new self("half_vertical"),
- new self("half_vertical_right"),
- new self("mojang"),
- new self("rhombus"),
- new self("skull"),
- new self("small_stripes"),
- new self("square_bottom_left"),
- new self("square_bottom_right"),
- new self("square_top_left"),
- new self("square_top_right"),
- new self("straight_cross"),
- new self("stripe_bottom"),
- new self("stripe_center"),
- new self("stripe_downleft"),
- new self("stripe_downright"),
- new self("stripe_left"),
- new self("stripe_middle"),
- new self("stripe_right"),
- new self("stripe_top"),
- new self("triangle_bottom"),
- new self("triangle_top"),
- new self("triangles_bottom"),
- new self("triangles_top")
- );
- }
+ case BORDER;
+ case BRICKS;
+ case CIRCLE;
+ case CREEPER;
+ case CROSS;
+ case CURLY_BORDER;
+ case DIAGONAL_LEFT;
+ case DIAGONAL_RIGHT;
+ case DIAGONAL_UP_LEFT;
+ case DIAGONAL_UP_RIGHT;
+ case FLOW;
+ case FLOWER;
+ case GLOBE;
+ case GRADIENT;
+ case GRADIENT_UP;
+ case GUSTER;
+ case HALF_HORIZONTAL;
+ case HALF_HORIZONTAL_BOTTOM;
+ case HALF_VERTICAL;
+ case HALF_VERTICAL_RIGHT;
+ case MOJANG;
+ case PIGLIN;
+ case RHOMBUS;
+ case SKULL;
+ case SMALL_STRIPES;
+ case SQUARE_BOTTOM_LEFT;
+ case SQUARE_BOTTOM_RIGHT;
+ case SQUARE_TOP_LEFT;
+ case SQUARE_TOP_RIGHT;
+ case STRAIGHT_CROSS;
+ case STRIPE_BOTTOM;
+ case STRIPE_CENTER;
+ case STRIPE_DOWNLEFT;
+ case STRIPE_DOWNRIGHT;
+ case STRIPE_LEFT;
+ case STRIPE_MIDDLE;
+ case STRIPE_RIGHT;
+ case STRIPE_TOP;
+ case TRIANGLE_BOTTOM;
+ case TRIANGLE_TOP;
+ case TRIANGLES_BOTTOM;
+ case TRIANGLES_TOP;
}
diff --git a/src/block/utils/BellAttachmentType.php b/src/block/utils/BellAttachmentType.php
index 963257031bc..29b88a2c400 100644
--- a/src/block/utils/BellAttachmentType.php
+++ b/src/block/utils/BellAttachmentType.php
@@ -23,28 +23,22 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static BellAttachmentType CEILING()
* @method static BellAttachmentType FLOOR()
* @method static BellAttachmentType ONE_WALL()
* @method static BellAttachmentType TWO_WALLS()
*/
-final class BellAttachmentType{
- use EnumTrait;
+enum BellAttachmentType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("ceiling"),
- new self("floor"),
- new self("one_wall"),
- new self("two_walls")
- );
- }
+ case CEILING;
+ case FLOOR;
+ case ONE_WALL;
+ case TWO_WALLS;
}
diff --git a/src/block/utils/BlockEventHelper.php b/src/block/utils/BlockEventHelper.php
new file mode 100644
index 00000000000..fc9b06d13c8
--- /dev/null
+++ b/src/block/utils/BlockEventHelper.php
@@ -0,0 +1,115 @@
+call();
+ if($ev->isCancelled()){
+ return false;
+ }
+ $newState = $ev->getNewState();
+ }
+
+ $position = $oldState->getPosition();
+ $position->getWorld()->setBlock($position, $newState);
+ return true;
+ }
+
+ public static function spread(Block $oldState, Block $newState, Block $source) : bool{
+ if(BlockSpreadEvent::hasHandlers()){
+ $ev = new BlockSpreadEvent($oldState, $source, $newState);
+ $ev->call();
+ if($ev->isCancelled()){
+ return false;
+ }
+ $newState = $ev->getNewState();
+ }
+
+ $position = $oldState->getPosition();
+ $position->getWorld()->setBlock($position, $newState);
+ return true;
+ }
+
+ public static function form(Block $oldState, Block $newState, Block $cause) : bool{
+ if(BlockFormEvent::hasHandlers()){
+ $ev = new BlockFormEvent($oldState, $newState, $cause);
+ $ev->call();
+ if($ev->isCancelled()){
+ return false;
+ }
+ $newState = $ev->getNewState();
+ }
+
+ $position = $oldState->getPosition();
+ $position->getWorld()->setBlock($position, $newState);
+ return true;
+ }
+
+ public static function melt(Block $oldState, Block $newState) : bool{
+ if(BlockMeltEvent::hasHandlers()){
+ $ev = new BlockMeltEvent($oldState, $newState);
+ $ev->call();
+ if($ev->isCancelled()){
+ return false;
+ }
+ $newState = $ev->getNewState();
+ }
+
+ $position = $oldState->getPosition();
+ $position->getWorld()->setBlock($position, $newState);
+ return true;
+ }
+
+ public static function die(Block $oldState, Block $newState) : bool{
+ if(BlockDeathEvent::hasHandlers()){
+ $ev = new BlockDeathEvent($oldState, $newState);
+ $ev->call();
+ if($ev->isCancelled()){
+ return false;
+ }
+ $newState = $ev->getNewState();
+ }
+
+ $position = $oldState->getPosition();
+ $position->getWorld()->setBlock($position, $newState);
+ return true;
+ }
+}
diff --git a/src/block/utils/BrewingStandSlot.php b/src/block/utils/BrewingStandSlot.php
index faeeaa7c4ab..21500b1c977 100644
--- a/src/block/utils/BrewingStandSlot.php
+++ b/src/block/utils/BrewingStandSlot.php
@@ -24,37 +24,31 @@
namespace pocketmine\block\utils;
use pocketmine\block\inventory\BrewingStandInventory;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static BrewingStandSlot EAST()
* @method static BrewingStandSlot NORTHWEST()
* @method static BrewingStandSlot SOUTHWEST()
*/
-final class BrewingStandSlot{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum BrewingStandSlot{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("east", BrewingStandInventory::SLOT_BOTTLE_LEFT),
- new self("northwest", BrewingStandInventory::SLOT_BOTTLE_MIDDLE),
- new self("southwest", BrewingStandInventory::SLOT_BOTTLE_RIGHT)
- );
- }
-
- private function __construct(string $enumName, private int $slotNumber){
- $this->Enum___construct($enumName);
- }
+ case EAST;
+ case NORTHWEST;
+ case SOUTHWEST;
/**
* Returns the brewing stand inventory slot number associated with this visual slot.
*/
- public function getSlotNumber() : int{ return $this->slotNumber; }
+ public function getSlotNumber() : int{
+ return match($this){
+ self::EAST => BrewingStandInventory::SLOT_BOTTLE_LEFT,
+ self::NORTHWEST => BrewingStandInventory::SLOT_BOTTLE_MIDDLE,
+ self::SOUTHWEST => BrewingStandInventory::SLOT_BOTTLE_RIGHT
+ };
+ }
}
diff --git a/src/block/utils/CandleTrait.php b/src/block/utils/CandleTrait.php
index 99e164a8435..c9da97ee0cc 100644
--- a/src/block/utils/CandleTrait.php
+++ b/src/block/utils/CandleTrait.php
@@ -24,7 +24,6 @@
namespace pocketmine\block\utils;
use pocketmine\block\Block;
-use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\projectile\Projectile;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\VanillaEnchantments;
@@ -33,36 +32,29 @@
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
+use pocketmine\world\sound\BlazeShootSound;
use pocketmine\world\sound\FireExtinguishSound;
use pocketmine\world\sound\FlintSteelSound;
trait CandleTrait{
- private bool $lit = false;
-
- protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->bool($this->lit);
- }
+ use LightableTrait;
public function getLightLevel() : int{
return $this->lit ? 3 : 0;
}
- public function isLit() : bool{ return $this->lit; }
-
- /** @return $this */
- public function setLit(bool $lit) : self{
- $this->lit = $lit;
- return $this;
- }
-
/** @see Block::onInteract() */
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
- if($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
+ if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE || $item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
if($this->lit){
return true;
}
if($item instanceof Durable){
$item->applyDamage(1);
+ }elseif($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
+ $item->pop();
+ //TODO: not sure if this is intentional, but it's what Bedrock currently does as of 1.20.10
+ $this->position->getWorld()->addSound($this->position, new BlazeShootSound());
}
$this->position->getWorld()->addSound($this->position, new FlintSteelSound());
$this->position->getWorld()->setBlock($this->position, $this->setLit(true));
diff --git a/src/block/utils/ChiseledBookshelfSlot.php b/src/block/utils/ChiseledBookshelfSlot.php
new file mode 100644
index 00000000000..b5a676bed2b
--- /dev/null
+++ b/src/block/utils/ChiseledBookshelfSlot.php
@@ -0,0 +1,53 @@
+ 1){
+ throw new \InvalidArgumentException("X must be between 0 and 1, got $x");
+ }
+ if($y < 0 || $y > 1){
+ throw new \InvalidArgumentException("Y must be between 0 and 1, got $y");
+ }
+
+ $slot = ($y < 0.5 ? self::SLOTS_PER_SHELF : 0) + match(true){
+ //we can't use simple maths here as the action is aligned to the 16x16 pixel grid :(
+ $x < 6 / 16 => 0,
+ $x < 11 / 16 => 1,
+ default => 2
+ };
+
+ return self::from($slot);
+ }
+}
diff --git a/src/block/utils/ColoredTrait.php b/src/block/utils/ColoredTrait.php
index dab86fb6634..2ecd58e2035 100644
--- a/src/block/utils/ColoredTrait.php
+++ b/src/block/utils/ColoredTrait.php
@@ -28,11 +28,11 @@
trait ColoredTrait{
/** @var DyeColor */
- private $color;
+ private $color = DyeColor::WHITE;
/** @see Block::describeBlockItemState() */
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->dyeColor($this->color);
+ $w->enum($this->color);
}
public function getColor() : DyeColor{ return $this->color; }
diff --git a/src/block/utils/CopperMaterial.php b/src/block/utils/CopperMaterial.php
new file mode 100644
index 00000000000..6df22620b70
--- /dev/null
+++ b/src/block/utils/CopperMaterial.php
@@ -0,0 +1,38 @@
+value] = $member;
- }
-
- /**
- * @var self[]
- * @phpstan-var array
- */
- private static array $levelMap = [];
-
- private function __construct(
- string $name,
- private int $value
- ){
- $this->Enum___construct($name);
- }
+ case NONE = 0;
+ case EXPOSED = 1;
+ case WEATHERED = 2;
+ case OXIDIZED = 3;
public function getPrevious() : ?self{
- return self::$levelMap[$this->value - 1] ?? null;
+ return self::tryFrom($this->value - 1);
}
public function getNext() : ?self{
- return self::$levelMap[$this->value + 1] ?? null;
+ return self::tryFrom($this->value + 1);
}
}
diff --git a/src/block/utils/CopperTrait.php b/src/block/utils/CopperTrait.php
index 5fede94ddd9..5ad8aa82d15 100644
--- a/src/block/utils/CopperTrait.php
+++ b/src/block/utils/CopperTrait.php
@@ -23,8 +23,6 @@
namespace pocketmine\block\utils;
-use pocketmine\block\BlockIdentifier;
-use pocketmine\block\BlockTypeInfo;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Axe;
use pocketmine\item\Item;
@@ -36,16 +34,11 @@
use pocketmine\world\sound\ScrapeSound;
trait CopperTrait{
- private CopperOxidation $oxidation;
+ private CopperOxidation $oxidation = CopperOxidation::NONE;
private bool $waxed = false;
- public function __construct(BlockIdentifier $identifier, string $name, BlockTypeInfo $typeInfo){
- $this->oxidation = CopperOxidation::NONE();
- parent::__construct($identifier, $name, $typeInfo);
- }
-
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->copperOxidation($this->oxidation);
+ $w->enum($this->oxidation);
$w->bool($this->waxed);
}
diff --git a/src/block/utils/CoralType.php b/src/block/utils/CoralType.php
index 5a4d88fa9c5..cba3e8ddee4 100644
--- a/src/block/utils/CoralType.php
+++ b/src/block/utils/CoralType.php
@@ -23,13 +23,11 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static CoralType BRAIN()
* @method static CoralType BUBBLE()
@@ -37,27 +35,22 @@
* @method static CoralType HORN()
* @method static CoralType TUBE()
*/
-final class CoralType{
- use EnumTrait {
- __construct as Enum___construct;
+enum CoralType{
+ use LegacyEnumShimTrait;
+
+ case TUBE;
+ case BRAIN;
+ case BUBBLE;
+ case FIRE;
+ case HORN;
+
+ public function getDisplayName() : string{
+ return match($this){
+ self::TUBE => "Tube",
+ self::BRAIN => "Brain",
+ self::BUBBLE => "Bubble",
+ self::FIRE => "Fire",
+ self::HORN => "Horn",
+ };
}
-
- protected static function setup() : void{
- self::registerAll(
- new self("tube", "Tube"),
- new self("brain", "Brain"),
- new self("bubble", "Bubble"),
- new self("fire", "Fire"),
- new self("horn", "Horn"),
- );
- }
-
- private function __construct(
- string $name,
- private string $displayName
- ){
- $this->Enum___construct($name);
- }
-
- public function getDisplayName() : string{ return $this->displayName; }
}
diff --git a/src/block/utils/CoralTypeTrait.php b/src/block/utils/CoralTypeTrait.php
index 5dcd539d43a..a335bf9eccb 100644
--- a/src/block/utils/CoralTypeTrait.php
+++ b/src/block/utils/CoralTypeTrait.php
@@ -27,12 +27,12 @@
use pocketmine\data\runtime\RuntimeDataDescriber;
trait CoralTypeTrait{
- protected CoralType $coralType;
+ protected CoralType $coralType = CoralType::TUBE;
protected bool $dead = false;
/** @see Block::describeBlockItemState() */
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
- $w->coralType($this->coralType);
+ $w->enum($this->coralType);
$w->bool($this->dead);
}
diff --git a/src/block/utils/CropGrowthHelper.php b/src/block/utils/CropGrowthHelper.php
new file mode 100644
index 00000000000..e85b0b82d33
--- /dev/null
+++ b/src/block/utils/CropGrowthHelper.php
@@ -0,0 +1,120 @@
+getPosition();
+
+ $world = $position->getWorld();
+ $baseX = $position->getFloorX();
+ $baseY = $position->getFloorY();
+ $baseZ = $position->getFloorZ();
+
+ $farmland = $world->getBlockAt($baseX, $baseY - 1, $baseZ);
+
+ if($farmland instanceof Farmland){
+ $result += $farmland->getWetness() > 0 ? self::ON_HYDRATED_FARMLAND_BONUS : self::ON_DRY_FARMLAND_BONUS;
+ }
+
+ $xRow = false;
+ $zRow = false;
+ $improperArrangement = false;
+
+ for($x = -1; $x <= 1; $x++){
+ for($z = -1; $z <= 1; $z++){
+ if($x === 0 && $z === 0){
+ continue;
+ }
+ $nextFarmland = $world->getBlockAt($baseX + $x, $baseY - 1, $baseZ + $z);
+
+ if(!$nextFarmland instanceof Farmland){
+ continue;
+ }
+
+ $result += $nextFarmland->getWetness() > 0 ? self::ADJACENT_HYDRATED_FARMLAND_BONUS : self::ADJACENT_DRY_FARMLAND_BONUS;
+
+ if(!$improperArrangement){
+ $nextCrop = $world->getBlockAt($baseX + $x, $baseY, $baseZ + $z);
+ if($nextCrop->hasSameTypeId($block)){
+ match(0){
+ $x => $zRow ? $improperArrangement = true : $xRow = true,
+ $z => $xRow ? $improperArrangement = true : $zRow = true,
+ default => $improperArrangement = true,
+ };
+ }
+ }
+ }
+ }
+
+ //crops can be arranged in rows, but the rows must not cross and must be spaced apart by at least one block
+ if($improperArrangement){
+ $result /= self::IMPROPER_ARRANGEMENT_DIVISOR;
+ }
+
+ return $result;
+ }
+
+ public static function hasEnoughLight(Block $block, int $minLevel = self::MIN_LIGHT_LEVEL) : bool{
+ $position = $block->getPosition();
+ $world = $position->getWorld();
+
+ //crop growth is not affected by time of day since 1.11 or so
+ return $world->getPotentialLightAt($position->x, $position->y, $position->z) >= $minLevel;
+ }
+
+ public static function canGrow(Block $block) : bool{
+ //while it may be tempting to use mt_rand(0, 25) < multiplier, this would make crops grow a bit faster than
+ //vanilla in most cases due to the remainder of 25 / multiplier not being discarded
+ return mt_rand(0, (int) (25 / self::calculateMultiplier($block))) === 0 && self::hasEnoughLight($block);
+ }
+}
diff --git a/src/block/utils/DirtType.php b/src/block/utils/DirtType.php
index 536268676d6..9bed30d1c5b 100644
--- a/src/block/utils/DirtType.php
+++ b/src/block/utils/DirtType.php
@@ -23,26 +23,20 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static DirtType COARSE()
* @method static DirtType NORMAL()
* @method static DirtType ROOTED()
*/
-final class DirtType{
- use EnumTrait;
+enum DirtType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("normal"),
- new self("coarse"),
- new self("rooted")
- );
- }
+ case NORMAL;
+ case COARSE;
+ case ROOTED;
}
diff --git a/src/block/utils/DripleafState.php b/src/block/utils/DripleafState.php
new file mode 100644
index 00000000000..4b0cedce7e7
--- /dev/null
+++ b/src/block/utils/DripleafState.php
@@ -0,0 +1,52 @@
+ null,
+ self::UNSTABLE, self::PARTIAL_TILT => 10,
+ self::FULL_TILT => 100,
+ };
+ }
+}
diff --git a/src/block/utils/DyeColor.php b/src/block/utils/DyeColor.php
index 81c51618a08..5b205eda5e5 100644
--- a/src/block/utils/DyeColor.php
+++ b/src/block/utils/DyeColor.php
@@ -24,13 +24,12 @@
namespace pocketmine\block\utils;
use pocketmine\color\Color;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
+use function spl_object_id;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static DyeColor BLACK()
* @method static DyeColor BLUE()
@@ -48,46 +47,70 @@
* @method static DyeColor RED()
* @method static DyeColor WHITE()
* @method static DyeColor YELLOW()
+ *
+ * @phpstan-type TMetadata array{0: string, 1: Color}
*/
-final class DyeColor{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum DyeColor{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new DyeColor("white", "White", new Color(0xf0, 0xf0, 0xf0)),
- new DyeColor("orange", "Orange", new Color(0xf9, 0x80, 0x1d)),
- new DyeColor("magenta", "Magenta", new Color(0xc7, 0x4e, 0xbd)),
- new DyeColor("light_blue", "Light Blue", new Color(0x3a, 0xb3, 0xda)),
- new DyeColor("yellow", "Yellow", new Color(0xfe, 0xd8, 0x3d)),
- new DyeColor("lime", "Lime", new Color(0x80, 0xc7, 0x1f)),
- new DyeColor("pink", "Pink", new Color(0xf3, 0x8b, 0xaa)),
- new DyeColor("gray", "Gray", new Color(0x47, 0x4f, 0x52)),
- new DyeColor("light_gray", "Light Gray", new Color(0x9d, 0x9d, 0x97)),
- new DyeColor("cyan", "Cyan", new Color(0x16, 0x9c, 0x9c)),
- new DyeColor("purple", "Purple", new Color(0x89, 0x32, 0xb8)),
- new DyeColor("blue", "Blue", new Color(0x3c, 0x44, 0xaa)),
- new DyeColor("brown", "Brown", new Color(0x83, 0x54, 0x32)),
- new DyeColor("green", "Green", new Color(0x5e, 0x7c, 0x16)),
- new DyeColor("red", "Red", new Color(0xb0, 0x2e, 0x26)),
- new DyeColor("black", "Black", new Color(0x1d, 0x1d, 0x21))
- );
+ case WHITE;
+ case ORANGE;
+ case MAGENTA;
+ case LIGHT_BLUE;
+ case YELLOW;
+ case LIME;
+ case PINK;
+ case GRAY;
+ case LIGHT_GRAY;
+ case CYAN;
+ case PURPLE;
+ case BLUE;
+ case BROWN;
+ case GREEN;
+ case RED;
+ case BLACK;
+
+ /**
+ * This function exists only to permit the use of named arguments and to make the code easier to read in PhpStorm.
+ *
+ * @phpstan-return TMetadata
+ */
+ private static function meta(string $displayName, Color $rgbValue) : array{
+ return [$displayName, $rgbValue];
}
- private function __construct(
- string $enumName,
- private string $displayName,
- private Color $rgbValue
- ){
- $this->Enum___construct($enumName);
+ /**
+ * @phpstan-return TMetadata
+ */
+ private function getMetadata() : array{
+ /** @phpstan-var array $cache */
+ static $cache = [];
+
+ return $cache[spl_object_id($this)] ??= match($this){
+ self::WHITE => self::meta("White", new Color(0xf0, 0xf0, 0xf0)),
+ self::ORANGE => self::meta("Orange", new Color(0xf9, 0x80, 0x1d)),
+ self::MAGENTA => self::meta("Magenta", new Color(0xc7, 0x4e, 0xbd)),
+ self::LIGHT_BLUE => self::meta("Light Blue", new Color(0x3a, 0xb3, 0xda)),
+ self::YELLOW => self::meta("Yellow", new Color(0xfe, 0xd8, 0x3d)),
+ self::LIME => self::meta("Lime", new Color(0x80, 0xc7, 0x1f)),
+ self::PINK => self::meta("Pink", new Color(0xf3, 0x8b, 0xaa)),
+ self::GRAY => self::meta("Gray", new Color(0x47, 0x4f, 0x52)),
+ self::LIGHT_GRAY => self::meta("Light Gray", new Color(0x9d, 0x9d, 0x97)),
+ self::CYAN => self::meta("Cyan", new Color(0x16, 0x9c, 0x9c)),
+ self::PURPLE => self::meta("Purple", new Color(0x89, 0x32, 0xb8)),
+ self::BLUE => self::meta("Blue", new Color(0x3c, 0x44, 0xaa)),
+ self::BROWN => self::meta("Brown", new Color(0x83, 0x54, 0x32)),
+ self::GREEN => self::meta("Green", new Color(0x5e, 0x7c, 0x16)),
+ self::RED => self::meta("Red", new Color(0xb0, 0x2e, 0x26)),
+ self::BLACK => self::meta("Black", new Color(0x1d, 0x1d, 0x21)),
+ };
}
public function getDisplayName() : string{
- return $this->displayName;
+ return $this->getMetadata()[0];
}
public function getRgbValue() : Color{
- return $this->rgbValue;
+ return $this->getMetadata()[1];
}
}
diff --git a/src/block/utils/FortuneDropHelper.php b/src/block/utils/FortuneDropHelper.php
new file mode 100644
index 00000000000..4cf9b0249df
--- /dev/null
+++ b/src/block/utils/FortuneDropHelper.php
@@ -0,0 +1,129 @@
+getEnchantmentLevel(VanillaEnchantments::FORTUNE());
+
+ return mt_rand($min,
+ $fortuneLevel > 0 && mt_rand() / mt_getrandmax() > 2 / ($fortuneLevel + 2) ?
+ $maxBase * ($fortuneLevel + 1) :
+ $maxBase
+ );
+ }
+
+ /**
+ * Increases the drop amount according to a binomial distribution. The function will roll maxBase+level times, and add 1
+ * if a random number between 0-1 is less than the given probability. Each level of fortune adds one extra roll.
+ *
+ * As many as maxBase+level items can be dropped. This applies even if the fortune level is 0.
+ *
+ * @param float $chance The chance of adding 1 to the amount for each roll, must be in the range 0-1
+ * @param int $min Minimum amount
+ * @param int $minRolls Number of rolls if fortune level is 0, added to fortune level to calculate total rolls
+ *
+ * @return int the number of items to drop
+ */
+ public static function binomial(Item $usedItem, int $min, int $minRolls = 3, float $chance = 4 / 7) : int{
+ $fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
+
+ $count = $min;
+ $rolls = $minRolls + $fortuneLevel;
+ for($i = 0; $i < $rolls; ++$i){
+ if(mt_rand() / mt_getrandmax() < $chance){
+ ++$count;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Adds the fortune level to the base max and picks a random number between the minimim and adjusted maximum.
+ * Each amount in the range has an equal chance of being picked.
+ *
+ * @param int $maxBase Maximum base amount, as if the fortune level was 0
+ *
+ * @return int the number of items to drop
+ */
+ public static function discrete(Item $usedItem, int $min, int $maxBase) : int{
+ if($maxBase < $min){
+ throw new \InvalidArgumentException("Minimum base drop amount must be less than or equal to maximum base drop amount");
+ }
+
+ $max = $maxBase + $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
+ return mt_rand($min, $max);
+ }
+
+ /**
+ * Calculates a chance of getting an extra bonus drop by reducing the chance divisor by a given amount per fortune
+ * level.
+ *
+ * @param int $divisorBase The number to divide 1 by to get the chance, as if the fortune level was 0
+ * @param int $divisorSubtractPerLevel The amount to subtract from the divisor for each level of fortune
+ *
+ * @return bool whether the bonus drop should be added
+ */
+ public static function bonusChanceDivisor(Item $usedItem, int $divisorBase, int $divisorSubtractPerLevel) : bool{
+ $fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
+ return mt_rand(1, max(1, $divisorBase - ($fortuneLevel * $divisorSubtractPerLevel))) === 1;
+ }
+
+ /**
+ * Calculates a chance of getting an extra bonus drop by increasing the chance by a fixed amount per fortune level.
+ *
+ * @param float $chanceBase The base chance of getting a bonus drop, as if the fortune level was 0
+ * @param float $addedChancePerLevel The amount to add to the chance for each level of fortune
+ */
+ public static function bonusChanceFixed(Item $usedItem, float $chanceBase, float $addedChancePerLevel) : bool{
+ $fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
+ $chance = min(1, $chanceBase + ($fortuneLevel * $addedChancePerLevel));
+ return mt_rand() / mt_getrandmax() < $chance;
+ }
+}
diff --git a/src/block/utils/FroglightType.php b/src/block/utils/FroglightType.php
index f6b9c1d13f6..ab20d31ac57 100644
--- a/src/block/utils/FroglightType.php
+++ b/src/block/utils/FroglightType.php
@@ -23,26 +23,20 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static FroglightType OCHRE()
* @method static FroglightType PEARLESCENT()
* @method static FroglightType VERDANT()
*/
-final class FroglightType{
- use EnumTrait;
+enum FroglightType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("ochre"),
- new self("pearlescent"),
- new self("verdant")
- );
- }
+ case OCHRE;
+ case PEARLESCENT;
+ case VERDANT;
}
diff --git a/src/block/utils/LeavesType.php b/src/block/utils/LeavesType.php
index c7bc1a0ed3d..975551ad6de 100644
--- a/src/block/utils/LeavesType.php
+++ b/src/block/utils/LeavesType.php
@@ -23,17 +23,16 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static LeavesType ACACIA()
* @method static LeavesType AZALEA()
* @method static LeavesType BIRCH()
+ * @method static LeavesType CHERRY()
* @method static LeavesType DARK_OAK()
* @method static LeavesType FLOWERING_AZALEA()
* @method static LeavesType JUNGLE()
@@ -41,34 +40,32 @@
* @method static LeavesType OAK()
* @method static LeavesType SPRUCE()
*/
-final class LeavesType{
- use EnumTrait {
- register as Enum_register;
- __construct as Enum___construct;
- }
-
- protected static function setup() : void{
- self::registerAll(
- new self("oak", "Oak"),
- new self("spruce", "Spruce"),
- new self("birch", "Birch"),
- new self("jungle", "Jungle"),
- new self("acacia", "Acacia"),
- new self("dark_oak", "Dark Oak"),
- new self("mangrove", "Mangrove"),
- new self("azalea", "Azalea"),
- new self("flowering_azalea", "Flowering Azalea")
- );
- }
+enum LeavesType{
+ use LegacyEnumShimTrait;
- private function __construct(
- string $enumName,
- private string $displayName
- ){
- $this->Enum___construct($enumName);
- }
+ case OAK;
+ case SPRUCE;
+ case BIRCH;
+ case JUNGLE;
+ case ACACIA;
+ case DARK_OAK;
+ case MANGROVE;
+ case AZALEA;
+ case FLOWERING_AZALEA;
+ case CHERRY;
public function getDisplayName() : string{
- return $this->displayName;
+ return match($this){
+ self::OAK => "Oak",
+ self::SPRUCE => "Spruce",
+ self::BIRCH => "Birch",
+ self::JUNGLE => "Jungle",
+ self::ACACIA => "Acacia",
+ self::DARK_OAK => "Dark Oak",
+ self::MANGROVE => "Mangrove",
+ self::AZALEA => "Azalea",
+ self::FLOWERING_AZALEA => "Flowering Azalea",
+ self::CHERRY => "Cherry"
+ };
}
}
diff --git a/src/block/utils/LeverFacing.php b/src/block/utils/LeverFacing.php
index 99eeb88d309..1af92d6c4c5 100644
--- a/src/block/utils/LeverFacing.php
+++ b/src/block/utils/LeverFacing.php
@@ -24,13 +24,11 @@
namespace pocketmine\block\utils;
use pocketmine\math\Facing;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static LeverFacing DOWN_AXIS_X()
* @method static LeverFacing DOWN_AXIS_Z()
@@ -41,27 +39,26 @@
* @method static LeverFacing UP_AXIS_Z()
* @method static LeverFacing WEST()
*/
-final class LeverFacing{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum LeverFacing{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("up_axis_x", Facing::UP),
- new self("up_axis_z", Facing::UP),
- new self("down_axis_x", Facing::DOWN),
- new self("down_axis_z", Facing::DOWN),
- new self("north", Facing::NORTH),
- new self("east", Facing::EAST),
- new self("south", Facing::SOUTH),
- new self("west", Facing::WEST),
- );
- }
+ case UP_AXIS_X;
+ case UP_AXIS_Z;
+ case DOWN_AXIS_X;
+ case DOWN_AXIS_Z;
+ case NORTH;
+ case EAST;
+ case SOUTH;
+ case WEST;
- private function __construct(string $enumName, private int $facing){
- $this->Enum___construct($enumName);
+ public function getFacing() : int{
+ return match($this){
+ self::UP_AXIS_X, self::UP_AXIS_Z => Facing::UP,
+ self::DOWN_AXIS_X, self::DOWN_AXIS_Z => Facing::DOWN,
+ self::NORTH => Facing::NORTH,
+ self::EAST => Facing::EAST,
+ self::SOUTH => Facing::SOUTH,
+ self::WEST => Facing::WEST,
+ };
}
-
- public function getFacing() : int{ return $this->facing; }
}
diff --git a/src/block/utils/LightableTrait.php b/src/block/utils/LightableTrait.php
new file mode 100644
index 00000000000..51ce54f42c1
--- /dev/null
+++ b/src/block/utils/LightableTrait.php
@@ -0,0 +1,46 @@
+bool($this->lit);
+ }
+
+ public function isLit() : bool{
+ return $this->lit;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setLit(bool $lit = true) : self{
+ $this->lit = $lit;
+ return $this;
+ }
+}
diff --git a/src/block/utils/MinimumCostFlowCalculator.php b/src/block/utils/MinimumCostFlowCalculator.php
index 84b003bbd12..a690a748709 100644
--- a/src/block/utils/MinimumCostFlowCalculator.php
+++ b/src/block/utils/MinimumCostFlowCalculator.php
@@ -58,17 +58,10 @@ private function calculateFlowCost(int $blockX, int $blockY, int $blockZ, int $a
if($j === $originOpposite || $j === $lastOpposite){
continue;
}
-
- $x = $blockX;
- $y = $blockY;
- $z = $blockZ;
-
- match($j){
- Facing::WEST => --$x,
- Facing::EAST => ++$x,
- Facing::NORTH => --$z,
- Facing::SOUTH => ++$z
- };
+ [$dx, $dy, $dz] = Facing::OFFSET[$j];
+ $x = $blockX + $dx;
+ $y = $blockY + $dy;
+ $z = $blockZ + $dz;
if(!isset($this->flowCostVisited[$hash = World::blockHash($x, $y, $z)])){
if(!$this->world->isInWorld($x, $y, $z) || !$this->canFlowInto($this->world->getBlockAt($x, $y, $z))){
@@ -109,16 +102,10 @@ public function getOptimalFlowDirections(int $originX, int $originY, int $origin
$flowCost = array_fill_keys(Facing::HORIZONTAL, 1000);
$maxCost = intdiv(4, $this->flowDecayPerBlock);
foreach(Facing::HORIZONTAL as $j){
- $x = $originX;
- $y = $originY;
- $z = $originZ;
-
- match($j){
- Facing::WEST => --$x,
- Facing::EAST => ++$x,
- Facing::NORTH => --$z,
- Facing::SOUTH => ++$z
- };
+ [$dx, $dy, $dz] = Facing::OFFSET[$j];
+ $x = $originX + $dx;
+ $y = $originY + $dy;
+ $z = $originZ + $dz;
if(!$this->world->isInWorld($x, $y, $z) || !$this->canFlowInto($this->world->getBlockAt($x, $y, $z))){
$this->flowCostVisited[World::blockHash($x, $y, $z)] = self::BLOCKED;
diff --git a/src/block/utils/MobHeadType.php b/src/block/utils/MobHeadType.php
index 45d31e9bb03..af1fe1c4c2a 100644
--- a/src/block/utils/MobHeadType.php
+++ b/src/block/utils/MobHeadType.php
@@ -23,45 +23,40 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static MobHeadType CREEPER()
* @method static MobHeadType DRAGON()
+ * @method static MobHeadType PIGLIN()
* @method static MobHeadType PLAYER()
* @method static MobHeadType SKELETON()
* @method static MobHeadType WITHER_SKELETON()
* @method static MobHeadType ZOMBIE()
*/
-final class MobHeadType{
- use EnumTrait {
- __construct as Enum___construct;
- }
-
- protected static function setup() : void{
- self::registerAll(
- new MobHeadType("skeleton", "Skeleton Skull"),
- new MobHeadType("wither_skeleton", "Wither Skeleton Skull"),
- new MobHeadType("zombie", "Zombie Head"),
- new MobHeadType("player", "Player Head"),
- new MobHeadType("creeper", "Creeper Head"),
- new MobHeadType("dragon", "Dragon Head")
- );
- }
+enum MobHeadType{
+ use LegacyEnumShimTrait;
- private function __construct(
- string $enumName,
- private string $displayName
- ){
- $this->Enum___construct($enumName);
- }
+ case SKELETON;
+ case WITHER_SKELETON;
+ case ZOMBIE;
+ case PLAYER;
+ case CREEPER;
+ case DRAGON;
+ case PIGLIN;
public function getDisplayName() : string{
- return $this->displayName;
+ return match($this){
+ self::SKELETON => "Skeleton Skull",
+ self::WITHER_SKELETON => "Wither Skeleton Skull",
+ self::ZOMBIE => "Zombie Head",
+ self::PLAYER => "Player Head",
+ self::CREEPER => "Creeper Head",
+ self::DRAGON => "Dragon Head",
+ self::PIGLIN => "Piglin Head"
+ };
}
}
diff --git a/src/block/utils/MultiAnyFacingTrait.php b/src/block/utils/MultiAnyFacingTrait.php
new file mode 100644
index 00000000000..66f26d98009
--- /dev/null
+++ b/src/block/utils/MultiAnyFacingTrait.php
@@ -0,0 +1,72 @@
+facingFlags($this->faces);
+ }
+
+ /** @return int[] */
+ public function getFaces() : array{ return $this->faces; }
+
+ public function hasFace(int $face) : bool{
+ return isset($this->faces[$face]);
+ }
+
+ /**
+ * @param int[] $faces
+ * @return $this
+ */
+ public function setFaces(array $faces) : self{
+ $uniqueFaces = [];
+ foreach($faces as $face){
+ Facing::validate($face);
+ $uniqueFaces[$face] = $face;
+ }
+ $this->faces = $uniqueFaces;
+ return $this;
+ }
+
+ /** @return $this */
+ public function setFace(int $face, bool $value) : self{
+ Facing::validate($face);
+ if($value){
+ $this->faces[$face] = $face;
+ }else{
+ unset($this->faces[$face]);
+ }
+ return $this;
+ }
+}
diff --git a/src/block/utils/MultiAnySupportTrait.php b/src/block/utils/MultiAnySupportTrait.php
new file mode 100644
index 00000000000..ae1da7befe1
--- /dev/null
+++ b/src/block/utils/MultiAnySupportTrait.php
@@ -0,0 +1,96 @@
+faces = $this->getInitialPlaceFaces($blockReplace);
+ $availableFaces = $this->getAvailableFaces();
+
+ if(count($availableFaces) === 0){
+ return false;
+ }
+
+ $opposite = Facing::opposite($face);
+ $placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
+ $this->faces[$placedFace] = $placedFace;
+
+ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+ }
+
+ public function onNearbyBlockChange() : void{
+ $changed = false;
+
+ foreach($this->faces as $face){
+ if($this->getAdjacentSupportType($face) !== SupportType::FULL){
+ unset($this->faces[$face]);
+ $changed = true;
+ }
+ }
+
+ if($changed){
+ $world = $this->position->getWorld();
+ if(count($this->faces) === 0){
+ $world->useBreakOn($this->position);
+ }else{
+ $world->setBlock($this->position, $this);
+ }
+ }
+ }
+
+ /**
+ * @return array $faces
+ */
+ private function getAvailableFaces() : array{
+ $faces = [];
+ foreach(Facing::ALL as $face){
+ if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
+ $faces[$face] = $face;
+ }
+ }
+ return $faces;
+ }
+}
diff --git a/src/block/utils/MushroomBlockType.php b/src/block/utils/MushroomBlockType.php
index 7f001d13da7..6bc499b2360 100644
--- a/src/block/utils/MushroomBlockType.php
+++ b/src/block/utils/MushroomBlockType.php
@@ -23,13 +23,11 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static MushroomBlockType ALL_CAP()
* @method static MushroomBlockType CAP_EAST()
@@ -43,22 +41,18 @@
* @method static MushroomBlockType CAP_WEST()
* @method static MushroomBlockType PORES()
*/
-final class MushroomBlockType{
- use EnumTrait;
+enum MushroomBlockType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("PORES"),
- new self("CAP_NORTHWEST"),
- new self("CAP_NORTH"),
- new self("CAP_NORTHEAST"),
- new self("CAP_WEST"),
- new self("CAP_MIDDLE"),
- new self("CAP_EAST"),
- new self("CAP_SOUTHWEST"),
- new self("CAP_SOUTH"),
- new self("CAP_SOUTHEAST"),
- new self("ALL_CAP")
- );
- }
+ case PORES;
+ case CAP_NORTHWEST;
+ case CAP_NORTH;
+ case CAP_NORTHEAST;
+ case CAP_WEST;
+ case CAP_MIDDLE;
+ case CAP_EAST;
+ case CAP_SOUTHWEST;
+ case CAP_SOUTH;
+ case CAP_SOUTHEAST;
+ case ALL_CAP;
}
diff --git a/src/block/utils/RecordType.php b/src/block/utils/RecordType.php
index 9b0ab580fc6..e63cee9204d 100644
--- a/src/block/utils/RecordType.php
+++ b/src/block/utils/RecordType.php
@@ -26,13 +26,12 @@
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
+use function spl_object_id;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static RecordType DISK_11()
* @method static RecordType DISK_13()
@@ -49,48 +48,63 @@
* @method static RecordType DISK_STRAD()
* @method static RecordType DISK_WAIT()
* @method static RecordType DISK_WARD()
+ *
+ * @phpstan-type TMetadata array{0: string, 1: LevelSoundEvent::*, 2: Translatable}
*/
-final class RecordType{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum RecordType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new RecordType("disk_13", "C418 - 13", LevelSoundEvent::RECORD_13, KnownTranslationFactory::item_record_13_desc()),
- new RecordType("disk_5", "Samuel Åberg - 5", LevelSoundEvent::RECORD_5, KnownTranslationFactory::item_record_5_desc()),
- new RecordType("disk_cat", "C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()),
- new RecordType("disk_blocks", "C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()),
- new RecordType("disk_chirp", "C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()),
- new RecordType("disk_far", "C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()),
- new RecordType("disk_mall", "C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()),
- new RecordType("disk_mellohi", "C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()),
- new RecordType("disk_otherside", "Lena Raine - otherside", LevelSoundEvent::RECORD_OTHERSIDE, KnownTranslationFactory::item_record_otherside_desc()),
- new RecordType("disk_pigstep", "Lena Raine - Pigstep", LevelSoundEvent::RECORD_PIGSTEP, KnownTranslationFactory::item_record_pigstep_desc()),
- new RecordType("disk_stal", "C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()),
- new RecordType("disk_strad", "C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()),
- new RecordType("disk_ward", "C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()),
- new RecordType("disk_11", "C418 - 11", LevelSoundEvent::RECORD_11, KnownTranslationFactory::item_record_11_desc()),
- new RecordType("disk_wait", "C418 - wait", LevelSoundEvent::RECORD_WAIT, KnownTranslationFactory::item_record_wait_desc())
- );
- }
+ case DISK_13;
+ case DISK_5;
+ case DISK_CAT;
+ case DISK_BLOCKS;
+ case DISK_CHIRP;
+ case DISK_FAR;
+ case DISK_MALL;
+ case DISK_MELLOHI;
+ case DISK_OTHERSIDE;
+ case DISK_PIGSTEP;
+ case DISK_STAL;
+ case DISK_STRAD;
+ case DISK_WARD;
+ case DISK_11;
+ case DISK_WAIT;
- private function __construct(
- string $enumName,
- private string $soundName,
- private int $soundId,
- private Translatable $translatableName
- ){
- $this->Enum___construct($enumName);
+ /**
+ * @phpstan-return TMetadata
+ */
+ private function getMetadata() : array{
+ /** @phpstan-var array $cache */
+ static $cache = [];
+
+ return $cache[spl_object_id($this)] ??= match($this){
+ self::DISK_13 => ["C418 - 13", LevelSoundEvent::RECORD_13, KnownTranslationFactory::item_record_13_desc()],
+ self::DISK_5 => ["Samuel Åberg - 5", LevelSoundEvent::RECORD_5, KnownTranslationFactory::item_record_5_desc()],
+ self::DISK_CAT => ["C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()],
+ self::DISK_BLOCKS => ["C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()],
+ self::DISK_CHIRP => ["C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()],
+ self::DISK_FAR => ["C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()],
+ self::DISK_MALL => ["C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()],
+ self::DISK_MELLOHI => ["C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()],
+ self::DISK_OTHERSIDE => ["Lena Raine - otherside", LevelSoundEvent::RECORD_OTHERSIDE, KnownTranslationFactory::item_record_otherside_desc()],
+ self::DISK_PIGSTEP => ["Lena Raine - Pigstep", LevelSoundEvent::RECORD_PIGSTEP, KnownTranslationFactory::item_record_pigstep_desc()],
+ self::DISK_STAL => ["C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()],
+ self::DISK_STRAD => ["C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()],
+ self::DISK_WARD => ["C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()],
+ self::DISK_11 => ["C418 - 11", LevelSoundEvent::RECORD_11, KnownTranslationFactory::item_record_11_desc()],
+ self::DISK_WAIT => ["C418 - wait", LevelSoundEvent::RECORD_WAIT, KnownTranslationFactory::item_record_wait_desc()]
+ };
}
public function getSoundName() : string{
- return $this->soundName;
+ return $this->getMetadata()[0];
}
public function getSoundId() : int{
- return $this->soundId;
+ return $this->getMetadata()[1];
}
- public function getTranslatableName() : Translatable{ return $this->translatableName; }
+ public function getTranslatableName() : Translatable{
+ return $this->getMetadata()[2];
+ }
}
diff --git a/src/block/utils/SaplingType.php b/src/block/utils/SaplingType.php
index 516ee1516bc..9fe87936cd0 100644
--- a/src/block/utils/SaplingType.php
+++ b/src/block/utils/SaplingType.php
@@ -23,14 +23,12 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
use pocketmine\world\generator\object\TreeType;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static SaplingType ACACIA()
* @method static SaplingType BIRCH()
@@ -39,33 +37,29 @@
* @method static SaplingType OAK()
* @method static SaplingType SPRUCE()
*/
-final class SaplingType{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum SaplingType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("oak", TreeType::OAK()),
- new self("spruce", TreeType::SPRUCE()),
- new self("birch", TreeType::BIRCH()),
- new self("jungle", TreeType::JUNGLE()),
- new self("acacia", TreeType::ACACIA()),
- new self("dark_oak", TreeType::DARK_OAK()),
- //TODO: cherry
- );
- }
+ case OAK;
+ case SPRUCE;
+ case BIRCH;
+ case JUNGLE;
+ case ACACIA;
+ case DARK_OAK;
+ //TODO: cherry
- private function __construct(
- string $enumName,
- private TreeType $treeType,
- ){
- $this->Enum___construct($enumName);
+ public function getTreeType() : TreeType{
+ return match($this){
+ self::OAK => TreeType::OAK,
+ self::SPRUCE => TreeType::SPRUCE,
+ self::BIRCH => TreeType::BIRCH,
+ self::JUNGLE => TreeType::JUNGLE,
+ self::ACACIA => TreeType::ACACIA,
+ self::DARK_OAK => TreeType::DARK_OAK,
+ };
}
- public function getTreeType() : TreeType{ return $this->treeType; }
-
public function getDisplayName() : string{
- return $this->treeType->getDisplayName();
+ return $this->getTreeType()->getDisplayName();
}
}
diff --git a/src/block/utils/SignLikeRotationTrait.php b/src/block/utils/SignLikeRotationTrait.php
index 233c750954d..36b2dce3fa1 100644
--- a/src/block/utils/SignLikeRotationTrait.php
+++ b/src/block/utils/SignLikeRotationTrait.php
@@ -31,7 +31,7 @@ trait SignLikeRotationTrait{
private $rotation = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
- $w->boundedInt(4, 0, 15, $this->rotation);
+ $w->boundedIntAuto(0, 15, $this->rotation);
}
public function getRotation() : int{ return $this->rotation; }
diff --git a/src/block/utils/SlabType.php b/src/block/utils/SlabType.php
index 1da1b3e5a34..90f3e095357 100644
--- a/src/block/utils/SlabType.php
+++ b/src/block/utils/SlabType.php
@@ -23,26 +23,20 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static SlabType BOTTOM()
* @method static SlabType DOUBLE()
* @method static SlabType TOP()
*/
-final class SlabType{
- use EnumTrait;
+enum SlabType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("bottom"),
- new self("top"),
- new self("double")
- );
- }
+ case BOTTOM;
+ case TOP;
+ case DOUBLE;
}
diff --git a/src/block/utils/StairShape.php b/src/block/utils/StairShape.php
index c98aec8fbad..b89b9adbb29 100644
--- a/src/block/utils/StairShape.php
+++ b/src/block/utils/StairShape.php
@@ -23,13 +23,11 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static StairShape INNER_LEFT()
* @method static StairShape INNER_RIGHT()
@@ -37,16 +35,12 @@
* @method static StairShape OUTER_RIGHT()
* @method static StairShape STRAIGHT()
*/
-final class StairShape{
- use EnumTrait;
+enum StairShape{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("straight"),
- new self("inner_left"),
- new self("inner_right"),
- new self("outer_left"),
- new self("outer_right")
- );
- }
+ case STRAIGHT;
+ case INNER_LEFT;
+ case INNER_RIGHT;
+ case OUTER_LEFT;
+ case OUTER_RIGHT;
}
diff --git a/src/block/utils/StaticSupportTrait.php b/src/block/utils/StaticSupportTrait.php
new file mode 100644
index 00000000000..0e40230cc6e
--- /dev/null
+++ b/src/block/utils/StaticSupportTrait.php
@@ -0,0 +1,57 @@
+canBeSupportedAt($blockReplace) && parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
+ }
+
+ /**
+ * @see Block::onNearbyBlockChange()
+ */
+ public function onNearbyBlockChange() : void{
+ if(!$this->canBeSupportedAt($this)){
+ $this->position->getWorld()->useBreakOn($this->position);
+ }else{
+ parent::onNearbyBlockChange();
+ }
+ }
+}
diff --git a/src/block/utils/SupportType.php b/src/block/utils/SupportType.php
index b4480bf57fd..9513d864011 100644
--- a/src/block/utils/SupportType.php
+++ b/src/block/utils/SupportType.php
@@ -23,36 +23,30 @@
namespace pocketmine\block\utils;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static SupportType CENTER()
* @method static SupportType EDGE()
* @method static SupportType FULL()
* @method static SupportType NONE()
*/
-final class SupportType{
- use EnumTrait;
-
- protected static function setup() : void{
- self::registerAll(
- new self("full"),
- new self("center"),
- new self("edge"),
- new self("none")
- );
- }
+enum SupportType{
+ use LegacyEnumShimTrait;
+
+ case FULL;
+ case CENTER;
+ case EDGE;
+ case NONE;
public function hasEdgeSupport() : bool{
- return $this->equals(self::EDGE()) || $this->equals(self::FULL());
+ return $this === self::EDGE || $this === self::FULL;
}
public function hasCenterSupport() : bool{
- return $this->equals(self::CENTER()) || $this->equals(self::FULL());
+ return $this === self::CENTER || $this === self::FULL;
}
}
diff --git a/src/block/utils/TallGrassTrait.php b/src/block/utils/TallGrassTrait.php
new file mode 100644
index 00000000000..88fc36e1276
--- /dev/null
+++ b/src/block/utils/TallGrassTrait.php
@@ -0,0 +1,54 @@
+Enum___construct($enumName);
+ public function getDisplayName() : string{
+ return match($this){
+ self::OAK => "Oak",
+ self::SPRUCE => "Spruce",
+ self::BIRCH => "Birch",
+ self::JUNGLE => "Jungle",
+ self::ACACIA => "Acacia",
+ self::DARK_OAK => "Dark Oak",
+ self::MANGROVE => "Mangrove",
+ self::CRIMSON => "Crimson",
+ self::WARPED => "Warped",
+ self::CHERRY => "Cherry",
+ };
}
- public function getDisplayName() : string{ return $this->displayName; }
-
- public function isFlammable() : bool{ return $this->flammable; }
+ public function isFlammable() : bool{
+ return $this !== self::CRIMSON && $this !== self::WARPED;
+ }
- public function getStandardLogSuffix() : ?string{ return $this->standardLogSuffix; }
+ public function getStandardLogSuffix() : ?string{
+ return $this === self::CRIMSON || $this === self::WARPED ? "Stem" : null;
+ }
- public function getAllSidedLogSuffix() : ?string{ return $this->allSidedLogSuffix; }
+ public function getAllSidedLogSuffix() : ?string{
+ return $this === self::CRIMSON || $this === self::WARPED ? "Hyphae" : null;
+ }
}
diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php
index 1268e715b7f..a6a7e303bbf 100644
--- a/src/command/SimpleCommandMap.php
+++ b/src/command/SimpleCommandMap.php
@@ -64,6 +64,7 @@
use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
+use pocketmine\command\defaults\XpCommand;
use pocketmine\command\utils\CommandStringHelper;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
@@ -128,7 +129,8 @@ private function setDefaultCommands() : void{
new TitleCommand(),
new TransferServerCommand(),
new VersionCommand(),
- new WhitelistCommand()
+ new WhitelistCommand(),
+ new XpCommand(),
]);
}
diff --git a/src/command/defaults/DefaultGamemodeCommand.php b/src/command/defaults/DefaultGamemodeCommand.php
index 3860b9e34e8..d3030eb27b3 100644
--- a/src/command/defaults/DefaultGamemodeCommand.php
+++ b/src/command/defaults/DefaultGamemodeCommand.php
@@ -28,6 +28,7 @@
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\GameMode;
+use pocketmine\ServerProperties;
use function count;
class DefaultGamemodeCommand extends VanillaCommand{
@@ -52,7 +53,8 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
return true;
}
- $sender->getServer()->getConfigGroup()->setConfigString("gamemode", $gameMode->name());
+ //TODO: this probably shouldn't use the enum name directly
+ $sender->getServer()->getConfigGroup()->setConfigString(ServerProperties::GAME_MODE, $gameMode->name);
$sender->sendMessage(KnownTranslationFactory::commands_defaultgamemode_success($gameMode->getTranslatableName()));
return true;
}
diff --git a/src/command/defaults/DifficultyCommand.php b/src/command/defaults/DifficultyCommand.php
index 98eb3be3bce..dee75e025e8 100644
--- a/src/command/defaults/DifficultyCommand.php
+++ b/src/command/defaults/DifficultyCommand.php
@@ -28,6 +28,7 @@
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
+use pocketmine\ServerProperties;
use pocketmine\world\World;
use function count;
@@ -54,7 +55,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
}
if($difficulty !== -1){
- $sender->getServer()->getConfigGroup()->setConfigInt("difficulty", $difficulty);
+ $sender->getServer()->getConfigGroup()->setConfigInt(ServerProperties::DIFFICULTY, $difficulty);
//TODO: add per-world support
foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){
diff --git a/src/command/defaults/EnchantCommand.php b/src/command/defaults/EnchantCommand.php
index 583bd59ec91..191a146b036 100644
--- a/src/command/defaults/EnchantCommand.php
+++ b/src/command/defaults/EnchantCommand.php
@@ -25,6 +25,7 @@
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
+use pocketmine\item\enchantment\EnchantingHelper;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\StringToEnchantmentParser;
use pocketmine\lang\KnownTranslationFactory;
@@ -76,8 +77,9 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
}
}
- $item->addEnchantment(new EnchantmentInstance($enchantment, $level));
- $player->getInventory()->setItemInHand($item);
+ //this is necessary to deal with enchanted books, which are a different item type than regular books
+ $enchantedItem = EnchantingHelper::enchantItem($item, [new EnchantmentInstance($enchantment, $level)]);
+ $player->getInventory()->setItemInHand($enchantedItem);
self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName()));
return true;
diff --git a/src/command/defaults/GamemodeCommand.php b/src/command/defaults/GamemodeCommand.php
index 86ca8e9df66..666626a692d 100644
--- a/src/command/defaults/GamemodeCommand.php
+++ b/src/command/defaults/GamemodeCommand.php
@@ -61,13 +61,13 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
return true;
}
- if($target->getGamemode()->equals($gameMode)){
+ if($target->getGamemode() === $gameMode){
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
return true;
}
$target->setGamemode($gameMode);
- if(!$gameMode->equals($target->getGamemode())){
+ if($gameMode !== $target->getGamemode()){
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
}else{
if($target === $sender){
diff --git a/src/command/defaults/KillCommand.php b/src/command/defaults/KillCommand.php
index e58234ead3e..fbc7436902d 100644
--- a/src/command/defaults/KillCommand.php
+++ b/src/command/defaults/KillCommand.php
@@ -53,7 +53,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
return true;
}
- $player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
+ $player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, $player->getHealth()));
if($player === $sender){
$sender->sendMessage(KnownTranslationFactory::commands_kill_successful($sender->getName()));
}else{
diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php
index b5ca18628aa..3c0701ea423 100644
--- a/src/command/defaults/TimingsCommand.php
+++ b/src/command/defaults/TimingsCommand.php
@@ -35,6 +35,7 @@
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;
@@ -130,7 +131,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
];
fclose($fileTimings);
- $host = $sender->getServer()->getConfigGroup()->getPropertyString("timings.host", "timings.pmmp.io");
+ $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
diff --git a/src/command/defaults/WhitelistCommand.php b/src/command/defaults/WhitelistCommand.php
index 65860aefae5..fdf01ff5623 100644
--- a/src/command/defaults/WhitelistCommand.php
+++ b/src/command/defaults/WhitelistCommand.php
@@ -30,6 +30,7 @@
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\Server;
+use pocketmine\ServerProperties;
use function count;
use function implode;
use function sort;
@@ -71,7 +72,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
case "on":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_ENABLE)){
$server = $sender->getServer();
- $server->getConfigGroup()->setConfigBool("white-list", true);
+ $server->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, true);
$this->kickNonWhitelistedPlayers($server);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_enabled());
}
@@ -79,7 +80,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
return true;
case "off":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_DISABLE)){
- $sender->getServer()->getConfigGroup()->setConfigBool("white-list", false);
+ $sender->getServer()->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, false);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_disabled());
}
diff --git a/src/command/defaults/XpCommand.php b/src/command/defaults/XpCommand.php
new file mode 100644
index 00000000000..cb035236574
--- /dev/null
+++ b/src/command/defaults/XpCommand.php
@@ -0,0 +1,89 @@
+setPermissions([
+ DefaultPermissionNames::COMMAND_XP_SELF,
+ DefaultPermissionNames::COMMAND_XP_OTHER
+ ]);
+ }
+
+ public function execute(CommandSender $sender, string $commandLabel, array $args){
+ if(count($args) < 1){
+ throw new InvalidCommandSyntaxException();
+ }
+
+ $player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
+ if($player === null){
+ return true;
+ }
+
+ $xpManager = $player->getXpManager();
+ if(str_ends_with($args[0], "L")){
+ $xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError();
+ $maxXpLevel = (int) $xpLevelAttr->getMaxValue();
+ $currentXpLevel = $xpManager->getXpLevel();
+ $xpLevels = $this->getInteger($sender, substr($args[0], 0, -1), -$currentXpLevel, $maxXpLevel - $currentXpLevel);
+ if($xpLevels >= 0){
+ $xpManager->addXpLevels($xpLevels, false);
+ $sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName()));
+ }else{
+ $xpLevels = abs($xpLevels);
+ $xpManager->subtractXpLevels($xpLevels);
+ $sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName()));
+ }
+ }else{
+ $xp = $this->getInteger($sender, $args[0], max: Limits::INT32_MAX);
+ if($xp < 0){
+ $sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED));
+ }else{
+ $xpManager->addXp($xp, false);
+ $sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName()));
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/console/ConsoleReaderChildProcess.php b/src/console/ConsoleReaderChildProcess.php
index c37aaa8c98c..c0c4f0048f1 100644
--- a/src/console/ConsoleReaderChildProcess.php
+++ b/src/console/ConsoleReaderChildProcess.php
@@ -29,23 +29,21 @@
use function cli_set_process_title;
use function count;
use function dirname;
-use function feof;
use function fwrite;
-use function stream_socket_client;
+use function is_numeric;
+use const PHP_EOL;
+use const STDOUT;
-require dirname(__DIR__, 2) . '/vendor/autoload.php';
-
-if(count($argv) !== 2){
- die("Please provide a server to connect to");
+if(count($argv) !== 2 || !is_numeric($argv[1])){
+ echo "Usage: " . $argv[0] . " " . PHP_EOL;
+ exit(1);
}
+$commandTokenSeed = (int) $argv[1];
+
+require dirname(__DIR__, 2) . '/vendor/autoload.php';
+
@cli_set_process_title('PocketMine-MP Console Reader');
-$errCode = null;
-$errMessage = null;
-$socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0);
-if($socket === false){
- throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
-}
/** @phpstan-var ThreadSafeArray $channel */
$channel = new ThreadSafeArray();
@@ -75,15 +73,15 @@ public function run() : void{
};
$thread->start(NativeThread::INHERIT_NONE);
-while(!feof($socket)){
+while(true){
$line = $channel->synchronized(function() use ($channel) : ?string{
if(count($channel) === 0){
$channel->wait(1_000_000);
}
- $line = $channel->shift();
- return $line;
+ return $channel->shift();
});
- if(@fwrite($socket, ($line ?? "") . "\n") === false){
+ $message = $line !== null ? ConsoleReaderChildProcessUtils::createMessage($line, $commandTokenSeed) : "";
+ if(@fwrite(STDOUT, $message . "\n") === false){
//Always send even if there's no line, to check if the parent is alive
//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
//false even though the connection is actually broken. However, fwrite() will fail.
@@ -94,4 +92,4 @@ public function run() : void{
//For simplicity's sake, we don't bother with a graceful shutdown here.
//The parent process would normally forcibly terminate the child process anyway, so we only reach this point if the
//parent process was terminated forcibly and didn't clean up after itself.
-Process::kill(Process::pid(), false);
+Process::kill(Process::pid());
diff --git a/src/console/ConsoleReaderChildProcessDaemon.php b/src/console/ConsoleReaderChildProcessDaemon.php
index 138559f06e9..f7300b7a520 100644
--- a/src/console/ConsoleReaderChildProcessDaemon.php
+++ b/src/console/ConsoleReaderChildProcessDaemon.php
@@ -29,19 +29,16 @@
use function base64_encode;
use function fgets;
use function fopen;
+use function mt_rand;
use function preg_replace;
use function proc_close;
use function proc_open;
use function proc_terminate;
+use function rtrim;
use function sprintf;
use function stream_select;
-use function stream_socket_accept;
-use function stream_socket_get_name;
-use function stream_socket_server;
-use function stream_socket_shutdown;
use function trim;
use const PHP_BINARY;
-use const STREAM_SHUT_RDWR;
/**
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
@@ -58,44 +55,44 @@
* communication.
*/
final class ConsoleReaderChildProcessDaemon{
+ public const TOKEN_DELIMITER = ":";
+ public const TOKEN_HASH_ALGO = "xxh3";
+
private \PrefixedLogger $logger;
/** @var resource */
private $subprocess;
/** @var resource */
private $socket;
+ private int $commandTokenSeed;
public function __construct(
\Logger $logger
){
$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
+ $this->commandTokenSeed = mt_rand();
$this->prepareSubprocess();
}
private function prepareSubprocess() : void{
- $server = stream_socket_server("tcp://127.0.0.1:0");
- if($server === false){
- throw new \RuntimeException("Failed to open console reader socket server");
- }
- $address = Utils::assumeNotFalse(stream_socket_get_name($server, false), "stream_socket_get_name() shouldn't return false here");
-
//Windows sucks, and likes to corrupt UTF-8 file paths when they travel to the subprocess, so we base64 encode
//the path to avoid the problem. This is an abysmally shitty hack, but here we are :(
$sub = Utils::assumeNotFalse(proc_open(
- [PHP_BINARY, '-dopcache.enable_cli=0', '-r', sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))), $address],
[
+ PHP_BINARY,
+ '-dopcache.enable_cli=0',
+ '-r',
+ sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))),
+ (string) $this->commandTokenSeed
+ ],
+ [
+ 1 => ['socket'],
2 => fopen("php://stderr", "w"),
],
$pipes
), "Something has gone horribly wrong");
- $client = stream_socket_accept($server, 15);
- if($client === false){
- throw new AssumptionFailedError("stream_socket_accept() returned false");
- }
- stream_socket_shutdown($server, STREAM_SHUT_RDWR);
-
$this->subprocess = $sub;
- $this->socket = $client;
+ $this->socket = $pipes[1];
}
private function shutdownSubprocess() : void{
@@ -104,7 +101,6 @@ private function shutdownSubprocess() : void{
//the first place).
proc_terminate($this->subprocess);
proc_close($this->subprocess);
- stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
}
public function readLine() : ?string{
@@ -112,13 +108,27 @@ public function readLine() : ?string{
$w = null;
$e = null;
if(stream_select($r, $w, $e, 0, 0) === 1){
- $command = fgets($this->socket);
- if($command === false){
+ $line = fgets($this->socket);
+ if($line === false){
$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
$this->shutdownSubprocess();
$this->prepareSubprocess();
return null;
}
+ $line = rtrim($line, "\n");
+
+ if($line === ""){
+ //keepalive
+ return null;
+ }
+
+ $command = ConsoleReaderChildProcessUtils::parseMessage($line, $this->commandTokenSeed);
+ if($command === null){
+ //this is not a command - it may be some kind of error output from the subprocess
+ //write it directly to the console
+ $this->logger->warning("Unexpected output from child process: $line");
+ return null;
+ }
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
diff --git a/src/console/ConsoleReaderChildProcessUtils.php b/src/console/ConsoleReaderChildProcessUtils.php
new file mode 100644
index 00000000000..661e9b0f7bb
--- /dev/null
+++ b/src/console/ConsoleReaderChildProcessUtils.php
@@ -0,0 +1,71 @@
+ $counter]);
+ $counter++;
+ return $line . self::TOKEN_DELIMITER . $token;
+ }
+
+ /**
+ * Extracts a command from an IPC message from the console reader subprocess.
+ * Returns the user's input command, or null if this isn't a user input.
+ */
+ public static function parseMessage(string $message, int &$counter) : ?string{
+ $delimiterPos = strrpos($message, self::TOKEN_DELIMITER);
+ if($delimiterPos !== false){
+ $left = substr($message, 0, $delimiterPos);
+ $right = substr($message, $delimiterPos + strlen(self::TOKEN_DELIMITER));
+ $expectedToken = hash(self::TOKEN_HASH_ALGO, $left, options: ['seed' => $counter]);
+
+ if($expectedToken === $right){
+ $counter++;
+ return $left;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php
index 55e6f2f41d9..4942a127bd2 100644
--- a/src/crafting/CraftingManager.php
+++ b/src/crafting/CraftingManager.php
@@ -31,6 +31,7 @@
use pocketmine\utils\ObjectSet;
use function array_search;
use function count;
+use function spl_object_id;
use function usort;
class CraftingManager{
@@ -87,8 +88,8 @@ public function __construct(){
$this->recipeRegisteredCallbacks = new ObjectSet();
$this->recipeUnregisteredCallbacks = new ObjectSet();
- foreach(FurnaceType::getAll() as $furnaceType){
- $this->furnaceRecipeManagers[$furnaceType->id()] = new FurnaceRecipeManager();
+ foreach(FurnaceType::cases() as $furnaceType){
+ $this->furnaceRecipeManagers[spl_object_id($furnaceType)] = new FurnaceRecipeManager();
}
$recipeRegisteredCallbacks = $this->recipeRegisteredCallbacks;
@@ -193,7 +194,7 @@ public function getCraftingRecipeFromIndex(int $index) : ?CraftingRecipe{
}
public function getFurnaceRecipeManager(FurnaceType $furnaceType) : FurnaceRecipeManager{
- return $this->furnaceRecipeManagers[$furnaceType->id()];
+ return $this->furnaceRecipeManagers[spl_object_id($furnaceType)];
}
/**
diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php
index 0a28ca328f9..616c2a4bd37 100644
--- a/src/crafting/CraftingManagerFromDataHelper.php
+++ b/src/crafting/CraftingManagerFromDataHelper.php
@@ -209,15 +209,12 @@ private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string
public static function make(string $directoryPath) : CraftingManager{
$result = new CraftingManager();
- $ingredientDeserializerFunc = \Closure::fromCallable([self::class, "deserializeIngredient"]);
- $itemDeserializerFunc = \Closure::fromCallable([self::class, 'deserializeItemStack']);
-
foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'shapeless_crafting.json'), ShapelessRecipeData::class) as $recipe){
$recipeType = match($recipe->block){
- "crafting_table" => ShapelessRecipeType::CRAFTING(),
- "stonecutter" => ShapelessRecipeType::STONECUTTER(),
- "smithing_table" => ShapelessRecipeType::SMITHING(),
- "cartography_table" => ShapelessRecipeType::CARTOGRAPHY(),
+ "crafting_table" => ShapelessRecipeType::CRAFTING,
+ "stonecutter" => ShapelessRecipeType::STONECUTTER,
+ "smithing_table" => ShapelessRecipeType::SMITHING,
+ "cartography_table" => ShapelessRecipeType::CARTOGRAPHY,
default => null
};
if($recipeType === null){
@@ -225,7 +222,7 @@ public static function make(string $directoryPath) : CraftingManager{
}
$inputs = [];
foreach($recipe->input as $inputData){
- $input = $ingredientDeserializerFunc($inputData);
+ $input = self::deserializeIngredient($inputData);
if($input === null){ //unknown input item
continue 2;
}
@@ -233,12 +230,13 @@ public static function make(string $directoryPath) : CraftingManager{
}
$outputs = [];
foreach($recipe->output as $outputData){
- $output = $itemDeserializerFunc($outputData);
+ $output = self::deserializeItemStack($outputData);
if($output === null){ //unknown output item
continue 2;
}
$outputs[] = $output;
}
+ //TODO: check unlocking requirements - our current system doesn't support this
$result->registerShapelessRecipe(new ShapelessRecipe(
$inputs,
$outputs,
@@ -251,7 +249,7 @@ public static function make(string $directoryPath) : CraftingManager{
}
$inputs = [];
foreach(Utils::stringifyKeys($recipe->input) as $symbol => $inputData){
- $input = $ingredientDeserializerFunc($inputData);
+ $input = self::deserializeIngredient($inputData);
if($input === null){ //unknown input item
continue 2;
}
@@ -259,12 +257,13 @@ public static function make(string $directoryPath) : CraftingManager{
}
$outputs = [];
foreach($recipe->output as $outputData){
- $output = $itemDeserializerFunc($outputData);
+ $output = self::deserializeItemStack($outputData);
if($output === null){ //unknown output item
continue 2;
}
$outputs[] = $output;
}
+ //TODO: check unlocking requirements - our current system doesn't support this
$result->registerShapedRecipe(new ShapedRecipe(
$recipe->shape,
$inputs,
@@ -273,10 +272,11 @@ public static function make(string $directoryPath) : CraftingManager{
}
foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'smelting.json'), FurnaceRecipeData::class) as $recipe){
$furnaceType = match ($recipe->block){
- "furnace" => FurnaceType::FURNACE(),
- "blast_furnace" => FurnaceType::BLAST_FURNACE(),
- "smoker" => FurnaceType::SMOKER(),
- //TODO: campfire
+ "furnace" => FurnaceType::FURNACE,
+ "blast_furnace" => FurnaceType::BLAST_FURNACE,
+ "smoker" => FurnaceType::SMOKER,
+ "campfire" => FurnaceType::CAMPFIRE,
+ "soul_campfire" => FurnaceType::SOUL_CAMPFIRE,
default => null
};
if($furnaceType === null){
diff --git a/src/crafting/FurnaceType.php b/src/crafting/FurnaceType.php
index 649ff4484df..89834c82145 100644
--- a/src/crafting/FurnaceType.php
+++ b/src/crafting/FurnaceType.php
@@ -23,40 +23,51 @@
namespace pocketmine\crafting;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
use pocketmine\world\sound\BlastFurnaceSound;
+use pocketmine\world\sound\CampfireSound;
use pocketmine\world\sound\FurnaceSound;
use pocketmine\world\sound\SmokerSound;
use pocketmine\world\sound\Sound;
+use function spl_object_id;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static FurnaceType BLAST_FURNACE()
+ * @method static FurnaceType CAMPFIRE()
* @method static FurnaceType FURNACE()
* @method static FurnaceType SMOKER()
+ * @method static FurnaceType SOUL_CAMPFIRE()
+ *
+ * @phpstan-type TMetadata array{0: int, 1: Sound}
*/
-final class FurnaceType{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum FurnaceType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("furnace", 200, new FurnaceSound()),
- new self("blast_furnace", 100, new BlastFurnaceSound()),
- new self("smoker", 100, new SmokerSound()),
- );
- }
+ case FURNACE;
+ case BLAST_FURNACE;
+ case SMOKER;
+ case CAMPFIRE;
+ case SOUL_CAMPFIRE;
+
+ /**
+ * @phpstan-return TMetadata
+ */
+ private function getMetadata() : array{
+ /** @phpstan-var array $cache */
+ static $cache = [];
- private function __construct(string $enumName, private int $cookDurationTicks, private Sound $cookSound){
- $this->Enum___construct($enumName);
+ return $cache[spl_object_id($this)] ??= match($this){
+ self::FURNACE => [200, new FurnaceSound()],
+ self::BLAST_FURNACE => [100, new BlastFurnaceSound()],
+ self::SMOKER => [100, new SmokerSound()],
+ self::CAMPFIRE, self::SOUL_CAMPFIRE => [600, new CampfireSound()]
+ };
}
- public function getCookDurationTicks() : int{ return $this->cookDurationTicks; }
+ public function getCookDurationTicks() : int{ return $this->getMetadata()[0]; }
- public function getCookSound() : Sound{ return $this->cookSound; }
+ public function getCookSound() : Sound{ return $this->getMetadata()[1]; }
}
diff --git a/src/crafting/ShapelessRecipeType.php b/src/crafting/ShapelessRecipeType.php
index b115c22f49c..4f4624a4bff 100644
--- a/src/crafting/ShapelessRecipeType.php
+++ b/src/crafting/ShapelessRecipeType.php
@@ -23,28 +23,22 @@
namespace pocketmine\crafting;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static ShapelessRecipeType CARTOGRAPHY()
* @method static ShapelessRecipeType CRAFTING()
* @method static ShapelessRecipeType SMITHING()
* @method static ShapelessRecipeType STONECUTTER()
*/
-final class ShapelessRecipeType{
- use EnumTrait;
+enum ShapelessRecipeType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("crafting"),
- new self("stonecutter"),
- new self("smithing"),
- new self("cartography")
- );
- }
+ case CRAFTING;
+ case STONECUTTER;
+ case SMITHING;
+ case CARTOGRAPHY;
}
diff --git a/src/crafting/json/ShapedRecipeData.php b/src/crafting/json/ShapedRecipeData.php
index dd040f516ed..965a437ea62 100644
--- a/src/crafting/json/ShapedRecipeData.php
+++ b/src/crafting/json/ShapedRecipeData.php
@@ -23,7 +23,9 @@
namespace pocketmine\crafting\json;
-final class ShapedRecipeData{
+use function count;
+
+final class ShapedRecipeData implements \JsonSerializable{
/**
* @required
* @var string[]
@@ -51,22 +53,39 @@ final class ShapedRecipeData{
/** @required */
public int $priority;
+ /** @var RecipeIngredientData[] */
+ public array $unlockingIngredients = [];
+
/**
* TODO: convert this to use promoted properties - avoiding them for now since it would break JsonMapper
*
* @param string[] $shape
* @param RecipeIngredientData[] $input
* @param ItemStackData[] $output
+ * @param RecipeIngredientData[] $unlockingIngredients
*
* @phpstan-param list $shape
* @phpstan-param array $input
* @phpstan-param list $output
+ * @phpstan-param list $unlockingIngredients
*/
- public function __construct(array $shape, array $input, array $output, string $block, int $priority){
+ public function __construct(array $shape, array $input, array $output, string $block, int $priority, array $unlockingIngredients = []){
$this->block = $block;
$this->priority = $priority;
$this->shape = $shape;
$this->input = $input;
$this->output = $output;
+ $this->unlockingIngredients = $unlockingIngredients;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize() : array{
+ $result = (array) $this;
+ if(count($this->unlockingIngredients) === 0){
+ unset($result["unlockingIngredients"]);
+ }
+ return $result;
}
}
diff --git a/src/crafting/json/ShapelessRecipeData.php b/src/crafting/json/ShapelessRecipeData.php
index d59bafbbf3f..23cfebde0b0 100644
--- a/src/crafting/json/ShapelessRecipeData.php
+++ b/src/crafting/json/ShapelessRecipeData.php
@@ -23,7 +23,9 @@
namespace pocketmine\crafting\json;
-final class ShapelessRecipeData{
+use function count;
+
+final class ShapelessRecipeData implements \JsonSerializable{
/**
* @required
@@ -45,17 +47,34 @@ final class ShapelessRecipeData{
/** @required */
public int $priority;
+ /** @var RecipeIngredientData[] */
+ public array $unlockingIngredients = [];
+
/**
* @param RecipeIngredientData[] $input
* @param ItemStackData[] $output
+ * @param RecipeIngredientData[] $unlockingIngredients
*
* @phpstan-param list $input
* @phpstan-param list $output
+ * @phpstan-param list $unlockingIngredients
*/
- public function __construct(array $input, array $output, string $block, int $priority){
+ public function __construct(array $input, array $output, string $block, int $priority, array $unlockingIngredients = []){
$this->block = $block;
$this->priority = $priority;
$this->input = $input;
$this->output = $output;
+ $this->unlockingIngredients = $unlockingIngredients;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize() : array{
+ $result = (array) $this;
+ if(count($this->unlockingIngredients) === 0){
+ unset($result["unlockingIngredients"]);
+ }
+ return $result;
}
}
diff --git a/src/crash/CrashDump.php b/src/crash/CrashDump.php
index d7223eb2f15..49a587c34c5 100644
--- a/src/crash/CrashDump.php
+++ b/src/crash/CrashDump.php
@@ -29,11 +29,14 @@
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginManager;
use pocketmine\Server;
+use pocketmine\thread\ThreadCrashInfoFrame;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
+use pocketmine\YmlServerProperties;
use Symfony\Component\Filesystem\Path;
+use function array_map;
use function base64_encode;
use function error_get_last;
use function file;
@@ -60,6 +63,12 @@
use function substr;
use function zend_version;
use function zlib_encode;
+use const E_COMPILE_ERROR;
+use const E_CORE_ERROR;
+use const E_ERROR;
+use const E_PARSE;
+use const E_RECOVERABLE_ERROR;
+use const E_USER_ERROR;
use const FILE_IGNORE_NEW_LINES;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
@@ -82,6 +91,9 @@ class CrashDump{
public const PLUGIN_INVOLVEMENT_DIRECT = "direct";
public const PLUGIN_INVOLVEMENT_INDIRECT = "indirect";
+ public const FATAL_ERROR_MASK =
+ E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
+
private CrashDumpData $data;
private string $encodedData;
@@ -140,7 +152,7 @@ private function pluginsData() : void{
depends: $d->getDepend(),
softDepends: $d->getSoftDepend(),
main: $d->getMain(),
- load: mb_strtoupper($d->getOrder()->name()),
+ load: mb_strtoupper($d->getOrder()->name),
website: $d->getWebsite()
);
}
@@ -150,7 +162,7 @@ private function pluginsData() : void{
private function extraData() : void{
global $argv;
- if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){
+ if($this->server->getConfigGroup()->getPropertyBool(YmlServerProperties::AUTO_REPORT_SEND_SETTINGS, true)){
$this->data->parameters = (array) $argv;
if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){
$this->data->serverDotProperties = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties) ?? throw new AssumptionFailedError("Pattern is valid");
@@ -168,7 +180,7 @@ private function extraData() : void{
$this->data->jit_mode = Utils::getOpcacheJitMode();
- if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
+ if($this->server->getConfigGroup()->getPropertyBool(YmlServerProperties::AUTO_REPORT_SEND_PHPINFO, true)){
ob_start();
phpinfo();
$this->data->phpinfo = ob_get_contents(); // @phpstan-ignore-line
@@ -183,10 +195,10 @@ private function baseCrash() : void{
$error = $lastExceptionError;
}else{
$error = error_get_last();
- if($error === null){
+ if($error === null || ($error["type"] & self::FATAL_ERROR_MASK) === 0){
throw new \RuntimeException("Crash error information missing - did something use exit()?");
}
- $error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
+ $error["trace"] = Utils::printableTrace(Utils::currentTrace(3)); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
$error["fullFile"] = $error["file"];
$error["file"] = Filesystem::cleanPath($error["file"]);
try{
@@ -197,15 +209,14 @@ private function baseCrash() : void{
if(($pos = strpos($error["message"], "\n")) !== false){
$error["message"] = substr($error["message"], 0, $pos);
}
+ $error["thread"] = "Main";
}
$error["message"] = mb_scrub($error["message"], 'UTF-8');
if(isset($lastError)){
- if(isset($lastError["trace"])){
- $lastError["trace"] = Utils::printableTrace($lastError["trace"]);
- }
$this->data->lastError = $lastError;
$this->data->lastError["message"] = mb_scrub($this->data->lastError["message"], 'UTF-8');
+ $this->data->lastError["trace"] = array_map(array: $lastError["trace"], callback: fn(ThreadCrashInfoFrame $frame) => $frame->getPrintableFrame());
}
$this->data->error = $error;
@@ -215,16 +226,17 @@ private function baseCrash() : void{
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE;
if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace
foreach($error["trace"] as $frame){
- if(!isset($frame["file"])){
+ $frameFile = $frame->getFile();
+ if($frameFile === null){
continue; //PHP core
}
- if($this->determinePluginFromFile($frame["file"], false)){
+ if($this->determinePluginFromFile($frameFile, false)){
break;
}
}
}
- if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) && file_exists($error["fullFile"])){
+ if($this->server->getConfigGroup()->getPropertyBool(YmlServerProperties::AUTO_REPORT_SEND_CODE, true) && file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
if($file !== false){
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 && isset($file[$l]); ++$l){
@@ -233,7 +245,8 @@ private function baseCrash() : void{
}
}
- $this->data->trace = Utils::printableTrace($error["trace"]);
+ $this->data->trace = array_map(array: $error["trace"], callback: fn(ThreadCrashInfoFrame $frame) => $frame->getPrintableFrame());
+ $this->data->thread = $error["thread"];
}
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
diff --git a/src/crash/CrashDumpData.php b/src/crash/CrashDumpData.php
index 0f5358be5ca..b71e6f405b2 100644
--- a/src/crash/CrashDumpData.php
+++ b/src/crash/CrashDumpData.php
@@ -37,6 +37,8 @@ final class CrashDumpData implements \JsonSerializable{
/** @var mixed[] */
public array $error;
+ public string $thread;
+
public string $plugin_involvement;
public string $plugin = "";
diff --git a/src/crash/CrashDumpRenderer.php b/src/crash/CrashDumpRenderer.php
index 2858f43ec1d..617dcb7aba0 100644
--- a/src/crash/CrashDumpRenderer.php
+++ b/src/crash/CrashDumpRenderer.php
@@ -64,6 +64,7 @@ public function renderHumanReadable() : void{
$this->addLine();
+ $this->addLine("Thread: " . $this->data->thread);
$this->addLine("Error: " . $this->data->error["message"]);
$this->addLine("File: " . $this->data->error["file"]);
$this->addLine("Line: " . $this->data->error["line"]);
diff --git a/src/data/bedrock/BannerPatternTypeIdMap.php b/src/data/bedrock/BannerPatternTypeIdMap.php
index 293f8ec3870..7d4353d4f21 100644
--- a/src/data/bedrock/BannerPatternTypeIdMap.php
+++ b/src/data/bedrock/BannerPatternTypeIdMap.php
@@ -26,6 +26,7 @@
use pocketmine\block\utils\BannerPatternType;
use pocketmine\utils\SingletonTrait;
use function array_key_exists;
+use function spl_object_id;
final class BannerPatternTypeIdMap{
use SingletonTrait;
@@ -42,49 +43,57 @@ final class BannerPatternTypeIdMap{
private array $enumToId = [];
public function __construct(){
- $this->register("bo", BannerPatternType::BORDER());
- $this->register("bri", BannerPatternType::BRICKS());
- $this->register("mc", BannerPatternType::CIRCLE());
- $this->register("cre", BannerPatternType::CREEPER());
- $this->register("cr", BannerPatternType::CROSS());
- $this->register("cbo", BannerPatternType::CURLY_BORDER());
- $this->register("lud", BannerPatternType::DIAGONAL_LEFT());
- $this->register("rd", BannerPatternType::DIAGONAL_RIGHT());
- $this->register("ld", BannerPatternType::DIAGONAL_UP_LEFT());
- $this->register("rud", BannerPatternType::DIAGONAL_UP_RIGHT());
- $this->register("flo", BannerPatternType::FLOWER());
- $this->register("gra", BannerPatternType::GRADIENT());
- $this->register("gru", BannerPatternType::GRADIENT_UP());
- $this->register("hh", BannerPatternType::HALF_HORIZONTAL());
- $this->register("hhb", BannerPatternType::HALF_HORIZONTAL_BOTTOM());
- $this->register("vh", BannerPatternType::HALF_VERTICAL());
- $this->register("vhr", BannerPatternType::HALF_VERTICAL_RIGHT());
- $this->register("moj", BannerPatternType::MOJANG());
- $this->register("mr", BannerPatternType::RHOMBUS());
- $this->register("sku", BannerPatternType::SKULL());
- $this->register("ss", BannerPatternType::SMALL_STRIPES());
- $this->register("bl", BannerPatternType::SQUARE_BOTTOM_LEFT());
- $this->register("br", BannerPatternType::SQUARE_BOTTOM_RIGHT());
- $this->register("tl", BannerPatternType::SQUARE_TOP_LEFT());
- $this->register("tr", BannerPatternType::SQUARE_TOP_RIGHT());
- $this->register("sc", BannerPatternType::STRAIGHT_CROSS());
- $this->register("bs", BannerPatternType::STRIPE_BOTTOM());
- $this->register("cs", BannerPatternType::STRIPE_CENTER());
- $this->register("dls", BannerPatternType::STRIPE_DOWNLEFT());
- $this->register("drs", BannerPatternType::STRIPE_DOWNRIGHT());
- $this->register("ls", BannerPatternType::STRIPE_LEFT());
- $this->register("ms", BannerPatternType::STRIPE_MIDDLE());
- $this->register("rs", BannerPatternType::STRIPE_RIGHT());
- $this->register("ts", BannerPatternType::STRIPE_TOP());
- $this->register("bt", BannerPatternType::TRIANGLE_BOTTOM());
- $this->register("tt", BannerPatternType::TRIANGLE_TOP());
- $this->register("bts", BannerPatternType::TRIANGLES_BOTTOM());
- $this->register("tts", BannerPatternType::TRIANGLES_TOP());
+ foreach(BannerPatternType::cases() as $case){
+ $this->register(match($case){
+ BannerPatternType::BORDER => "bo",
+ BannerPatternType::BRICKS => "bri",
+ BannerPatternType::CIRCLE => "mc",
+ BannerPatternType::CREEPER => "cre",
+ BannerPatternType::CROSS => "cr",
+ BannerPatternType::CURLY_BORDER => "cbo",
+ BannerPatternType::DIAGONAL_LEFT => "lud",
+ BannerPatternType::DIAGONAL_RIGHT => "rd",
+ BannerPatternType::DIAGONAL_UP_LEFT => "ld",
+ BannerPatternType::DIAGONAL_UP_RIGHT => "rud",
+ BannerPatternType::FLOWER => "flo",
+ BannerPatternType::FLOW => "flw",
+ BannerPatternType::GLOBE => "glb",
+ BannerPatternType::GRADIENT => "gra",
+ BannerPatternType::GRADIENT_UP => "gru",
+ BannerPatternType::GUSTER => "gus",
+ BannerPatternType::HALF_HORIZONTAL => "hh",
+ BannerPatternType::HALF_HORIZONTAL_BOTTOM => "hhb",
+ BannerPatternType::HALF_VERTICAL => "vh",
+ BannerPatternType::HALF_VERTICAL_RIGHT => "vhr",
+ BannerPatternType::MOJANG => "moj",
+ BannerPatternType::PIGLIN => "pig",
+ BannerPatternType::RHOMBUS => "mr",
+ BannerPatternType::SKULL => "sku",
+ BannerPatternType::SMALL_STRIPES => "ss",
+ BannerPatternType::SQUARE_BOTTOM_LEFT => "bl",
+ BannerPatternType::SQUARE_BOTTOM_RIGHT => "br",
+ BannerPatternType::SQUARE_TOP_LEFT => "tl",
+ BannerPatternType::SQUARE_TOP_RIGHT => "tr",
+ BannerPatternType::STRAIGHT_CROSS => "sc",
+ BannerPatternType::STRIPE_BOTTOM => "bs",
+ BannerPatternType::STRIPE_CENTER => "cs",
+ BannerPatternType::STRIPE_DOWNLEFT => "dls",
+ BannerPatternType::STRIPE_DOWNRIGHT => "drs",
+ BannerPatternType::STRIPE_LEFT => "ls",
+ BannerPatternType::STRIPE_MIDDLE => "ms",
+ BannerPatternType::STRIPE_RIGHT => "rs",
+ BannerPatternType::STRIPE_TOP => "ts",
+ BannerPatternType::TRIANGLE_BOTTOM => "bt",
+ BannerPatternType::TRIANGLE_TOP => "tt",
+ BannerPatternType::TRIANGLES_BOTTOM => "bts",
+ BannerPatternType::TRIANGLES_TOP => "tts",
+ }, $case);
+ }
}
public function register(string $stringId, BannerPatternType $type) : void{
$this->idToEnum[$stringId] = $type;
- $this->enumToId[$type->id()] = $stringId;
+ $this->enumToId[spl_object_id($type)] = $stringId;
}
public function fromId(string $id) : ?BannerPatternType{
@@ -92,9 +101,10 @@ public function fromId(string $id) : ?BannerPatternType{
}
public function toId(BannerPatternType $type) : string{
- if(!array_key_exists($type->id(), $this->enumToId)){
- throw new \InvalidArgumentException("Missing mapping for banner pattern type " . $type->name());
+ $k = spl_object_id($type);
+ if(!array_key_exists($k, $this->enumToId)){
+ throw new \InvalidArgumentException("Missing mapping for banner pattern type " . $type->name);
}
- return $this->enumToId[$type->id()];
+ return $this->enumToId[$k];
}
}
diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php
index 2c9350ca3be..5c476ca1c68 100644
--- a/src/data/bedrock/BedrockDataFiles.php
+++ b/src/data/bedrock/BedrockDataFiles.php
@@ -35,6 +35,7 @@ private function __construct(){
public const BIOME_DEFINITIONS_FULL_NBT = BEDROCK_DATA_PATH . '/biome_definitions_full.nbt';
public const BIOME_ID_MAP_JSON = BEDROCK_DATA_PATH . '/biome_id_map.json';
public const BLOCK_ID_TO_ITEM_ID_MAP_JSON = BEDROCK_DATA_PATH . '/block_id_to_item_id_map.json';
+ public const BLOCK_PROPERTIES_TABLE_JSON = BEDROCK_DATA_PATH . '/block_properties_table.json';
public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json';
public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt';
public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json';
diff --git a/src/data/bedrock/BiomeIds.php b/src/data/bedrock/BiomeIds.php
index ac955527805..1169a51eaee 100644
--- a/src/data/bedrock/BiomeIds.php
+++ b/src/data/bedrock/BiomeIds.php
@@ -111,4 +111,15 @@ private function __construct(){
public const CRIMSON_FOREST = 179;
public const WARPED_FOREST = 180;
public const BASALT_DELTAS = 181;
+ public const JAGGED_PEAKS = 182;
+ public const FROZEN_PEAKS = 183;
+ public const SNOWY_SLOPES = 184;
+ public const GROVE = 185;
+ public const MEADOW = 186;
+ public const LUSH_CAVES = 187;
+ public const DRIPSTONE_CAVES = 188;
+ public const STONY_PEAKS = 189;
+ public const DEEP_DARK = 190;
+ public const MANGROVE_SWAMP = 191;
+ public const CHERRY_GROVE = 192;
}
diff --git a/src/data/bedrock/DyeColorIdMap.php b/src/data/bedrock/DyeColorIdMap.php
index 35db72c3e7e..60c50970595 100644
--- a/src/data/bedrock/DyeColorIdMap.php
+++ b/src/data/bedrock/DyeColorIdMap.php
@@ -26,21 +26,14 @@
use pocketmine\block\utils\DyeColor;
use pocketmine\data\bedrock\item\ItemTypeNames;
use pocketmine\utils\SingletonTrait;
+use function spl_object_id;
final class DyeColorIdMap{
use SingletonTrait;
-
- /**
- * @var DyeColor[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait {
+ register as registerInt;
+ }
/**
* @var DyeColor[]
@@ -55,33 +48,34 @@ final class DyeColorIdMap{
private array $enumToItemId = [];
private function __construct(){
- $this->register(0, ItemTypeNames::WHITE_DYE, DyeColor::WHITE());
- $this->register(1, ItemTypeNames::ORANGE_DYE, DyeColor::ORANGE());
- $this->register(2, ItemTypeNames::MAGENTA_DYE, DyeColor::MAGENTA());
- $this->register(3, ItemTypeNames::LIGHT_BLUE_DYE, DyeColor::LIGHT_BLUE());
- $this->register(4, ItemTypeNames::YELLOW_DYE, DyeColor::YELLOW());
- $this->register(5, ItemTypeNames::LIME_DYE, DyeColor::LIME());
- $this->register(6, ItemTypeNames::PINK_DYE, DyeColor::PINK());
- $this->register(7, ItemTypeNames::GRAY_DYE, DyeColor::GRAY());
- $this->register(8, ItemTypeNames::LIGHT_GRAY_DYE, DyeColor::LIGHT_GRAY());
- $this->register(9, ItemTypeNames::CYAN_DYE, DyeColor::CYAN());
- $this->register(10, ItemTypeNames::PURPLE_DYE, DyeColor::PURPLE());
- $this->register(11, ItemTypeNames::BLUE_DYE, DyeColor::BLUE());
- $this->register(12, ItemTypeNames::BROWN_DYE, DyeColor::BROWN());
- $this->register(13, ItemTypeNames::GREEN_DYE, DyeColor::GREEN());
- $this->register(14, ItemTypeNames::RED_DYE, DyeColor::RED());
- $this->register(15, ItemTypeNames::BLACK_DYE, DyeColor::BLACK());
+ foreach(DyeColor::cases() as $case){
+ [$colorId, $dyeItemId] = match($case){
+ DyeColor::WHITE => [0, ItemTypeNames::WHITE_DYE],
+ DyeColor::ORANGE => [1, ItemTypeNames::ORANGE_DYE],
+ DyeColor::MAGENTA => [2, ItemTypeNames::MAGENTA_DYE],
+ DyeColor::LIGHT_BLUE => [3, ItemTypeNames::LIGHT_BLUE_DYE],
+ DyeColor::YELLOW => [4, ItemTypeNames::YELLOW_DYE],
+ DyeColor::LIME => [5, ItemTypeNames::LIME_DYE],
+ DyeColor::PINK => [6, ItemTypeNames::PINK_DYE],
+ DyeColor::GRAY => [7, ItemTypeNames::GRAY_DYE],
+ DyeColor::LIGHT_GRAY => [8, ItemTypeNames::LIGHT_GRAY_DYE],
+ DyeColor::CYAN => [9, ItemTypeNames::CYAN_DYE],
+ DyeColor::PURPLE => [10, ItemTypeNames::PURPLE_DYE],
+ DyeColor::BLUE => [11, ItemTypeNames::BLUE_DYE],
+ DyeColor::BROWN => [12, ItemTypeNames::BROWN_DYE],
+ DyeColor::GREEN => [13, ItemTypeNames::GREEN_DYE],
+ DyeColor::RED => [14, ItemTypeNames::RED_DYE],
+ DyeColor::BLACK => [15, ItemTypeNames::BLACK_DYE],
+ };
+
+ $this->register($colorId, $dyeItemId, $case);
+ }
}
private function register(int $id, string $itemId, DyeColor $color) : void{
- $this->idToEnum[$id] = $color;
- $this->enumToId[$color->id()] = $id;
+ $this->registerInt($id, $color);
$this->itemIdToEnum[$itemId] = $color;
- $this->enumToItemId[$color->id()] = $itemId;
- }
-
- public function toId(DyeColor $color) : int{
- return $this->enumToId[$color->id()]; //TODO: is it possible for this to be missing?
+ $this->enumToItemId[spl_object_id($color)] = $itemId;
}
public function toInvertedId(DyeColor $color) : int{
@@ -89,11 +83,7 @@ public function toInvertedId(DyeColor $color) : int{
}
public function toItemId(DyeColor $color) : string{
- return $this->enumToItemId[$color->id()];
- }
-
- public function fromId(int $id) : ?DyeColor{
- return $this->idToEnum[$id] ?? null;
+ return $this->enumToItemId[spl_object_id($color)];
}
public function fromInvertedId(int $id) : ?DyeColor{
diff --git a/src/data/bedrock/EffectIdMap.php b/src/data/bedrock/EffectIdMap.php
index 6dce86d9b9a..cadd8c397c1 100644
--- a/src/data/bedrock/EffectIdMap.php
+++ b/src/data/bedrock/EffectIdMap.php
@@ -26,23 +26,11 @@
use pocketmine\entity\effect\Effect;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\utils\SingletonTrait;
-use function array_key_exists;
-use function spl_object_id;
final class EffectIdMap{
use SingletonTrait;
-
- /**
- * @var Effect[]
- * @phpstan-var array
- */
- private array $idToEffect = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $effectToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
$this->register(EffectIds::SPEED, VanillaEffects::SPEED());
@@ -76,24 +64,4 @@ private function __construct(){
//TODO: VILLAGE_HERO
$this->register(EffectIds::DARKNESS, VanillaEffects::DARKNESS());
}
-
- //TODO: not a big fan of the code duplication here :(
-
- public function register(int $mcpeId, Effect $effect) : void{
- $this->idToEffect[$mcpeId] = $effect;
- $this->effectToId[spl_object_id($effect)] = $mcpeId;
- }
-
- public function fromId(int $id) : ?Effect{
- //we might not have all the effect IDs registered
- return $this->idToEffect[$id] ?? null;
- }
-
- public function toId(Effect $effect) : int{
- if(!array_key_exists(spl_object_id($effect), $this->effectToId)){
- //this should never happen, so we treat it as an exceptional condition
- throw new \InvalidArgumentException("Effect does not have a mapped ID");
- }
- return $this->effectToId[spl_object_id($effect)];
- }
}
diff --git a/src/data/bedrock/EnchantmentIdMap.php b/src/data/bedrock/EnchantmentIdMap.php
index d7f436fa38f..e3d652b19ce 100644
--- a/src/data/bedrock/EnchantmentIdMap.php
+++ b/src/data/bedrock/EnchantmentIdMap.php
@@ -26,25 +26,14 @@
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\utils\SingletonTrait;
-use function array_key_exists;
-use function spl_object_id;
/**
* Handles translation of internal enchantment types to and from Minecraft: Bedrock IDs.
*/
final class EnchantmentIdMap{
use SingletonTrait;
-
- /**
- * @var Enchantment[]
- * @phpstan-var array
- */
- private array $idToEnch = [];
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enchToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
$this->register(EnchantmentIds::PROTECTION, VanillaEnchantments::PROTECTION());
@@ -54,6 +43,7 @@ private function __construct(){
$this->register(EnchantmentIds::PROJECTILE_PROTECTION, VanillaEnchantments::PROJECTILE_PROTECTION());
$this->register(EnchantmentIds::THORNS, VanillaEnchantments::THORNS());
$this->register(EnchantmentIds::RESPIRATION, VanillaEnchantments::RESPIRATION());
+ $this->register(EnchantmentIds::AQUA_AFFINITY, VanillaEnchantments::AQUA_AFFINITY());
$this->register(EnchantmentIds::SHARPNESS, VanillaEnchantments::SHARPNESS());
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
@@ -62,6 +52,7 @@ private function __construct(){
$this->register(EnchantmentIds::FIRE_ASPECT, VanillaEnchantments::FIRE_ASPECT());
$this->register(EnchantmentIds::EFFICIENCY, VanillaEnchantments::EFFICIENCY());
+ $this->register(EnchantmentIds::FORTUNE, VanillaEnchantments::FORTUNE());
$this->register(EnchantmentIds::SILK_TOUCH, VanillaEnchantments::SILK_TOUCH());
$this->register(EnchantmentIds::UNBREAKING, VanillaEnchantments::UNBREAKING());
@@ -76,22 +67,4 @@ private function __construct(){
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
}
-
- public function register(int $mcpeId, Enchantment $enchantment) : void{
- $this->idToEnch[$mcpeId] = $enchantment;
- $this->enchToId[spl_object_id($enchantment)] = $mcpeId;
- }
-
- public function fromId(int $id) : ?Enchantment{
- //we might not have all the enchantment IDs registered
- return $this->idToEnch[$id] ?? null;
- }
-
- public function toId(Enchantment $enchantment) : int{
- if(!array_key_exists(spl_object_id($enchantment), $this->enchToId)){
- //this should never happen, so we treat it as an exceptional condition
- throw new \InvalidArgumentException("Enchantment does not have a mapped ID");
- }
- return $this->enchToId[spl_object_id($enchantment)];
- }
}
diff --git a/src/data/bedrock/GoatHornTypeIdMap.php b/src/data/bedrock/GoatHornTypeIdMap.php
new file mode 100644
index 00000000000..0510a09cee7
--- /dev/null
+++ b/src/data/bedrock/GoatHornTypeIdMap.php
@@ -0,0 +1,48 @@
+ */
+ use IntSaveIdMapTrait;
+
+ private function __construct(){
+ foreach(GoatHornType::cases() as $case){
+ $this->register(match($case){
+ GoatHornType::PONDER => GoatHornTypeIds::PONDER,
+ GoatHornType::SING => GoatHornTypeIds::SING,
+ GoatHornType::SEEK => GoatHornTypeIds::SEEK,
+ GoatHornType::FEEL => GoatHornTypeIds::FEEL,
+ GoatHornType::ADMIRE => GoatHornTypeIds::ADMIRE,
+ GoatHornType::CALL => GoatHornTypeIds::CALL,
+ GoatHornType::YEARN => GoatHornTypeIds::YEARN,
+ GoatHornType::DREAM => GoatHornTypeIds::DREAM
+ }, $case);
+ }
+ }
+}
diff --git a/tests/phpunit/data/bedrock/DyeColorIdMapTest.php b/src/data/bedrock/GoatHornTypeIds.php
similarity index 66%
rename from tests/phpunit/data/bedrock/DyeColorIdMapTest.php
rename to src/data/bedrock/GoatHornTypeIds.php
index b5561019faa..048d246fe0a 100644
--- a/tests/phpunit/data/bedrock/DyeColorIdMapTest.php
+++ b/src/data/bedrock/GoatHornTypeIds.php
@@ -23,16 +23,13 @@
namespace pocketmine\data\bedrock;
-use PHPUnit\Framework\TestCase;
-use pocketmine\block\utils\DyeColor;
-
-class DyeColorIdMapTest extends TestCase{
-
- public function testAllColorsMapped() : void{
- foreach(DyeColor::getAll() as $color){
- $id = DyeColorIdMap::getInstance()->toId($color);
- $color2 = DyeColorIdMap::getInstance()->fromId($id);
- self::assertTrue($color2 !== null && $color->equals($color2));
- }
- }
+final class GoatHornTypeIds{
+ public const PONDER = 0;
+ public const SING = 1;
+ public const SEEK = 2;
+ public const FEEL = 3;
+ public const ADMIRE = 4;
+ public const CALL = 5;
+ public const YEARN = 6;
+ public const DREAM = 7;
}
diff --git a/src/data/bedrock/IntSaveIdMapTrait.php b/src/data/bedrock/IntSaveIdMapTrait.php
new file mode 100644
index 00000000000..cf1631fd5f2
--- /dev/null
+++ b/src/data/bedrock/IntSaveIdMapTrait.php
@@ -0,0 +1,81 @@
+
+ */
+ private array $idToEnum = [];
+
+ /**
+ * @var int[]
+ * @phpstan-var array
+ */
+ private array $enumToId = [];
+
+ /**
+ * @phpstan-param TObject $enum
+ */
+ protected function getRuntimeId(object $enum) : int{
+ //this is fine for enums and non-cloning object registries
+ return spl_object_id($enum);
+ }
+
+ /**
+ * @phpstan-param TObject $enum
+ */
+ public function register(int $saveId, object $enum) : void{
+ $this->idToEnum[$saveId] = $enum;
+ $this->enumToId[$this->getRuntimeId($enum)] = $saveId;
+ }
+
+ /**
+ * @phpstan-return TObject|null
+ */
+ public function fromId(int $id) : ?object{
+ //we might not have all the effect IDs registered
+ return $this->idToEnum[$id] ?? null;
+ }
+
+ /**
+ * @phpstan-param TObject $enum
+ */
+ public function toId(object $enum) : int{
+ $runtimeId = $this->getRuntimeId($enum);
+ if(!array_key_exists($runtimeId, $this->enumToId)){
+ //this should never happen, so we treat it as an exceptional condition
+ throw new \InvalidArgumentException("Object does not have a mapped save ID");
+ }
+ return $this->enumToId[$runtimeId];
+ }
+}
diff --git a/src/data/bedrock/MedicineTypeIdMap.php b/src/data/bedrock/MedicineTypeIdMap.php
index a85dbb7a8ee..90fd835509d 100644
--- a/src/data/bedrock/MedicineTypeIdMap.php
+++ b/src/data/bedrock/MedicineTypeIdMap.php
@@ -28,39 +28,17 @@
final class MedicineTypeIdMap{
use SingletonTrait;
-
- /**
- * @var MedicineType[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
- $this->register(MedicineTypeIds::ANTIDOTE, MedicineType::ANTIDOTE());
- $this->register(MedicineTypeIds::ELIXIR, MedicineType::ELIXIR());
- $this->register(MedicineTypeIds::EYE_DROPS, MedicineType::EYE_DROPS());
- $this->register(MedicineTypeIds::TONIC, MedicineType::TONIC());
- }
-
- private function register(int $id, MedicineType $type) : void{
- $this->idToEnum[$id] = $type;
- $this->enumToId[$type->id()] = $id;
- }
-
- public function fromId(int $id) : ?MedicineType{
- return $this->idToEnum[$id] ?? null;
- }
-
- public function toId(MedicineType $type) : int{
- if(!isset($this->enumToId[$type->id()])){
- throw new \InvalidArgumentException("Type does not have a mapped ID");
+ foreach(MedicineType::cases() as $case){
+ $this->register(match($case){
+ MedicineType::ANTIDOTE => MedicineTypeIds::ANTIDOTE,
+ MedicineType::ELIXIR => MedicineTypeIds::ELIXIR,
+ MedicineType::EYE_DROPS => MedicineTypeIds::EYE_DROPS,
+ MedicineType::TONIC => MedicineTypeIds::TONIC,
+ }, $case);
}
- return $this->enumToId[$type->id()];
}
}
diff --git a/src/data/bedrock/MobHeadTypeIdMap.php b/src/data/bedrock/MobHeadTypeIdMap.php
index 9b9fe2c060f..bf16e6eba0a 100644
--- a/src/data/bedrock/MobHeadTypeIdMap.php
+++ b/src/data/bedrock/MobHeadTypeIdMap.php
@@ -28,41 +28,20 @@
final class MobHeadTypeIdMap{
use SingletonTrait;
-
- /**
- * @var MobHeadType[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
- $this->register(0, MobHeadType::SKELETON());
- $this->register(1, MobHeadType::WITHER_SKELETON());
- $this->register(2, MobHeadType::ZOMBIE());
- $this->register(3, MobHeadType::PLAYER());
- $this->register(4, MobHeadType::CREEPER());
- $this->register(5, MobHeadType::DRAGON());
- }
-
- private function register(int $id, MobHeadType $type) : void{
- $this->idToEnum[$id] = $type;
- $this->enumToId[$type->id()] = $id;
- }
-
- public function fromId(int $id) : ?MobHeadType{
- return $this->idToEnum[$id] ?? null;
- }
-
- public function toId(MobHeadType $type) : int{
- if(!isset($this->enumToId[$type->id()])){
- throw new \InvalidArgumentException("Type does not have a mapped ID");
+ foreach(MobHeadType::cases() as $case){
+ $this->register(match($case){
+ MobHeadType::SKELETON => 0,
+ MobHeadType::WITHER_SKELETON => 1,
+ MobHeadType::ZOMBIE => 2,
+ MobHeadType::PLAYER => 3,
+ MobHeadType::CREEPER => 4,
+ MobHeadType::DRAGON => 5,
+ MobHeadType::PIGLIN => 6,
+ }, $case);
}
- return $this->enumToId[$type->id()];
}
}
diff --git a/src/data/bedrock/MushroomBlockTypeIdMap.php b/src/data/bedrock/MushroomBlockTypeIdMap.php
index 2eec1754997..a25336d897d 100644
--- a/src/data/bedrock/MushroomBlockTypeIdMap.php
+++ b/src/data/bedrock/MushroomBlockTypeIdMap.php
@@ -26,49 +26,27 @@
use pocketmine\block\utils\MushroomBlockType;
use pocketmine\data\bedrock\block\BlockLegacyMetadata as LegacyMeta;
use pocketmine\utils\SingletonTrait;
-use function array_key_exists;
final class MushroomBlockTypeIdMap{
use SingletonTrait;
-
- /**
- * @var MushroomBlockType[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
public function __construct(){
- $this->register(LegacyMeta::MUSHROOM_BLOCK_ALL_PORES, MushroomBlockType::PORES());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER, MushroomBlockType::CAP_NORTHWEST());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE, MushroomBlockType::CAP_NORTH());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER, MushroomBlockType::CAP_NORTHEAST());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE, MushroomBlockType::CAP_WEST());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY, MushroomBlockType::CAP_MIDDLE());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE, MushroomBlockType::CAP_EAST());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER, MushroomBlockType::CAP_SOUTHWEST());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE, MushroomBlockType::CAP_SOUTH());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER, MushroomBlockType::CAP_SOUTHEAST());
- $this->register(LegacyMeta::MUSHROOM_BLOCK_ALL_CAP, MushroomBlockType::ALL_CAP());
- }
-
- public function register(int $id, MushroomBlockType $type) : void{
- $this->idToEnum[$id] = $type;
- $this->enumToId[$type->id()] = $id;
- }
-
- public function fromId(int $id) : ?MushroomBlockType{
- return $this->idToEnum[$id] ?? null;
- }
-
- public function toId(MushroomBlockType $type) : int{
- if(!array_key_exists($type->id(), $this->enumToId)){
- throw new \InvalidArgumentException("Mushroom block type does not have a mapped ID"); //this should never happen
+ foreach(MushroomBlockType::cases() as $case){
+ $this->register(match($case){
+ MushroomBlockType::PORES => LegacyMeta::MUSHROOM_BLOCK_ALL_PORES,
+ MushroomBlockType::CAP_NORTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER,
+ MushroomBlockType::CAP_NORTH => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE,
+ MushroomBlockType::CAP_NORTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER,
+ MushroomBlockType::CAP_WEST => LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE,
+ MushroomBlockType::CAP_MIDDLE => LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY,
+ MushroomBlockType::CAP_EAST => LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE,
+ MushroomBlockType::CAP_SOUTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER,
+ MushroomBlockType::CAP_SOUTH => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE,
+ MushroomBlockType::CAP_SOUTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER,
+ MushroomBlockType::ALL_CAP => LegacyMeta::MUSHROOM_BLOCK_ALL_CAP,
+ }, $case);
}
- return $this->enumToId[$type->id()];
}
}
diff --git a/src/data/bedrock/NoteInstrumentIdMap.php b/src/data/bedrock/NoteInstrumentIdMap.php
index 0b8a4373577..c847ecd98c0 100644
--- a/src/data/bedrock/NoteInstrumentIdMap.php
+++ b/src/data/bedrock/NoteInstrumentIdMap.php
@@ -28,51 +28,29 @@
final class NoteInstrumentIdMap{
use SingletonTrait;
-
- /**
- * @var NoteInstrument[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
- $this->register(0, NoteInstrument::PIANO());
- $this->register(1, NoteInstrument::BASS_DRUM());
- $this->register(2, NoteInstrument::SNARE());
- $this->register(3, NoteInstrument::CLICKS_AND_STICKS());
- $this->register(4, NoteInstrument::DOUBLE_BASS());
- $this->register(5, NoteInstrument::BELL());
- $this->register(6, NoteInstrument::FLUTE());
- $this->register(7, NoteInstrument::CHIME());
- $this->register(8, NoteInstrument::GUITAR());
- $this->register(9, NoteInstrument::XYLOPHONE());
- $this->register(10, NoteInstrument::IRON_XYLOPHONE());
- $this->register(11, NoteInstrument::COW_BELL());
- $this->register(12, NoteInstrument::DIDGERIDOO());
- $this->register(13, NoteInstrument::BIT());
- $this->register(14, NoteInstrument::BANJO());
- $this->register(15, NoteInstrument::PLING());
- }
-
- private function register(int $id, NoteInstrument $instrument) : void{
- $this->idToEnum[$id] = $instrument;
- $this->enumToId[$instrument->id()] = $id;
- }
-
- public function fromId(int $id) : ?NoteInstrument{
- return $this->idToEnum[$id] ?? null;
- }
-
- public function toId(NoteInstrument $instrument) : int{
- if(!isset($this->enumToId[$instrument->id()])){
- throw new \InvalidArgumentException("Type does not have a mapped ID");
+ foreach(NoteInstrument::cases() as $case){
+ $this->register(match($case){
+ NoteInstrument::PIANO => 0,
+ NoteInstrument::BASS_DRUM => 1,
+ NoteInstrument::SNARE => 2,
+ NoteInstrument::CLICKS_AND_STICKS => 3,
+ NoteInstrument::DOUBLE_BASS => 4,
+ NoteInstrument::BELL => 5,
+ NoteInstrument::FLUTE => 6,
+ NoteInstrument::CHIME => 7,
+ NoteInstrument::GUITAR => 8,
+ NoteInstrument::XYLOPHONE => 9,
+ NoteInstrument::IRON_XYLOPHONE => 10,
+ NoteInstrument::COW_BELL => 11,
+ NoteInstrument::DIDGERIDOO => 12,
+ NoteInstrument::BIT => 13,
+ NoteInstrument::BANJO => 14,
+ NoteInstrument::PLING => 15,
+ }, $case);
}
- return $this->enumToId[$instrument->id()];
}
}
diff --git a/src/data/bedrock/PotionTypeIdMap.php b/src/data/bedrock/PotionTypeIdMap.php
index 2e6fef3cc8f..4e7d8d4c79f 100644
--- a/src/data/bedrock/PotionTypeIdMap.php
+++ b/src/data/bedrock/PotionTypeIdMap.php
@@ -28,77 +28,56 @@
final class PotionTypeIdMap{
use SingletonTrait;
-
- /**
- * @var PotionType[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
- $this->register(PotionTypeIds::WATER, PotionType::WATER());
- $this->register(PotionTypeIds::MUNDANE, PotionType::MUNDANE());
- $this->register(PotionTypeIds::LONG_MUNDANE, PotionType::LONG_MUNDANE());
- $this->register(PotionTypeIds::THICK, PotionType::THICK());
- $this->register(PotionTypeIds::AWKWARD, PotionType::AWKWARD());
- $this->register(PotionTypeIds::NIGHT_VISION, PotionType::NIGHT_VISION());
- $this->register(PotionTypeIds::LONG_NIGHT_VISION, PotionType::LONG_NIGHT_VISION());
- $this->register(PotionTypeIds::INVISIBILITY, PotionType::INVISIBILITY());
- $this->register(PotionTypeIds::LONG_INVISIBILITY, PotionType::LONG_INVISIBILITY());
- $this->register(PotionTypeIds::LEAPING, PotionType::LEAPING());
- $this->register(PotionTypeIds::LONG_LEAPING, PotionType::LONG_LEAPING());
- $this->register(PotionTypeIds::STRONG_LEAPING, PotionType::STRONG_LEAPING());
- $this->register(PotionTypeIds::FIRE_RESISTANCE, PotionType::FIRE_RESISTANCE());
- $this->register(PotionTypeIds::LONG_FIRE_RESISTANCE, PotionType::LONG_FIRE_RESISTANCE());
- $this->register(PotionTypeIds::SWIFTNESS, PotionType::SWIFTNESS());
- $this->register(PotionTypeIds::LONG_SWIFTNESS, PotionType::LONG_SWIFTNESS());
- $this->register(PotionTypeIds::STRONG_SWIFTNESS, PotionType::STRONG_SWIFTNESS());
- $this->register(PotionTypeIds::SLOWNESS, PotionType::SLOWNESS());
- $this->register(PotionTypeIds::LONG_SLOWNESS, PotionType::LONG_SLOWNESS());
- $this->register(PotionTypeIds::WATER_BREATHING, PotionType::WATER_BREATHING());
- $this->register(PotionTypeIds::LONG_WATER_BREATHING, PotionType::LONG_WATER_BREATHING());
- $this->register(PotionTypeIds::HEALING, PotionType::HEALING());
- $this->register(PotionTypeIds::STRONG_HEALING, PotionType::STRONG_HEALING());
- $this->register(PotionTypeIds::HARMING, PotionType::HARMING());
- $this->register(PotionTypeIds::STRONG_HARMING, PotionType::STRONG_HARMING());
- $this->register(PotionTypeIds::POISON, PotionType::POISON());
- $this->register(PotionTypeIds::LONG_POISON, PotionType::LONG_POISON());
- $this->register(PotionTypeIds::STRONG_POISON, PotionType::STRONG_POISON());
- $this->register(PotionTypeIds::REGENERATION, PotionType::REGENERATION());
- $this->register(PotionTypeIds::LONG_REGENERATION, PotionType::LONG_REGENERATION());
- $this->register(PotionTypeIds::STRONG_REGENERATION, PotionType::STRONG_REGENERATION());
- $this->register(PotionTypeIds::STRENGTH, PotionType::STRENGTH());
- $this->register(PotionTypeIds::LONG_STRENGTH, PotionType::LONG_STRENGTH());
- $this->register(PotionTypeIds::STRONG_STRENGTH, PotionType::STRONG_STRENGTH());
- $this->register(PotionTypeIds::WEAKNESS, PotionType::WEAKNESS());
- $this->register(PotionTypeIds::LONG_WEAKNESS, PotionType::LONG_WEAKNESS());
- $this->register(PotionTypeIds::WITHER, PotionType::WITHER());
- $this->register(PotionTypeIds::TURTLE_MASTER, PotionType::TURTLE_MASTER());
- $this->register(PotionTypeIds::LONG_TURTLE_MASTER, PotionType::LONG_TURTLE_MASTER());
- $this->register(PotionTypeIds::STRONG_TURTLE_MASTER, PotionType::STRONG_TURTLE_MASTER());
- $this->register(PotionTypeIds::SLOW_FALLING, PotionType::SLOW_FALLING());
- $this->register(PotionTypeIds::LONG_SLOW_FALLING, PotionType::LONG_SLOW_FALLING());
- }
-
- private function register(int $id, PotionType $type) : void{
- $this->idToEnum[$id] = $type;
- $this->enumToId[$type->id()] = $id;
- }
-
- public function fromId(int $id) : ?PotionType{
- return $this->idToEnum[$id] ?? null;
- }
-
- public function toId(PotionType $type) : int{
- if(!isset($this->enumToId[$type->id()])){
- throw new \InvalidArgumentException("Type does not have a mapped ID");
+ foreach(PotionType::cases() as $case){
+ $this->register(match($case){
+ PotionType::WATER => PotionTypeIds::WATER,
+ PotionType::MUNDANE => PotionTypeIds::MUNDANE,
+ PotionType::LONG_MUNDANE => PotionTypeIds::LONG_MUNDANE,
+ PotionType::THICK => PotionTypeIds::THICK,
+ PotionType::AWKWARD => PotionTypeIds::AWKWARD,
+ PotionType::NIGHT_VISION => PotionTypeIds::NIGHT_VISION,
+ PotionType::LONG_NIGHT_VISION => PotionTypeIds::LONG_NIGHT_VISION,
+ PotionType::INVISIBILITY => PotionTypeIds::INVISIBILITY,
+ PotionType::LONG_INVISIBILITY => PotionTypeIds::LONG_INVISIBILITY,
+ PotionType::LEAPING => PotionTypeIds::LEAPING,
+ PotionType::LONG_LEAPING => PotionTypeIds::LONG_LEAPING,
+ PotionType::STRONG_LEAPING => PotionTypeIds::STRONG_LEAPING,
+ PotionType::FIRE_RESISTANCE => PotionTypeIds::FIRE_RESISTANCE,
+ PotionType::LONG_FIRE_RESISTANCE => PotionTypeIds::LONG_FIRE_RESISTANCE,
+ PotionType::SWIFTNESS => PotionTypeIds::SWIFTNESS,
+ PotionType::LONG_SWIFTNESS => PotionTypeIds::LONG_SWIFTNESS,
+ PotionType::STRONG_SWIFTNESS => PotionTypeIds::STRONG_SWIFTNESS,
+ PotionType::SLOWNESS => PotionTypeIds::SLOWNESS,
+ PotionType::LONG_SLOWNESS => PotionTypeIds::LONG_SLOWNESS,
+ PotionType::WATER_BREATHING => PotionTypeIds::WATER_BREATHING,
+ PotionType::LONG_WATER_BREATHING => PotionTypeIds::LONG_WATER_BREATHING,
+ PotionType::HEALING => PotionTypeIds::HEALING,
+ PotionType::STRONG_HEALING => PotionTypeIds::STRONG_HEALING,
+ PotionType::HARMING => PotionTypeIds::HARMING,
+ PotionType::STRONG_HARMING => PotionTypeIds::STRONG_HARMING,
+ PotionType::POISON => PotionTypeIds::POISON,
+ PotionType::LONG_POISON => PotionTypeIds::LONG_POISON,
+ PotionType::STRONG_POISON => PotionTypeIds::STRONG_POISON,
+ PotionType::REGENERATION => PotionTypeIds::REGENERATION,
+ PotionType::LONG_REGENERATION => PotionTypeIds::LONG_REGENERATION,
+ PotionType::STRONG_REGENERATION => PotionTypeIds::STRONG_REGENERATION,
+ PotionType::STRENGTH => PotionTypeIds::STRENGTH,
+ PotionType::LONG_STRENGTH => PotionTypeIds::LONG_STRENGTH,
+ PotionType::STRONG_STRENGTH => PotionTypeIds::STRONG_STRENGTH,
+ PotionType::WEAKNESS => PotionTypeIds::WEAKNESS,
+ PotionType::LONG_WEAKNESS => PotionTypeIds::LONG_WEAKNESS,
+ PotionType::WITHER => PotionTypeIds::WITHER,
+ PotionType::TURTLE_MASTER => PotionTypeIds::TURTLE_MASTER,
+ PotionType::LONG_TURTLE_MASTER => PotionTypeIds::LONG_TURTLE_MASTER,
+ PotionType::STRONG_TURTLE_MASTER => PotionTypeIds::STRONG_TURTLE_MASTER,
+ PotionType::SLOW_FALLING => PotionTypeIds::SLOW_FALLING,
+ PotionType::LONG_SLOW_FALLING => PotionTypeIds::LONG_SLOW_FALLING,
+ PotionType::STRONG_SLOWNESS => PotionTypeIds::STRONG_SLOWNESS,
+ }, $case);
}
- return $this->enumToId[$type->id()];
}
}
diff --git a/src/data/bedrock/PotionTypeIds.php b/src/data/bedrock/PotionTypeIds.php
index aa69461cefe..f2d82f39f5a 100644
--- a/src/data/bedrock/PotionTypeIds.php
+++ b/src/data/bedrock/PotionTypeIds.php
@@ -66,4 +66,5 @@ final class PotionTypeIds{
public const STRONG_TURTLE_MASTER = 39;
public const SLOW_FALLING = 40;
public const LONG_SLOW_FALLING = 41;
+ public const STRONG_SLOWNESS = 42;
}
diff --git a/src/data/bedrock/SuspiciousStewTypeIdMap.php b/src/data/bedrock/SuspiciousStewTypeIdMap.php
index 1dc86abf197..d6a31f0ea79 100644
--- a/src/data/bedrock/SuspiciousStewTypeIdMap.php
+++ b/src/data/bedrock/SuspiciousStewTypeIdMap.php
@@ -28,45 +28,24 @@
final class SuspiciousStewTypeIdMap{
use SingletonTrait;
-
- /**
- * @var SuspiciousStewType[]
- * @phpstan-var array
- */
- private array $idToEnum = [];
-
- /**
- * @var int[]
- * @phpstan-var array
- */
- private array $enumToId = [];
+ /** @phpstan-use IntSaveIdMapTrait */
+ use IntSaveIdMapTrait;
private function __construct(){
- $this->register(SuspiciousStewTypeIds::POPPY, SuspiciousStewType::POPPY());
- $this->register(SuspiciousStewTypeIds::CORNFLOWER, SuspiciousStewType::CORNFLOWER());
- $this->register(SuspiciousStewTypeIds::TULIP, SuspiciousStewType::TULIP());
- $this->register(SuspiciousStewTypeIds::AZURE_BLUET, SuspiciousStewType::AZURE_BLUET());
- $this->register(SuspiciousStewTypeIds::LILY_OF_THE_VALLEY, SuspiciousStewType::LILY_OF_THE_VALLEY());
- $this->register(SuspiciousStewTypeIds::DANDELION, SuspiciousStewType::DANDELION());
- $this->register(SuspiciousStewTypeIds::BLUE_ORCHID, SuspiciousStewType::BLUE_ORCHID());
- $this->register(SuspiciousStewTypeIds::ALLIUM, SuspiciousStewType::ALLIUM());
- $this->register(SuspiciousStewTypeIds::OXEYE_DAISY, SuspiciousStewType::OXEYE_DAISY());
- $this->register(SuspiciousStewTypeIds::WITHER_ROSE, SuspiciousStewType::WITHER_ROSE());
- }
-
- private function register(int $id, SuspiciousStewType $type) : void{
- $this->idToEnum[$id] = $type;
- $this->enumToId[$type->id()] = $id;
- }
-
- public function fromId(int $id) : ?SuspiciousStewType{
- return $this->idToEnum[$id] ?? null;
- }
-
- public function toId(SuspiciousStewType $type) : int{
- if(!isset($this->enumToId[$type->id()])){
- throw new \InvalidArgumentException("Type does not have a mapped ID");
+ foreach(SuspiciousStewType::cases() as $case){
+ $this->register(match($case){
+ SuspiciousStewType::POPPY => SuspiciousStewTypeIds::POPPY,
+ SuspiciousStewType::CORNFLOWER => SuspiciousStewTypeIds::CORNFLOWER,
+ SuspiciousStewType::TULIP => SuspiciousStewTypeIds::TULIP,
+ SuspiciousStewType::AZURE_BLUET => SuspiciousStewTypeIds::AZURE_BLUET,
+ SuspiciousStewType::LILY_OF_THE_VALLEY => SuspiciousStewTypeIds::LILY_OF_THE_VALLEY,
+ SuspiciousStewType::DANDELION => SuspiciousStewTypeIds::DANDELION,
+ SuspiciousStewType::BLUE_ORCHID => SuspiciousStewTypeIds::BLUE_ORCHID,
+ SuspiciousStewType::ALLIUM => SuspiciousStewTypeIds::ALLIUM,
+ SuspiciousStewType::OXEYE_DAISY => SuspiciousStewTypeIds::OXEYE_DAISY,
+ SuspiciousStewType::WITHER_ROSE => SuspiciousStewTypeIds::WITHER_ROSE,
+ }, $case);
}
- return $this->enumToId[$type->id()];
+
}
}
diff --git a/src/data/bedrock/block/BlockStateData.php b/src/data/bedrock/block/BlockStateData.php
index e30836f3b0e..6624c4ae06e 100644
--- a/src/data/bedrock/block/BlockStateData.php
+++ b/src/data/bedrock/block/BlockStateData.php
@@ -38,12 +38,15 @@
final class BlockStateData{
/**
* Bedrock version of the most recent backwards-incompatible change to blockstates.
+ *
+ * This is *not* the same as current game version. It should match the numbers in the
+ * newest blockstate upgrade schema used in BedrockBlockUpgradeSchema.
*/
public const CURRENT_VERSION =
(1 << 24) | //major
- (19 << 16) | //minor
- (80 << 8) | //patch
- (11); //revision
+ (21 << 16) | //minor
+ (40 << 8) | //patch
+ (1); //revision
public const TAG_NAME = "name";
public const TAG_STATES = "states";
@@ -111,7 +114,10 @@ public static function fromNbt(CompoundTag $nbt) : self{
return new self($name, $states->getValue(), $version);
}
- public function toNbt() : CompoundTag{
+ /**
+ * Encodes the blockstate as a TAG_Compound, exactly as it would be in vanilla Bedrock.
+ */
+ public function toVanillaNbt() : CompoundTag{
$statesTag = CompoundTag::create();
foreach(Utils::stringifyKeys($this->states) as $key => $value){
$statesTag->setTag($key, $value);
@@ -119,7 +125,15 @@ public function toNbt() : CompoundTag{
return CompoundTag::create()
->setString(self::TAG_NAME, $this->name)
->setInt(self::TAG_VERSION, $this->version)
- ->setTag(self::TAG_STATES, $statesTag)
+ ->setTag(self::TAG_STATES, $statesTag);
+ }
+
+ /**
+ * Encodes the blockstate as a TAG_Compound, but with extra PM-specific metadata, used for fixing bugs in old saved
+ * data. This should be used for anything saved to disk.
+ */
+ public function toNbt() : CompoundTag{
+ return $this->toVanillaNbt()
->setLong(VersionInfo::TAG_WORLD_DATA_VERSION, VersionInfo::WORLD_DATA_VERSION);
}
diff --git a/src/data/bedrock/block/BlockStateNames.php b/src/data/bedrock/block/BlockStateNames.php
index e241380a03f..0f4b8742613 100644
--- a/src/data/bedrock/block/BlockStateNames.php
+++ b/src/data/bedrock/block/BlockStateNames.php
@@ -34,7 +34,6 @@ private function __construct(){
public const ACTIVE = "active";
public const AGE = "age";
public const AGE_BIT = "age_bit";
- public const ALLOW_UNDERWATER_BIT = "allow_underwater_bit";
public const ATTACHED_BIT = "attached_bit";
public const ATTACHMENT = "attachment";
public const BAMBOO_LEAF_SIZE = "bamboo_leaf_size";
@@ -42,7 +41,6 @@ private function __construct(){
public const BIG_DRIPLEAF_HEAD = "big_dripleaf_head";
public const BIG_DRIPLEAF_TILT = "big_dripleaf_tilt";
public const BITE_COUNTER = "bite_counter";
- public const BLOCK_LIGHT_LEVEL = "block_light_level";
public const BLOOM = "bloom";
public const BOOKS_STORED = "books_stored";
public const BREWING_STAND_SLOT_A_BIT = "brewing_stand_slot_a_bit";
@@ -53,27 +51,19 @@ private function __construct(){
public const CAN_SUMMON = "can_summon";
public const CANDLES = "candles";
public const CAULDRON_LIQUID = "cauldron_liquid";
- public const CHEMISTRY_TABLE_TYPE = "chemistry_table_type";
- public const CHISEL_TYPE = "chisel_type";
public const CLUSTER_COUNT = "cluster_count";
- public const COLOR = "color";
- public const COLOR_BIT = "color_bit";
public const COMPOSTER_FILL_LEVEL = "composter_fill_level";
public const CONDITIONAL_BIT = "conditional_bit";
- public const CORAL_COLOR = "coral_color";
public const CORAL_DIRECTION = "coral_direction";
public const CORAL_FAN_DIRECTION = "coral_fan_direction";
- public const CORAL_HANG_TYPE_BIT = "coral_hang_type_bit";
public const COVERED_BIT = "covered_bit";
public const CRACKED_STATE = "cracked_state";
- public const DAMAGE = "damage";
+ public const CRAFTING = "crafting";
public const DEAD_BIT = "dead_bit";
public const DEPRECATED = "deprecated";
public const DIRECTION = "direction";
- public const DIRT_TYPE = "dirt_type";
public const DISARMED_BIT = "disarmed_bit";
public const DOOR_HINGE_BIT = "door_hinge_bit";
- public const DOUBLE_PLANT_TYPE = "double_plant_type";
public const DRAG_DOWN = "drag_down";
public const DRIPSTONE_THICKNESS = "dripstone_thickness";
public const END_PORTAL_EYE_BIT = "end_portal_eye_bit";
@@ -81,7 +71,6 @@ private function __construct(){
public const EXTINGUISHED = "extinguished";
public const FACING_DIRECTION = "facing_direction";
public const FILL_LEVEL = "fill_level";
- public const FLOWER_TYPE = "flower_type";
public const GROUND_SIGN_DIRECTION = "ground_sign_direction";
public const GROWING_PLANT_AGE = "growing_plant_age";
public const GROWTH = "growth";
@@ -98,20 +87,22 @@ private function __construct(){
public const LEVER_DIRECTION = "lever_direction";
public const LIQUID_DEPTH = "liquid_depth";
public const LIT = "lit";
+ public const MC_BLOCK_FACE = "minecraft:block_face";
+ public const MC_CARDINAL_DIRECTION = "minecraft:cardinal_direction";
+ public const MC_FACING_DIRECTION = "minecraft:facing_direction";
+ public const MC_VERTICAL_HALF = "minecraft:vertical_half";
public const MOISTURIZED_AMOUNT = "moisturized_amount";
- public const MONSTER_EGG_STONE_TYPE = "monster_egg_stone_type";
public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits";
- public const NEW_LEAF_TYPE = "new_leaf_type";
public const OCCUPIED_BIT = "occupied_bit";
- public const OLD_LEAF_TYPE = "old_leaf_type";
+ 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 PERSISTENT_BIT = "persistent_bit";
public const PILLAR_AXIS = "pillar_axis";
public const PORTAL_AXIS = "portal_axis";
public const POWERED_BIT = "powered_bit";
- public const PRISMARINE_BLOCK_TYPE = "prismarine_block_type";
public const PROPAGULE_STAGE = "propagule_stage";
public const RAIL_DATA_BIT = "rail_data_bit";
public const RAIL_DIRECTION = "rail_direction";
@@ -119,35 +110,23 @@ private function __construct(){
public const REPEATER_DELAY = "repeater_delay";
public const RESPAWN_ANCHOR_CHARGE = "respawn_anchor_charge";
public const ROTATION = "rotation";
- public const SAND_STONE_TYPE = "sand_stone_type";
- public const SAND_TYPE = "sand_type";
- public const SAPLING_TYPE = "sapling_type";
+ public const SCULK_SENSOR_PHASE = "sculk_sensor_phase";
public const SEA_GRASS_TYPE = "sea_grass_type";
- public const SPONGE_TYPE = "sponge_type";
public const STABILITY = "stability";
public const STABILITY_CHECK = "stability_check";
- public const STONE_BRICK_TYPE = "stone_brick_type";
- public const STONE_SLAB_TYPE = "stone_slab_type";
- public const STONE_SLAB_TYPE_2 = "stone_slab_type_2";
- public const STONE_SLAB_TYPE_3 = "stone_slab_type_3";
- public const STONE_SLAB_TYPE_4 = "stone_slab_type_4";
- public const STONE_TYPE = "stone_type";
- public const STRIPPED_BIT = "stripped_bit";
public const STRUCTURE_BLOCK_TYPE = "structure_block_type";
- public const STRUCTURE_VOID_TYPE = "structure_void_type";
public const SUSPENDED_BIT = "suspended_bit";
- public const TALL_GRASS_TYPE = "tall_grass_type";
public const TOGGLE_BIT = "toggle_bit";
- public const TOP_SLOT_BIT = "top_slot_bit";
public const TORCH_FACING_DIRECTION = "torch_facing_direction";
+ public const TRIAL_SPAWNER_STATE = "trial_spawner_state";
public const TRIGGERED_BIT = "triggered_bit";
public const TURTLE_EGG_COUNT = "turtle_egg_count";
public const TWISTING_VINES_AGE = "twisting_vines_age";
public const UPDATE_BIT = "update_bit";
public const UPPER_BLOCK_BIT = "upper_block_bit";
public const UPSIDE_DOWN_BIT = "upside_down_bit";
+ public const VAULT_STATE = "vault_state";
public const VINE_DIRECTION_BITS = "vine_direction_bits";
- public const WALL_BLOCK_TYPE = "wall_block_type";
public const WALL_CONNECTION_TYPE_EAST = "wall_connection_type_east";
public const WALL_CONNECTION_TYPE_NORTH = "wall_connection_type_north";
public const WALL_CONNECTION_TYPE_SOUTH = "wall_connection_type_south";
@@ -155,5 +134,4 @@ private function __construct(){
public const WALL_POST_BIT = "wall_post_bit";
public const WEEPING_VINES_AGE = "weeping_vines_age";
public const WEIRDO_DIRECTION = "weirdo_direction";
- public const WOOD_TYPE = "wood_type";
}
diff --git a/src/data/bedrock/block/BlockStateStringValues.php b/src/data/bedrock/block/BlockStateStringValues.php
index ba77cd3873d..9dfdcfb6340 100644
--- a/src/data/bedrock/block/BlockStateStringValues.php
+++ b/src/data/bedrock/block/BlockStateStringValues.php
@@ -52,76 +52,16 @@ private function __construct(){
public const CAULDRON_LIQUID_POWDER_SNOW = "powder_snow";
public const CAULDRON_LIQUID_WATER = "water";
- public const CHEMISTRY_TABLE_TYPE_COMPOUND_CREATOR = "compound_creator";
- public const CHEMISTRY_TABLE_TYPE_ELEMENT_CONSTRUCTOR = "element_constructor";
- public const CHEMISTRY_TABLE_TYPE_LAB_TABLE = "lab_table";
- public const CHEMISTRY_TABLE_TYPE_MATERIAL_REDUCER = "material_reducer";
-
- public const CHISEL_TYPE_CHISELED = "chiseled";
- public const CHISEL_TYPE_DEFAULT = "default";
- public const CHISEL_TYPE_LINES = "lines";
- public const CHISEL_TYPE_SMOOTH = "smooth";
-
- public const COLOR_BLACK = "black";
- public const COLOR_BLUE = "blue";
- public const COLOR_BROWN = "brown";
- public const COLOR_CYAN = "cyan";
- public const COLOR_GRAY = "gray";
- public const COLOR_GREEN = "green";
- public const COLOR_LIGHT_BLUE = "light_blue";
- public const COLOR_LIME = "lime";
- public const COLOR_MAGENTA = "magenta";
- public const COLOR_ORANGE = "orange";
- public const COLOR_PINK = "pink";
- public const COLOR_PURPLE = "purple";
- public const COLOR_RED = "red";
- public const COLOR_SILVER = "silver";
- public const COLOR_WHITE = "white";
- public const COLOR_YELLOW = "yellow";
-
- public const CORAL_COLOR_BLUE = "blue";
- public const CORAL_COLOR_PINK = "pink";
- public const CORAL_COLOR_PURPLE = "purple";
- public const CORAL_COLOR_RED = "red";
- public const CORAL_COLOR_YELLOW = "yellow";
-
public const CRACKED_STATE_CRACKED = "cracked";
public const CRACKED_STATE_MAX_CRACKED = "max_cracked";
public const CRACKED_STATE_NO_CRACKS = "no_cracks";
- public const DAMAGE_BROKEN = "broken";
- public const DAMAGE_SLIGHTLY_DAMAGED = "slightly_damaged";
- public const DAMAGE_UNDAMAGED = "undamaged";
- public const DAMAGE_VERY_DAMAGED = "very_damaged";
-
- public const DIRT_TYPE_COARSE = "coarse";
- public const DIRT_TYPE_NORMAL = "normal";
-
- public const DOUBLE_PLANT_TYPE_FERN = "fern";
- public const DOUBLE_PLANT_TYPE_GRASS = "grass";
- public const DOUBLE_PLANT_TYPE_PAEONIA = "paeonia";
- public const DOUBLE_PLANT_TYPE_ROSE = "rose";
- public const DOUBLE_PLANT_TYPE_SUNFLOWER = "sunflower";
- public const DOUBLE_PLANT_TYPE_SYRINGA = "syringa";
-
public const DRIPSTONE_THICKNESS_BASE = "base";
public const DRIPSTONE_THICKNESS_FRUSTUM = "frustum";
public const DRIPSTONE_THICKNESS_MERGE = "merge";
public const DRIPSTONE_THICKNESS_MIDDLE = "middle";
public const DRIPSTONE_THICKNESS_TIP = "tip";
- public const FLOWER_TYPE_ALLIUM = "allium";
- public const FLOWER_TYPE_CORNFLOWER = "cornflower";
- public const FLOWER_TYPE_HOUSTONIA = "houstonia";
- public const FLOWER_TYPE_LILY_OF_THE_VALLEY = "lily_of_the_valley";
- public const FLOWER_TYPE_ORCHID = "orchid";
- public const FLOWER_TYPE_OXEYE = "oxeye";
- public const FLOWER_TYPE_POPPY = "poppy";
- public const FLOWER_TYPE_TULIP_ORANGE = "tulip_orange";
- public const FLOWER_TYPE_TULIP_PINK = "tulip_pink";
- public const FLOWER_TYPE_TULIP_RED = "tulip_red";
- public const FLOWER_TYPE_TULIP_WHITE = "tulip_white";
-
public const LEVER_DIRECTION_DOWN_EAST_WEST = "down_east_west";
public const LEVER_DIRECTION_DOWN_NORTH_SOUTH = "down_north_south";
public const LEVER_DIRECTION_EAST = "east";
@@ -131,20 +71,40 @@ private function __construct(){
public const LEVER_DIRECTION_UP_NORTH_SOUTH = "up_north_south";
public const LEVER_DIRECTION_WEST = "west";
- public const MONSTER_EGG_STONE_TYPE_CHISELED_STONE_BRICK = "chiseled_stone_brick";
- public const MONSTER_EGG_STONE_TYPE_COBBLESTONE = "cobblestone";
- public const MONSTER_EGG_STONE_TYPE_CRACKED_STONE_BRICK = "cracked_stone_brick";
- public const MONSTER_EGG_STONE_TYPE_MOSSY_STONE_BRICK = "mossy_stone_brick";
- public const MONSTER_EGG_STONE_TYPE_STONE = "stone";
- public const MONSTER_EGG_STONE_TYPE_STONE_BRICK = "stone_brick";
-
- public const NEW_LEAF_TYPE_ACACIA = "acacia";
- public const NEW_LEAF_TYPE_DARK_OAK = "dark_oak";
-
- public const OLD_LEAF_TYPE_BIRCH = "birch";
- public const OLD_LEAF_TYPE_JUNGLE = "jungle";
- public const OLD_LEAF_TYPE_OAK = "oak";
- public const OLD_LEAF_TYPE_SPRUCE = "spruce";
+ public const MC_BLOCK_FACE_DOWN = "down";
+ public const MC_BLOCK_FACE_EAST = "east";
+ public const MC_BLOCK_FACE_NORTH = "north";
+ public const MC_BLOCK_FACE_SOUTH = "south";
+ public const MC_BLOCK_FACE_UP = "up";
+ public const MC_BLOCK_FACE_WEST = "west";
+
+ public const MC_CARDINAL_DIRECTION_EAST = "east";
+ public const MC_CARDINAL_DIRECTION_NORTH = "north";
+ public const MC_CARDINAL_DIRECTION_SOUTH = "south";
+ public const MC_CARDINAL_DIRECTION_WEST = "west";
+
+ public const MC_FACING_DIRECTION_DOWN = "down";
+ public const MC_FACING_DIRECTION_EAST = "east";
+ public const MC_FACING_DIRECTION_NORTH = "north";
+ public const MC_FACING_DIRECTION_SOUTH = "south";
+ public const MC_FACING_DIRECTION_UP = "up";
+ public const MC_FACING_DIRECTION_WEST = "west";
+
+ public const MC_VERTICAL_HALF_BOTTOM = "bottom";
+ public const MC_VERTICAL_HALF_TOP = "top";
+
+ public const ORIENTATION_DOWN_EAST = "down_east";
+ public const ORIENTATION_DOWN_NORTH = "down_north";
+ public const ORIENTATION_DOWN_SOUTH = "down_south";
+ public const ORIENTATION_DOWN_WEST = "down_west";
+ public const ORIENTATION_EAST_UP = "east_up";
+ public const ORIENTATION_NORTH_UP = "north_up";
+ public const ORIENTATION_SOUTH_UP = "south_up";
+ public const ORIENTATION_UP_EAST = "up_east";
+ public const ORIENTATION_UP_NORTH = "up_north";
+ public const ORIENTATION_UP_SOUTH = "up_south";
+ public const ORIENTATION_UP_WEST = "up_west";
+ public const ORIENTATION_WEST_UP = "west_up";
public const PILLAR_AXIS_X = "x";
public const PILLAR_AXIS_Y = "y";
@@ -154,79 +114,10 @@ private function __construct(){
public const PORTAL_AXIS_X = "x";
public const PORTAL_AXIS_Z = "z";
- public const PRISMARINE_BLOCK_TYPE_BRICKS = "bricks";
- public const PRISMARINE_BLOCK_TYPE_DARK = "dark";
- public const PRISMARINE_BLOCK_TYPE_DEFAULT = "default";
-
- public const SAND_STONE_TYPE_CUT = "cut";
- public const SAND_STONE_TYPE_DEFAULT = "default";
- public const SAND_STONE_TYPE_HEIROGLYPHS = "heiroglyphs";
- public const SAND_STONE_TYPE_SMOOTH = "smooth";
-
- public const SAND_TYPE_NORMAL = "normal";
- public const SAND_TYPE_RED = "red";
-
- public const SAPLING_TYPE_ACACIA = "acacia";
- public const SAPLING_TYPE_BIRCH = "birch";
- public const SAPLING_TYPE_DARK_OAK = "dark_oak";
- public const SAPLING_TYPE_JUNGLE = "jungle";
- public const SAPLING_TYPE_OAK = "oak";
- public const SAPLING_TYPE_SPRUCE = "spruce";
-
public const SEA_GRASS_TYPE_DEFAULT = "default";
public const SEA_GRASS_TYPE_DOUBLE_BOT = "double_bot";
public const SEA_GRASS_TYPE_DOUBLE_TOP = "double_top";
- public const SPONGE_TYPE_DRY = "dry";
- public const SPONGE_TYPE_WET = "wet";
-
- public const STONE_BRICK_TYPE_CHISELED = "chiseled";
- public const STONE_BRICK_TYPE_CRACKED = "cracked";
- public const STONE_BRICK_TYPE_DEFAULT = "default";
- public const STONE_BRICK_TYPE_MOSSY = "mossy";
- public const STONE_BRICK_TYPE_SMOOTH = "smooth";
-
- public const STONE_SLAB_TYPE_BRICK = "brick";
- public const STONE_SLAB_TYPE_COBBLESTONE = "cobblestone";
- public const STONE_SLAB_TYPE_NETHER_BRICK = "nether_brick";
- public const STONE_SLAB_TYPE_QUARTZ = "quartz";
- public const STONE_SLAB_TYPE_SANDSTONE = "sandstone";
- public const STONE_SLAB_TYPE_SMOOTH_STONE = "smooth_stone";
- public const STONE_SLAB_TYPE_STONE_BRICK = "stone_brick";
- public const STONE_SLAB_TYPE_WOOD = "wood";
-
- public const STONE_SLAB_TYPE_2_MOSSY_COBBLESTONE = "mossy_cobblestone";
- public const STONE_SLAB_TYPE_2_PRISMARINE_BRICK = "prismarine_brick";
- public const STONE_SLAB_TYPE_2_PRISMARINE_DARK = "prismarine_dark";
- public const STONE_SLAB_TYPE_2_PRISMARINE_ROUGH = "prismarine_rough";
- public const STONE_SLAB_TYPE_2_PURPUR = "purpur";
- public const STONE_SLAB_TYPE_2_RED_NETHER_BRICK = "red_nether_brick";
- public const STONE_SLAB_TYPE_2_RED_SANDSTONE = "red_sandstone";
- public const STONE_SLAB_TYPE_2_SMOOTH_SANDSTONE = "smooth_sandstone";
-
- public const STONE_SLAB_TYPE_3_ANDESITE = "andesite";
- public const STONE_SLAB_TYPE_3_DIORITE = "diorite";
- public const STONE_SLAB_TYPE_3_END_STONE_BRICK = "end_stone_brick";
- public const STONE_SLAB_TYPE_3_GRANITE = "granite";
- public const STONE_SLAB_TYPE_3_POLISHED_ANDESITE = "polished_andesite";
- public const STONE_SLAB_TYPE_3_POLISHED_DIORITE = "polished_diorite";
- public const STONE_SLAB_TYPE_3_POLISHED_GRANITE = "polished_granite";
- public const STONE_SLAB_TYPE_3_SMOOTH_RED_SANDSTONE = "smooth_red_sandstone";
-
- public const STONE_SLAB_TYPE_4_CUT_RED_SANDSTONE = "cut_red_sandstone";
- public const STONE_SLAB_TYPE_4_CUT_SANDSTONE = "cut_sandstone";
- public const STONE_SLAB_TYPE_4_MOSSY_STONE_BRICK = "mossy_stone_brick";
- public const STONE_SLAB_TYPE_4_SMOOTH_QUARTZ = "smooth_quartz";
- public const STONE_SLAB_TYPE_4_STONE = "stone";
-
- public const STONE_TYPE_ANDESITE = "andesite";
- public const STONE_TYPE_ANDESITE_SMOOTH = "andesite_smooth";
- public const STONE_TYPE_DIORITE = "diorite";
- public const STONE_TYPE_DIORITE_SMOOTH = "diorite_smooth";
- public const STONE_TYPE_GRANITE = "granite";
- public const STONE_TYPE_GRANITE_SMOOTH = "granite_smooth";
- public const STONE_TYPE_STONE = "stone";
-
public const STRUCTURE_BLOCK_TYPE_CORNER = "corner";
public const STRUCTURE_BLOCK_TYPE_DATA = "data";
public const STRUCTURE_BLOCK_TYPE_EXPORT = "export";
@@ -234,14 +125,6 @@ private function __construct(){
public const STRUCTURE_BLOCK_TYPE_LOAD = "load";
public const STRUCTURE_BLOCK_TYPE_SAVE = "save";
- public const STRUCTURE_VOID_TYPE_AIR = "air";
- public const STRUCTURE_VOID_TYPE_VOID = "void";
-
- public const TALL_GRASS_TYPE_DEFAULT = "default";
- public const TALL_GRASS_TYPE_FERN = "fern";
- public const TALL_GRASS_TYPE_SNOW = "snow";
- public const TALL_GRASS_TYPE_TALL = "tall";
-
public const TORCH_FACING_DIRECTION_EAST = "east";
public const TORCH_FACING_DIRECTION_NORTH = "north";
public const TORCH_FACING_DIRECTION_SOUTH = "south";
@@ -254,20 +137,10 @@ private function __construct(){
public const TURTLE_EGG_COUNT_THREE_EGG = "three_egg";
public const TURTLE_EGG_COUNT_TWO_EGG = "two_egg";
- public const WALL_BLOCK_TYPE_ANDESITE = "andesite";
- public const WALL_BLOCK_TYPE_BRICK = "brick";
- public const WALL_BLOCK_TYPE_COBBLESTONE = "cobblestone";
- public const WALL_BLOCK_TYPE_DIORITE = "diorite";
- public const WALL_BLOCK_TYPE_END_BRICK = "end_brick";
- public const WALL_BLOCK_TYPE_GRANITE = "granite";
- public const WALL_BLOCK_TYPE_MOSSY_COBBLESTONE = "mossy_cobblestone";
- public const WALL_BLOCK_TYPE_MOSSY_STONE_BRICK = "mossy_stone_brick";
- public const WALL_BLOCK_TYPE_NETHER_BRICK = "nether_brick";
- public const WALL_BLOCK_TYPE_PRISMARINE = "prismarine";
- public const WALL_BLOCK_TYPE_RED_NETHER_BRICK = "red_nether_brick";
- public const WALL_BLOCK_TYPE_RED_SANDSTONE = "red_sandstone";
- public const WALL_BLOCK_TYPE_SANDSTONE = "sandstone";
- public const WALL_BLOCK_TYPE_STONE_BRICK = "stone_brick";
+ public const VAULT_STATE_ACTIVE = "active";
+ public const VAULT_STATE_EJECTING = "ejecting";
+ public const VAULT_STATE_INACTIVE = "inactive";
+ public const VAULT_STATE_UNLOCKING = "unlocking";
public const WALL_CONNECTION_TYPE_EAST_NONE = "none";
public const WALL_CONNECTION_TYPE_EAST_SHORT = "short";
@@ -285,11 +158,4 @@ private function __construct(){
public const WALL_CONNECTION_TYPE_WEST_SHORT = "short";
public const WALL_CONNECTION_TYPE_WEST_TALL = "tall";
- public const WOOD_TYPE_ACACIA = "acacia";
- public const WOOD_TYPE_BIRCH = "birch";
- public const WOOD_TYPE_DARK_OAK = "dark_oak";
- public const WOOD_TYPE_JUNGLE = "jungle";
- public const WOOD_TYPE_OAK = "oak";
- public const WOOD_TYPE_SPRUCE = "spruce";
-
}
diff --git a/src/data/bedrock/block/BlockTypeNames.php b/src/data/bedrock/block/BlockTypeNames.php
index f94b721763c..029a5f6aadb 100644
--- a/src/data/bedrock/block/BlockTypeNames.php
+++ b/src/data/bedrock/block/BlockTypeNames.php
@@ -33,26 +33,38 @@ private function __construct(){
public const ACACIA_BUTTON = "minecraft:acacia_button";
public const ACACIA_DOOR = "minecraft:acacia_door";
+ public const ACACIA_DOUBLE_SLAB = "minecraft:acacia_double_slab";
public const ACACIA_FENCE = "minecraft:acacia_fence";
public const ACACIA_FENCE_GATE = "minecraft:acacia_fence_gate";
public const ACACIA_HANGING_SIGN = "minecraft:acacia_hanging_sign";
+ public const ACACIA_LEAVES = "minecraft:acacia_leaves";
public const ACACIA_LOG = "minecraft:acacia_log";
+ public const ACACIA_PLANKS = "minecraft:acacia_planks";
public const ACACIA_PRESSURE_PLATE = "minecraft:acacia_pressure_plate";
+ public const ACACIA_SAPLING = "minecraft:acacia_sapling";
+ public const ACACIA_SLAB = "minecraft:acacia_slab";
public const ACACIA_STAIRS = "minecraft:acacia_stairs";
public const ACACIA_STANDING_SIGN = "minecraft:acacia_standing_sign";
public const ACACIA_TRAPDOOR = "minecraft:acacia_trapdoor";
public const ACACIA_WALL_SIGN = "minecraft:acacia_wall_sign";
+ public const ACACIA_WOOD = "minecraft:acacia_wood";
public const ACTIVATOR_RAIL = "minecraft:activator_rail";
public const AIR = "minecraft:air";
+ public const ALLIUM = "minecraft:allium";
public const ALLOW = "minecraft:allow";
public const AMETHYST_BLOCK = "minecraft:amethyst_block";
public const AMETHYST_CLUSTER = "minecraft:amethyst_cluster";
public const ANCIENT_DEBRIS = "minecraft:ancient_debris";
+ public const ANDESITE = "minecraft:andesite";
+ public const ANDESITE_DOUBLE_SLAB = "minecraft:andesite_double_slab";
+ public const ANDESITE_SLAB = "minecraft:andesite_slab";
public const ANDESITE_STAIRS = "minecraft:andesite_stairs";
+ public const ANDESITE_WALL = "minecraft:andesite_wall";
public const ANVIL = "minecraft:anvil";
public const AZALEA = "minecraft:azalea";
public const AZALEA_LEAVES = "minecraft:azalea_leaves";
public const AZALEA_LEAVES_FLOWERED = "minecraft:azalea_leaves_flowered";
+ public const AZURE_BLUET = "minecraft:azure_bluet";
public const BAMBOO = "minecraft:bamboo";
public const BAMBOO_BLOCK = "minecraft:bamboo_block";
public const BAMBOO_BUTTON = "minecraft:bamboo_button";
@@ -86,18 +98,31 @@ private function __construct(){
public const BIG_DRIPLEAF = "minecraft:big_dripleaf";
public const BIRCH_BUTTON = "minecraft:birch_button";
public const BIRCH_DOOR = "minecraft:birch_door";
+ public const BIRCH_DOUBLE_SLAB = "minecraft:birch_double_slab";
public const BIRCH_FENCE = "minecraft:birch_fence";
public const BIRCH_FENCE_GATE = "minecraft:birch_fence_gate";
public const BIRCH_HANGING_SIGN = "minecraft:birch_hanging_sign";
+ public const BIRCH_LEAVES = "minecraft:birch_leaves";
public const BIRCH_LOG = "minecraft:birch_log";
+ public const BIRCH_PLANKS = "minecraft:birch_planks";
public const BIRCH_PRESSURE_PLATE = "minecraft:birch_pressure_plate";
+ public const BIRCH_SAPLING = "minecraft:birch_sapling";
+ public const BIRCH_SLAB = "minecraft:birch_slab";
public const BIRCH_STAIRS = "minecraft:birch_stairs";
public const BIRCH_STANDING_SIGN = "minecraft:birch_standing_sign";
public const BIRCH_TRAPDOOR = "minecraft:birch_trapdoor";
public const BIRCH_WALL_SIGN = "minecraft:birch_wall_sign";
+ public const BIRCH_WOOD = "minecraft:birch_wood";
public const BLACK_CANDLE = "minecraft:black_candle";
public const BLACK_CANDLE_CAKE = "minecraft:black_candle_cake";
+ public const BLACK_CARPET = "minecraft:black_carpet";
+ public const BLACK_CONCRETE = "minecraft:black_concrete";
+ public const BLACK_CONCRETE_POWDER = "minecraft:black_concrete_powder";
public const BLACK_GLAZED_TERRACOTTA = "minecraft:black_glazed_terracotta";
+ public const BLACK_SHULKER_BOX = "minecraft:black_shulker_box";
+ public const BLACK_STAINED_GLASS = "minecraft:black_stained_glass";
+ public const BLACK_STAINED_GLASS_PANE = "minecraft:black_stained_glass_pane";
+ public const BLACK_TERRACOTTA = "minecraft:black_terracotta";
public const BLACK_WOOL = "minecraft:black_wool";
public const BLACKSTONE = "minecraft:blackstone";
public const BLACKSTONE_DOUBLE_SLAB = "minecraft:blackstone_double_slab";
@@ -107,22 +132,48 @@ private function __construct(){
public const BLAST_FURNACE = "minecraft:blast_furnace";
public const BLUE_CANDLE = "minecraft:blue_candle";
public const BLUE_CANDLE_CAKE = "minecraft:blue_candle_cake";
+ public const BLUE_CARPET = "minecraft:blue_carpet";
+ public const BLUE_CONCRETE = "minecraft:blue_concrete";
+ public const BLUE_CONCRETE_POWDER = "minecraft:blue_concrete_powder";
public const BLUE_GLAZED_TERRACOTTA = "minecraft:blue_glazed_terracotta";
public const BLUE_ICE = "minecraft:blue_ice";
+ public const BLUE_ORCHID = "minecraft:blue_orchid";
+ public const BLUE_SHULKER_BOX = "minecraft:blue_shulker_box";
+ public const BLUE_STAINED_GLASS = "minecraft:blue_stained_glass";
+ public const BLUE_STAINED_GLASS_PANE = "minecraft:blue_stained_glass_pane";
+ public const BLUE_TERRACOTTA = "minecraft:blue_terracotta";
public const BLUE_WOOL = "minecraft:blue_wool";
public const BONE_BLOCK = "minecraft:bone_block";
public const BOOKSHELF = "minecraft:bookshelf";
public const BORDER_BLOCK = "minecraft:border_block";
+ public const BRAIN_CORAL = "minecraft:brain_coral";
+ public const BRAIN_CORAL_BLOCK = "minecraft:brain_coral_block";
+ public const BRAIN_CORAL_FAN = "minecraft:brain_coral_fan";
+ public const BRAIN_CORAL_WALL_FAN = "minecraft:brain_coral_wall_fan";
public const BREWING_STAND = "minecraft:brewing_stand";
public const BRICK_BLOCK = "minecraft:brick_block";
+ public const BRICK_DOUBLE_SLAB = "minecraft:brick_double_slab";
+ public const BRICK_SLAB = "minecraft:brick_slab";
public const BRICK_STAIRS = "minecraft:brick_stairs";
+ public const BRICK_WALL = "minecraft:brick_wall";
public const BROWN_CANDLE = "minecraft:brown_candle";
public const BROWN_CANDLE_CAKE = "minecraft:brown_candle_cake";
+ public const BROWN_CARPET = "minecraft:brown_carpet";
+ public const BROWN_CONCRETE = "minecraft:brown_concrete";
+ public const BROWN_CONCRETE_POWDER = "minecraft:brown_concrete_powder";
public const BROWN_GLAZED_TERRACOTTA = "minecraft:brown_glazed_terracotta";
public const BROWN_MUSHROOM = "minecraft:brown_mushroom";
public const BROWN_MUSHROOM_BLOCK = "minecraft:brown_mushroom_block";
+ public const BROWN_SHULKER_BOX = "minecraft:brown_shulker_box";
+ public const BROWN_STAINED_GLASS = "minecraft:brown_stained_glass";
+ public const BROWN_STAINED_GLASS_PANE = "minecraft:brown_stained_glass_pane";
+ public const BROWN_TERRACOTTA = "minecraft:brown_terracotta";
public const BROWN_WOOL = "minecraft:brown_wool";
public const BUBBLE_COLUMN = "minecraft:bubble_column";
+ public const BUBBLE_CORAL = "minecraft:bubble_coral";
+ public const BUBBLE_CORAL_BLOCK = "minecraft:bubble_coral_block";
+ public const BUBBLE_CORAL_FAN = "minecraft:bubble_coral_fan";
+ public const BUBBLE_CORAL_WALL_FAN = "minecraft:bubble_coral_wall_fan";
public const BUDDING_AMETHYST = "minecraft:budding_amethyst";
public const CACTUS = "minecraft:cactus";
public const CAKE = "minecraft:cake";
@@ -132,7 +183,6 @@ private function __construct(){
public const CAMPFIRE = "minecraft:campfire";
public const CANDLE = "minecraft:candle";
public const CANDLE_CAKE = "minecraft:candle_cake";
- public const CARPET = "minecraft:carpet";
public const CARROTS = "minecraft:carrots";
public const CARTOGRAPHY_TABLE = "minecraft:cartography_table";
public const CARVED_PUMPKIN = "minecraft:carved_pumpkin";
@@ -143,7 +193,6 @@ private function __construct(){
public const CHAIN = "minecraft:chain";
public const CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block";
public const CHEMICAL_HEAT = "minecraft:chemical_heat";
- public const CHEMISTRY_TABLE = "minecraft:chemistry_table";
public const CHERRY_BUTTON = "minecraft:cherry_button";
public const CHERRY_DOOR = "minecraft:cherry_door";
public const CHERRY_DOUBLE_SLAB = "minecraft:cherry_double_slab";
@@ -162,45 +211,58 @@ private function __construct(){
public const CHERRY_WALL_SIGN = "minecraft:cherry_wall_sign";
public const CHERRY_WOOD = "minecraft:cherry_wood";
public const CHEST = "minecraft:chest";
+ public const CHIPPED_ANVIL = "minecraft:chipped_anvil";
public const CHISELED_BOOKSHELF = "minecraft:chiseled_bookshelf";
+ public const CHISELED_COPPER = "minecraft:chiseled_copper";
public const CHISELED_DEEPSLATE = "minecraft:chiseled_deepslate";
public const CHISELED_NETHER_BRICKS = "minecraft:chiseled_nether_bricks";
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_SANDSTONE = "minecraft:chiseled_sandstone";
+ public const CHISELED_STONE_BRICKS = "minecraft:chiseled_stone_bricks";
+ public const CHISELED_TUFF = "minecraft:chiseled_tuff";
+ public const CHISELED_TUFF_BRICKS = "minecraft:chiseled_tuff_bricks";
public const CHORUS_FLOWER = "minecraft:chorus_flower";
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 COAL_BLOCK = "minecraft:coal_block";
public const COAL_ORE = "minecraft:coal_ore";
+ public const COARSE_DIRT = "minecraft:coarse_dirt";
public const COBBLED_DEEPSLATE = "minecraft:cobbled_deepslate";
public const COBBLED_DEEPSLATE_DOUBLE_SLAB = "minecraft:cobbled_deepslate_double_slab";
public const COBBLED_DEEPSLATE_SLAB = "minecraft:cobbled_deepslate_slab";
public const COBBLED_DEEPSLATE_STAIRS = "minecraft:cobbled_deepslate_stairs";
public const COBBLED_DEEPSLATE_WALL = "minecraft:cobbled_deepslate_wall";
public const COBBLESTONE = "minecraft:cobblestone";
+ public const COBBLESTONE_DOUBLE_SLAB = "minecraft:cobblestone_double_slab";
+ public const COBBLESTONE_SLAB = "minecraft:cobblestone_slab";
public const COBBLESTONE_WALL = "minecraft:cobblestone_wall";
public const COCOA = "minecraft:cocoa";
- public const COLORED_TORCH_BP = "minecraft:colored_torch_bp";
- public const COLORED_TORCH_RG = "minecraft:colored_torch_rg";
+ public const COLORED_TORCH_BLUE = "minecraft:colored_torch_blue";
+ public const COLORED_TORCH_GREEN = "minecraft:colored_torch_green";
+ public const COLORED_TORCH_PURPLE = "minecraft:colored_torch_purple";
+ public const COLORED_TORCH_RED = "minecraft:colored_torch_red";
public const COMMAND_BLOCK = "minecraft:command_block";
public const COMPOSTER = "minecraft:composter";
- public const CONCRETE = "minecraft:concrete";
- public const CONCRETE_POWDER = "minecraft:concrete_powder";
+ public const COMPOUND_CREATOR = "minecraft:compound_creator";
public const CONDUIT = "minecraft:conduit";
public const COPPER_BLOCK = "minecraft:copper_block";
+ public const COPPER_BULB = "minecraft:copper_bulb";
+ public const COPPER_DOOR = "minecraft:copper_door";
+ public const COPPER_GRATE = "minecraft:copper_grate";
public const COPPER_ORE = "minecraft:copper_ore";
- public const CORAL = "minecraft:coral";
- public const CORAL_BLOCK = "minecraft:coral_block";
- public const CORAL_FAN = "minecraft:coral_fan";
- public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead";
- public const CORAL_FAN_HANG = "minecraft:coral_fan_hang";
- public const CORAL_FAN_HANG2 = "minecraft:coral_fan_hang2";
- public const CORAL_FAN_HANG3 = "minecraft:coral_fan_hang3";
+ public const COPPER_TRAPDOOR = "minecraft:copper_trapdoor";
+ public const CORNFLOWER = "minecraft:cornflower";
public const CRACKED_DEEPSLATE_BRICKS = "minecraft:cracked_deepslate_bricks";
public const CRACKED_DEEPSLATE_TILES = "minecraft:cracked_deepslate_tiles";
public const CRACKED_NETHER_BRICKS = "minecraft:cracked_nether_bricks";
public const CRACKED_POLISHED_BLACKSTONE_BRICKS = "minecraft:cracked_polished_blackstone_bricks";
+ public const CRACKED_STONE_BRICKS = "minecraft:cracked_stone_bricks";
+ public const CRAFTER = "minecraft:crafter";
public const CRAFTING_TABLE = "minecraft:crafting_table";
+ public const CREEPER_HEAD = "minecraft:creeper_head";
public const CRIMSON_BUTTON = "minecraft:crimson_button";
public const CRIMSON_DOOR = "minecraft:crimson_door";
public const CRIMSON_DOUBLE_SLAB = "minecraft:crimson_double_slab";
@@ -223,24 +285,68 @@ private function __construct(){
public const CUT_COPPER = "minecraft:cut_copper";
public const CUT_COPPER_SLAB = "minecraft:cut_copper_slab";
public const CUT_COPPER_STAIRS = "minecraft:cut_copper_stairs";
+ public const CUT_RED_SANDSTONE = "minecraft:cut_red_sandstone";
+ public const CUT_RED_SANDSTONE_DOUBLE_SLAB = "minecraft:cut_red_sandstone_double_slab";
+ public const CUT_RED_SANDSTONE_SLAB = "minecraft:cut_red_sandstone_slab";
+ public const CUT_SANDSTONE = "minecraft:cut_sandstone";
+ public const CUT_SANDSTONE_DOUBLE_SLAB = "minecraft:cut_sandstone_double_slab";
+ public const CUT_SANDSTONE_SLAB = "minecraft:cut_sandstone_slab";
public const CYAN_CANDLE = "minecraft:cyan_candle";
public const CYAN_CANDLE_CAKE = "minecraft:cyan_candle_cake";
+ public const CYAN_CARPET = "minecraft:cyan_carpet";
+ public const CYAN_CONCRETE = "minecraft:cyan_concrete";
+ public const CYAN_CONCRETE_POWDER = "minecraft:cyan_concrete_powder";
public const CYAN_GLAZED_TERRACOTTA = "minecraft:cyan_glazed_terracotta";
+ public const CYAN_SHULKER_BOX = "minecraft:cyan_shulker_box";
+ public const CYAN_STAINED_GLASS = "minecraft:cyan_stained_glass";
+ public const CYAN_STAINED_GLASS_PANE = "minecraft:cyan_stained_glass_pane";
+ public const CYAN_TERRACOTTA = "minecraft:cyan_terracotta";
public const CYAN_WOOL = "minecraft:cyan_wool";
+ public const DAMAGED_ANVIL = "minecraft:damaged_anvil";
+ public const DANDELION = "minecraft:dandelion";
public const DARK_OAK_BUTTON = "minecraft:dark_oak_button";
public const DARK_OAK_DOOR = "minecraft:dark_oak_door";
+ public const DARK_OAK_DOUBLE_SLAB = "minecraft:dark_oak_double_slab";
public const DARK_OAK_FENCE = "minecraft:dark_oak_fence";
public const DARK_OAK_FENCE_GATE = "minecraft:dark_oak_fence_gate";
public const DARK_OAK_HANGING_SIGN = "minecraft:dark_oak_hanging_sign";
+ public const DARK_OAK_LEAVES = "minecraft:dark_oak_leaves";
public const DARK_OAK_LOG = "minecraft:dark_oak_log";
+ public const DARK_OAK_PLANKS = "minecraft:dark_oak_planks";
public const DARK_OAK_PRESSURE_PLATE = "minecraft:dark_oak_pressure_plate";
+ public const DARK_OAK_SAPLING = "minecraft:dark_oak_sapling";
+ public const DARK_OAK_SLAB = "minecraft:dark_oak_slab";
public const DARK_OAK_STAIRS = "minecraft:dark_oak_stairs";
public const DARK_OAK_TRAPDOOR = "minecraft:dark_oak_trapdoor";
+ public const DARK_OAK_WOOD = "minecraft:dark_oak_wood";
+ public const DARK_PRISMARINE = "minecraft:dark_prismarine";
+ public const DARK_PRISMARINE_DOUBLE_SLAB = "minecraft:dark_prismarine_double_slab";
+ public const DARK_PRISMARINE_SLAB = "minecraft:dark_prismarine_slab";
public const DARK_PRISMARINE_STAIRS = "minecraft:dark_prismarine_stairs";
public const DARKOAK_STANDING_SIGN = "minecraft:darkoak_standing_sign";
public const DARKOAK_WALL_SIGN = "minecraft:darkoak_wall_sign";
public const DAYLIGHT_DETECTOR = "minecraft:daylight_detector";
public const DAYLIGHT_DETECTOR_INVERTED = "minecraft:daylight_detector_inverted";
+ public const DEAD_BRAIN_CORAL = "minecraft:dead_brain_coral";
+ public const DEAD_BRAIN_CORAL_BLOCK = "minecraft:dead_brain_coral_block";
+ public const DEAD_BRAIN_CORAL_FAN = "minecraft:dead_brain_coral_fan";
+ public const DEAD_BRAIN_CORAL_WALL_FAN = "minecraft:dead_brain_coral_wall_fan";
+ public const DEAD_BUBBLE_CORAL = "minecraft:dead_bubble_coral";
+ public const DEAD_BUBBLE_CORAL_BLOCK = "minecraft:dead_bubble_coral_block";
+ public const DEAD_BUBBLE_CORAL_FAN = "minecraft:dead_bubble_coral_fan";
+ public const DEAD_BUBBLE_CORAL_WALL_FAN = "minecraft:dead_bubble_coral_wall_fan";
+ public const DEAD_FIRE_CORAL = "minecraft:dead_fire_coral";
+ public const DEAD_FIRE_CORAL_BLOCK = "minecraft:dead_fire_coral_block";
+ public const DEAD_FIRE_CORAL_FAN = "minecraft:dead_fire_coral_fan";
+ public const DEAD_FIRE_CORAL_WALL_FAN = "minecraft:dead_fire_coral_wall_fan";
+ public const DEAD_HORN_CORAL = "minecraft:dead_horn_coral";
+ public const DEAD_HORN_CORAL_BLOCK = "minecraft:dead_horn_coral_block";
+ public const DEAD_HORN_CORAL_FAN = "minecraft:dead_horn_coral_fan";
+ public const DEAD_HORN_CORAL_WALL_FAN = "minecraft:dead_horn_coral_wall_fan";
+ public const DEAD_TUBE_CORAL = "minecraft:dead_tube_coral";
+ public const DEAD_TUBE_CORAL_BLOCK = "minecraft:dead_tube_coral_block";
+ public const DEAD_TUBE_CORAL_FAN = "minecraft:dead_tube_coral_fan";
+ public const DEAD_TUBE_CORAL_WALL_FAN = "minecraft:dead_tube_coral_wall_fan";
public const DEADBUSH = "minecraft:deadbush";
public const DECORATED_POT = "minecraft:decorated_pot";
public const DEEPSLATE = "minecraft:deepslate";
@@ -263,21 +369,23 @@ private function __construct(){
public const DEEPSLATE_TILE_WALL = "minecraft:deepslate_tile_wall";
public const DEEPSLATE_TILES = "minecraft:deepslate_tiles";
public const DENY = "minecraft:deny";
+ public const DEPRECATED_ANVIL = "minecraft:deprecated_anvil";
+ public const DEPRECATED_PURPUR_BLOCK_1 = "minecraft:deprecated_purpur_block_1";
+ public const DEPRECATED_PURPUR_BLOCK_2 = "minecraft:deprecated_purpur_block_2";
public const DETECTOR_RAIL = "minecraft:detector_rail";
public const DIAMOND_BLOCK = "minecraft:diamond_block";
public const DIAMOND_ORE = "minecraft:diamond_ore";
+ public const DIORITE = "minecraft:diorite";
+ public const DIORITE_DOUBLE_SLAB = "minecraft:diorite_double_slab";
+ public const DIORITE_SLAB = "minecraft:diorite_slab";
public const DIORITE_STAIRS = "minecraft:diorite_stairs";
+ public const DIORITE_WALL = "minecraft:diorite_wall";
public const DIRT = "minecraft:dirt";
public const DIRT_WITH_ROOTS = "minecraft:dirt_with_roots";
public const DISPENSER = "minecraft:dispenser";
public const DOUBLE_CUT_COPPER_SLAB = "minecraft:double_cut_copper_slab";
- public const DOUBLE_PLANT = "minecraft:double_plant";
- public const DOUBLE_STONE_BLOCK_SLAB = "minecraft:double_stone_block_slab";
- public const DOUBLE_STONE_BLOCK_SLAB2 = "minecraft:double_stone_block_slab2";
- public const DOUBLE_STONE_BLOCK_SLAB3 = "minecraft:double_stone_block_slab3";
- public const DOUBLE_STONE_BLOCK_SLAB4 = "minecraft:double_stone_block_slab4";
- public const DOUBLE_WOODEN_SLAB = "minecraft:double_wooden_slab";
public const DRAGON_EGG = "minecraft:dragon_egg";
+ public const DRAGON_HEAD = "minecraft:dragon_head";
public const DRIED_KELP_BLOCK = "minecraft:dried_kelp_block";
public const DRIPSTONE_BLOCK = "minecraft:dripstone_block";
public const DROPPER = "minecraft:dropper";
@@ -400,6 +508,7 @@ private function __construct(){
public const ELEMENT_97 = "minecraft:element_97";
public const ELEMENT_98 = "minecraft:element_98";
public const ELEMENT_99 = "minecraft:element_99";
+ public const ELEMENT_CONSTRUCTOR = "minecraft:element_constructor";
public const EMERALD_BLOCK = "minecraft:emerald_block";
public const EMERALD_ORE = "minecraft:emerald_ore";
public const ENCHANTING_TABLE = "minecraft:enchanting_table";
@@ -410,15 +519,28 @@ private function __construct(){
public const END_PORTAL_FRAME = "minecraft:end_portal_frame";
public const END_ROD = "minecraft:end_rod";
public const END_STONE = "minecraft:end_stone";
+ public const END_STONE_BRICK_DOUBLE_SLAB = "minecraft:end_stone_brick_double_slab";
+ public const END_STONE_BRICK_SLAB = "minecraft:end_stone_brick_slab";
+ public const END_STONE_BRICK_WALL = "minecraft:end_stone_brick_wall";
public const ENDER_CHEST = "minecraft:ender_chest";
+ public const EXPOSED_CHISELED_COPPER = "minecraft:exposed_chiseled_copper";
public const EXPOSED_COPPER = "minecraft:exposed_copper";
+ public const EXPOSED_COPPER_BULB = "minecraft:exposed_copper_bulb";
+ public const EXPOSED_COPPER_DOOR = "minecraft:exposed_copper_door";
+ public const EXPOSED_COPPER_GRATE = "minecraft:exposed_copper_grate";
+ public const EXPOSED_COPPER_TRAPDOOR = "minecraft:exposed_copper_trapdoor";
public const EXPOSED_CUT_COPPER = "minecraft:exposed_cut_copper";
public const EXPOSED_CUT_COPPER_SLAB = "minecraft:exposed_cut_copper_slab";
public const EXPOSED_CUT_COPPER_STAIRS = "minecraft:exposed_cut_copper_stairs";
public const EXPOSED_DOUBLE_CUT_COPPER_SLAB = "minecraft:exposed_double_cut_copper_slab";
public const FARMLAND = "minecraft:farmland";
public const FENCE_GATE = "minecraft:fence_gate";
+ public const FERN = "minecraft:fern";
public const FIRE = "minecraft:fire";
+ public const FIRE_CORAL = "minecraft:fire_coral";
+ public const FIRE_CORAL_BLOCK = "minecraft:fire_coral_block";
+ public const FIRE_CORAL_FAN = "minecraft:fire_coral_fan";
+ public const FIRE_CORAL_WALL_FAN = "minecraft:fire_coral_wall_fan";
public const FLETCHING_TABLE = "minecraft:fletching_table";
public const FLOWER_POT = "minecraft:flower_pot";
public const FLOWERING_AZALEA = "minecraft:flowering_azalea";
@@ -438,32 +560,91 @@ private function __construct(){
public const GOLD_BLOCK = "minecraft:gold_block";
public const GOLD_ORE = "minecraft:gold_ore";
public const GOLDEN_RAIL = "minecraft:golden_rail";
+ public const GRANITE = "minecraft:granite";
+ public const GRANITE_DOUBLE_SLAB = "minecraft:granite_double_slab";
+ public const GRANITE_SLAB = "minecraft:granite_slab";
public const GRANITE_STAIRS = "minecraft:granite_stairs";
- public const GRASS = "minecraft:grass";
+ public const GRANITE_WALL = "minecraft:granite_wall";
+ public const GRASS_BLOCK = "minecraft:grass_block";
public const GRASS_PATH = "minecraft:grass_path";
public const GRAVEL = "minecraft:gravel";
public const GRAY_CANDLE = "minecraft:gray_candle";
public const GRAY_CANDLE_CAKE = "minecraft:gray_candle_cake";
+ public const GRAY_CARPET = "minecraft:gray_carpet";
+ public const GRAY_CONCRETE = "minecraft:gray_concrete";
+ public const GRAY_CONCRETE_POWDER = "minecraft:gray_concrete_powder";
public const GRAY_GLAZED_TERRACOTTA = "minecraft:gray_glazed_terracotta";
+ public const GRAY_SHULKER_BOX = "minecraft:gray_shulker_box";
+ public const GRAY_STAINED_GLASS = "minecraft:gray_stained_glass";
+ public const GRAY_STAINED_GLASS_PANE = "minecraft:gray_stained_glass_pane";
+ public const GRAY_TERRACOTTA = "minecraft:gray_terracotta";
public const GRAY_WOOL = "minecraft:gray_wool";
public const GREEN_CANDLE = "minecraft:green_candle";
public const GREEN_CANDLE_CAKE = "minecraft:green_candle_cake";
+ public const GREEN_CARPET = "minecraft:green_carpet";
+ public const GREEN_CONCRETE = "minecraft:green_concrete";
+ public const GREEN_CONCRETE_POWDER = "minecraft:green_concrete_powder";
public const GREEN_GLAZED_TERRACOTTA = "minecraft:green_glazed_terracotta";
+ public const GREEN_SHULKER_BOX = "minecraft:green_shulker_box";
+ public const GREEN_STAINED_GLASS = "minecraft:green_stained_glass";
+ public const GREEN_STAINED_GLASS_PANE = "minecraft:green_stained_glass_pane";
+ public const GREEN_TERRACOTTA = "minecraft:green_terracotta";
public const GREEN_WOOL = "minecraft:green_wool";
public const GRINDSTONE = "minecraft:grindstone";
public const HANGING_ROOTS = "minecraft:hanging_roots";
+ public const HARD_BLACK_STAINED_GLASS = "minecraft:hard_black_stained_glass";
+ public const HARD_BLACK_STAINED_GLASS_PANE = "minecraft:hard_black_stained_glass_pane";
+ public const HARD_BLUE_STAINED_GLASS = "minecraft:hard_blue_stained_glass";
+ public const HARD_BLUE_STAINED_GLASS_PANE = "minecraft:hard_blue_stained_glass_pane";
+ public const HARD_BROWN_STAINED_GLASS = "minecraft:hard_brown_stained_glass";
+ public const HARD_BROWN_STAINED_GLASS_PANE = "minecraft:hard_brown_stained_glass_pane";
+ public const HARD_CYAN_STAINED_GLASS = "minecraft:hard_cyan_stained_glass";
+ public const HARD_CYAN_STAINED_GLASS_PANE = "minecraft:hard_cyan_stained_glass_pane";
public const HARD_GLASS = "minecraft:hard_glass";
public const HARD_GLASS_PANE = "minecraft:hard_glass_pane";
- public const HARD_STAINED_GLASS = "minecraft:hard_stained_glass";
- public const HARD_STAINED_GLASS_PANE = "minecraft:hard_stained_glass_pane";
+ public const HARD_GRAY_STAINED_GLASS = "minecraft:hard_gray_stained_glass";
+ public const HARD_GRAY_STAINED_GLASS_PANE = "minecraft:hard_gray_stained_glass_pane";
+ public const HARD_GREEN_STAINED_GLASS = "minecraft:hard_green_stained_glass";
+ public const HARD_GREEN_STAINED_GLASS_PANE = "minecraft:hard_green_stained_glass_pane";
+ public const HARD_LIGHT_BLUE_STAINED_GLASS = "minecraft:hard_light_blue_stained_glass";
+ public const HARD_LIGHT_BLUE_STAINED_GLASS_PANE = "minecraft:hard_light_blue_stained_glass_pane";
+ public const HARD_LIGHT_GRAY_STAINED_GLASS = "minecraft:hard_light_gray_stained_glass";
+ public const HARD_LIGHT_GRAY_STAINED_GLASS_PANE = "minecraft:hard_light_gray_stained_glass_pane";
+ public const HARD_LIME_STAINED_GLASS = "minecraft:hard_lime_stained_glass";
+ public const HARD_LIME_STAINED_GLASS_PANE = "minecraft:hard_lime_stained_glass_pane";
+ public const HARD_MAGENTA_STAINED_GLASS = "minecraft:hard_magenta_stained_glass";
+ public const HARD_MAGENTA_STAINED_GLASS_PANE = "minecraft:hard_magenta_stained_glass_pane";
+ public const HARD_ORANGE_STAINED_GLASS = "minecraft:hard_orange_stained_glass";
+ public const HARD_ORANGE_STAINED_GLASS_PANE = "minecraft:hard_orange_stained_glass_pane";
+ public const HARD_PINK_STAINED_GLASS = "minecraft:hard_pink_stained_glass";
+ public const HARD_PINK_STAINED_GLASS_PANE = "minecraft:hard_pink_stained_glass_pane";
+ public const HARD_PURPLE_STAINED_GLASS = "minecraft:hard_purple_stained_glass";
+ public const HARD_PURPLE_STAINED_GLASS_PANE = "minecraft:hard_purple_stained_glass_pane";
+ public const HARD_RED_STAINED_GLASS = "minecraft:hard_red_stained_glass";
+ public const HARD_RED_STAINED_GLASS_PANE = "minecraft:hard_red_stained_glass_pane";
+ public const HARD_WHITE_STAINED_GLASS = "minecraft:hard_white_stained_glass";
+ public const HARD_WHITE_STAINED_GLASS_PANE = "minecraft:hard_white_stained_glass_pane";
+ public const HARD_YELLOW_STAINED_GLASS = "minecraft:hard_yellow_stained_glass";
+ public const HARD_YELLOW_STAINED_GLASS_PANE = "minecraft:hard_yellow_stained_glass_pane";
public const HARDENED_CLAY = "minecraft:hardened_clay";
public const HAY_BLOCK = "minecraft:hay_block";
+ public const HEAVY_CORE = "minecraft:heavy_core";
public const HEAVY_WEIGHTED_PRESSURE_PLATE = "minecraft:heavy_weighted_pressure_plate";
public const HONEY_BLOCK = "minecraft:honey_block";
public const HONEYCOMB_BLOCK = "minecraft:honeycomb_block";
public const HOPPER = "minecraft:hopper";
+ public const HORN_CORAL = "minecraft:horn_coral";
+ public const HORN_CORAL_BLOCK = "minecraft:horn_coral_block";
+ public const HORN_CORAL_FAN = "minecraft:horn_coral_fan";
+ public const HORN_CORAL_WALL_FAN = "minecraft:horn_coral_wall_fan";
public const ICE = "minecraft:ice";
+ public const INFESTED_CHISELED_STONE_BRICKS = "minecraft:infested_chiseled_stone_bricks";
+ public const INFESTED_COBBLESTONE = "minecraft:infested_cobblestone";
+ public const INFESTED_CRACKED_STONE_BRICKS = "minecraft:infested_cracked_stone_bricks";
public const INFESTED_DEEPSLATE = "minecraft:infested_deepslate";
+ public const INFESTED_MOSSY_STONE_BRICKS = "minecraft:infested_mossy_stone_bricks";
+ public const INFESTED_STONE = "minecraft:infested_stone";
+ public const INFESTED_STONE_BRICKS = "minecraft:infested_stone_bricks";
public const INFO_UPDATE = "minecraft:info_update";
public const INFO_UPDATE2 = "minecraft:info_update2";
public const INVISIBLE_BEDROCK = "minecraft:invisible_bedrock";
@@ -476,40 +657,83 @@ private function __construct(){
public const JUKEBOX = "minecraft:jukebox";
public const JUNGLE_BUTTON = "minecraft:jungle_button";
public const JUNGLE_DOOR = "minecraft:jungle_door";
+ public const JUNGLE_DOUBLE_SLAB = "minecraft:jungle_double_slab";
public const JUNGLE_FENCE = "minecraft:jungle_fence";
public const JUNGLE_FENCE_GATE = "minecraft:jungle_fence_gate";
public const JUNGLE_HANGING_SIGN = "minecraft:jungle_hanging_sign";
+ public const JUNGLE_LEAVES = "minecraft:jungle_leaves";
public const JUNGLE_LOG = "minecraft:jungle_log";
+ public const JUNGLE_PLANKS = "minecraft:jungle_planks";
public const JUNGLE_PRESSURE_PLATE = "minecraft:jungle_pressure_plate";
+ public const JUNGLE_SAPLING = "minecraft:jungle_sapling";
+ public const JUNGLE_SLAB = "minecraft:jungle_slab";
public const JUNGLE_STAIRS = "minecraft:jungle_stairs";
public const JUNGLE_STANDING_SIGN = "minecraft:jungle_standing_sign";
public const JUNGLE_TRAPDOOR = "minecraft:jungle_trapdoor";
public const JUNGLE_WALL_SIGN = "minecraft:jungle_wall_sign";
+ public const JUNGLE_WOOD = "minecraft:jungle_wood";
public const KELP = "minecraft:kelp";
+ public const LAB_TABLE = "minecraft:lab_table";
public const LADDER = "minecraft:ladder";
public const LANTERN = "minecraft:lantern";
public const LAPIS_BLOCK = "minecraft:lapis_block";
public const LAPIS_ORE = "minecraft:lapis_ore";
public const LARGE_AMETHYST_BUD = "minecraft:large_amethyst_bud";
+ public const LARGE_FERN = "minecraft:large_fern";
public const LAVA = "minecraft:lava";
- public const LAVA_CAULDRON = "minecraft:lava_cauldron";
- public const LEAVES = "minecraft:leaves";
- public const LEAVES2 = "minecraft:leaves2";
public const LECTERN = "minecraft:lectern";
public const LEVER = "minecraft:lever";
- public const LIGHT_BLOCK = "minecraft:light_block";
+ public const LIGHT_BLOCK_0 = "minecraft:light_block_0";
+ public const LIGHT_BLOCK_1 = "minecraft:light_block_1";
+ public const LIGHT_BLOCK_10 = "minecraft:light_block_10";
+ public const LIGHT_BLOCK_11 = "minecraft:light_block_11";
+ public const LIGHT_BLOCK_12 = "minecraft:light_block_12";
+ public const LIGHT_BLOCK_13 = "minecraft:light_block_13";
+ public const LIGHT_BLOCK_14 = "minecraft:light_block_14";
+ public const LIGHT_BLOCK_15 = "minecraft:light_block_15";
+ public const LIGHT_BLOCK_2 = "minecraft:light_block_2";
+ public const LIGHT_BLOCK_3 = "minecraft:light_block_3";
+ public const LIGHT_BLOCK_4 = "minecraft:light_block_4";
+ public const LIGHT_BLOCK_5 = "minecraft:light_block_5";
+ public const LIGHT_BLOCK_6 = "minecraft:light_block_6";
+ public const LIGHT_BLOCK_7 = "minecraft:light_block_7";
+ public const LIGHT_BLOCK_8 = "minecraft:light_block_8";
+ public const LIGHT_BLOCK_9 = "minecraft:light_block_9";
public const LIGHT_BLUE_CANDLE = "minecraft:light_blue_candle";
public const LIGHT_BLUE_CANDLE_CAKE = "minecraft:light_blue_candle_cake";
+ public const LIGHT_BLUE_CARPET = "minecraft:light_blue_carpet";
+ public const LIGHT_BLUE_CONCRETE = "minecraft:light_blue_concrete";
+ public const LIGHT_BLUE_CONCRETE_POWDER = "minecraft:light_blue_concrete_powder";
public const LIGHT_BLUE_GLAZED_TERRACOTTA = "minecraft:light_blue_glazed_terracotta";
+ public const LIGHT_BLUE_SHULKER_BOX = "minecraft:light_blue_shulker_box";
+ public const LIGHT_BLUE_STAINED_GLASS = "minecraft:light_blue_stained_glass";
+ public const LIGHT_BLUE_STAINED_GLASS_PANE = "minecraft:light_blue_stained_glass_pane";
+ public const LIGHT_BLUE_TERRACOTTA = "minecraft:light_blue_terracotta";
public const LIGHT_BLUE_WOOL = "minecraft:light_blue_wool";
public const LIGHT_GRAY_CANDLE = "minecraft:light_gray_candle";
public const LIGHT_GRAY_CANDLE_CAKE = "minecraft:light_gray_candle_cake";
+ public const LIGHT_GRAY_CARPET = "minecraft:light_gray_carpet";
+ public const LIGHT_GRAY_CONCRETE = "minecraft:light_gray_concrete";
+ public const LIGHT_GRAY_CONCRETE_POWDER = "minecraft:light_gray_concrete_powder";
+ public const LIGHT_GRAY_SHULKER_BOX = "minecraft:light_gray_shulker_box";
+ public const LIGHT_GRAY_STAINED_GLASS = "minecraft:light_gray_stained_glass";
+ public const LIGHT_GRAY_STAINED_GLASS_PANE = "minecraft:light_gray_stained_glass_pane";
+ public const LIGHT_GRAY_TERRACOTTA = "minecraft:light_gray_terracotta";
public const LIGHT_GRAY_WOOL = "minecraft:light_gray_wool";
public const LIGHT_WEIGHTED_PRESSURE_PLATE = "minecraft:light_weighted_pressure_plate";
public const LIGHTNING_ROD = "minecraft:lightning_rod";
+ public const LILAC = "minecraft:lilac";
+ public const LILY_OF_THE_VALLEY = "minecraft:lily_of_the_valley";
public const LIME_CANDLE = "minecraft:lime_candle";
public const LIME_CANDLE_CAKE = "minecraft:lime_candle_cake";
+ public const LIME_CARPET = "minecraft:lime_carpet";
+ public const LIME_CONCRETE = "minecraft:lime_concrete";
+ public const LIME_CONCRETE_POWDER = "minecraft:lime_concrete_powder";
public const LIME_GLAZED_TERRACOTTA = "minecraft:lime_glazed_terracotta";
+ public const LIME_SHULKER_BOX = "minecraft:lime_shulker_box";
+ public const LIME_STAINED_GLASS = "minecraft:lime_stained_glass";
+ public const LIME_STAINED_GLASS_PANE = "minecraft:lime_stained_glass_pane";
+ public const LIME_TERRACOTTA = "minecraft:lime_terracotta";
public const LIME_WOOL = "minecraft:lime_wool";
public const LIT_BLAST_FURNACE = "minecraft:lit_blast_furnace";
public const LIT_DEEPSLATE_REDSTONE_ORE = "minecraft:lit_deepslate_redstone_ore";
@@ -522,7 +746,14 @@ private function __construct(){
public const LOOM = "minecraft:loom";
public const MAGENTA_CANDLE = "minecraft:magenta_candle";
public const MAGENTA_CANDLE_CAKE = "minecraft:magenta_candle_cake";
+ public const MAGENTA_CARPET = "minecraft:magenta_carpet";
+ public const MAGENTA_CONCRETE = "minecraft:magenta_concrete";
+ public const MAGENTA_CONCRETE_POWDER = "minecraft:magenta_concrete_powder";
public const MAGENTA_GLAZED_TERRACOTTA = "minecraft:magenta_glazed_terracotta";
+ public const MAGENTA_SHULKER_BOX = "minecraft:magenta_shulker_box";
+ public const MAGENTA_STAINED_GLASS = "minecraft:magenta_stained_glass";
+ public const MAGENTA_STAINED_GLASS_PANE = "minecraft:magenta_stained_glass_pane";
+ public const MAGENTA_TERRACOTTA = "minecraft:magenta_terracotta";
public const MAGENTA_WOOL = "minecraft:magenta_wool";
public const MAGMA = "minecraft:magma";
public const MANGROVE_BUTTON = "minecraft:mangrove_button";
@@ -543,16 +774,23 @@ private function __construct(){
public const MANGROVE_TRAPDOOR = "minecraft:mangrove_trapdoor";
public const MANGROVE_WALL_SIGN = "minecraft:mangrove_wall_sign";
public const MANGROVE_WOOD = "minecraft:mangrove_wood";
+ public const MATERIAL_REDUCER = "minecraft:material_reducer";
public const MEDIUM_AMETHYST_BUD = "minecraft:medium_amethyst_bud";
public const MELON_BLOCK = "minecraft:melon_block";
public const MELON_STEM = "minecraft:melon_stem";
public const MOB_SPAWNER = "minecraft:mob_spawner";
- public const MONSTER_EGG = "minecraft:monster_egg";
public const MOSS_BLOCK = "minecraft:moss_block";
public const MOSS_CARPET = "minecraft:moss_carpet";
public const MOSSY_COBBLESTONE = "minecraft:mossy_cobblestone";
+ public const MOSSY_COBBLESTONE_DOUBLE_SLAB = "minecraft:mossy_cobblestone_double_slab";
+ public const MOSSY_COBBLESTONE_SLAB = "minecraft:mossy_cobblestone_slab";
public const MOSSY_COBBLESTONE_STAIRS = "minecraft:mossy_cobblestone_stairs";
+ public const MOSSY_COBBLESTONE_WALL = "minecraft:mossy_cobblestone_wall";
+ public const MOSSY_STONE_BRICK_DOUBLE_SLAB = "minecraft:mossy_stone_brick_double_slab";
+ public const MOSSY_STONE_BRICK_SLAB = "minecraft:mossy_stone_brick_slab";
public const MOSSY_STONE_BRICK_STAIRS = "minecraft:mossy_stone_brick_stairs";
+ public const MOSSY_STONE_BRICK_WALL = "minecraft:mossy_stone_brick_wall";
+ public const MOSSY_STONE_BRICKS = "minecraft:mossy_stone_bricks";
public const MOVING_BLOCK = "minecraft:moving_block";
public const MUD = "minecraft:mud";
public const MUD_BRICK_DOUBLE_SLAB = "minecraft:mud_brick_double_slab";
@@ -561,10 +799,14 @@ private function __construct(){
public const MUD_BRICK_WALL = "minecraft:mud_brick_wall";
public const MUD_BRICKS = "minecraft:mud_bricks";
public const MUDDY_MANGROVE_ROOTS = "minecraft:muddy_mangrove_roots";
+ public const MUSHROOM_STEM = "minecraft:mushroom_stem";
public const MYCELIUM = "minecraft:mycelium";
public const NETHER_BRICK = "minecraft:nether_brick";
+ public const NETHER_BRICK_DOUBLE_SLAB = "minecraft:nether_brick_double_slab";
public const NETHER_BRICK_FENCE = "minecraft:nether_brick_fence";
+ public const NETHER_BRICK_SLAB = "minecraft:nether_brick_slab";
public const NETHER_BRICK_STAIRS = "minecraft:nether_brick_stairs";
+ public const NETHER_BRICK_WALL = "minecraft:nether_brick_wall";
public const NETHER_GOLD_ORE = "minecraft:nether_gold_ore";
public const NETHER_SPROUTS = "minecraft:nether_sprouts";
public const NETHER_WART = "minecraft:nether_wart";
@@ -572,20 +814,42 @@ private function __construct(){
public const NETHERITE_BLOCK = "minecraft:netherite_block";
public const NETHERRACK = "minecraft:netherrack";
public const NETHERREACTOR = "minecraft:netherreactor";
+ public const NORMAL_STONE_DOUBLE_SLAB = "minecraft:normal_stone_double_slab";
+ public const NORMAL_STONE_SLAB = "minecraft:normal_stone_slab";
public const NORMAL_STONE_STAIRS = "minecraft:normal_stone_stairs";
public const NOTEBLOCK = "minecraft:noteblock";
+ public const OAK_DOUBLE_SLAB = "minecraft:oak_double_slab";
public const OAK_FENCE = "minecraft:oak_fence";
public const OAK_HANGING_SIGN = "minecraft:oak_hanging_sign";
+ public const OAK_LEAVES = "minecraft:oak_leaves";
public const OAK_LOG = "minecraft:oak_log";
+ public const OAK_PLANKS = "minecraft:oak_planks";
+ public const OAK_SAPLING = "minecraft:oak_sapling";
+ public const OAK_SLAB = "minecraft:oak_slab";
public const OAK_STAIRS = "minecraft:oak_stairs";
+ public const OAK_WOOD = "minecraft:oak_wood";
public const OBSERVER = "minecraft:observer";
public const OBSIDIAN = "minecraft:obsidian";
public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight";
public const ORANGE_CANDLE = "minecraft:orange_candle";
public const ORANGE_CANDLE_CAKE = "minecraft:orange_candle_cake";
+ public const ORANGE_CARPET = "minecraft:orange_carpet";
+ public const ORANGE_CONCRETE = "minecraft:orange_concrete";
+ public const ORANGE_CONCRETE_POWDER = "minecraft:orange_concrete_powder";
public const ORANGE_GLAZED_TERRACOTTA = "minecraft:orange_glazed_terracotta";
+ public const ORANGE_SHULKER_BOX = "minecraft:orange_shulker_box";
+ public const ORANGE_STAINED_GLASS = "minecraft:orange_stained_glass";
+ public const ORANGE_STAINED_GLASS_PANE = "minecraft:orange_stained_glass_pane";
+ public const ORANGE_TERRACOTTA = "minecraft:orange_terracotta";
+ public const ORANGE_TULIP = "minecraft:orange_tulip";
public const ORANGE_WOOL = "minecraft:orange_wool";
+ public const OXEYE_DAISY = "minecraft:oxeye_daisy";
+ public const OXIDIZED_CHISELED_COPPER = "minecraft:oxidized_chiseled_copper";
public const OXIDIZED_COPPER = "minecraft:oxidized_copper";
+ public const OXIDIZED_COPPER_BULB = "minecraft:oxidized_copper_bulb";
+ public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
+ public const OXIDIZED_COPPER_GRATE = "minecraft:oxidized_copper_grate";
+ public const OXIDIZED_COPPER_TRAPDOOR = "minecraft:oxidized_copper_trapdoor";
public const OXIDIZED_CUT_COPPER = "minecraft:oxidized_cut_copper";
public const OXIDIZED_CUT_COPPER_SLAB = "minecraft:oxidized_cut_copper_slab";
public const OXIDIZED_CUT_COPPER_STAIRS = "minecraft:oxidized_cut_copper_stairs";
@@ -593,16 +857,33 @@ private function __construct(){
public const PACKED_ICE = "minecraft:packed_ice";
public const PACKED_MUD = "minecraft:packed_mud";
public const PEARLESCENT_FROGLIGHT = "minecraft:pearlescent_froglight";
+ public const PEONY = "minecraft:peony";
+ public const PETRIFIED_OAK_DOUBLE_SLAB = "minecraft:petrified_oak_double_slab";
+ public const PETRIFIED_OAK_SLAB = "minecraft:petrified_oak_slab";
+ public const PIGLIN_HEAD = "minecraft:piglin_head";
public const PINK_CANDLE = "minecraft:pink_candle";
public const PINK_CANDLE_CAKE = "minecraft:pink_candle_cake";
+ public const PINK_CARPET = "minecraft:pink_carpet";
+ public const PINK_CONCRETE = "minecraft:pink_concrete";
+ public const PINK_CONCRETE_POWDER = "minecraft:pink_concrete_powder";
public const PINK_GLAZED_TERRACOTTA = "minecraft:pink_glazed_terracotta";
public const PINK_PETALS = "minecraft:pink_petals";
+ public const PINK_SHULKER_BOX = "minecraft:pink_shulker_box";
+ public const PINK_STAINED_GLASS = "minecraft:pink_stained_glass";
+ public const PINK_STAINED_GLASS_PANE = "minecraft:pink_stained_glass_pane";
+ public const PINK_TERRACOTTA = "minecraft:pink_terracotta";
+ public const PINK_TULIP = "minecraft:pink_tulip";
public const PINK_WOOL = "minecraft:pink_wool";
public const PISTON = "minecraft:piston";
public const PISTON_ARM_COLLISION = "minecraft:piston_arm_collision";
- public const PLANKS = "minecraft:planks";
+ public const PITCHER_CROP = "minecraft:pitcher_crop";
+ public const PITCHER_PLANT = "minecraft:pitcher_plant";
+ public const PLAYER_HEAD = "minecraft:player_head";
public const PODZOL = "minecraft:podzol";
public const POINTED_DRIPSTONE = "minecraft:pointed_dripstone";
+ public const POLISHED_ANDESITE = "minecraft:polished_andesite";
+ public const POLISHED_ANDESITE_DOUBLE_SLAB = "minecraft:polished_andesite_double_slab";
+ public const POLISHED_ANDESITE_SLAB = "minecraft:polished_andesite_slab";
public const POLISHED_ANDESITE_STAIRS = "minecraft:polished_andesite_stairs";
public const POLISHED_BASALT = "minecraft:polished_basalt";
public const POLISHED_BLACKSTONE = "minecraft:polished_blackstone";
@@ -622,27 +903,58 @@ private function __construct(){
public const POLISHED_DEEPSLATE_SLAB = "minecraft:polished_deepslate_slab";
public const POLISHED_DEEPSLATE_STAIRS = "minecraft:polished_deepslate_stairs";
public const POLISHED_DEEPSLATE_WALL = "minecraft:polished_deepslate_wall";
+ public const POLISHED_DIORITE = "minecraft:polished_diorite";
+ public const POLISHED_DIORITE_DOUBLE_SLAB = "minecraft:polished_diorite_double_slab";
+ public const POLISHED_DIORITE_SLAB = "minecraft:polished_diorite_slab";
public const POLISHED_DIORITE_STAIRS = "minecraft:polished_diorite_stairs";
+ public const POLISHED_GRANITE = "minecraft:polished_granite";
+ public const POLISHED_GRANITE_DOUBLE_SLAB = "minecraft:polished_granite_double_slab";
+ public const POLISHED_GRANITE_SLAB = "minecraft:polished_granite_slab";
public const POLISHED_GRANITE_STAIRS = "minecraft:polished_granite_stairs";
+ public const POLISHED_TUFF = "minecraft:polished_tuff";
+ public const POLISHED_TUFF_DOUBLE_SLAB = "minecraft:polished_tuff_double_slab";
+ public const POLISHED_TUFF_SLAB = "minecraft:polished_tuff_slab";
+ public const POLISHED_TUFF_STAIRS = "minecraft:polished_tuff_stairs";
+ public const POLISHED_TUFF_WALL = "minecraft:polished_tuff_wall";
+ public const POPPY = "minecraft:poppy";
public const PORTAL = "minecraft:portal";
public const POTATOES = "minecraft:potatoes";
public const POWDER_SNOW = "minecraft:powder_snow";
public const POWERED_COMPARATOR = "minecraft:powered_comparator";
public const POWERED_REPEATER = "minecraft:powered_repeater";
public const PRISMARINE = "minecraft:prismarine";
+ public const PRISMARINE_BRICK_DOUBLE_SLAB = "minecraft:prismarine_brick_double_slab";
+ public const PRISMARINE_BRICK_SLAB = "minecraft:prismarine_brick_slab";
+ public const PRISMARINE_BRICKS = "minecraft:prismarine_bricks";
public const PRISMARINE_BRICKS_STAIRS = "minecraft:prismarine_bricks_stairs";
+ public const PRISMARINE_DOUBLE_SLAB = "minecraft:prismarine_double_slab";
+ public const PRISMARINE_SLAB = "minecraft:prismarine_slab";
public const PRISMARINE_STAIRS = "minecraft:prismarine_stairs";
+ public const PRISMARINE_WALL = "minecraft:prismarine_wall";
public const PUMPKIN = "minecraft:pumpkin";
public const PUMPKIN_STEM = "minecraft:pumpkin_stem";
public const PURPLE_CANDLE = "minecraft:purple_candle";
public const PURPLE_CANDLE_CAKE = "minecraft:purple_candle_cake";
+ public const PURPLE_CARPET = "minecraft:purple_carpet";
+ public const PURPLE_CONCRETE = "minecraft:purple_concrete";
+ public const PURPLE_CONCRETE_POWDER = "minecraft:purple_concrete_powder";
public const PURPLE_GLAZED_TERRACOTTA = "minecraft:purple_glazed_terracotta";
+ public const PURPLE_SHULKER_BOX = "minecraft:purple_shulker_box";
+ public const PURPLE_STAINED_GLASS = "minecraft:purple_stained_glass";
+ public const PURPLE_STAINED_GLASS_PANE = "minecraft:purple_stained_glass_pane";
+ public const PURPLE_TERRACOTTA = "minecraft:purple_terracotta";
public const PURPLE_WOOL = "minecraft:purple_wool";
public const PURPUR_BLOCK = "minecraft:purpur_block";
+ public const PURPUR_DOUBLE_SLAB = "minecraft:purpur_double_slab";
+ public const PURPUR_PILLAR = "minecraft:purpur_pillar";
+ public const PURPUR_SLAB = "minecraft:purpur_slab";
public const PURPUR_STAIRS = "minecraft:purpur_stairs";
public const QUARTZ_BLOCK = "minecraft:quartz_block";
public const QUARTZ_BRICKS = "minecraft:quartz_bricks";
+ public const QUARTZ_DOUBLE_SLAB = "minecraft:quartz_double_slab";
public const QUARTZ_ORE = "minecraft:quartz_ore";
+ public const QUARTZ_PILLAR = "minecraft:quartz_pillar";
+ public const QUARTZ_SLAB = "minecraft:quartz_slab";
public const QUARTZ_STAIRS = "minecraft:quartz_stairs";
public const RAIL = "minecraft:rail";
public const RAW_COPPER_BLOCK = "minecraft:raw_copper_block";
@@ -650,14 +962,28 @@ private function __construct(){
public const RAW_IRON_BLOCK = "minecraft:raw_iron_block";
public const RED_CANDLE = "minecraft:red_candle";
public const RED_CANDLE_CAKE = "minecraft:red_candle_cake";
- public const RED_FLOWER = "minecraft:red_flower";
+ public const RED_CARPET = "minecraft:red_carpet";
+ public const RED_CONCRETE = "minecraft:red_concrete";
+ public const RED_CONCRETE_POWDER = "minecraft:red_concrete_powder";
public const RED_GLAZED_TERRACOTTA = "minecraft:red_glazed_terracotta";
public const RED_MUSHROOM = "minecraft:red_mushroom";
public const RED_MUSHROOM_BLOCK = "minecraft:red_mushroom_block";
public const RED_NETHER_BRICK = "minecraft:red_nether_brick";
+ public const RED_NETHER_BRICK_DOUBLE_SLAB = "minecraft:red_nether_brick_double_slab";
+ public const RED_NETHER_BRICK_SLAB = "minecraft:red_nether_brick_slab";
public const RED_NETHER_BRICK_STAIRS = "minecraft:red_nether_brick_stairs";
+ public const RED_NETHER_BRICK_WALL = "minecraft:red_nether_brick_wall";
+ public const RED_SAND = "minecraft:red_sand";
public const RED_SANDSTONE = "minecraft:red_sandstone";
+ public const RED_SANDSTONE_DOUBLE_SLAB = "minecraft:red_sandstone_double_slab";
+ public const RED_SANDSTONE_SLAB = "minecraft:red_sandstone_slab";
public const RED_SANDSTONE_STAIRS = "minecraft:red_sandstone_stairs";
+ public const RED_SANDSTONE_WALL = "minecraft:red_sandstone_wall";
+ public const RED_SHULKER_BOX = "minecraft:red_shulker_box";
+ public const RED_STAINED_GLASS = "minecraft:red_stained_glass";
+ public const RED_STAINED_GLASS_PANE = "minecraft:red_stained_glass_pane";
+ public const RED_TERRACOTTA = "minecraft:red_terracotta";
+ public const RED_TULIP = "minecraft:red_tulip";
public const RED_WOOL = "minecraft:red_wool";
public const REDSTONE_BLOCK = "minecraft:redstone_block";
public const REDSTONE_LAMP = "minecraft:redstone_lamp";
@@ -669,10 +995,13 @@ private function __construct(){
public const REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block";
public const RESERVED6 = "minecraft:reserved6";
public const RESPAWN_ANCHOR = "minecraft:respawn_anchor";
+ public const ROSE_BUSH = "minecraft:rose_bush";
public const SAND = "minecraft:sand";
public const SANDSTONE = "minecraft:sandstone";
+ public const SANDSTONE_DOUBLE_SLAB = "minecraft:sandstone_double_slab";
+ public const SANDSTONE_SLAB = "minecraft:sandstone_slab";
public const SANDSTONE_STAIRS = "minecraft:sandstone_stairs";
- public const SAPLING = "minecraft:sapling";
+ public const SANDSTONE_WALL = "minecraft:sandstone_wall";
public const SCAFFOLDING = "minecraft:scaffolding";
public const SCULK = "minecraft:sculk";
public const SCULK_CATALYST = "minecraft:sculk_catalyst";
@@ -682,20 +1011,32 @@ private function __construct(){
public const SEA_LANTERN = "minecraft:sea_lantern";
public const SEA_PICKLE = "minecraft:sea_pickle";
public const SEAGRASS = "minecraft:seagrass";
+ public const SHORT_GRASS = "minecraft:short_grass";
public const SHROOMLIGHT = "minecraft:shroomlight";
- public const SHULKER_BOX = "minecraft:shulker_box";
public const SILVER_GLAZED_TERRACOTTA = "minecraft:silver_glazed_terracotta";
- public const SKULL = "minecraft:skull";
+ public const SKELETON_SKULL = "minecraft:skeleton_skull";
public const SLIME = "minecraft:slime";
public const SMALL_AMETHYST_BUD = "minecraft:small_amethyst_bud";
public const SMALL_DRIPLEAF_BLOCK = "minecraft:small_dripleaf_block";
public const SMITHING_TABLE = "minecraft:smithing_table";
public const SMOKER = "minecraft:smoker";
public const SMOOTH_BASALT = "minecraft:smooth_basalt";
+ public const SMOOTH_QUARTZ = "minecraft:smooth_quartz";
+ public const SMOOTH_QUARTZ_DOUBLE_SLAB = "minecraft:smooth_quartz_double_slab";
+ public const SMOOTH_QUARTZ_SLAB = "minecraft:smooth_quartz_slab";
public const SMOOTH_QUARTZ_STAIRS = "minecraft:smooth_quartz_stairs";
+ public const SMOOTH_RED_SANDSTONE = "minecraft:smooth_red_sandstone";
+ public const SMOOTH_RED_SANDSTONE_DOUBLE_SLAB = "minecraft:smooth_red_sandstone_double_slab";
+ public const SMOOTH_RED_SANDSTONE_SLAB = "minecraft:smooth_red_sandstone_slab";
public const SMOOTH_RED_SANDSTONE_STAIRS = "minecraft:smooth_red_sandstone_stairs";
+ public const SMOOTH_SANDSTONE = "minecraft:smooth_sandstone";
+ public const SMOOTH_SANDSTONE_DOUBLE_SLAB = "minecraft:smooth_sandstone_double_slab";
+ public const SMOOTH_SANDSTONE_SLAB = "minecraft:smooth_sandstone_slab";
public const SMOOTH_SANDSTONE_STAIRS = "minecraft:smooth_sandstone_stairs";
public const SMOOTH_STONE = "minecraft:smooth_stone";
+ public const SMOOTH_STONE_DOUBLE_SLAB = "minecraft:smooth_stone_double_slab";
+ public const SMOOTH_STONE_SLAB = "minecraft:smooth_stone_slab";
+ public const SNIFFER_EGG = "minecraft:sniffer_egg";
public const SNOW = "minecraft:snow";
public const SNOW_LAYER = "minecraft:snow_layer";
public const SOUL_CAMPFIRE = "minecraft:soul_campfire";
@@ -708,55 +1049,64 @@ private function __construct(){
public const SPORE_BLOSSOM = "minecraft:spore_blossom";
public const SPRUCE_BUTTON = "minecraft:spruce_button";
public const SPRUCE_DOOR = "minecraft:spruce_door";
+ public const SPRUCE_DOUBLE_SLAB = "minecraft:spruce_double_slab";
public const SPRUCE_FENCE = "minecraft:spruce_fence";
public const SPRUCE_FENCE_GATE = "minecraft:spruce_fence_gate";
public const SPRUCE_HANGING_SIGN = "minecraft:spruce_hanging_sign";
+ public const SPRUCE_LEAVES = "minecraft:spruce_leaves";
public const SPRUCE_LOG = "minecraft:spruce_log";
+ public const SPRUCE_PLANKS = "minecraft:spruce_planks";
public const SPRUCE_PRESSURE_PLATE = "minecraft:spruce_pressure_plate";
+ public const SPRUCE_SAPLING = "minecraft:spruce_sapling";
+ public const SPRUCE_SLAB = "minecraft:spruce_slab";
public const SPRUCE_STAIRS = "minecraft:spruce_stairs";
public const SPRUCE_STANDING_SIGN = "minecraft:spruce_standing_sign";
public const SPRUCE_TRAPDOOR = "minecraft:spruce_trapdoor";
public const SPRUCE_WALL_SIGN = "minecraft:spruce_wall_sign";
- public const STAINED_GLASS = "minecraft:stained_glass";
- public const STAINED_GLASS_PANE = "minecraft:stained_glass_pane";
- public const STAINED_HARDENED_CLAY = "minecraft:stained_hardened_clay";
+ public const SPRUCE_WOOD = "minecraft:spruce_wood";
public const STANDING_BANNER = "minecraft:standing_banner";
public const STANDING_SIGN = "minecraft:standing_sign";
public const STICKY_PISTON = "minecraft:sticky_piston";
public const STICKY_PISTON_ARM_COLLISION = "minecraft:sticky_piston_arm_collision";
public const STONE = "minecraft:stone";
- public const STONE_BLOCK_SLAB = "minecraft:stone_block_slab";
- public const STONE_BLOCK_SLAB2 = "minecraft:stone_block_slab2";
- public const STONE_BLOCK_SLAB3 = "minecraft:stone_block_slab3";
- public const STONE_BLOCK_SLAB4 = "minecraft:stone_block_slab4";
+ public const STONE_BRICK_DOUBLE_SLAB = "minecraft:stone_brick_double_slab";
+ public const STONE_BRICK_SLAB = "minecraft:stone_brick_slab";
public const STONE_BRICK_STAIRS = "minecraft:stone_brick_stairs";
+ public const STONE_BRICK_WALL = "minecraft:stone_brick_wall";
+ public const STONE_BRICKS = "minecraft:stone_bricks";
public const STONE_BUTTON = "minecraft:stone_button";
public const STONE_PRESSURE_PLATE = "minecraft:stone_pressure_plate";
public const STONE_STAIRS = "minecraft:stone_stairs";
- public const STONEBRICK = "minecraft:stonebrick";
public const STONECUTTER = "minecraft:stonecutter";
public const STONECUTTER_BLOCK = "minecraft:stonecutter_block";
public const STRIPPED_ACACIA_LOG = "minecraft:stripped_acacia_log";
+ public const STRIPPED_ACACIA_WOOD = "minecraft:stripped_acacia_wood";
public const STRIPPED_BAMBOO_BLOCK = "minecraft:stripped_bamboo_block";
public const STRIPPED_BIRCH_LOG = "minecraft:stripped_birch_log";
+ public const STRIPPED_BIRCH_WOOD = "minecraft:stripped_birch_wood";
public const STRIPPED_CHERRY_LOG = "minecraft:stripped_cherry_log";
public const STRIPPED_CHERRY_WOOD = "minecraft:stripped_cherry_wood";
public const STRIPPED_CRIMSON_HYPHAE = "minecraft:stripped_crimson_hyphae";
public const STRIPPED_CRIMSON_STEM = "minecraft:stripped_crimson_stem";
public const STRIPPED_DARK_OAK_LOG = "minecraft:stripped_dark_oak_log";
+ public const STRIPPED_DARK_OAK_WOOD = "minecraft:stripped_dark_oak_wood";
public const STRIPPED_JUNGLE_LOG = "minecraft:stripped_jungle_log";
+ public const STRIPPED_JUNGLE_WOOD = "minecraft:stripped_jungle_wood";
public const STRIPPED_MANGROVE_LOG = "minecraft:stripped_mangrove_log";
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_SPRUCE_LOG = "minecraft:stripped_spruce_log";
+ public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood";
public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae";
public const STRIPPED_WARPED_STEM = "minecraft:stripped_warped_stem";
public const STRUCTURE_BLOCK = "minecraft:structure_block";
public const STRUCTURE_VOID = "minecraft:structure_void";
+ public const SUNFLOWER = "minecraft:sunflower";
public const SUSPICIOUS_GRAVEL = "minecraft:suspicious_gravel";
public const SUSPICIOUS_SAND = "minecraft:suspicious_sand";
public const SWEET_BERRY_BUSH = "minecraft:sweet_berry_bush";
- public const TALLGRASS = "minecraft:tallgrass";
+ public const TALL_GRASS = "minecraft:tall_grass";
public const TARGET = "minecraft:target";
public const TINTED_GLASS = "minecraft:tinted_glass";
public const TNT = "minecraft:tnt";
@@ -765,17 +1115,33 @@ private function __construct(){
public const TORCHFLOWER_CROP = "minecraft:torchflower_crop";
public const TRAPDOOR = "minecraft:trapdoor";
public const TRAPPED_CHEST = "minecraft:trapped_chest";
+ public const TRIAL_SPAWNER = "minecraft:trial_spawner";
public const TRIP_WIRE = "minecraft:trip_wire";
public const TRIPWIRE_HOOK = "minecraft:tripwire_hook";
+ public const TUBE_CORAL = "minecraft:tube_coral";
+ public const TUBE_CORAL_BLOCK = "minecraft:tube_coral_block";
+ public const TUBE_CORAL_FAN = "minecraft:tube_coral_fan";
+ public const TUBE_CORAL_WALL_FAN = "minecraft:tube_coral_wall_fan";
public const TUFF = "minecraft:tuff";
+ public const TUFF_BRICK_DOUBLE_SLAB = "minecraft:tuff_brick_double_slab";
+ public const TUFF_BRICK_SLAB = "minecraft:tuff_brick_slab";
+ public const TUFF_BRICK_STAIRS = "minecraft:tuff_brick_stairs";
+ public const TUFF_BRICK_WALL = "minecraft:tuff_brick_wall";
+ public const TUFF_BRICKS = "minecraft:tuff_bricks";
+ public const TUFF_DOUBLE_SLAB = "minecraft:tuff_double_slab";
+ public const TUFF_SLAB = "minecraft:tuff_slab";
+ public const TUFF_STAIRS = "minecraft:tuff_stairs";
+ public const TUFF_WALL = "minecraft:tuff_wall";
public const TURTLE_EGG = "minecraft:turtle_egg";
public const TWISTING_VINES = "minecraft:twisting_vines";
+ public const UNDERWATER_TNT = "minecraft:underwater_tnt";
public const UNDERWATER_TORCH = "minecraft:underwater_torch";
public const UNDYED_SHULKER_BOX = "minecraft:undyed_shulker_box";
public const UNKNOWN = "minecraft:unknown";
public const UNLIT_REDSTONE_TORCH = "minecraft:unlit_redstone_torch";
public const UNPOWERED_COMPARATOR = "minecraft:unpowered_comparator";
public const UNPOWERED_REPEATER = "minecraft:unpowered_repeater";
+ public const VAULT = "minecraft:vault";
public const VERDANT_FROGLIGHT = "minecraft:verdant_froglight";
public const VINE = "minecraft:vine";
public const WALL_BANNER = "minecraft:wall_banner";
@@ -801,47 +1167,87 @@ private function __construct(){
public const WARPED_WART_BLOCK = "minecraft:warped_wart_block";
public const WATER = "minecraft:water";
public const WATERLILY = "minecraft:waterlily";
+ public const WAXED_CHISELED_COPPER = "minecraft:waxed_chiseled_copper";
public const WAXED_COPPER = "minecraft:waxed_copper";
+ public const WAXED_COPPER_BULB = "minecraft:waxed_copper_bulb";
+ public const WAXED_COPPER_DOOR = "minecraft:waxed_copper_door";
+ public const WAXED_COPPER_GRATE = "minecraft:waxed_copper_grate";
+ public const WAXED_COPPER_TRAPDOOR = "minecraft:waxed_copper_trapdoor";
public const WAXED_CUT_COPPER = "minecraft:waxed_cut_copper";
public const WAXED_CUT_COPPER_SLAB = "minecraft:waxed_cut_copper_slab";
public const WAXED_CUT_COPPER_STAIRS = "minecraft:waxed_cut_copper_stairs";
public const WAXED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_double_cut_copper_slab";
+ public const WAXED_EXPOSED_CHISELED_COPPER = "minecraft:waxed_exposed_chiseled_copper";
public const WAXED_EXPOSED_COPPER = "minecraft:waxed_exposed_copper";
+ public const WAXED_EXPOSED_COPPER_BULB = "minecraft:waxed_exposed_copper_bulb";
+ public const WAXED_EXPOSED_COPPER_DOOR = "minecraft:waxed_exposed_copper_door";
+ public const WAXED_EXPOSED_COPPER_GRATE = "minecraft:waxed_exposed_copper_grate";
+ public const WAXED_EXPOSED_COPPER_TRAPDOOR = "minecraft:waxed_exposed_copper_trapdoor";
public const WAXED_EXPOSED_CUT_COPPER = "minecraft:waxed_exposed_cut_copper";
public const WAXED_EXPOSED_CUT_COPPER_SLAB = "minecraft:waxed_exposed_cut_copper_slab";
public const WAXED_EXPOSED_CUT_COPPER_STAIRS = "minecraft:waxed_exposed_cut_copper_stairs";
public const WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_exposed_double_cut_copper_slab";
+ public const WAXED_OXIDIZED_CHISELED_COPPER = "minecraft:waxed_oxidized_chiseled_copper";
public const WAXED_OXIDIZED_COPPER = "minecraft:waxed_oxidized_copper";
+ public const WAXED_OXIDIZED_COPPER_BULB = "minecraft:waxed_oxidized_copper_bulb";
+ public const WAXED_OXIDIZED_COPPER_DOOR = "minecraft:waxed_oxidized_copper_door";
+ public const WAXED_OXIDIZED_COPPER_GRATE = "minecraft:waxed_oxidized_copper_grate";
+ public const WAXED_OXIDIZED_COPPER_TRAPDOOR = "minecraft:waxed_oxidized_copper_trapdoor";
public const WAXED_OXIDIZED_CUT_COPPER = "minecraft:waxed_oxidized_cut_copper";
public const WAXED_OXIDIZED_CUT_COPPER_SLAB = "minecraft:waxed_oxidized_cut_copper_slab";
public const WAXED_OXIDIZED_CUT_COPPER_STAIRS = "minecraft:waxed_oxidized_cut_copper_stairs";
public const WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_oxidized_double_cut_copper_slab";
+ public const WAXED_WEATHERED_CHISELED_COPPER = "minecraft:waxed_weathered_chiseled_copper";
public const WAXED_WEATHERED_COPPER = "minecraft:waxed_weathered_copper";
+ public const WAXED_WEATHERED_COPPER_BULB = "minecraft:waxed_weathered_copper_bulb";
+ public const WAXED_WEATHERED_COPPER_DOOR = "minecraft:waxed_weathered_copper_door";
+ public const WAXED_WEATHERED_COPPER_GRATE = "minecraft:waxed_weathered_copper_grate";
+ public const WAXED_WEATHERED_COPPER_TRAPDOOR = "minecraft:waxed_weathered_copper_trapdoor";
public const WAXED_WEATHERED_CUT_COPPER = "minecraft:waxed_weathered_cut_copper";
public const WAXED_WEATHERED_CUT_COPPER_SLAB = "minecraft:waxed_weathered_cut_copper_slab";
public const WAXED_WEATHERED_CUT_COPPER_STAIRS = "minecraft:waxed_weathered_cut_copper_stairs";
public const WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_weathered_double_cut_copper_slab";
+ public const WEATHERED_CHISELED_COPPER = "minecraft:weathered_chiseled_copper";
public const WEATHERED_COPPER = "minecraft:weathered_copper";
+ public const WEATHERED_COPPER_BULB = "minecraft:weathered_copper_bulb";
+ public const WEATHERED_COPPER_DOOR = "minecraft:weathered_copper_door";
+ public const WEATHERED_COPPER_GRATE = "minecraft:weathered_copper_grate";
+ public const WEATHERED_COPPER_TRAPDOOR = "minecraft:weathered_copper_trapdoor";
public const WEATHERED_CUT_COPPER = "minecraft:weathered_cut_copper";
public const WEATHERED_CUT_COPPER_SLAB = "minecraft:weathered_cut_copper_slab";
public const WEATHERED_CUT_COPPER_STAIRS = "minecraft:weathered_cut_copper_stairs";
public const WEATHERED_DOUBLE_CUT_COPPER_SLAB = "minecraft:weathered_double_cut_copper_slab";
public const WEB = "minecraft:web";
public const WEEPING_VINES = "minecraft:weeping_vines";
+ public const WET_SPONGE = "minecraft:wet_sponge";
public const WHEAT = "minecraft:wheat";
public const WHITE_CANDLE = "minecraft:white_candle";
public const WHITE_CANDLE_CAKE = "minecraft:white_candle_cake";
+ public const WHITE_CARPET = "minecraft:white_carpet";
+ public const WHITE_CONCRETE = "minecraft:white_concrete";
+ public const WHITE_CONCRETE_POWDER = "minecraft:white_concrete_powder";
public const WHITE_GLAZED_TERRACOTTA = "minecraft:white_glazed_terracotta";
+ public const WHITE_SHULKER_BOX = "minecraft:white_shulker_box";
+ public const WHITE_STAINED_GLASS = "minecraft:white_stained_glass";
+ public const WHITE_STAINED_GLASS_PANE = "minecraft:white_stained_glass_pane";
+ public const WHITE_TERRACOTTA = "minecraft:white_terracotta";
+ public const WHITE_TULIP = "minecraft:white_tulip";
public const WHITE_WOOL = "minecraft:white_wool";
public const WITHER_ROSE = "minecraft:wither_rose";
- public const WOOD = "minecraft:wood";
+ public const WITHER_SKELETON_SKULL = "minecraft:wither_skeleton_skull";
public const WOODEN_BUTTON = "minecraft:wooden_button";
public const WOODEN_DOOR = "minecraft:wooden_door";
public const WOODEN_PRESSURE_PLATE = "minecraft:wooden_pressure_plate";
- public const WOODEN_SLAB = "minecraft:wooden_slab";
public const YELLOW_CANDLE = "minecraft:yellow_candle";
public const YELLOW_CANDLE_CAKE = "minecraft:yellow_candle_cake";
- public const YELLOW_FLOWER = "minecraft:yellow_flower";
+ public const YELLOW_CARPET = "minecraft:yellow_carpet";
+ public const YELLOW_CONCRETE = "minecraft:yellow_concrete";
+ public const YELLOW_CONCRETE_POWDER = "minecraft:yellow_concrete_powder";
public const YELLOW_GLAZED_TERRACOTTA = "minecraft:yellow_glazed_terracotta";
+ public const YELLOW_SHULKER_BOX = "minecraft:yellow_shulker_box";
+ public const YELLOW_STAINED_GLASS = "minecraft:yellow_stained_glass";
+ public const YELLOW_STAINED_GLASS_PANE = "minecraft:yellow_stained_glass_pane";
+ public const YELLOW_TERRACOTTA = "minecraft:yellow_terracotta";
public const YELLOW_WOOL = "minecraft:yellow_wool";
+ public const ZOMBIE_HEAD = "minecraft:zombie_head";
}
diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php
index 8d884743f52..aebfd67ffac 100644
--- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php
+++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php
@@ -24,6 +24,7 @@
namespace pocketmine\data\bedrock\block\convert;
use pocketmine\block\ActivatorRail;
+use pocketmine\block\AmethystCluster;
use pocketmine\block\Anvil;
use pocketmine\block\Bamboo;
use pocketmine\block\BambooSapling;
@@ -31,6 +32,8 @@
use pocketmine\block\Bed;
use pocketmine\block\Beetroot;
use pocketmine\block\Bell;
+use pocketmine\block\BigDripleafHead;
+use pocketmine\block\BigDripleafStem;
use pocketmine\block\Block;
use pocketmine\block\BoneBlock;
use pocketmine\block\BrewingStand;
@@ -40,6 +43,7 @@
use pocketmine\block\Cake;
use pocketmine\block\CakeWithCandle;
use pocketmine\block\CakeWithDyedCandle;
+use pocketmine\block\Campfire;
use pocketmine\block\Candle;
use pocketmine\block\Carpet;
use pocketmine\block\Carrot;
@@ -48,19 +52,25 @@
use pocketmine\block\Chain;
use pocketmine\block\ChemistryTable;
use pocketmine\block\Chest;
+use pocketmine\block\ChiseledBookshelf;
use pocketmine\block\ChorusFlower;
use pocketmine\block\CocoaBlock;
use pocketmine\block\Concrete;
use pocketmine\block\ConcretePowder;
use pocketmine\block\Copper;
+use pocketmine\block\CopperBulb;
+use pocketmine\block\CopperDoor;
+use pocketmine\block\CopperGrate;
use pocketmine\block\CopperSlab;
use pocketmine\block\CopperStairs;
+use pocketmine\block\CopperTrapdoor;
use pocketmine\block\Coral;
use pocketmine\block\CoralBlock;
use pocketmine\block\DaylightSensor;
use pocketmine\block\DetectorRail;
use pocketmine\block\Dirt;
use pocketmine\block\Door;
+use pocketmine\block\DoublePitcherCrop;
use pocketmine\block\DoublePlant;
use pocketmine\block\DoubleTallGrass;
use pocketmine\block\DyedCandle;
@@ -98,6 +108,8 @@
use pocketmine\block\NetherPortal;
use pocketmine\block\NetherVines;
use pocketmine\block\NetherWartPlant;
+use pocketmine\block\PinkPetals;
+use pocketmine\block\PitcherCrop;
use pocketmine\block\Potato;
use pocketmine\block\PoweredRail;
use pocketmine\block\PumpkinStem;
@@ -115,7 +127,9 @@
use pocketmine\block\SimplePillar;
use pocketmine\block\SimplePressurePlate;
use pocketmine\block\Slab;
+use pocketmine\block\SmallDripleaf;
use pocketmine\block\SnowLayer;
+use pocketmine\block\SoulCampfire;
use pocketmine\block\Sponge;
use pocketmine\block\StainedGlass;
use pocketmine\block\StainedGlassPane;
@@ -130,6 +144,7 @@
use pocketmine\block\SweetBerryBush;
use pocketmine\block\TNT;
use pocketmine\block\Torch;
+use pocketmine\block\TorchflowerCrop;
use pocketmine\block\Trapdoor;
use pocketmine\block\TrappedChest;
use pocketmine\block\Tripwire;
@@ -138,9 +153,11 @@
use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DirtType;
+use pocketmine\block\utils\DripleafState;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\FroglightType;
use pocketmine\block\utils\LeverFacing;
+use pocketmine\block\utils\MobHeadType;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\block\Vine;
use pocketmine\block\Wall;
@@ -169,7 +186,6 @@
use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
-use pocketmine\utils\AssumptionFailedError;
use function get_class;
final class BlockObjectToStateSerializer implements BlockStateSerializer{
@@ -191,8 +207,12 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
public function __construct(){
$this->registerCandleSerializers();
$this->registerFlatColorBlockSerializers();
+ $this->registerFlatCoralSerializers();
$this->registerCauldronSerializers();
- $this->registerWoodBlockSerializers();
+ $this->registerFlatWoodBlockSerializers();
+ $this->registerLeavesSerializers();
+ $this->registerSaplingSerializers();
+ $this->registerMobHeadSerializers();
$this->registerSimpleSerializers();
$this->registerSerializers();
}
@@ -263,120 +283,503 @@ public function serializeBlock(Block $blockState) : BlockStateData{
private function registerCandleSerializers() : void{
$this->map(Blocks::CANDLE(), fn(Candle $block) => Helper::encodeCandle($block, new Writer(Ids::CANDLE)));
$this->map(Blocks::DYED_CANDLE(), fn(DyedCandle $block) => Helper::encodeCandle($block, new Writer(match($block->getColor()){
- DyeColor::BLACK() => Ids::BLACK_CANDLE,
- DyeColor::BLUE() => Ids::BLUE_CANDLE,
- DyeColor::BROWN() => Ids::BROWN_CANDLE,
- DyeColor::CYAN() => Ids::CYAN_CANDLE,
- DyeColor::GRAY() => Ids::GRAY_CANDLE,
- DyeColor::GREEN() => Ids::GREEN_CANDLE,
- DyeColor::LIGHT_BLUE() => Ids::LIGHT_BLUE_CANDLE,
- DyeColor::LIGHT_GRAY() => Ids::LIGHT_GRAY_CANDLE,
- DyeColor::LIME() => Ids::LIME_CANDLE,
- DyeColor::MAGENTA() => Ids::MAGENTA_CANDLE,
- DyeColor::ORANGE() => Ids::ORANGE_CANDLE,
- DyeColor::PINK() => Ids::PINK_CANDLE,
- DyeColor::PURPLE() => Ids::PURPLE_CANDLE,
- DyeColor::RED() => Ids::RED_CANDLE,
- DyeColor::WHITE() => Ids::WHITE_CANDLE,
- DyeColor::YELLOW() => Ids::YELLOW_CANDLE,
- default => throw new AssumptionFailedError("Unhandled DyeColor " . $block->getColor()->name())
+ DyeColor::BLACK => Ids::BLACK_CANDLE,
+ DyeColor::BLUE => Ids::BLUE_CANDLE,
+ DyeColor::BROWN => Ids::BROWN_CANDLE,
+ DyeColor::CYAN => Ids::CYAN_CANDLE,
+ DyeColor::GRAY => Ids::GRAY_CANDLE,
+ DyeColor::GREEN => Ids::GREEN_CANDLE,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CANDLE,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CANDLE,
+ DyeColor::LIME => Ids::LIME_CANDLE,
+ DyeColor::MAGENTA => Ids::MAGENTA_CANDLE,
+ DyeColor::ORANGE => Ids::ORANGE_CANDLE,
+ DyeColor::PINK => Ids::PINK_CANDLE,
+ DyeColor::PURPLE => Ids::PURPLE_CANDLE,
+ DyeColor::RED => Ids::RED_CANDLE,
+ DyeColor::WHITE => Ids::WHITE_CANDLE,
+ DyeColor::YELLOW => Ids::YELLOW_CANDLE,
})));
$this->map(Blocks::CAKE_WITH_CANDLE(), fn(CakeWithCandle $block) => Writer::create(Ids::CANDLE_CAKE)
->writeBool(StateNames::LIT, $block->isLit()));
$this->map(Blocks::CAKE_WITH_DYED_CANDLE(), fn(CakeWithDyedCandle $block) => Writer::create(match($block->getColor()){
- DyeColor::BLACK() => Ids::BLACK_CANDLE_CAKE,
- DyeColor::BLUE() => Ids::BLUE_CANDLE_CAKE,
- DyeColor::BROWN() => Ids::BROWN_CANDLE_CAKE,
- DyeColor::CYAN() => Ids::CYAN_CANDLE_CAKE,
- DyeColor::GRAY() => Ids::GRAY_CANDLE_CAKE,
- DyeColor::GREEN() => Ids::GREEN_CANDLE_CAKE,
- DyeColor::LIGHT_BLUE() => Ids::LIGHT_BLUE_CANDLE_CAKE,
- DyeColor::LIGHT_GRAY() => Ids::LIGHT_GRAY_CANDLE_CAKE,
- DyeColor::LIME() => Ids::LIME_CANDLE_CAKE,
- DyeColor::MAGENTA() => Ids::MAGENTA_CANDLE_CAKE,
- DyeColor::ORANGE() => Ids::ORANGE_CANDLE_CAKE,
- DyeColor::PINK() => Ids::PINK_CANDLE_CAKE,
- DyeColor::PURPLE() => Ids::PURPLE_CANDLE_CAKE,
- DyeColor::RED() => Ids::RED_CANDLE_CAKE,
- DyeColor::WHITE() => Ids::WHITE_CANDLE_CAKE,
- DyeColor::YELLOW() => Ids::YELLOW_CANDLE_CAKE,
- default => throw new AssumptionFailedError("Unhandled DyeColor " . $block->getColor()->name())
+ DyeColor::BLACK => Ids::BLACK_CANDLE_CAKE,
+ DyeColor::BLUE => Ids::BLUE_CANDLE_CAKE,
+ DyeColor::BROWN => Ids::BROWN_CANDLE_CAKE,
+ DyeColor::CYAN => Ids::CYAN_CANDLE_CAKE,
+ DyeColor::GRAY => Ids::GRAY_CANDLE_CAKE,
+ DyeColor::GREEN => Ids::GREEN_CANDLE_CAKE,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CANDLE_CAKE,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CANDLE_CAKE,
+ DyeColor::LIME => Ids::LIME_CANDLE_CAKE,
+ DyeColor::MAGENTA => Ids::MAGENTA_CANDLE_CAKE,
+ DyeColor::ORANGE => Ids::ORANGE_CANDLE_CAKE,
+ DyeColor::PINK => Ids::PINK_CANDLE_CAKE,
+ DyeColor::PURPLE => Ids::PURPLE_CANDLE_CAKE,
+ DyeColor::RED => Ids::RED_CANDLE_CAKE,
+ DyeColor::WHITE => Ids::WHITE_CANDLE_CAKE,
+ DyeColor::YELLOW => Ids::YELLOW_CANDLE_CAKE,
})->writeBool(StateNames::LIT, $block->isLit()));
}
public function registerFlatColorBlockSerializers() : void{
+ $this->map(Blocks::STAINED_HARDENED_GLASS(), fn(StainedHardenedGlass $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::HARD_BLACK_STAINED_GLASS,
+ DyeColor::BLUE => Ids::HARD_BLUE_STAINED_GLASS,
+ DyeColor::BROWN => Ids::HARD_BROWN_STAINED_GLASS,
+ DyeColor::CYAN => Ids::HARD_CYAN_STAINED_GLASS,
+ DyeColor::GRAY => Ids::HARD_GRAY_STAINED_GLASS,
+ DyeColor::GREEN => Ids::HARD_GREEN_STAINED_GLASS,
+ DyeColor::LIGHT_BLUE => Ids::HARD_LIGHT_BLUE_STAINED_GLASS,
+ DyeColor::LIGHT_GRAY => Ids::HARD_LIGHT_GRAY_STAINED_GLASS,
+ DyeColor::LIME => Ids::HARD_LIME_STAINED_GLASS,
+ DyeColor::MAGENTA => Ids::HARD_MAGENTA_STAINED_GLASS,
+ DyeColor::ORANGE => Ids::HARD_ORANGE_STAINED_GLASS,
+ DyeColor::PINK => Ids::HARD_PINK_STAINED_GLASS,
+ DyeColor::PURPLE => Ids::HARD_PURPLE_STAINED_GLASS,
+ DyeColor::RED => Ids::HARD_RED_STAINED_GLASS,
+ DyeColor::WHITE => Ids::HARD_WHITE_STAINED_GLASS,
+ DyeColor::YELLOW => Ids::HARD_YELLOW_STAINED_GLASS,
+ }));
+
+ $this->map(Blocks::STAINED_HARDENED_GLASS_PANE(), fn(StainedHardenedGlassPane $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::HARD_BLACK_STAINED_GLASS_PANE,
+ DyeColor::BLUE => Ids::HARD_BLUE_STAINED_GLASS_PANE,
+ DyeColor::BROWN => Ids::HARD_BROWN_STAINED_GLASS_PANE,
+ DyeColor::CYAN => Ids::HARD_CYAN_STAINED_GLASS_PANE,
+ DyeColor::GRAY => Ids::HARD_GRAY_STAINED_GLASS_PANE,
+ DyeColor::GREEN => Ids::HARD_GREEN_STAINED_GLASS_PANE,
+ DyeColor::LIGHT_BLUE => Ids::HARD_LIGHT_BLUE_STAINED_GLASS_PANE,
+ DyeColor::LIGHT_GRAY => Ids::HARD_LIGHT_GRAY_STAINED_GLASS_PANE,
+ DyeColor::LIME => Ids::HARD_LIME_STAINED_GLASS_PANE,
+ DyeColor::MAGENTA => Ids::HARD_MAGENTA_STAINED_GLASS_PANE,
+ DyeColor::ORANGE => Ids::HARD_ORANGE_STAINED_GLASS_PANE,
+ DyeColor::PINK => Ids::HARD_PINK_STAINED_GLASS_PANE,
+ DyeColor::PURPLE => Ids::HARD_PURPLE_STAINED_GLASS_PANE,
+ DyeColor::RED => Ids::HARD_RED_STAINED_GLASS_PANE,
+ DyeColor::WHITE => Ids::HARD_WHITE_STAINED_GLASS_PANE,
+ DyeColor::YELLOW => Ids::HARD_YELLOW_STAINED_GLASS_PANE,
+ }));
+
$this->map(Blocks::GLAZED_TERRACOTTA(), function(GlazedTerracotta $block) : Writer{
- return Writer::create(match($color = $block->getColor()){
- DyeColor::BLACK() => Ids::BLACK_GLAZED_TERRACOTTA,
- DyeColor::BLUE() => Ids::BLUE_GLAZED_TERRACOTTA,
- DyeColor::BROWN() => Ids::BROWN_GLAZED_TERRACOTTA,
- DyeColor::CYAN() => Ids::CYAN_GLAZED_TERRACOTTA,
- DyeColor::GRAY() => Ids::GRAY_GLAZED_TERRACOTTA,
- DyeColor::GREEN() => Ids::GREEN_GLAZED_TERRACOTTA,
- DyeColor::LIGHT_BLUE() => Ids::LIGHT_BLUE_GLAZED_TERRACOTTA,
- DyeColor::LIGHT_GRAY() => Ids::SILVER_GLAZED_TERRACOTTA,
- DyeColor::LIME() => Ids::LIME_GLAZED_TERRACOTTA,
- DyeColor::MAGENTA() => Ids::MAGENTA_GLAZED_TERRACOTTA,
- DyeColor::ORANGE() => Ids::ORANGE_GLAZED_TERRACOTTA,
- DyeColor::PINK() => Ids::PINK_GLAZED_TERRACOTTA,
- DyeColor::PURPLE() => Ids::PURPLE_GLAZED_TERRACOTTA,
- DyeColor::RED() => Ids::RED_GLAZED_TERRACOTTA,
- DyeColor::WHITE() => Ids::WHITE_GLAZED_TERRACOTTA,
- DyeColor::YELLOW() => Ids::YELLOW_GLAZED_TERRACOTTA,
- default => throw new AssumptionFailedError("Unhandled dye colour " . $color->name())
+ return Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_GLAZED_TERRACOTTA,
+ DyeColor::BLUE => Ids::BLUE_GLAZED_TERRACOTTA,
+ DyeColor::BROWN => Ids::BROWN_GLAZED_TERRACOTTA,
+ DyeColor::CYAN => Ids::CYAN_GLAZED_TERRACOTTA,
+ DyeColor::GRAY => Ids::GRAY_GLAZED_TERRACOTTA,
+ DyeColor::GREEN => Ids::GREEN_GLAZED_TERRACOTTA,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_GLAZED_TERRACOTTA,
+ DyeColor::LIGHT_GRAY => Ids::SILVER_GLAZED_TERRACOTTA,
+ DyeColor::LIME => Ids::LIME_GLAZED_TERRACOTTA,
+ DyeColor::MAGENTA => Ids::MAGENTA_GLAZED_TERRACOTTA,
+ DyeColor::ORANGE => Ids::ORANGE_GLAZED_TERRACOTTA,
+ DyeColor::PINK => Ids::PINK_GLAZED_TERRACOTTA,
+ DyeColor::PURPLE => Ids::PURPLE_GLAZED_TERRACOTTA,
+ DyeColor::RED => Ids::RED_GLAZED_TERRACOTTA,
+ DyeColor::WHITE => Ids::WHITE_GLAZED_TERRACOTTA,
+ DyeColor::YELLOW => Ids::YELLOW_GLAZED_TERRACOTTA,
})
->writeHorizontalFacing($block->getFacing());
});
- $this->map(Blocks::WOOL(), fn(Wool $block) => Writer::create(match($color = $block->getColor()){
- DyeColor::BLACK() => Ids::BLACK_WOOL,
- DyeColor::BLUE() => Ids::BLUE_WOOL,
- DyeColor::BROWN() => Ids::BROWN_WOOL,
- DyeColor::CYAN() => Ids::CYAN_WOOL,
- DyeColor::GRAY() => Ids::GRAY_WOOL,
- DyeColor::GREEN() => Ids::GREEN_WOOL,
- DyeColor::LIGHT_BLUE() => Ids::LIGHT_BLUE_WOOL,
- DyeColor::LIGHT_GRAY() => Ids::LIGHT_GRAY_WOOL,
- DyeColor::LIME() => Ids::LIME_WOOL,
- DyeColor::MAGENTA() => Ids::MAGENTA_WOOL,
- DyeColor::ORANGE() => Ids::ORANGE_WOOL,
- DyeColor::PINK() => Ids::PINK_WOOL,
- DyeColor::PURPLE() => Ids::PURPLE_WOOL,
- DyeColor::RED() => Ids::RED_WOOL,
- DyeColor::WHITE() => Ids::WHITE_WOOL,
- DyeColor::YELLOW() => Ids::YELLOW_WOOL,
- default => throw new AssumptionFailedError("Unhandled dye colour " . $color->name())
+ $this->map(Blocks::WOOL(), fn(Wool $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_WOOL,
+ DyeColor::BLUE => Ids::BLUE_WOOL,
+ DyeColor::BROWN => Ids::BROWN_WOOL,
+ DyeColor::CYAN => Ids::CYAN_WOOL,
+ DyeColor::GRAY => Ids::GRAY_WOOL,
+ DyeColor::GREEN => Ids::GREEN_WOOL,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_WOOL,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_WOOL,
+ DyeColor::LIME => Ids::LIME_WOOL,
+ DyeColor::MAGENTA => Ids::MAGENTA_WOOL,
+ DyeColor::ORANGE => Ids::ORANGE_WOOL,
+ DyeColor::PINK => Ids::PINK_WOOL,
+ DyeColor::PURPLE => Ids::PURPLE_WOOL,
+ DyeColor::RED => Ids::RED_WOOL,
+ DyeColor::WHITE => Ids::WHITE_WOOL,
+ DyeColor::YELLOW => Ids::YELLOW_WOOL,
+ }));
+
+ $this->map(Blocks::CARPET(), fn(Carpet $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_CARPET,
+ DyeColor::BLUE => Ids::BLUE_CARPET,
+ DyeColor::BROWN => Ids::BROWN_CARPET,
+ DyeColor::CYAN => Ids::CYAN_CARPET,
+ DyeColor::GRAY => Ids::GRAY_CARPET,
+ DyeColor::GREEN => Ids::GREEN_CARPET,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CARPET,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CARPET,
+ DyeColor::LIME => Ids::LIME_CARPET,
+ DyeColor::MAGENTA => Ids::MAGENTA_CARPET,
+ DyeColor::ORANGE => Ids::ORANGE_CARPET,
+ DyeColor::PINK => Ids::PINK_CARPET,
+ DyeColor::PURPLE => Ids::PURPLE_CARPET,
+ DyeColor::RED => Ids::RED_CARPET,
+ DyeColor::WHITE => Ids::WHITE_CARPET,
+ DyeColor::YELLOW => Ids::YELLOW_CARPET,
+ }));
+
+ $this->map(Blocks::DYED_SHULKER_BOX(), fn(DyedShulkerBox $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_SHULKER_BOX,
+ DyeColor::BLUE => Ids::BLUE_SHULKER_BOX,
+ DyeColor::BROWN => Ids::BROWN_SHULKER_BOX,
+ DyeColor::CYAN => Ids::CYAN_SHULKER_BOX,
+ DyeColor::GRAY => Ids::GRAY_SHULKER_BOX,
+ DyeColor::GREEN => Ids::GREEN_SHULKER_BOX,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_SHULKER_BOX,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_SHULKER_BOX,
+ DyeColor::LIME => Ids::LIME_SHULKER_BOX,
+ DyeColor::MAGENTA => Ids::MAGENTA_SHULKER_BOX,
+ DyeColor::ORANGE => Ids::ORANGE_SHULKER_BOX,
+ DyeColor::PINK => Ids::PINK_SHULKER_BOX,
+ DyeColor::PURPLE => Ids::PURPLE_SHULKER_BOX,
+ DyeColor::RED => Ids::RED_SHULKER_BOX,
+ DyeColor::WHITE => Ids::WHITE_SHULKER_BOX,
+ DyeColor::YELLOW => Ids::YELLOW_SHULKER_BOX,
+ }));
+
+ $this->map(Blocks::CONCRETE(), fn(Concrete $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_CONCRETE,
+ DyeColor::BLUE => Ids::BLUE_CONCRETE,
+ DyeColor::BROWN => Ids::BROWN_CONCRETE,
+ DyeColor::CYAN => Ids::CYAN_CONCRETE,
+ DyeColor::GRAY => Ids::GRAY_CONCRETE,
+ DyeColor::GREEN => Ids::GREEN_CONCRETE,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CONCRETE,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CONCRETE,
+ DyeColor::LIME => Ids::LIME_CONCRETE,
+ DyeColor::MAGENTA => Ids::MAGENTA_CONCRETE,
+ DyeColor::ORANGE => Ids::ORANGE_CONCRETE,
+ DyeColor::PINK => Ids::PINK_CONCRETE,
+ DyeColor::PURPLE => Ids::PURPLE_CONCRETE,
+ DyeColor::RED => Ids::RED_CONCRETE,
+ DyeColor::WHITE => Ids::WHITE_CONCRETE,
+ DyeColor::YELLOW => Ids::YELLOW_CONCRETE,
}));
+
+ $this->map(Blocks::CONCRETE_POWDER(), fn(ConcretePowder $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_CONCRETE_POWDER,
+ DyeColor::BLUE => Ids::BLUE_CONCRETE_POWDER,
+ DyeColor::BROWN => Ids::BROWN_CONCRETE_POWDER,
+ DyeColor::CYAN => Ids::CYAN_CONCRETE_POWDER,
+ DyeColor::GRAY => Ids::GRAY_CONCRETE_POWDER,
+ DyeColor::GREEN => Ids::GREEN_CONCRETE_POWDER,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_CONCRETE_POWDER,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_CONCRETE_POWDER,
+ DyeColor::LIME => Ids::LIME_CONCRETE_POWDER,
+ DyeColor::MAGENTA => Ids::MAGENTA_CONCRETE_POWDER,
+ DyeColor::ORANGE => Ids::ORANGE_CONCRETE_POWDER,
+ DyeColor::PINK => Ids::PINK_CONCRETE_POWDER,
+ DyeColor::PURPLE => Ids::PURPLE_CONCRETE_POWDER,
+ DyeColor::RED => Ids::RED_CONCRETE_POWDER,
+ DyeColor::WHITE => Ids::WHITE_CONCRETE_POWDER,
+ DyeColor::YELLOW => Ids::YELLOW_CONCRETE_POWDER,
+ }));
+
+ $this->map(Blocks::STAINED_CLAY(), fn(StainedHardenedClay $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_TERRACOTTA,
+ DyeColor::BLUE => Ids::BLUE_TERRACOTTA,
+ DyeColor::BROWN => Ids::BROWN_TERRACOTTA,
+ DyeColor::CYAN => Ids::CYAN_TERRACOTTA,
+ DyeColor::GRAY => Ids::GRAY_TERRACOTTA,
+ DyeColor::GREEN => Ids::GREEN_TERRACOTTA,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_TERRACOTTA,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_TERRACOTTA,
+ DyeColor::LIME => Ids::LIME_TERRACOTTA,
+ DyeColor::MAGENTA => Ids::MAGENTA_TERRACOTTA,
+ DyeColor::ORANGE => Ids::ORANGE_TERRACOTTA,
+ DyeColor::PINK => Ids::PINK_TERRACOTTA,
+ DyeColor::PURPLE => Ids::PURPLE_TERRACOTTA,
+ DyeColor::RED => Ids::RED_TERRACOTTA,
+ DyeColor::WHITE => Ids::WHITE_TERRACOTTA,
+ DyeColor::YELLOW => Ids::YELLOW_TERRACOTTA,
+ }));
+
+ $this->map(Blocks::STAINED_GLASS(), fn(StainedGlass $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_STAINED_GLASS,
+ DyeColor::BLUE => Ids::BLUE_STAINED_GLASS,
+ DyeColor::BROWN => Ids::BROWN_STAINED_GLASS,
+ DyeColor::CYAN => Ids::CYAN_STAINED_GLASS,
+ DyeColor::GRAY => Ids::GRAY_STAINED_GLASS,
+ DyeColor::GREEN => Ids::GREEN_STAINED_GLASS,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_STAINED_GLASS,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_STAINED_GLASS,
+ DyeColor::LIME => Ids::LIME_STAINED_GLASS,
+ DyeColor::MAGENTA => Ids::MAGENTA_STAINED_GLASS,
+ DyeColor::ORANGE => Ids::ORANGE_STAINED_GLASS,
+ DyeColor::PINK => Ids::PINK_STAINED_GLASS,
+ DyeColor::PURPLE => Ids::PURPLE_STAINED_GLASS,
+ DyeColor::RED => Ids::RED_STAINED_GLASS,
+ DyeColor::WHITE => Ids::WHITE_STAINED_GLASS,
+ DyeColor::YELLOW => Ids::YELLOW_STAINED_GLASS,
+ }));
+
+ $this->map(Blocks::STAINED_GLASS_PANE(), fn(StainedGlassPane $block) => Writer::create(match($block->getColor()){
+ DyeColor::BLACK => Ids::BLACK_STAINED_GLASS_PANE,
+ DyeColor::BLUE => Ids::BLUE_STAINED_GLASS_PANE,
+ DyeColor::BROWN => Ids::BROWN_STAINED_GLASS_PANE,
+ DyeColor::CYAN => Ids::CYAN_STAINED_GLASS_PANE,
+ DyeColor::GRAY => Ids::GRAY_STAINED_GLASS_PANE,
+ DyeColor::GREEN => Ids::GREEN_STAINED_GLASS_PANE,
+ DyeColor::LIGHT_BLUE => Ids::LIGHT_BLUE_STAINED_GLASS_PANE,
+ DyeColor::LIGHT_GRAY => Ids::LIGHT_GRAY_STAINED_GLASS_PANE,
+ DyeColor::LIME => Ids::LIME_STAINED_GLASS_PANE,
+ DyeColor::MAGENTA => Ids::MAGENTA_STAINED_GLASS_PANE,
+ DyeColor::ORANGE => Ids::ORANGE_STAINED_GLASS_PANE,
+ DyeColor::PINK => Ids::PINK_STAINED_GLASS_PANE,
+ DyeColor::PURPLE => Ids::PURPLE_STAINED_GLASS_PANE,
+ DyeColor::RED => Ids::RED_STAINED_GLASS_PANE,
+ DyeColor::WHITE => Ids::WHITE_STAINED_GLASS_PANE,
+ DyeColor::YELLOW => Ids::YELLOW_STAINED_GLASS_PANE,
+ }));
+ }
+
+ private function registerFlatCoralSerializers() : void{
+ $this->map(Blocks::CORAL(), fn(Coral $block) => Writer::create(
+ match($block->getCoralType()){
+ CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL : Ids::BRAIN_CORAL,
+ CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL : Ids::BUBBLE_CORAL,
+ CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL : Ids::FIRE_CORAL,
+ CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL : Ids::HORN_CORAL,
+ CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL : Ids::TUBE_CORAL,
+ }
+ ));
+
+ $this->map(Blocks::CORAL_FAN(), fn(FloorCoralFan $block) => Writer::create(
+ match($block->getCoralType()){
+ CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_FAN : Ids::BRAIN_CORAL_FAN,
+ CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_FAN : Ids::BUBBLE_CORAL_FAN,
+ CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_FAN : Ids::FIRE_CORAL_FAN,
+ CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_FAN : Ids::HORN_CORAL_FAN,
+ CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_FAN : Ids::TUBE_CORAL_FAN,
+ })
+ ->writeInt(StateNames::CORAL_FAN_DIRECTION, match($axis = $block->getAxis()){
+ Axis::X => 0,
+ Axis::Z => 1,
+ default => throw new BlockStateSerializeException("Invalid axis {$axis}"),
+ }));
+
+ $this->map(Blocks::CORAL_BLOCK(), fn(CoralBlock $block) => Writer::create(
+ match($block->getCoralType()){
+ CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_BLOCK : Ids::BRAIN_CORAL_BLOCK,
+ CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_BLOCK : Ids::BUBBLE_CORAL_BLOCK,
+ CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_BLOCK : Ids::FIRE_CORAL_BLOCK,
+ CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_BLOCK : Ids::HORN_CORAL_BLOCK,
+ CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_BLOCK : Ids::TUBE_CORAL_BLOCK,
+ }
+ ));
+
+ $this->map(Blocks::WALL_CORAL_FAN(), fn(WallCoralFan $block) => Writer::create(
+ match($block->getCoralType()){
+ CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_WALL_FAN : Ids::TUBE_CORAL_WALL_FAN,
+ CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_WALL_FAN : Ids::BRAIN_CORAL_WALL_FAN,
+ CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_WALL_FAN : Ids::BUBBLE_CORAL_WALL_FAN,
+ CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_WALL_FAN : Ids::FIRE_CORAL_WALL_FAN,
+ CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_WALL_FAN : Ids::HORN_CORAL_WALL_FAN,
+ })
+ ->writeCoralFacing($block->getFacing())
+ );
}
private function registerCauldronSerializers() : void{
- $this->map(Blocks::CAULDRON(), fn() => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0, new Writer(Ids::CAULDRON)));
- $this->map(Blocks::LAVA_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_LAVA, $b->getFillLevel(), new Writer(Ids::LAVA_CAULDRON)));
+ $this->map(Blocks::CAULDRON(), fn() => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, 0));
+ $this->map(Blocks::LAVA_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_LAVA, $b->getFillLevel()));
//potion cauldrons store their real information in the block actor data
- $this->map(Blocks::POTION_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel(), new Writer(Ids::CAULDRON)));
- $this->map(Blocks::WATER_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel(), new Writer(Ids::CAULDRON)));
+ $this->map(Blocks::POTION_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel()));
+ $this->map(Blocks::WATER_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel()));
}
- private function registerWoodBlockSerializers() : void{
+ private function registerFlatWoodBlockSerializers() : void{
+ $this->map(Blocks::ACACIA_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::ACACIA_BUTTON)));
+ $this->map(Blocks::ACACIA_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::ACACIA_DOOR)));
+ $this->map(Blocks::ACACIA_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::ACACIA_FENCE_GATE)));
+ $this->map(Blocks::ACACIA_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::ACACIA_PRESSURE_PLATE)));
+ $this->map(Blocks::ACACIA_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::ACACIA_STANDING_SIGN)));
+ $this->map(Blocks::ACACIA_STAIRS(), fn(WoodenStairs $block) => Helper::encodeStairs($block, new Writer(Ids::ACACIA_STAIRS)));
+ $this->map(Blocks::ACACIA_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::ACACIA_TRAPDOOR)));
+ $this->map(Blocks::ACACIA_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::ACACIA_WALL_SIGN)));
+ $this->mapLog(Blocks::ACACIA_LOG(), Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG);
+ $this->mapLog(Blocks::ACACIA_WOOD(), Ids::ACACIA_WOOD, Ids::STRIPPED_ACACIA_WOOD);
$this->mapSimple(Blocks::ACACIA_FENCE(), Ids::ACACIA_FENCE);
- $this->mapSimple(Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE);
- $this->mapSimple(Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE);
- $this->mapSimple(Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE);
- $this->mapSimple(Blocks::OAK_FENCE(), Ids::OAK_FENCE);
- $this->mapSimple(Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE);
+ $this->mapSimple(Blocks::ACACIA_PLANKS(), Ids::ACACIA_PLANKS);
+ $this->mapSlab(Blocks::ACACIA_SLAB(), Ids::ACACIA_SLAB, Ids::ACACIA_DOUBLE_SLAB);
- $this->mapLog(Blocks::ACACIA_LOG(), Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG);
+ $this->map(Blocks::BIRCH_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::BIRCH_BUTTON)));
+ $this->map(Blocks::BIRCH_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::BIRCH_DOOR)));
+ $this->map(Blocks::BIRCH_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::BIRCH_FENCE_GATE)));
+ $this->map(Blocks::BIRCH_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::BIRCH_PRESSURE_PLATE)));
+ $this->map(Blocks::BIRCH_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::BIRCH_STANDING_SIGN)));
+ $this->map(Blocks::BIRCH_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::BIRCH_TRAPDOOR)));
+ $this->map(Blocks::BIRCH_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::BIRCH_WALL_SIGN)));
$this->mapLog(Blocks::BIRCH_LOG(), Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG);
+ $this->mapLog(Blocks::BIRCH_WOOD(), Ids::BIRCH_WOOD, Ids::STRIPPED_BIRCH_WOOD);
+ $this->mapSimple(Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE);
+ $this->mapSimple(Blocks::BIRCH_PLANKS(), Ids::BIRCH_PLANKS);
+ $this->mapSlab(Blocks::BIRCH_SLAB(), Ids::BIRCH_SLAB, Ids::BIRCH_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS);
+
+ $this->map(Blocks::CHERRY_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CHERRY_BUTTON)));
+ $this->map(Blocks::CHERRY_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CHERRY_DOOR)));
+ $this->map(Blocks::CHERRY_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CHERRY_FENCE_GATE)));
+ $this->map(Blocks::CHERRY_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::CHERRY_LOG, Ids::STRIPPED_CHERRY_LOG));
+ $this->map(Blocks::CHERRY_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CHERRY_PRESSURE_PLATE)));
+ $this->map(Blocks::CHERRY_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CHERRY_STANDING_SIGN)));
+ $this->map(Blocks::CHERRY_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CHERRY_TRAPDOOR)));
+ $this->map(Blocks::CHERRY_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CHERRY_WALL_SIGN)));
+ $this->mapSimple(Blocks::CHERRY_FENCE(), Ids::CHERRY_FENCE);
+ $this->mapSimple(Blocks::CHERRY_PLANKS(), Ids::CHERRY_PLANKS);
+ $this->mapSlab(Blocks::CHERRY_SLAB(), Ids::CHERRY_SLAB, Ids::CHERRY_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::CHERRY_STAIRS(), Ids::CHERRY_STAIRS);
+ $this->mapLog(Blocks::CHERRY_WOOD(), Ids::CHERRY_WOOD, Ids::STRIPPED_CHERRY_WOOD);
+
+ $this->map(Blocks::CRIMSON_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CRIMSON_BUTTON)));
+ $this->map(Blocks::CRIMSON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CRIMSON_DOOR)));
+ $this->map(Blocks::CRIMSON_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CRIMSON_FENCE_GATE)));
+ $this->map(Blocks::CRIMSON_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE));
+ $this->map(Blocks::CRIMSON_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CRIMSON_PRESSURE_PLATE)));
+ $this->map(Blocks::CRIMSON_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CRIMSON_STANDING_SIGN)));
+ $this->map(Blocks::CRIMSON_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM));
+ $this->map(Blocks::CRIMSON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CRIMSON_TRAPDOOR)));
+ $this->map(Blocks::CRIMSON_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CRIMSON_WALL_SIGN)));
+ $this->mapSimple(Blocks::CRIMSON_FENCE(), Ids::CRIMSON_FENCE);
+ $this->mapSimple(Blocks::CRIMSON_PLANKS(), Ids::CRIMSON_PLANKS);
+ $this->mapSlab(Blocks::CRIMSON_SLAB(), Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::CRIMSON_STAIRS(), Ids::CRIMSON_STAIRS);
+
+ $this->map(Blocks::DARK_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::DARK_OAK_BUTTON)));
+ $this->map(Blocks::DARK_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::DARK_OAK_DOOR)));
+ $this->map(Blocks::DARK_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::DARK_OAK_FENCE_GATE)));
+ $this->map(Blocks::DARK_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::DARK_OAK_PRESSURE_PLATE)));
+ $this->map(Blocks::DARK_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::DARKOAK_STANDING_SIGN)));
+ $this->map(Blocks::DARK_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::DARK_OAK_TRAPDOOR)));
+ $this->map(Blocks::DARK_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::DARKOAK_WALL_SIGN)));
$this->mapLog(Blocks::DARK_OAK_LOG(), Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG);
+ $this->mapLog(Blocks::DARK_OAK_WOOD(), Ids::DARK_OAK_WOOD, Ids::STRIPPED_DARK_OAK_WOOD);
+ $this->mapSimple(Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE);
+ $this->mapSimple(Blocks::DARK_OAK_PLANKS(), Ids::DARK_OAK_PLANKS);
+ $this->mapSlab(Blocks::DARK_OAK_SLAB(), Ids::DARK_OAK_SLAB, Ids::DARK_OAK_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS);
+
+ $this->map(Blocks::JUNGLE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::JUNGLE_BUTTON)));
+ $this->map(Blocks::JUNGLE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::JUNGLE_DOOR)));
+ $this->map(Blocks::JUNGLE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::JUNGLE_FENCE_GATE)));
+ $this->map(Blocks::JUNGLE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::JUNGLE_PRESSURE_PLATE)));
+ $this->map(Blocks::JUNGLE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::JUNGLE_STANDING_SIGN)));
+ $this->map(Blocks::JUNGLE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::JUNGLE_TRAPDOOR)));
+ $this->map(Blocks::JUNGLE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::JUNGLE_WALL_SIGN)));
$this->mapLog(Blocks::JUNGLE_LOG(), Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG);
+ $this->mapLog(Blocks::JUNGLE_WOOD(), Ids::JUNGLE_WOOD, Ids::STRIPPED_JUNGLE_WOOD);
+ $this->mapSimple(Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE);
+ $this->mapSimple(Blocks::JUNGLE_PLANKS(), Ids::JUNGLE_PLANKS);
+ $this->mapSlab(Blocks::JUNGLE_SLAB(), Ids::JUNGLE_SLAB, Ids::JUNGLE_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS);
+
+ $this->map(Blocks::MANGROVE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::MANGROVE_BUTTON)));
+ $this->map(Blocks::MANGROVE_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::MANGROVE_DOOR)));
+ $this->map(Blocks::MANGROVE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::MANGROVE_FENCE_GATE)));
+ $this->map(Blocks::MANGROVE_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG));
+ $this->map(Blocks::MANGROVE_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::MANGROVE_PRESSURE_PLATE)));
+ $this->map(Blocks::MANGROVE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::MANGROVE_STANDING_SIGN)));
+ $this->map(Blocks::MANGROVE_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::MANGROVE_TRAPDOOR)));
+ $this->map(Blocks::MANGROVE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::MANGROVE_WALL_SIGN)));
+ $this->mapSimple(Blocks::MANGROVE_FENCE(), Ids::MANGROVE_FENCE);
+ $this->mapSimple(Blocks::MANGROVE_PLANKS(), Ids::MANGROVE_PLANKS);
+ $this->mapSlab(Blocks::MANGROVE_SLAB(), Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::MANGROVE_STAIRS(), Ids::MANGROVE_STAIRS);
+ $this->mapLog(Blocks::MANGROVE_WOOD(), Ids::MANGROVE_WOOD, Ids::STRIPPED_MANGROVE_WOOD);
+
+ $this->map(Blocks::OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::WOODEN_BUTTON)));
+ $this->map(Blocks::OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::WOODEN_DOOR)));
+ $this->map(Blocks::OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::FENCE_GATE)));
+ $this->map(Blocks::OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WOODEN_PRESSURE_PLATE)));
+ $this->map(Blocks::OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::STANDING_SIGN)));
+ $this->map(Blocks::OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::TRAPDOOR)));
+ $this->map(Blocks::OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WALL_SIGN)));
$this->mapLog(Blocks::OAK_LOG(), Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG);
+ $this->mapLog(Blocks::OAK_WOOD(), Ids::OAK_WOOD, Ids::STRIPPED_OAK_WOOD);
+ $this->mapSimple(Blocks::OAK_FENCE(), Ids::OAK_FENCE);
+ $this->mapSimple(Blocks::OAK_PLANKS(), Ids::OAK_PLANKS);
+ $this->mapSlab(Blocks::OAK_SLAB(), Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS);
+
+ $this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON)));
+ $this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR)));
+ $this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE)));
+ $this->map(Blocks::SPRUCE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::SPRUCE_PRESSURE_PLATE)));
+ $this->map(Blocks::SPRUCE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::SPRUCE_STANDING_SIGN)));
+ $this->map(Blocks::SPRUCE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::SPRUCE_TRAPDOOR)));
+ $this->map(Blocks::SPRUCE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::SPRUCE_WALL_SIGN)));
$this->mapLog(Blocks::SPRUCE_LOG(), Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG);
+ $this->mapLog(Blocks::SPRUCE_WOOD(), Ids::SPRUCE_WOOD, Ids::STRIPPED_SPRUCE_WOOD);
+ $this->mapSimple(Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE);
+ $this->mapSimple(Blocks::SPRUCE_PLANKS(), Ids::SPRUCE_PLANKS);
+ $this->mapSlab(Blocks::SPRUCE_SLAB(), Ids::SPRUCE_SLAB, Ids::SPRUCE_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS);
+ //wood and slabs still use the old way of storing wood type
+
+ $this->map(Blocks::WARPED_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::WARPED_BUTTON)));
+ $this->map(Blocks::WARPED_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::WARPED_DOOR)));
+ $this->map(Blocks::WARPED_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::WARPED_FENCE_GATE)));
+ $this->map(Blocks::WARPED_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE));
+ $this->map(Blocks::WARPED_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WARPED_PRESSURE_PLATE)));
+ $this->map(Blocks::WARPED_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::WARPED_STANDING_SIGN)));
+ $this->map(Blocks::WARPED_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM));
+ $this->map(Blocks::WARPED_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::WARPED_TRAPDOOR)));
+ $this->map(Blocks::WARPED_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WARPED_WALL_SIGN)));
+ $this->mapSimple(Blocks::WARPED_FENCE(), Ids::WARPED_FENCE);
+ $this->mapSimple(Blocks::WARPED_PLANKS(), Ids::WARPED_PLANKS);
+ $this->mapSlab(Blocks::WARPED_SLAB(), Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS);
+ }
+
+ private function registerLeavesSerializers() : void{
+ //flattened IDs
+ $this->map(Blocks::AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES)));
+ $this->map(Blocks::CHERRY_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::CHERRY_LEAVES)));
+ $this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED)));
+ $this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES)));
+
+ //legacy mess
+ $this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::ACACIA_LEAVES)));
+ $this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::BIRCH_LEAVES)));
+ $this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::DARK_OAK_LEAVES)));
+ $this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::JUNGLE_LEAVES)));
+ $this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::OAK_LEAVES)));
+ $this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::SPRUCE_LEAVES)));
+ }
+
+ private function registerSaplingSerializers() : void{
+ foreach([
+ Ids::ACACIA_SAPLING => Blocks::ACACIA_SAPLING(),
+ Ids::BIRCH_SAPLING => Blocks::BIRCH_SAPLING(),
+ Ids::DARK_OAK_SAPLING => Blocks::DARK_OAK_SAPLING(),
+ Ids::JUNGLE_SAPLING => Blocks::JUNGLE_SAPLING(),
+ Ids::OAK_SAPLING => Blocks::OAK_SAPLING(),
+ Ids::SPRUCE_SAPLING => Blocks::SPRUCE_SAPLING(),
+ ] as $id => $block){
+ $this->map($block, fn(Sapling $block) => Helper::encodeSapling($block, new Writer($id)));
+ }
+ }
+
+ private function registerMobHeadSerializers() : void{
+ $this->map(Blocks::MOB_HEAD(), fn(MobHead $block) => Writer::create(match ($block->getMobHeadType()){
+ MobHeadType::CREEPER => Ids::CREEPER_HEAD,
+ MobHeadType::DRAGON => Ids::DRAGON_HEAD,
+ MobHeadType::PIGLIN => Ids::PIGLIN_HEAD,
+ MobHeadType::PLAYER => Ids::PLAYER_HEAD,
+ MobHeadType::SKELETON => Ids::SKELETON_SKULL,
+ MobHeadType::WITHER_SKELETON => Ids::WITHER_SKELETON_SKULL,
+ MobHeadType::ZOMBIE => Ids::ZOMBIE_HEAD,
+ })->writeFacingWithoutDown($block->getFacing()));
}
private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::AIR(), Ids::AIR);
$this->mapSimple(Blocks::AMETHYST(), Ids::AMETHYST_BLOCK);
$this->mapSimple(Blocks::ANCIENT_DEBRIS(), Ids::ANCIENT_DEBRIS);
+ $this->mapSimple(Blocks::ANDESITE(), Ids::ANDESITE);
$this->mapSimple(Blocks::BARRIER(), Ids::BARRIER);
$this->mapSimple(Blocks::BEACON(), Ids::BEACON);
$this->mapSimple(Blocks::BLACKSTONE(), Ids::BLACKSTONE);
@@ -384,12 +787,18 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::BOOKSHELF(), Ids::BOOKSHELF);
$this->mapSimple(Blocks::BRICKS(), Ids::BRICK_BLOCK);
$this->mapSimple(Blocks::BROWN_MUSHROOM(), Ids::BROWN_MUSHROOM);
+ $this->mapSimple(Blocks::BUDDING_AMETHYST(), Ids::BUDDING_AMETHYST);
$this->mapSimple(Blocks::CALCITE(), Ids::CALCITE);
$this->mapSimple(Blocks::CARTOGRAPHY_TABLE(), Ids::CARTOGRAPHY_TABLE);
$this->mapSimple(Blocks::CHEMICAL_HEAT(), Ids::CHEMICAL_HEAT);
$this->mapSimple(Blocks::CHISELED_DEEPSLATE(), Ids::CHISELED_DEEPSLATE);
$this->mapSimple(Blocks::CHISELED_NETHER_BRICKS(), Ids::CHISELED_NETHER_BRICKS);
$this->mapSimple(Blocks::CHISELED_POLISHED_BLACKSTONE(), Ids::CHISELED_POLISHED_BLACKSTONE);
+ $this->mapSimple(Blocks::CHISELED_RED_SANDSTONE(), Ids::CHISELED_RED_SANDSTONE);
+ $this->mapSimple(Blocks::CHISELED_SANDSTONE(), Ids::CHISELED_SANDSTONE);
+ $this->mapSimple(Blocks::CHISELED_STONE_BRICKS(), Ids::CHISELED_STONE_BRICKS);
+ $this->mapSimple(Blocks::CHISELED_TUFF(), Ids::CHISELED_TUFF);
+ $this->mapSimple(Blocks::CHISELED_TUFF_BRICKS(), Ids::CHISELED_TUFF_BRICKS);
$this->mapSimple(Blocks::CHORUS_PLANT(), Ids::CHORUS_PLANT);
$this->mapSimple(Blocks::CLAY(), Ids::CLAY);
$this->mapSimple(Blocks::COAL(), Ids::COAL_BLOCK);
@@ -402,11 +811,14 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::CRACKED_DEEPSLATE_TILES(), Ids::CRACKED_DEEPSLATE_TILES);
$this->mapSimple(Blocks::CRACKED_NETHER_BRICKS(), Ids::CRACKED_NETHER_BRICKS);
$this->mapSimple(Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS(), Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS);
+ $this->mapSimple(Blocks::CRACKED_STONE_BRICKS(), Ids::CRACKED_STONE_BRICKS);
$this->mapSimple(Blocks::CRAFTING_TABLE(), Ids::CRAFTING_TABLE);
- $this->mapSimple(Blocks::CRIMSON_FENCE(), Ids::CRIMSON_FENCE);
- $this->mapSimple(Blocks::CRIMSON_PLANKS(), Ids::CRIMSON_PLANKS);
+ $this->mapSimple(Blocks::CRIMSON_ROOTS(), Ids::CRIMSON_ROOTS);
$this->mapSimple(Blocks::CRYING_OBSIDIAN(), Ids::CRYING_OBSIDIAN);
- $this->mapSimple(Blocks::DANDELION(), Ids::YELLOW_FLOWER);
+ $this->mapSimple(Blocks::DANDELION(), Ids::DANDELION);
+ $this->mapSimple(Blocks::CUT_RED_SANDSTONE(), Ids::CUT_RED_SANDSTONE);
+ $this->mapSimple(Blocks::CUT_SANDSTONE(), Ids::CUT_SANDSTONE);
+ $this->mapSimple(Blocks::DARK_PRISMARINE(), Ids::DARK_PRISMARINE);
$this->mapSimple(Blocks::DEAD_BUSH(), Ids::DEADBUSH);
$this->mapSimple(Blocks::DEEPSLATE_BRICKS(), Ids::DEEPSLATE_BRICKS);
$this->mapSimple(Blocks::DEEPSLATE_COAL_ORE(), Ids::DEEPSLATE_COAL_ORE);
@@ -419,6 +831,7 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::DEEPSLATE_TILES(), Ids::DEEPSLATE_TILES);
$this->mapSimple(Blocks::DIAMOND(), Ids::DIAMOND_BLOCK);
$this->mapSimple(Blocks::DIAMOND_ORE(), Ids::DIAMOND_ORE);
+ $this->mapSimple(Blocks::DIORITE(), Ids::DIORITE);
$this->mapSimple(Blocks::DRAGON_EGG(), Ids::DRAGON_EGG);
$this->mapSimple(Blocks::DRIED_KELP(), Ids::DRIED_KELP_BLOCK);
$this->mapSimple(Blocks::ELEMENT_ACTINIUM(), Ids::ELEMENT_89);
@@ -545,6 +958,7 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::ENCHANTING_TABLE(), Ids::ENCHANTING_TABLE);
$this->mapSimple(Blocks::END_STONE(), Ids::END_STONE);
$this->mapSimple(Blocks::END_STONE_BRICKS(), Ids::END_BRICKS);
+ $this->mapSimple(Blocks::FERN(), Ids::FERN);
$this->mapSimple(Blocks::FLETCHING_TABLE(), Ids::FLETCHING_TABLE);
$this->mapSimple(Blocks::GILDED_BLACKSTONE(), Ids::GILDED_BLACKSTONE);
$this->mapSimple(Blocks::GLASS(), Ids::GLASS);
@@ -553,7 +967,8 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::GLOWSTONE(), Ids::GLOWSTONE);
$this->mapSimple(Blocks::GOLD(), Ids::GOLD_BLOCK);
$this->mapSimple(Blocks::GOLD_ORE(), Ids::GOLD_ORE);
- $this->mapSimple(Blocks::GRASS(), Ids::GRASS);
+ $this->mapSimple(Blocks::GRANITE(), Ids::GRANITE);
+ $this->mapSimple(Blocks::GRASS(), Ids::GRASS_BLOCK);
$this->mapSimple(Blocks::GRASS_PATH(), Ids::GRASS_PATH);
$this->mapSimple(Blocks::GRAVEL(), Ids::GRAVEL);
$this->mapSimple(Blocks::HANGING_ROOTS(), Ids::HANGING_ROOTS);
@@ -562,6 +977,12 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::HARDENED_GLASS_PANE(), Ids::HARD_GLASS_PANE);
$this->mapSimple(Blocks::HONEYCOMB(), Ids::HONEYCOMB_BLOCK);
$this->mapSimple(Blocks::ICE(), Ids::ICE);
+ $this->mapSimple(Blocks::INFESTED_CHISELED_STONE_BRICK(), Ids::INFESTED_CHISELED_STONE_BRICKS);
+ $this->mapSimple(Blocks::INFESTED_COBBLESTONE(), Ids::INFESTED_COBBLESTONE);
+ $this->mapSimple(Blocks::INFESTED_CRACKED_STONE_BRICK(), Ids::INFESTED_CRACKED_STONE_BRICKS);
+ $this->mapSimple(Blocks::INFESTED_MOSSY_STONE_BRICK(), Ids::INFESTED_MOSSY_STONE_BRICKS);
+ $this->mapSimple(Blocks::INFESTED_STONE(), Ids::INFESTED_STONE);
+ $this->mapSimple(Blocks::INFESTED_STONE_BRICK(), Ids::INFESTED_STONE_BRICKS);
$this->mapSimple(Blocks::INFO_UPDATE(), Ids::INFO_UPDATE);
$this->mapSimple(Blocks::INFO_UPDATE2(), Ids::INFO_UPDATE2);
$this->mapSimple(Blocks::INVISIBLE_BEDROCK(), Ids::INVISIBLE_BEDROCK);
@@ -574,12 +995,11 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::LEGACY_STONECUTTER(), Ids::STONECUTTER);
$this->mapSimple(Blocks::LILY_PAD(), Ids::WATERLILY);
$this->mapSimple(Blocks::MAGMA(), Ids::MAGMA);
- $this->mapSimple(Blocks::MANGROVE_FENCE(), Ids::MANGROVE_FENCE);
- $this->mapSimple(Blocks::MANGROVE_PLANKS(), Ids::MANGROVE_PLANKS);
$this->mapSimple(Blocks::MANGROVE_ROOTS(), Ids::MANGROVE_ROOTS);
$this->mapSimple(Blocks::MELON(), Ids::MELON_BLOCK);
$this->mapSimple(Blocks::MONSTER_SPAWNER(), Ids::MOB_SPAWNER);
$this->mapSimple(Blocks::MOSSY_COBBLESTONE(), Ids::MOSSY_COBBLESTONE);
+ $this->mapSimple(Blocks::MOSSY_STONE_BRICKS(), Ids::MOSSY_STONE_BRICKS);
$this->mapSimple(Blocks::MUD(), Ids::MUD);
$this->mapSimple(Blocks::MUD_BRICKS(), Ids::MUD_BRICKS);
$this->mapSimple(Blocks::MYCELIUM(), Ids::MYCELIUM);
@@ -596,9 +1016,15 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::PACKED_ICE(), Ids::PACKED_ICE);
$this->mapSimple(Blocks::PACKED_MUD(), Ids::PACKED_MUD);
$this->mapSimple(Blocks::PODZOL(), Ids::PODZOL);
+ $this->mapSimple(Blocks::POLISHED_ANDESITE(), Ids::POLISHED_ANDESITE);
$this->mapSimple(Blocks::POLISHED_BLACKSTONE(), Ids::POLISHED_BLACKSTONE);
$this->mapSimple(Blocks::POLISHED_BLACKSTONE_BRICKS(), Ids::POLISHED_BLACKSTONE_BRICKS);
$this->mapSimple(Blocks::POLISHED_DEEPSLATE(), Ids::POLISHED_DEEPSLATE);
+ $this->mapSimple(Blocks::POLISHED_DIORITE(), Ids::POLISHED_DIORITE);
+ $this->mapSimple(Blocks::POLISHED_GRANITE(), Ids::POLISHED_GRANITE);
+ $this->mapSimple(Blocks::POLISHED_TUFF(), Ids::POLISHED_TUFF);
+ $this->mapSimple(Blocks::PRISMARINE(), Ids::PRISMARINE);
+ $this->mapSimple(Blocks::PRISMARINE_BRICKS(), Ids::PRISMARINE_BRICKS);
$this->mapSimple(Blocks::QUARTZ_BRICKS(), Ids::QUARTZ_BRICKS);
$this->mapSimple(Blocks::RAW_COPPER(), Ids::RAW_COPPER_BLOCK);
$this->mapSimple(Blocks::RAW_GOLD(), Ids::RAW_GOLD_BLOCK);
@@ -606,8 +1032,12 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::REDSTONE(), Ids::REDSTONE_BLOCK);
$this->mapSimple(Blocks::RED_MUSHROOM(), Ids::RED_MUSHROOM);
$this->mapSimple(Blocks::RED_NETHER_BRICKS(), Ids::RED_NETHER_BRICK);
+ $this->mapSimple(Blocks::RED_SAND(), Ids::RED_SAND);
+ $this->mapSimple(Blocks::RED_SANDSTONE(), Ids::RED_SANDSTONE);
$this->mapSimple(Blocks::REINFORCED_DEEPSLATE(), Ids::REINFORCED_DEEPSLATE);
$this->mapSimple(Blocks::RESERVED6(), Ids::RESERVED6);
+ $this->mapSimple(Blocks::SAND(), Ids::SAND);
+ $this->mapSimple(Blocks::SANDSTONE(), Ids::SANDSTONE);
$this->mapSimple(Blocks::SCULK(), Ids::SCULK);
$this->mapSimple(Blocks::SEA_LANTERN(), Ids::SEA_LANTERN);
$this->mapSimple(Blocks::SHROOMLIGHT(), Ids::SHROOMLIGHT);
@@ -615,58 +1045,67 @@ private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::SLIME(), Ids::SLIME);
$this->mapSimple(Blocks::SMITHING_TABLE(), Ids::SMITHING_TABLE);
$this->mapSimple(Blocks::SMOOTH_BASALT(), Ids::SMOOTH_BASALT);
+ $this->mapSimple(Blocks::SMOOTH_RED_SANDSTONE(), Ids::SMOOTH_RED_SANDSTONE);
+ $this->mapSimple(Blocks::SMOOTH_SANDSTONE(), Ids::SMOOTH_SANDSTONE);
$this->mapSimple(Blocks::SMOOTH_STONE(), Ids::SMOOTH_STONE);
$this->mapSimple(Blocks::SNOW(), Ids::SNOW);
$this->mapSimple(Blocks::SOUL_SAND(), Ids::SOUL_SAND);
$this->mapSimple(Blocks::SOUL_SOIL(), Ids::SOUL_SOIL);
$this->mapSimple(Blocks::SPORE_BLOSSOM(), Ids::SPORE_BLOSSOM);
+ $this->mapSimple(Blocks::STONE(), Ids::STONE);
+ $this->mapSimple(Blocks::STONE_BRICKS(), Ids::STONE_BRICKS);
+ $this->mapSimple(Blocks::TALL_GRASS(), Ids::SHORT_GRASS); //no, this is not a typo - tall_grass is now the double block, just to be confusing :(
$this->mapSimple(Blocks::TINTED_GLASS(), Ids::TINTED_GLASS);
+ $this->mapSimple(Blocks::TORCHFLOWER(), Ids::TORCHFLOWER);
$this->mapSimple(Blocks::TUFF(), Ids::TUFF);
- $this->mapSimple(Blocks::WARPED_FENCE(), Ids::WARPED_FENCE);
- $this->mapSimple(Blocks::WARPED_PLANKS(), Ids::WARPED_PLANKS);
+ $this->mapSimple(Blocks::TUFF_BRICKS(), Ids::TUFF_BRICKS);
$this->mapSimple(Blocks::WARPED_WART_BLOCK(), Ids::WARPED_WART_BLOCK);
+ $this->mapSimple(Blocks::WARPED_ROOTS(), Ids::WARPED_ROOTS);
$this->mapSimple(Blocks::WITHER_ROSE(), Ids::WITHER_ROSE);
+
+ $this->mapSimple(Blocks::ALLIUM(), Ids::ALLIUM);
+ $this->mapSimple(Blocks::CORNFLOWER(), Ids::CORNFLOWER);
+ $this->mapSimple(Blocks::AZURE_BLUET(), Ids::AZURE_BLUET);
+ $this->mapSimple(Blocks::LILY_OF_THE_VALLEY(), Ids::LILY_OF_THE_VALLEY);
+ $this->mapSimple(Blocks::BLUE_ORCHID(), Ids::BLUE_ORCHID);
+ $this->mapSimple(Blocks::OXEYE_DAISY(), Ids::OXEYE_DAISY);
+ $this->mapSimple(Blocks::POPPY(), Ids::POPPY);
+ $this->mapSimple(Blocks::ORANGE_TULIP(), Ids::ORANGE_TULIP);
+ $this->mapSimple(Blocks::PINK_TULIP(), Ids::PINK_TULIP);
+ $this->mapSimple(Blocks::RED_TULIP(), Ids::RED_TULIP);
+ $this->mapSimple(Blocks::WHITE_TULIP(), Ids::WHITE_TULIP);
}
private function registerSerializers() : void{
- $this->map(Blocks::ACACIA_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::ACACIA_BUTTON)));
- $this->map(Blocks::ACACIA_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::ACACIA_DOOR)));
- $this->map(Blocks::ACACIA_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::ACACIA_FENCE_GATE)));
- $this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_ACACIA));
- $this->map(Blocks::ACACIA_PLANKS(), fn() => Writer::create(Ids::PLANKS)
- ->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_ACACIA));
- $this->map(Blocks::ACACIA_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::ACACIA_PRESSURE_PLATE)));
- $this->map(Blocks::ACACIA_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_ACACIA));
- $this->map(Blocks::ACACIA_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::ACACIA_STANDING_SIGN)));
- $this->map(Blocks::ACACIA_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_ACACIA));
- $this->map(Blocks::ACACIA_STAIRS(), fn(WoodenStairs $block) => Helper::encodeStairs($block, new Writer(Ids::ACACIA_STAIRS)));
- $this->map(Blocks::ACACIA_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::ACACIA_TRAPDOOR)));
- $this->map(Blocks::ACACIA_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::ACACIA_WALL_SIGN)));
- $this->map(Blocks::ACACIA_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->map(Blocks::ACTIVATOR_RAIL(), function(ActivatorRail $block) : Writer{
return Writer::create(Ids::ACTIVATOR_RAIL)
->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered())
->writeInt(StateNames::RAIL_DIRECTION, $block->getShape());
});
- $this->map(Blocks::ALLIUM(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_ALLIUM));
- $this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), fn() => Writer::create(Ids::BROWN_MUSHROOM_BLOCK)
+ $this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), fn() => Writer::create(Ids::MUSHROOM_STEM)
->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM));
- $this->map(Blocks::ANDESITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_ANDESITE));
- $this->map(Blocks::ANDESITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_ANDESITE));
+ $this->map(Blocks::AMETHYST_CLUSTER(), fn(AmethystCluster $block) => Writer::create(
+ match($stage = $block->getStage()){
+ AmethystCluster::STAGE_SMALL_BUD => Ids::SMALL_AMETHYST_BUD,
+ AmethystCluster::STAGE_MEDIUM_BUD => Ids::MEDIUM_AMETHYST_BUD,
+ AmethystCluster::STAGE_LARGE_BUD => Ids::LARGE_AMETHYST_BUD,
+ AmethystCluster::STAGE_CLUSTER => Ids::AMETHYST_CLUSTER,
+ default => throw new BlockStateSerializeException("Invalid Amethyst Cluster stage $stage"),
+ })
+ ->writeBlockFace($block->getFacing())
+ );
+ $this->mapSlab(Blocks::ANDESITE_SLAB(), Ids::ANDESITE_SLAB, Ids::ANDESITE_DOUBLE_SLAB);
$this->map(Blocks::ANDESITE_STAIRS(), fn(Stair $block) => Helper::encodeStairs($block, new Writer(Ids::ANDESITE_STAIRS)));
- $this->map(Blocks::ANDESITE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_ANDESITE));
- $this->map(Blocks::ANVIL(), function(Anvil $block) : Writer{
- return Writer::create(Ids::ANVIL)
- ->writeLegacyHorizontalFacing($block->getFacing())
- ->writeString(StateNames::DAMAGE, match($damage = $block->getDamage()){
- 0 => StringValues::DAMAGE_UNDAMAGED,
- 1 => StringValues::DAMAGE_SLIGHTLY_DAMAGED,
- 2 => StringValues::DAMAGE_VERY_DAMAGED,
- default => throw new BlockStateSerializeException("Invalid Anvil damage {$damage}"),
- });
- });
- $this->map(Blocks::AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES)));
- $this->map(Blocks::AZURE_BLUET(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_HOUSTONIA));
+ $this->map(Blocks::ANDESITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::ANDESITE_WALL)));
+ $this->map(Blocks::ANVIL(), fn(Anvil $block) : Writer => Writer::create(
+ match($damage = $block->getDamage()){
+ 0 => Ids::ANVIL,
+ 1 => Ids::CHIPPED_ANVIL,
+ 2 => Ids::DAMAGED_ANVIL,
+ default => throw new BlockStateSerializeException("Invalid Anvil damage {$damage}"),
+ })
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ );
$this->map(Blocks::BAMBOO(), function(Bamboo $block) : Writer{
return Writer::create(Ids::BAMBOO)
->writeBool(StateNames::AGE_BIT, $block->isReady())
@@ -680,10 +1119,7 @@ private function registerSerializers() : void{
});
$this->map(Blocks::BAMBOO_SAPLING(), function(BambooSapling $block) : Writer{
return Writer::create(Ids::BAMBOO_SAPLING)
- ->writeBool(StateNames::AGE_BIT, $block->isReady())
-
- //TODO: bug in MCPE
- ->writeString(StateNames::SAPLING_TYPE, StringValues::SAPLING_TYPE_OAK);
+ ->writeBool(StateNames::AGE_BIT, $block->isReady());
});
$this->map(Blocks::BANNER(), function(FloorBanner $block) : Writer{
return Writer::create(Ids::STANDING_BANNER)
@@ -716,26 +1152,28 @@ private function registerSerializers() : void{
->writeLegacyHorizontalFacing($block->getFacing());
});
- $this->map(Blocks::BIRCH_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::BIRCH_BUTTON)));
- $this->map(Blocks::BIRCH_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::BIRCH_DOOR)));
- $this->map(Blocks::BIRCH_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::BIRCH_FENCE_GATE)));
- $this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_BIRCH));
- $this->map(Blocks::BIRCH_PLANKS(), fn() => Writer::create(Ids::PLANKS)
- ->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_BIRCH));
- $this->map(Blocks::BIRCH_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::BIRCH_PRESSURE_PLATE)));
- $this->map(Blocks::BIRCH_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_BIRCH));
- $this->map(Blocks::BIRCH_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::BIRCH_STANDING_SIGN)));
- $this->map(Blocks::BIRCH_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_BIRCH));
- $this->mapStairs(Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS);
- $this->map(Blocks::BIRCH_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::BIRCH_TRAPDOOR)));
- $this->map(Blocks::BIRCH_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::BIRCH_WALL_SIGN)));
- $this->map(Blocks::BIRCH_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
+ $this->map(Blocks::BIG_DRIPLEAF_HEAD(), function(BigDripleafHead $block) : Writer{
+ return Writer::create(Ids::BIG_DRIPLEAF)
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ ->writeString(StateNames::BIG_DRIPLEAF_TILT, match($block->getLeafState()){
+ DripleafState::STABLE => StringValues::BIG_DRIPLEAF_TILT_NONE,
+ DripleafState::UNSTABLE => StringValues::BIG_DRIPLEAF_TILT_UNSTABLE,
+ DripleafState::PARTIAL_TILT => StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT,
+ DripleafState::FULL_TILT => StringValues::BIG_DRIPLEAF_TILT_FULL_TILT,
+ })
+ ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, true);
+ });
+ $this->map(Blocks::BIG_DRIPLEAF_STEM(), function(BigDripleafStem $block) : Writer{
+ return Writer::create(Ids::BIG_DRIPLEAF)
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ ->writeString(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE)
+ ->writeBool(StateNames::BIG_DRIPLEAF_HEAD, false);
+ });
$this->mapSlab(Blocks::BLACKSTONE_SLAB(), Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS);
$this->map(Blocks::BLACKSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::BLACKSTONE_WALL)));
$this->map(Blocks::BLAST_FURNACE(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::BLAST_FURNACE, Ids::LIT_BLAST_FURNACE));
- $this->map(Blocks::BLUE_ORCHID(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_ORCHID));
- $this->map(Blocks::BLUE_TORCH(), fn(Torch $block) => Helper::encodeColoredTorch($block, false, Writer::create(Ids::COLORED_TORCH_BP)));
+ $this->map(Blocks::BLUE_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_BLUE)));
$this->map(Blocks::BONE_BLOCK(), function(BoneBlock $block) : Writer{
return Writer::create(Ids::BONE_BLOCK)
->writeInt(StateNames::DEPRECATED, 0)
@@ -743,13 +1181,13 @@ private function registerSerializers() : void{
});
$this->map(Blocks::BREWING_STAND(), function(BrewingStand $block) : Writer{
return Writer::create(Ids::BREWING_STAND)
- ->writeBool(StateNames::BREWING_STAND_SLOT_A_BIT, $block->hasSlot(BrewingStandSlot::EAST()))
- ->writeBool(StateNames::BREWING_STAND_SLOT_B_BIT, $block->hasSlot(BrewingStandSlot::SOUTHWEST()))
- ->writeBool(StateNames::BREWING_STAND_SLOT_C_BIT, $block->hasSlot(BrewingStandSlot::NORTHWEST()));
+ ->writeBool(StateNames::BREWING_STAND_SLOT_A_BIT, $block->hasSlot(BrewingStandSlot::EAST))
+ ->writeBool(StateNames::BREWING_STAND_SLOT_B_BIT, $block->hasSlot(BrewingStandSlot::SOUTHWEST))
+ ->writeBool(StateNames::BREWING_STAND_SLOT_C_BIT, $block->hasSlot(BrewingStandSlot::NORTHWEST));
});
- $this->map(Blocks::BRICK_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_BRICK));
+ $this->mapSlab(Blocks::BRICK_SLAB(), Ids::BRICK_SLAB, Ids::BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::BRICK_STAIRS(), Ids::BRICK_STAIRS);
- $this->map(Blocks::BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_BRICK));
+ $this->map(Blocks::BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::BRICK_WALL)));
$this->map(Blocks::BROWN_MUSHROOM_BLOCK(), fn(BrownMushroomBlock $block) => Helper::encodeMushroomBlock($block, new Writer(Ids::BROWN_MUSHROOM_BLOCK)));
$this->map(Blocks::CACTUS(), function(Cactus $block) : Writer{
return Writer::create(Ids::CACTUS)
@@ -759,14 +1197,15 @@ private function registerSerializers() : void{
return Writer::create(Ids::CAKE)
->writeInt(StateNames::BITE_COUNTER, $block->getBites());
});
- $this->map(Blocks::CARPET(), function(Carpet $block) : Writer{
- return Writer::create(Ids::CARPET)
- ->writeColor($block->getColor());
+ $this->map(Blocks::CAMPFIRE(), function(Campfire $block) : Writer{
+ return Writer::create(Ids::CAMPFIRE)
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ ->writeBool(StateNames::EXTINGUISHED, !$block->isLit());
});
$this->map(Blocks::CARROTS(), fn(Carrot $block) => Helper::encodeCrops($block, new Writer(Ids::CARROTS)));
$this->map(Blocks::CARVED_PUMPKIN(), function(CarvedPumpkin $block) : Writer{
return Writer::create(Ids::CARVED_PUMPKIN)
- ->writeLegacyHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::CAVE_VINES(), function(CaveVines $block) : Writer{
//I have no idea why this only has 3 IDs - there are 4 in Java and 4 visually distinct states in Bedrock
@@ -785,12 +1224,18 @@ private function registerSerializers() : void{
});
$this->map(Blocks::CHEST(), function(Chest $block) : Writer{
return Writer::create(Ids::CHEST)
- ->writeHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
- $this->map(Blocks::CHISELED_QUARTZ(), fn(SimplePillar $block) => Helper::encodeQuartz(StringValues::CHISEL_TYPE_CHISELED, $block->getAxis()));
- $this->map(Blocks::CHISELED_RED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::RED_SANDSTONE, StringValues::SAND_STONE_TYPE_HEIROGLYPHS));
- $this->map(Blocks::CHISELED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::SANDSTONE, StringValues::SAND_STONE_TYPE_HEIROGLYPHS));
- $this->map(Blocks::CHISELED_STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_CHISELED));
+ $this->map(Blocks::CHISELED_BOOKSHELF(), function(ChiseledBookshelf $block) : Writer{
+ $flags = 0;
+ foreach($block->getSlots() as $slot){
+ $flags |= 1 << $slot->value;
+ }
+ return Writer::create(Ids::CHISELED_BOOKSHELF)
+ ->writeLegacyHorizontalFacing($block->getFacing())
+ ->writeInt(StateNames::BOOKS_STORED, $flags);
+ });
+ $this->map(Blocks::CHISELED_QUARTZ(), fn(SimplePillar $block) => Helper::encodeQuartz($block->getAxis(), Writer::create(Ids::CHISELED_QUARTZ_BLOCK)));
$this->map(Blocks::CHORUS_FLOWER(), function(ChorusFlower $block) : Writer{
return Writer::create(Ids::CHORUS_FLOWER)
->writeInt(StateNames::AGE, $block->getAge());
@@ -798,9 +1243,9 @@ private function registerSerializers() : void{
$this->mapSlab(Blocks::COBBLED_DEEPSLATE_SLAB(), Ids::COBBLED_DEEPSLATE_SLAB, Ids::COBBLED_DEEPSLATE_DOUBLE_SLAB);
$this->mapStairs(Blocks::COBBLED_DEEPSLATE_STAIRS(), Ids::COBBLED_DEEPSLATE_STAIRS);
$this->map(Blocks::COBBLED_DEEPSLATE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::COBBLED_DEEPSLATE_WALL)));
- $this->map(Blocks::COBBLESTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_COBBLESTONE));
+ $this->mapSlab(Blocks::COBBLESTONE_SLAB(), Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS);
- $this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_COBBLESTONE));
+ $this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::COBBLESTONE_WALL)));
$this->map(Blocks::COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
@@ -808,6 +1253,40 @@ private function registerSerializers() : void{
Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER)
);
});
+ $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{
+ $oxidation = $block->getOxidation();
+ return new Writer($block->isWaxed() ?
+ Helper::selectCopperId($oxidation,
+ Ids::WAXED_CHISELED_COPPER,
+ Ids::WAXED_EXPOSED_CHISELED_COPPER,
+ Ids::WAXED_WEATHERED_CHISELED_COPPER,
+ Ids::WAXED_OXIDIZED_CHISELED_COPPER
+ ) :
+ Helper::selectCopperId($oxidation,
+ Ids::CHISELED_COPPER,
+ Ids::EXPOSED_CHISELED_COPPER,
+ Ids::WEATHERED_CHISELED_COPPER,
+ Ids::OXIDIZED_CHISELED_COPPER
+ )
+ );
+ });
+ $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{
+ $oxidation = $block->getOxidation();
+ return new Writer($block->isWaxed() ?
+ Helper::selectCopperId($oxidation,
+ Ids::WAXED_COPPER_GRATE,
+ Ids::WAXED_EXPOSED_COPPER_GRATE,
+ Ids::WAXED_WEATHERED_COPPER_GRATE,
+ Ids::WAXED_OXIDIZED_COPPER_GRATE
+ ) :
+ Helper::selectCopperId($oxidation,
+ Ids::COPPER_GRATE,
+ Ids::EXPOSED_COPPER_GRATE,
+ Ids::WEATHERED_COPPER_GRATE,
+ Ids::OXIDIZED_COPPER_GRATE
+ )
+ );
+ });
$this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
@@ -875,73 +1354,76 @@ private function registerSerializers() : void{
)
);
});
+ $this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{
+ $oxidation = $block->getOxidation();
+ return Writer::create($block->isWaxed() ?
+ Helper::selectCopperId($oxidation,
+ Ids::WAXED_COPPER_BULB,
+ Ids::WAXED_EXPOSED_COPPER_BULB,
+ Ids::WAXED_WEATHERED_COPPER_BULB,
+ Ids::WAXED_OXIDIZED_COPPER_BULB) :
+ Helper::selectCopperId($oxidation,
+ Ids::COPPER_BULB,
+ Ids::EXPOSED_COPPER_BULB,
+ Ids::WEATHERED_COPPER_BULB,
+ Ids::OXIDIZED_COPPER_BULB
+ ))
+ ->writeBool(StateNames::LIT, $block->isLit())
+ ->writeBool(StateNames::POWERED_BIT, $block->isPowered());
+ });
+ $this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{
+ $oxidation = $block->getOxidation();
+ return Helper::encodeDoor(
+ $block,
+ new Writer($block->isWaxed() ?
+ Helper::selectCopperId(
+ $oxidation,
+ Ids::WAXED_COPPER_DOOR,
+ Ids::WAXED_EXPOSED_COPPER_DOOR,
+ Ids::WAXED_WEATHERED_COPPER_DOOR,
+ Ids::WAXED_OXIDIZED_COPPER_DOOR
+ ) :
+ Helper::selectCopperId(
+ $oxidation,
+ Ids::COPPER_DOOR,
+ Ids::EXPOSED_COPPER_DOOR,
+ Ids::WEATHERED_COPPER_DOOR,
+ Ids::OXIDIZED_COPPER_DOOR
+ )
+ )
+ );
+ });
+ $this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{
+ $oxidation = $block->getOxidation();
+ return Helper::encodeTrapdoor(
+ $block,
+ new Writer($block->isWaxed() ?
+ Helper::selectCopperId(
+ $oxidation,
+ Ids::WAXED_COPPER_TRAPDOOR,
+ Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
+ Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
+ Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR
+ ) :
+ Helper::selectCopperId(
+ $oxidation,
+ Ids::COPPER_TRAPDOOR,
+ Ids::EXPOSED_COPPER_TRAPDOOR,
+ Ids::WEATHERED_COPPER_TRAPDOOR,
+ Ids::OXIDIZED_COPPER_TRAPDOOR
+ )
+ )
+ );
+ });
$this->map(Blocks::COCOA_POD(), function(CocoaBlock $block) : Writer{
return Writer::create(Ids::COCOA)
->writeInt(StateNames::AGE, $block->getAge())
->writeLegacyHorizontalFacing(Facing::opposite($block->getFacing()));
});
- $this->map(Blocks::COMPOUND_CREATOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_COMPOUND_CREATOR, new Writer(Ids::CHEMISTRY_TABLE)));
- $this->map(Blocks::CONCRETE(), function(Concrete $block) : Writer{
- return Writer::create(Ids::CONCRETE)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::CONCRETE_POWDER(), function(ConcretePowder $block) : Writer{
- return Writer::create(Ids::CONCRETE_POWDER)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::CORAL(), function(Coral $block) : Writer{
- return Writer::create(Ids::CORAL)
- ->writeBool(StateNames::DEAD_BIT, $block->isDead())
- ->writeCoralType($block->getCoralType());
- });
- $this->map(Blocks::CORAL_BLOCK(), function(CoralBlock $block) : Writer{
- return Writer::create(Ids::CORAL_BLOCK)
- ->writeBool(StateNames::DEAD_BIT, $block->isDead())
- ->writeCoralType($block->getCoralType());
- });
- $this->map(Blocks::CORAL_FAN(), function(FloorCoralFan $block) : Writer{
- return Writer::create($block->isDead() ? Ids::CORAL_FAN_DEAD : Ids::CORAL_FAN)
- ->writeCoralType($block->getCoralType())
- ->writeInt(StateNames::CORAL_FAN_DIRECTION, match($axis = $block->getAxis()){
- Axis::X => 0,
- Axis::Z => 1,
- default => throw new BlockStateSerializeException("Invalid axis {$axis}"),
- });
- });
- $this->map(Blocks::CORNFLOWER(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_CORNFLOWER));
- $this->map(Blocks::CRACKED_STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_CRACKED));
- $this->map(Blocks::CRIMSON_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CRIMSON_BUTTON)));
- $this->map(Blocks::CRIMSON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CRIMSON_DOOR)));
- $this->map(Blocks::CRIMSON_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CRIMSON_FENCE_GATE)));
- $this->map(Blocks::CRIMSON_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE));
- $this->map(Blocks::CRIMSON_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CRIMSON_PRESSURE_PLATE)));
- $this->map(Blocks::CRIMSON_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CRIMSON_STANDING_SIGN)));
- $this->mapSlab(Blocks::CRIMSON_SLAB(), Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB);
- $this->mapStairs(Blocks::CRIMSON_STAIRS(), Ids::CRIMSON_STAIRS);
- $this->map(Blocks::CRIMSON_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM));
- $this->map(Blocks::CRIMSON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CRIMSON_TRAPDOOR)));
- $this->map(Blocks::CRIMSON_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CRIMSON_WALL_SIGN)));
- $this->map(Blocks::CUT_RED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::RED_SANDSTONE, StringValues::SAND_STONE_TYPE_CUT));
- $this->map(Blocks::CUT_RED_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_CUT_RED_SANDSTONE));
- $this->map(Blocks::CUT_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::SANDSTONE, StringValues::SAND_STONE_TYPE_CUT));
- $this->map(Blocks::CUT_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_CUT_SANDSTONE));
- $this->map(Blocks::DARK_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::DARK_OAK_BUTTON)));
- $this->map(Blocks::DARK_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::DARK_OAK_DOOR)));
- $this->map(Blocks::DARK_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::DARK_OAK_FENCE_GATE)));
- $this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_DARK_OAK));
- $this->map(Blocks::DARK_OAK_PLANKS(), fn() => Writer::create(Ids::PLANKS)
- ->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_DARK_OAK));
- $this->map(Blocks::DARK_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::DARK_OAK_PRESSURE_PLATE)));
- $this->map(Blocks::DARK_OAK_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_DARK_OAK));
- $this->map(Blocks::DARK_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::DARKOAK_STANDING_SIGN)));
- $this->map(Blocks::DARK_OAK_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_DARK_OAK));
- $this->mapStairs(Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS);
- $this->map(Blocks::DARK_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::DARK_OAK_TRAPDOOR)));
- $this->map(Blocks::DARK_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::DARKOAK_WALL_SIGN)));
- $this->map(Blocks::DARK_OAK_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
- $this->map(Blocks::DARK_PRISMARINE(), fn() => Writer::create(Ids::PRISMARINE)
- ->writeString(StateNames::PRISMARINE_BLOCK_TYPE, StringValues::PRISMARINE_BLOCK_TYPE_DARK));
- $this->map(Blocks::DARK_PRISMARINE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_PRISMARINE_DARK));
+ $this->map(Blocks::COMPOUND_CREATOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::COMPOUND_CREATOR)));
+ $this->mapSlab(Blocks::CUT_RED_SANDSTONE_SLAB(), Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB);
+ $this->mapSlab(Blocks::CUT_SANDSTONE_SLAB(), Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB);
+ $this->mapSlab(Blocks::DARK_PRISMARINE_SLAB(), Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB);
$this->mapStairs(Blocks::DARK_PRISMARINE_STAIRS(), Ids::DARK_PRISMARINE_STAIRS);
$this->map(Blocks::DAYLIGHT_SENSOR(), function(DaylightSensor $block) : Writer{
return Writer::create($block->isInverted() ? Ids::DAYLIGHT_DETECTOR_INVERTED : Ids::DAYLIGHT_DETECTOR)
@@ -963,51 +1445,39 @@ private function registerSerializers() : void{
->writeBool(StateNames::RAIL_DATA_BIT, $block->isActivated())
->writeInt(StateNames::RAIL_DIRECTION, $block->getShape());
});
- $this->map(Blocks::DIORITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_DIORITE));
- $this->map(Blocks::DIORITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_DIORITE));
+ $this->mapSlab(Blocks::DIORITE_SLAB(), Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB);
$this->mapStairs(Blocks::DIORITE_STAIRS(), Ids::DIORITE_STAIRS);
- $this->map(Blocks::DIORITE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_DIORITE));
+ $this->map(Blocks::DIORITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::DIORITE_WALL)));
$this->map(Blocks::DIRT(), function(Dirt $block) : Writer{
- $dirtType = $block->getDirtType();
- if($dirtType->equals(DirtType::ROOTED())){
- return new Writer(Ids::DIRT_WITH_ROOTS);
- }
- return Writer::create(Ids::DIRT)
- ->writeString(StateNames::DIRT_TYPE, match($dirtType){
- DirtType::COARSE() => StringValues::DIRT_TYPE_COARSE,
- DirtType::NORMAL() => StringValues::DIRT_TYPE_NORMAL,
- default => throw new AssumptionFailedError("Unhandled dirt type " . $dirtType->name())
- });
- });
- $this->map(Blocks::DOUBLE_TALLGRASS(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_GRASS, Writer::create(Ids::DOUBLE_PLANT)));
- $this->map(Blocks::DYED_SHULKER_BOX(), function(DyedShulkerBox $block) : Writer{
- return Writer::create(Ids::SHULKER_BOX)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::ELEMENT_CONSTRUCTOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_ELEMENT_CONSTRUCTOR, new Writer(Ids::CHEMISTRY_TABLE)));
+ return Writer::create(match($block->getDirtType()){
+ DirtType::NORMAL => Ids::DIRT,
+ DirtType::COARSE => Ids::COARSE_DIRT,
+ DirtType::ROOTED => Ids::DIRT_WITH_ROOTS,
+ });
+ });
+ $this->map(Blocks::DOUBLE_TALLGRASS(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::TALL_GRASS)));
+ $this->map(Blocks::ELEMENT_CONSTRUCTOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::ELEMENT_CONSTRUCTOR)));
$this->map(Blocks::ENDER_CHEST(), function(EnderChest $block) : Writer{
return Writer::create(Ids::ENDER_CHEST)
- ->writeHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::END_PORTAL_FRAME(), function(EndPortalFrame $block) : Writer{
return Writer::create(Ids::END_PORTAL_FRAME)
->writeBool(StateNames::END_PORTAL_EYE_BIT, $block->hasEye())
- ->writeLegacyHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::END_ROD(), function(EndRod $block) : Writer{
return Writer::create(Ids::END_ROD)
->writeEndRodFacingDirection($block->getFacing());
});
- $this->map(Blocks::END_STONE_BRICK_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_END_STONE_BRICK));
+ $this->mapSlab(Blocks::END_STONE_BRICK_SLAB(), Ids::END_STONE_BRICK_SLAB, Ids::END_STONE_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::END_STONE_BRICK_STAIRS(), Ids::END_BRICK_STAIRS);
- $this->map(Blocks::END_STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_END_BRICK));
- $this->map(Blocks::FAKE_WOODEN_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_WOOD));
+ $this->map(Blocks::END_STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::END_STONE_BRICK_WALL)));
+ $this->mapSlab(Blocks::FAKE_WOODEN_SLAB(), Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB);
$this->map(Blocks::FARMLAND(), function(Farmland $block) : Writer{
return Writer::create(Ids::FARMLAND)
->writeInt(StateNames::MOISTURIZED_AMOUNT, $block->getWetness());
});
- $this->map(Blocks::FERN(), fn() => Writer::create(Ids::TALLGRASS)
- ->writeString(StateNames::TALL_GRASS_TYPE, StringValues::TALL_GRASS_TYPE_FERN));
$this->map(Blocks::FIRE(), function(Fire $block) : Writer{
return Writer::create(Ids::FIRE)
->writeInt(StateNames::AGE, $block->getAge());
@@ -1016,13 +1486,11 @@ private function registerSerializers() : void{
return Writer::create(Ids::FLOWER_POT)
->writeBool(StateNames::UPDATE_BIT, false); //to keep MCPE happy
});
- $this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED)));
$this->map(Blocks::FROGLIGHT(), function(Froglight $block){
return Writer::create(match($block->getFroglightType()){
- FroglightType::OCHRE() => Ids::OCHRE_FROGLIGHT,
- FroglightType::PEARLESCENT() => Ids::PEARLESCENT_FROGLIGHT,
- FroglightType::VERDANT() => Ids::VERDANT_FROGLIGHT,
- default => throw new AssumptionFailedError("Unhandled froglight type " . $block->getFroglightType()->name())
+ FroglightType::OCHRE => Ids::OCHRE_FROGLIGHT,
+ FroglightType::PEARLESCENT => Ids::PEARLESCENT_FROGLIGHT,
+ FroglightType::VERDANT => Ids::VERDANT_FROGLIGHT,
})
->writePillarAxis($block->getAxis());
});
@@ -1036,11 +1504,10 @@ private function registerSerializers() : void{
->writeFacingFlags($block->getFaces());
});
$this->map(Blocks::GLOWING_ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::GLOW_FRAME));
- $this->map(Blocks::GRANITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_GRANITE));
- $this->map(Blocks::GRANITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_GRANITE));
+ $this->mapSlab(Blocks::GRANITE_SLAB(), Ids::GRANITE_SLAB, Ids::GRANITE_DOUBLE_SLAB);
$this->mapStairs(Blocks::GRANITE_STAIRS(), Ids::GRANITE_STAIRS);
- $this->map(Blocks::GRANITE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_GRANITE));
- $this->map(Blocks::GREEN_TORCH(), fn(Torch $block) => Helper::encodeColoredTorch($block, true, Writer::create(Ids::COLORED_TORCH_RG)));
+ $this->map(Blocks::GRANITE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::GRANITE_WALL)));
+ $this->map(Blocks::GREEN_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_GREEN)));
$this->map(Blocks::HAY_BALE(), function(HayBale $block) : Writer{
return Writer::create(Ids::HAY_BLOCK)
->writeInt(StateNames::DEPRECATED, 0)
@@ -1051,36 +1518,10 @@ private function registerSerializers() : void{
->writeBool(StateNames::TOGGLE_BIT, $block->isPowered())
->writeFacingWithoutUp($block->getFacing());
});
- $this->map(Blocks::INFESTED_CHISELED_STONE_BRICK(), fn() => Writer::create(Ids::MONSTER_EGG)
- ->writeString(StateNames::MONSTER_EGG_STONE_TYPE, StringValues::MONSTER_EGG_STONE_TYPE_CHISELED_STONE_BRICK));
- $this->map(Blocks::INFESTED_COBBLESTONE(), fn() => Writer::create(Ids::MONSTER_EGG)
- ->writeString(StateNames::MONSTER_EGG_STONE_TYPE, StringValues::MONSTER_EGG_STONE_TYPE_COBBLESTONE));
- $this->map(Blocks::INFESTED_CRACKED_STONE_BRICK(), fn() => Writer::create(Ids::MONSTER_EGG)
- ->writeString(StateNames::MONSTER_EGG_STONE_TYPE, StringValues::MONSTER_EGG_STONE_TYPE_CRACKED_STONE_BRICK));
- $this->map(Blocks::INFESTED_MOSSY_STONE_BRICK(), fn() => Writer::create(Ids::MONSTER_EGG)
- ->writeString(StateNames::MONSTER_EGG_STONE_TYPE, StringValues::MONSTER_EGG_STONE_TYPE_MOSSY_STONE_BRICK));
- $this->map(Blocks::INFESTED_STONE(), fn() => Writer::create(Ids::MONSTER_EGG)
- ->writeString(StateNames::MONSTER_EGG_STONE_TYPE, StringValues::MONSTER_EGG_STONE_TYPE_STONE));
- $this->map(Blocks::INFESTED_STONE_BRICK(), fn() => Writer::create(Ids::MONSTER_EGG)
- ->writeString(StateNames::MONSTER_EGG_STONE_TYPE, StringValues::MONSTER_EGG_STONE_TYPE_STONE_BRICK));
$this->map(Blocks::IRON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::IRON_DOOR)));
$this->map(Blocks::IRON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::IRON_TRAPDOOR)));
$this->map(Blocks::ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::FRAME));
- $this->map(Blocks::JUNGLE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::JUNGLE_BUTTON)));
- $this->map(Blocks::JUNGLE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::JUNGLE_DOOR)));
- $this->map(Blocks::JUNGLE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::JUNGLE_FENCE_GATE)));
- $this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_JUNGLE));
- $this->map(Blocks::JUNGLE_PLANKS(), fn() => Writer::create(Ids::PLANKS)
- ->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_JUNGLE));
- $this->map(Blocks::JUNGLE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::JUNGLE_PRESSURE_PLATE)));
- $this->map(Blocks::JUNGLE_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_JUNGLE));
- $this->map(Blocks::JUNGLE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::JUNGLE_STANDING_SIGN)));
- $this->map(Blocks::JUNGLE_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_JUNGLE));
- $this->mapStairs(Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS);
- $this->map(Blocks::JUNGLE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::JUNGLE_TRAPDOOR)));
- $this->map(Blocks::JUNGLE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::JUNGLE_WALL_SIGN)));
- $this->map(Blocks::JUNGLE_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
- $this->map(Blocks::LAB_TABLE(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_LAB_TABLE, new Writer(Ids::CHEMISTRY_TABLE)));
+ $this->map(Blocks::LAB_TABLE(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::LAB_TABLE)));
$this->map(Blocks::LADDER(), function(Ladder $block) : Writer{
return Writer::create(Ids::LADDER)
->writeHorizontalFacing($block->getFacing());
@@ -1089,91 +1530,79 @@ private function registerSerializers() : void{
return Writer::create(Ids::LANTERN)
->writeBool(StateNames::HANGING, $block->isHanging());
});
- $this->map(Blocks::LARGE_FERN(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_FERN, Writer::create(Ids::DOUBLE_PLANT)));
+ $this->map(Blocks::LARGE_FERN(), fn(DoubleTallGrass $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::LARGE_FERN)));
$this->map(Blocks::LAVA(), fn(Lava $block) => Helper::encodeLiquid($block, Ids::LAVA, Ids::FLOWING_LAVA));
$this->map(Blocks::LECTERN(), function(Lectern $block) : Writer{
return Writer::create(Ids::LECTERN)
->writeBool(StateNames::POWERED_BIT, $block->isProducingSignal())
- ->writeLegacyHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::LEVER(), function(Lever $block) : Writer{
return Writer::create(Ids::LEVER)
->writeBool(StateNames::OPEN_BIT, $block->isActivated())
- ->writeString(StateNames::LEVER_DIRECTION, match($block->getFacing()->id()){
- LeverFacing::DOWN_AXIS_Z()->id() => StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH,
- LeverFacing::DOWN_AXIS_X()->id() => StringValues::LEVER_DIRECTION_DOWN_EAST_WEST,
- LeverFacing::UP_AXIS_Z()->id() => StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH,
- LeverFacing::UP_AXIS_X()->id() => StringValues::LEVER_DIRECTION_UP_EAST_WEST,
- LeverFacing::NORTH()->id() => StringValues::LEVER_DIRECTION_NORTH,
- LeverFacing::SOUTH()->id() => StringValues::LEVER_DIRECTION_SOUTH,
- LeverFacing::WEST()->id() => StringValues::LEVER_DIRECTION_WEST,
- LeverFacing::EAST()->id() => StringValues::LEVER_DIRECTION_EAST,
- default => throw new BlockStateSerializeException("Invalid Lever facing " . $block->getFacing()->name()),
+ ->writeString(StateNames::LEVER_DIRECTION, match($block->getFacing()){
+ LeverFacing::DOWN_AXIS_Z => StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH,
+ LeverFacing::DOWN_AXIS_X => StringValues::LEVER_DIRECTION_DOWN_EAST_WEST,
+ LeverFacing::UP_AXIS_Z => StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH,
+ LeverFacing::UP_AXIS_X => StringValues::LEVER_DIRECTION_UP_EAST_WEST,
+ LeverFacing::NORTH => StringValues::LEVER_DIRECTION_NORTH,
+ LeverFacing::SOUTH => StringValues::LEVER_DIRECTION_SOUTH,
+ LeverFacing::WEST => StringValues::LEVER_DIRECTION_WEST,
+ LeverFacing::EAST => StringValues::LEVER_DIRECTION_EAST,
});
});
$this->map(Blocks::LIGHT(), function(Light $block) : Writer{
- return Writer::create(Ids::LIGHT_BLOCK)
- ->writeInt(StateNames::BLOCK_LIGHT_LEVEL, $block->getLightLevel());
+ return Writer::create(match($block->getLightLevel()){
+ 0 => Ids::LIGHT_BLOCK_0,
+ 1 => Ids::LIGHT_BLOCK_1,
+ 2 => Ids::LIGHT_BLOCK_2,
+ 3 => Ids::LIGHT_BLOCK_3,
+ 4 => Ids::LIGHT_BLOCK_4,
+ 5 => Ids::LIGHT_BLOCK_5,
+ 6 => Ids::LIGHT_BLOCK_6,
+ 7 => Ids::LIGHT_BLOCK_7,
+ 8 => Ids::LIGHT_BLOCK_8,
+ 9 => Ids::LIGHT_BLOCK_9,
+ 10 => Ids::LIGHT_BLOCK_10,
+ 11 => Ids::LIGHT_BLOCK_11,
+ 12 => Ids::LIGHT_BLOCK_12,
+ 13 => Ids::LIGHT_BLOCK_13,
+ 14 => Ids::LIGHT_BLOCK_14,
+ 15 => Ids::LIGHT_BLOCK_15,
+ default => throw new BlockStateSerializeException("Invalid light level " . $block->getLightLevel()),
+ });
});
$this->map(Blocks::LIGHTNING_ROD(), function(LightningRod $block) : Writer{
return Writer::create(Ids::LIGHTNING_ROD)
->writeFacingDirection($block->getFacing());
});
- $this->map(Blocks::LILAC(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_SYRINGA, Writer::create(Ids::DOUBLE_PLANT)));
- $this->map(Blocks::LILY_OF_THE_VALLEY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_LILY_OF_THE_VALLEY));
+ $this->map(Blocks::LILAC(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::LILAC)));
$this->map(Blocks::LIT_PUMPKIN(), function(LitPumpkin $block) : Writer{
return Writer::create(Ids::LIT_PUMPKIN)
- ->writeLegacyHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::LOOM(), function(Loom $block) : Writer{
return Writer::create(Ids::LOOM)
->writeLegacyHorizontalFacing($block->getFacing());
});
- $this->map(Blocks::MANGROVE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::MANGROVE_BUTTON)));
- $this->map(Blocks::MANGROVE_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::MANGROVE_DOOR)));
- $this->map(Blocks::MANGROVE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::MANGROVE_FENCE_GATE)));
- $this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES)));
- $this->map(Blocks::MANGROVE_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG));
- $this->map(Blocks::MANGROVE_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::MANGROVE_PRESSURE_PLATE)));
- $this->map(Blocks::MANGROVE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::MANGROVE_STANDING_SIGN)));
- $this->mapSlab(Blocks::MANGROVE_SLAB(), Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB);
- $this->mapStairs(Blocks::MANGROVE_STAIRS(), Ids::MANGROVE_STAIRS);
- $this->map(Blocks::MANGROVE_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::MANGROVE_TRAPDOOR)));
- $this->map(Blocks::MANGROVE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::MANGROVE_WALL_SIGN)));
- $this->map(Blocks::MANGROVE_WOOD(), function(Wood $block) : Writer{
- //we can't use the standard method for this because mangrove_wood has a useless property attached to it
- if(!$block->isStripped()){
- return Writer::create(Ids::MANGROVE_WOOD)
- ->writePillarAxis($block->getAxis())
- ->writeBool(StateNames::STRIPPED_BIT, false); //this is useless, but it has to be written
- }else{
- return Writer::create(Ids::STRIPPED_MANGROVE_WOOD)
- ->writePillarAxis($block->getAxis());
- }
- });
- $this->map(Blocks::MATERIAL_REDUCER(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_MATERIAL_REDUCER, new Writer(Ids::CHEMISTRY_TABLE)));
+ $this->map(Blocks::MATERIAL_REDUCER(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::MATERIAL_REDUCER)));
$this->map(Blocks::MELON_STEM(), fn(MelonStem $block) => Helper::encodeStem($block, new Writer(Ids::MELON_STEM)));
- $this->map(Blocks::MOB_HEAD(), function(MobHead $block) : Writer{
- return Writer::create(Ids::SKULL)
- ->writeFacingWithoutDown($block->getFacing());
- });
- $this->map(Blocks::MOSSY_COBBLESTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_MOSSY_COBBLESTONE));
+ $this->mapSlab(Blocks::MOSSY_COBBLESTONE_SLAB(), Ids::MOSSY_COBBLESTONE_SLAB, Ids::MOSSY_COBBLESTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::MOSSY_COBBLESTONE_STAIRS(), Ids::MOSSY_COBBLESTONE_STAIRS);
- $this->map(Blocks::MOSSY_COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_MOSSY_COBBLESTONE));
- $this->map(Blocks::MOSSY_STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_MOSSY));
- $this->map(Blocks::MOSSY_STONE_BRICK_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_MOSSY_STONE_BRICK));
+ $this->map(Blocks::MOSSY_COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::MOSSY_COBBLESTONE_WALL)));
+ $this->mapSlab(Blocks::MOSSY_STONE_BRICK_SLAB(), Ids::MOSSY_STONE_BRICK_SLAB, Ids::MOSSY_STONE_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::MOSSY_STONE_BRICK_STAIRS(), Ids::MOSSY_STONE_BRICK_STAIRS);
- $this->map(Blocks::MOSSY_STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_MOSSY_STONE_BRICK));
+ $this->map(Blocks::MOSSY_STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::MOSSY_STONE_BRICK_WALL)));
$this->mapSlab(Blocks::MUD_BRICK_SLAB(), Ids::MUD_BRICK_SLAB, Ids::MUD_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::MUD_BRICK_STAIRS(), Ids::MUD_BRICK_STAIRS);
$this->map(Blocks::MUD_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::MUD_BRICK_WALL)));
$this->map(Blocks::MUDDY_MANGROVE_ROOTS(), fn(SimplePillar $block) => Writer::create(Ids::MUDDY_MANGROVE_ROOTS)
->writePillarAxis($block->getAxis()));
- $this->map(Blocks::MUSHROOM_STEM(), fn() => Writer::create(Ids::BROWN_MUSHROOM_BLOCK)
+ $this->map(Blocks::MUSHROOM_STEM(), fn() => Writer::create(Ids::MUSHROOM_STEM)
->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_STEM));
- $this->map(Blocks::NETHER_BRICK_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_NETHER_BRICK));
+ $this->mapSlab(Blocks::NETHER_BRICK_SLAB(), Ids::NETHER_BRICK_SLAB, Ids::NETHER_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::NETHER_BRICK_STAIRS(), Ids::NETHER_BRICK_STAIRS);
- $this->map(Blocks::NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_NETHER_BRICK));
+ $this->map(Blocks::NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::NETHER_BRICK_WALL)));
$this->map(Blocks::NETHER_PORTAL(), function(NetherPortal $block) : Writer{
return Writer::create(Ids::PORTAL)
->writeString(StateNames::PORTAL_AXIS, match($block->getAxis()){
@@ -1186,26 +1615,27 @@ private function registerSerializers() : void{
return Writer::create(Ids::NETHER_WART)
->writeInt(StateNames::AGE, $block->getAge());
});
- $this->map(Blocks::OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::WOODEN_BUTTON)));
- $this->map(Blocks::OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::WOODEN_DOOR)));
- $this->map(Blocks::OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::FENCE_GATE)));
- $this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_OAK));
- $this->map(Blocks::OAK_PLANKS(), fn() => Writer::create(Ids::PLANKS)
- ->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_OAK));
- $this->map(Blocks::OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WOODEN_PRESSURE_PLATE)));
- $this->map(Blocks::OAK_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_OAK));
- $this->map(Blocks::OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::STANDING_SIGN)));
- $this->map(Blocks::OAK_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_OAK));
- $this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS);
- $this->map(Blocks::OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::TRAPDOOR)));
- $this->map(Blocks::OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WALL_SIGN)));
- $this->map(Blocks::OAK_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
- $this->map(Blocks::ORANGE_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_ORANGE));
- $this->map(Blocks::OXEYE_DAISY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_OXEYE));
- $this->map(Blocks::PEONY(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_PAEONIA, Writer::create(Ids::DOUBLE_PLANT)));
- $this->map(Blocks::PINK_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_PINK));
- $this->map(Blocks::POLISHED_ANDESITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_ANDESITE_SMOOTH));
- $this->map(Blocks::POLISHED_ANDESITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_POLISHED_ANDESITE));
+ $this->map(Blocks::PEONY(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::PEONY)));
+ $this->map(Blocks::PINK_PETALS(), function(PinkPetals $block) : Writer{
+ return Writer::create(Ids::PINK_PETALS)
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ ->writeInt(StateNames::GROWTH, $block->getCount() - 1);
+ });
+ $this->map(Blocks::PITCHER_PLANT(), function(DoublePlant $block) : Writer{
+ return Writer::create(Ids::PITCHER_PLANT)
+ ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop());
+ });
+ $this->map(Blocks::PITCHER_CROP(), function(PitcherCrop $block) : Writer{
+ return Writer::create(Ids::PITCHER_CROP)
+ ->writeInt(StateNames::GROWTH, $block->getAge())
+ ->writeBool(StateNames::UPPER_BLOCK_BIT, false);
+ });
+ $this->map(Blocks::DOUBLE_PITCHER_CROP(), function(DoublePitcherCrop $block) : Writer{
+ return Writer::create(Ids::PITCHER_CROP)
+ ->writeInt(StateNames::GROWTH, $block->getAge() + 1 + PitcherCrop::MAX_AGE)
+ ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop());
+ });
+ $this->mapSlab(Blocks::POLISHED_ANDESITE_SLAB(), Ids::POLISHED_ANDESITE_SLAB, Ids::POLISHED_ANDESITE_DOUBLE_SLAB);
$this->mapStairs(Blocks::POLISHED_ANDESITE_STAIRS(), Ids::POLISHED_ANDESITE_STAIRS);
$this->map(Blocks::POLISHED_BASALT(), function(SimplePillar $block) : Writer{
return Writer::create(Ids::POLISHED_BASALT)
@@ -1222,49 +1652,40 @@ private function registerSerializers() : void{
$this->mapSlab(Blocks::POLISHED_DEEPSLATE_SLAB(), Ids::POLISHED_DEEPSLATE_SLAB, Ids::POLISHED_DEEPSLATE_DOUBLE_SLAB);
$this->mapStairs(Blocks::POLISHED_DEEPSLATE_STAIRS(), Ids::POLISHED_DEEPSLATE_STAIRS);
$this->map(Blocks::POLISHED_DEEPSLATE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_DEEPSLATE_WALL)));
- $this->map(Blocks::POLISHED_DIORITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_DIORITE_SMOOTH));
- $this->map(Blocks::POLISHED_DIORITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_POLISHED_DIORITE));
+ $this->mapSlab(Blocks::POLISHED_DIORITE_SLAB(), Ids::POLISHED_DIORITE_SLAB, Ids::POLISHED_DIORITE_DOUBLE_SLAB);
$this->mapStairs(Blocks::POLISHED_DIORITE_STAIRS(), Ids::POLISHED_DIORITE_STAIRS);
- $this->map(Blocks::POLISHED_GRANITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_GRANITE_SMOOTH));
- $this->map(Blocks::POLISHED_GRANITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_POLISHED_GRANITE));
+ $this->mapSlab(Blocks::POLISHED_GRANITE_SLAB(), Ids::POLISHED_GRANITE_SLAB, Ids::POLISHED_GRANITE_DOUBLE_SLAB);
$this->mapStairs(Blocks::POLISHED_GRANITE_STAIRS(), Ids::POLISHED_GRANITE_STAIRS);
- $this->map(Blocks::POPPY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_POPPY));
+ $this->mapSlab(Blocks::POLISHED_TUFF_SLAB(), Ids::POLISHED_TUFF_SLAB, Ids::POLISHED_TUFF_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::POLISHED_TUFF_STAIRS(), Ids::POLISHED_TUFF_STAIRS);
+ $this->map(Blocks::POLISHED_TUFF_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_TUFF_WALL)));
$this->map(Blocks::POTATOES(), fn(Potato $block) => Helper::encodeCrops($block, new Writer(Ids::POTATOES)));
$this->map(Blocks::POWERED_RAIL(), function(PoweredRail $block) : Writer{
return Writer::create(Ids::GOLDEN_RAIL)
->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered())
->writeInt(StateNames::RAIL_DIRECTION, $block->getShape());
});
- $this->map(Blocks::PRISMARINE(), fn() => Writer::create(Ids::PRISMARINE)
- ->writeString(StateNames::PRISMARINE_BLOCK_TYPE, StringValues::PRISMARINE_BLOCK_TYPE_DEFAULT));
- $this->map(Blocks::PRISMARINE_BRICKS(), fn() => Writer::create(Ids::PRISMARINE)
- ->writeString(StateNames::PRISMARINE_BLOCK_TYPE, StringValues::PRISMARINE_BLOCK_TYPE_BRICKS));
- $this->map(Blocks::PRISMARINE_BRICKS_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_PRISMARINE_BRICK));
+ $this->mapSlab(Blocks::PRISMARINE_BRICKS_SLAB(), Ids::PRISMARINE_BRICK_SLAB, Ids::PRISMARINE_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::PRISMARINE_BRICKS_STAIRS(), Ids::PRISMARINE_BRICKS_STAIRS);
- $this->map(Blocks::PRISMARINE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_PRISMARINE_ROUGH));
+ $this->mapSlab(Blocks::PRISMARINE_SLAB(), Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB);
$this->mapStairs(Blocks::PRISMARINE_STAIRS(), Ids::PRISMARINE_STAIRS);
- $this->map(Blocks::PRISMARINE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_PRISMARINE));
+ $this->map(Blocks::PRISMARINE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::PRISMARINE_WALL)));
$this->map(Blocks::PUMPKIN(), function() : Writer{
return Writer::create(Ids::PUMPKIN)
- ->writeLegacyHorizontalFacing(Facing::SOUTH); //no longer used
+ ->writeCardinalHorizontalFacing(Facing::SOUTH); //no longer used
});
$this->map(Blocks::PUMPKIN_STEM(), fn(PumpkinStem $block) => Helper::encodeStem($block, new Writer(Ids::PUMPKIN_STEM)));
- $this->map(Blocks::PURPLE_TORCH(), fn(Torch $block) => Helper::encodeColoredTorch($block, true, Writer::create(Ids::COLORED_TORCH_BP)));
- $this->map(Blocks::PURPUR(), function() : Writer{
- return Writer::create(Ids::PURPUR_BLOCK)
- ->writeString(StateNames::CHISEL_TYPE, StringValues::CHISEL_TYPE_DEFAULT)
- ->writePillarAxis(Axis::Y); //useless, but MCPE wants it
- });
+ $this->map(Blocks::PURPUR(), fn() => Writer::create(Ids::PURPUR_BLOCK)->writePillarAxis(Axis::Y));
+ $this->map(Blocks::PURPLE_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_PURPLE)));
$this->map(Blocks::PURPUR_PILLAR(), function(SimplePillar $block) : Writer{
- return Writer::create(Ids::PURPUR_BLOCK)
- ->writeString(StateNames::CHISEL_TYPE, StringValues::CHISEL_TYPE_LINES)
+ return Writer::create(Ids::PURPUR_PILLAR)
->writePillarAxis($block->getAxis());
});
- $this->map(Blocks::PURPUR_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_PURPUR));
+ $this->mapSlab(Blocks::PURPUR_SLAB(), Ids::PURPUR_SLAB, Ids::PURPUR_DOUBLE_SLAB);
$this->mapStairs(Blocks::PURPUR_STAIRS(), Ids::PURPUR_STAIRS);
- $this->map(Blocks::QUARTZ(), fn() => Helper::encodeQuartz(StringValues::CHISEL_TYPE_DEFAULT, Axis::Y));
- $this->map(Blocks::QUARTZ_PILLAR(), fn(SimplePillar $block) => Helper::encodeQuartz(StringValues::CHISEL_TYPE_LINES, $block->getAxis()));
- $this->map(Blocks::QUARTZ_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_QUARTZ));
+ $this->map(Blocks::QUARTZ(), fn() => Helper::encodeQuartz(Axis::Y, Writer::create(Ids::QUARTZ_BLOCK)));
+ $this->map(Blocks::QUARTZ_PILLAR(), fn(SimplePillar $block) => Helper::encodeQuartz($block->getAxis(), Writer::create(Ids::QUARTZ_PILLAR)));
+ $this->mapSlab(Blocks::QUARTZ_SLAB(), Ids::QUARTZ_SLAB, Ids::QUARTZ_DOUBLE_SLAB);
$this->mapStairs(Blocks::QUARTZ_STAIRS(), Ids::QUARTZ_STAIRS);
$this->map(Blocks::RAIL(), function(Rail $block) : Writer{
return Writer::create(Ids::RAIL)
@@ -1274,13 +1695,13 @@ private function registerSerializers() : void{
return Writer::create($block->isPowered() ? Ids::POWERED_COMPARATOR : Ids::UNPOWERED_COMPARATOR)
->writeBool(StateNames::OUTPUT_LIT_BIT, $block->isPowered())
->writeBool(StateNames::OUTPUT_SUBTRACT_BIT, $block->isSubtractMode())
- ->writeLegacyHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::REDSTONE_LAMP(), fn(RedstoneLamp $block) => new Writer($block->isPowered() ? Ids::LIT_REDSTONE_LAMP : Ids::REDSTONE_LAMP));
$this->map(Blocks::REDSTONE_ORE(), fn(RedstoneOre $block) => new Writer($block->isLit() ? Ids::LIT_REDSTONE_ORE : Ids::REDSTONE_ORE));
$this->map(Blocks::REDSTONE_REPEATER(), function(RedstoneRepeater $block) : Writer{
return Writer::create($block->isPowered() ? Ids::POWERED_REPEATER : Ids::UNPOWERED_REPEATER)
- ->writeLegacyHorizontalFacing($block->getFacing())
+ ->writeCardinalHorizontalFacing($block->getFacing())
->writeInt(StateNames::REPEATER_DELAY, $block->getDelay() - 1);
});
$this->map(Blocks::REDSTONE_TORCH(), function(RedstoneTorch $block) : Writer{
@@ -1292,45 +1713,46 @@ private function registerSerializers() : void{
->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength());
});
$this->map(Blocks::RED_MUSHROOM_BLOCK(), fn(RedMushroomBlock $block) => Helper::encodeMushroomBlock($block, new Writer(Ids::RED_MUSHROOM_BLOCK)));
- $this->map(Blocks::RED_NETHER_BRICK_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_RED_NETHER_BRICK));
+ $this->mapSlab(Blocks::RED_NETHER_BRICK_SLAB(), Ids::RED_NETHER_BRICK_SLAB, Ids::RED_NETHER_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::RED_NETHER_BRICK_STAIRS(), Ids::RED_NETHER_BRICK_STAIRS);
- $this->map(Blocks::RED_NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_RED_NETHER_BRICK));
- $this->map(Blocks::RED_SAND(), fn() => Writer::create(Ids::SAND)
- ->writeString(StateNames::SAND_TYPE, StringValues::SAND_TYPE_RED));
- $this->map(Blocks::RED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::RED_SANDSTONE, StringValues::SAND_STONE_TYPE_DEFAULT));
- $this->map(Blocks::RED_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_RED_SANDSTONE));
+ $this->map(Blocks::RED_NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_NETHER_BRICK_WALL)));
+ $this->mapSlab(Blocks::RED_SANDSTONE_SLAB(), Ids::RED_SANDSTONE_SLAB, Ids::RED_SANDSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::RED_SANDSTONE_STAIRS(), Ids::RED_SANDSTONE_STAIRS);
- $this->map(Blocks::RED_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_RED_SANDSTONE));
- $this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeColoredTorch($block, false, Writer::create(Ids::COLORED_TORCH_RG)));
- $this->map(Blocks::RED_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_RED));
- $this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_ROSE, Writer::create(Ids::DOUBLE_PLANT)));
- $this->map(Blocks::SAND(), fn() => Writer::create(Ids::SAND)
- ->writeString(StateNames::SAND_TYPE, StringValues::SAND_TYPE_NORMAL));
- $this->map(Blocks::SANDSTONE(), fn() => Helper::encodeSandstone(Ids::SANDSTONE, StringValues::SAND_STONE_TYPE_DEFAULT));
- $this->map(Blocks::SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_SANDSTONE));
+ $this->map(Blocks::RED_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_SANDSTONE_WALL)));
+ $this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_RED)));
+ $this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH)));
+ $this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS);
- $this->map(Blocks::SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_SANDSTONE));
+ $this->map(Blocks::SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::SANDSTONE_WALL)));
$this->map(Blocks::SEA_PICKLE(), function(SeaPickle $block) : Writer{
return Writer::create(Ids::SEA_PICKLE)
->writeBool(StateNames::DEAD_BIT, !$block->isUnderwater())
->writeInt(StateNames::CLUSTER_COUNT, $block->getCount() - 1);
});
+ $this->map(Blocks::SMALL_DRIPLEAF(), function(SmallDripleaf $block) : Writer{
+ return Writer::create(Ids::SMALL_DRIPLEAF_BLOCK)
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ ->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop());
+ });
$this->map(Blocks::SMOKER(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::SMOKER, Ids::LIT_SMOKER));
- $this->map(Blocks::SMOOTH_QUARTZ(), fn() => Helper::encodeQuartz(StringValues::CHISEL_TYPE_SMOOTH, Axis::Y));
- $this->map(Blocks::SMOOTH_QUARTZ_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_SMOOTH_QUARTZ));
+ $this->map(Blocks::SMOOTH_QUARTZ(), fn() => Helper::encodeQuartz(Axis::Y, Writer::create(Ids::SMOOTH_QUARTZ)));
+ $this->mapSlab(Blocks::SMOOTH_QUARTZ_SLAB(), Ids::SMOOTH_QUARTZ_SLAB, Ids::SMOOTH_QUARTZ_DOUBLE_SLAB);
$this->mapStairs(Blocks::SMOOTH_QUARTZ_STAIRS(), Ids::SMOOTH_QUARTZ_STAIRS);
- $this->map(Blocks::SMOOTH_RED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::RED_SANDSTONE, StringValues::SAND_STONE_TYPE_SMOOTH));
- $this->map(Blocks::SMOOTH_RED_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_SMOOTH_RED_SANDSTONE));
+ $this->mapSlab(Blocks::SMOOTH_RED_SANDSTONE_SLAB(), Ids::SMOOTH_RED_SANDSTONE_SLAB, Ids::SMOOTH_RED_SANDSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::SMOOTH_RED_SANDSTONE_STAIRS(), Ids::SMOOTH_RED_SANDSTONE_STAIRS);
- $this->map(Blocks::SMOOTH_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::SANDSTONE, StringValues::SAND_STONE_TYPE_SMOOTH));
- $this->map(Blocks::SMOOTH_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_SMOOTH_SANDSTONE));
+ $this->mapSlab(Blocks::SMOOTH_SANDSTONE_SLAB(), Ids::SMOOTH_SANDSTONE_SLAB, Ids::SMOOTH_SANDSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::SMOOTH_SANDSTONE_STAIRS(), Ids::SMOOTH_SANDSTONE_STAIRS);
- $this->map(Blocks::SMOOTH_STONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_SMOOTH_STONE));
+ $this->mapSlab(Blocks::SMOOTH_STONE_SLAB(), Ids::SMOOTH_STONE_SLAB, Ids::SMOOTH_STONE_DOUBLE_SLAB);
$this->map(Blocks::SNOW_LAYER(), function(SnowLayer $block) : Writer{
return Writer::create(Ids::SNOW_LAYER)
->writeBool(StateNames::COVERED_BIT, false)
->writeInt(StateNames::HEIGHT, $block->getLayers() - 1);
});
+ $this->map(Blocks::SOUL_CAMPFIRE(), function(SoulCampfire $block) : Writer{
+ return Writer::create(Ids::SOUL_CAMPFIRE)
+ ->writeCardinalHorizontalFacing($block->getFacing())
+ ->writeBool(StateNames::EXTINGUISHED, !$block->isLit());
+ });
$this->map(Blocks::SOUL_FIRE(), function() : Writer{
return Writer::create(Ids::SOUL_FIRE)
->writeInt(StateNames::AGE, 0); //useless for soul fire, we don't track it
@@ -1343,78 +1765,39 @@ private function registerSerializers() : void{
return Writer::create(Ids::SOUL_TORCH)
->writeTorchFacing($block->getFacing());
});
- $this->map(Blocks::SPONGE(), function(Sponge $block) : Writer{
- return Writer::create(Ids::SPONGE)
- ->writeString(StateNames::SPONGE_TYPE, $block->isWet() ? StringValues::SPONGE_TYPE_WET : StringValues::SPONGE_TYPE_DRY);
- });
- $this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON)));
- $this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR)));
- $this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE)));
- $this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_SPRUCE));
- $this->map(Blocks::SPRUCE_PLANKS(), fn() => Writer::create(Ids::PLANKS)
- ->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_SPRUCE));
- $this->map(Blocks::SPRUCE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::SPRUCE_PRESSURE_PLATE)));
- $this->map(Blocks::SPRUCE_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_SPRUCE));
- $this->map(Blocks::SPRUCE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::SPRUCE_STANDING_SIGN)));
- $this->map(Blocks::SPRUCE_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_SPRUCE));
- $this->mapStairs(Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS);
- $this->map(Blocks::SPRUCE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::SPRUCE_TRAPDOOR)));
- $this->map(Blocks::SPRUCE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::SPRUCE_WALL_SIGN)));
- $this->map(Blocks::SPRUCE_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
- $this->map(Blocks::STAINED_CLAY(), function(StainedHardenedClay $block) : Writer{
- return Writer::create(Ids::STAINED_HARDENED_CLAY)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::STAINED_GLASS(), function(StainedGlass $block) : Writer{
- return Writer::create(Ids::STAINED_GLASS)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::STAINED_GLASS_PANE(), function(StainedGlassPane $block) : Writer{
- return Writer::create(Ids::STAINED_GLASS_PANE)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::STAINED_HARDENED_GLASS(), function(StainedHardenedGlass $block) : Writer{
- return Writer::create(Ids::HARD_STAINED_GLASS)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::STAINED_HARDENED_GLASS_PANE(), function(StainedHardenedGlassPane $block) : Writer{
- return Writer::create(Ids::HARD_STAINED_GLASS_PANE)
- ->writeColor($block->getColor());
- });
- $this->map(Blocks::STONE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_STONE));
+ $this->map(Blocks::SPONGE(), fn(Sponge $block) => Writer::create($block->isWet() ? Ids::WET_SPONGE : Ids::SPONGE));
$this->map(Blocks::STONECUTTER(), fn(Stonecutter $block) => Writer::create(Ids::STONECUTTER_BLOCK)
- ->writeHorizontalFacing($block->getFacing()));
- $this->map(Blocks::STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_DEFAULT));
- $this->map(Blocks::STONE_BRICK_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab1($block, StringValues::STONE_SLAB_TYPE_STONE_BRICK));
+ ->writeCardinalHorizontalFacing($block->getFacing()));
+ $this->mapSlab(Blocks::STONE_BRICK_SLAB(), Ids::STONE_BRICK_SLAB, Ids::STONE_BRICK_DOUBLE_SLAB);
$this->mapStairs(Blocks::STONE_BRICK_STAIRS(), Ids::STONE_BRICK_STAIRS);
- $this->map(Blocks::STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_STONE_BRICK));
+ $this->map(Blocks::STONE_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::STONE_BRICK_WALL)));
$this->map(Blocks::STONE_BUTTON(), fn(StoneButton $block) => Helper::encodeButton($block, new Writer(Ids::STONE_BUTTON)));
$this->map(Blocks::STONE_PRESSURE_PLATE(), fn(StonePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::STONE_PRESSURE_PLATE)));
- $this->map(Blocks::STONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_STONE));
+ $this->mapSlab(Blocks::STONE_SLAB(), Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::STONE_STAIRS(), Ids::NORMAL_STONE_STAIRS);
$this->map(Blocks::SUGARCANE(), function(Sugarcane $block) : Writer{
return Writer::create(Ids::REEDS)
->writeInt(StateNames::AGE, $block->getAge());
});
- $this->map(Blocks::SUNFLOWER(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_SUNFLOWER, Writer::create(Ids::DOUBLE_PLANT)));
+ $this->map(Blocks::SUNFLOWER(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::SUNFLOWER)));
$this->map(Blocks::SWEET_BERRY_BUSH(), function(SweetBerryBush $block) : Writer{
return Writer::create(Ids::SWEET_BERRY_BUSH)
->writeInt(StateNames::GROWTH, $block->getAge());
});
- $this->map(Blocks::TALL_GRASS(), fn() => Writer::create(Ids::TALLGRASS)
- ->writeString(StateNames::TALL_GRASS_TYPE, StringValues::TALL_GRASS_TYPE_TALL));
- $this->map(Blocks::TNT(), function(TNT $block) : Writer{
- return Writer::create(Ids::TNT)
- ->writeBool(StateNames::ALLOW_UNDERWATER_BIT, $block->worksUnderwater())
- ->writeBool(StateNames::EXPLODE_BIT, $block->isUnstable());
- });
+ $this->map(Blocks::TNT(), fn(TNT $block) => Writer::create($block->worksUnderwater() ? Ids::UNDERWATER_TNT : Ids::TNT)
+ ->writeBool(StateNames::EXPLODE_BIT, $block->isUnstable())
+ );
$this->map(Blocks::TORCH(), function(Torch $block) : Writer{
return Writer::create(Ids::TORCH)
->writeTorchFacing($block->getFacing());
});
+ $this->map(Blocks::TORCHFLOWER_CROP(), function(TorchflowerCrop $block){
+ return Writer::create(Ids::TORCHFLOWER_CROP)
+ ->writeInt(StateNames::GROWTH, $block->isReady() ? 1 : 0);
+ });
$this->map(Blocks::TRAPPED_CHEST(), function(TrappedChest $block) : Writer{
return Writer::create(Ids::TRAPPED_CHEST)
- ->writeHorizontalFacing($block->getFacing());
+ ->writeCardinalHorizontalFacing($block->getFacing());
});
$this->map(Blocks::TRIPWIRE(), function(Tripwire $block) : Writer{
return Writer::create(Ids::TRIP_WIRE)
@@ -1429,6 +1812,12 @@ private function registerSerializers() : void{
->writeBool(StateNames::POWERED_BIT, $block->isPowered())
->writeLegacyHorizontalFacing($block->getFacing());
});
+ $this->mapSlab(Blocks::TUFF_BRICK_SLAB(), Ids::TUFF_BRICK_SLAB, Ids::TUFF_BRICK_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::TUFF_BRICK_STAIRS(), Ids::TUFF_BRICK_STAIRS);
+ $this->map(Blocks::TUFF_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::TUFF_BRICK_WALL)));
+ $this->mapSlab(Blocks::TUFF_SLAB(), Ids::TUFF_SLAB, Ids::TUFF_DOUBLE_SLAB);
+ $this->mapStairs(Blocks::TUFF_STAIRS(), Ids::TUFF_STAIRS);
+ $this->map(Blocks::TUFF_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::TUFF_WALL)));
$this->map(Blocks::TWISTING_VINES(), function(NetherVines $block) : Writer{
return Writer::create(Ids::TWISTING_VINES)
->writeInt(StateNames::TWISTING_VINES_AGE, $block->getAge());
@@ -1445,29 +1834,6 @@ private function registerSerializers() : void{
return Writer::create(Ids::WALL_BANNER)
->writeHorizontalFacing($block->getFacing());
});
- $this->map(Blocks::WALL_CORAL_FAN(), function(WallCoralFan $block) : Writer{
- $coralType = $block->getCoralType();
- return Writer::create(match($coralType->id()){
- CoralType::TUBE()->id(), CoralType::BRAIN()->id() => Ids::CORAL_FAN_HANG,
- CoralType::BUBBLE()->id(), CoralType::FIRE()->id() => Ids::CORAL_FAN_HANG2,
- CoralType::HORN()->id() => Ids::CORAL_FAN_HANG3,
- default => throw new BlockStateSerializeException("Invalid Coral type " . $coralType->name()),
- })
- ->writeBool(StateNames::CORAL_HANG_TYPE_BIT, $coralType->equals(CoralType::BRAIN()) || $coralType->equals(CoralType::FIRE()))
- ->writeBool(StateNames::DEAD_BIT, $block->isDead())
- ->writeCoralFacing($block->getFacing());
- });
- $this->map(Blocks::WARPED_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::WARPED_BUTTON)));
- $this->map(Blocks::WARPED_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::WARPED_DOOR)));
- $this->map(Blocks::WARPED_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::WARPED_FENCE_GATE)));
- $this->map(Blocks::WARPED_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE));
- $this->map(Blocks::WARPED_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WARPED_PRESSURE_PLATE)));
- $this->map(Blocks::WARPED_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::WARPED_STANDING_SIGN)));
- $this->mapSlab(Blocks::WARPED_SLAB(), Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB);
- $this->mapStairs(Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS);
- $this->map(Blocks::WARPED_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM));
- $this->map(Blocks::WARPED_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::WARPED_TRAPDOOR)));
- $this->map(Blocks::WARPED_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WARPED_WALL_SIGN)));
$this->map(Blocks::WATER(), fn(Water $block) => Helper::encodeLiquid($block, Ids::WATER, Ids::FLOWING_WATER));
$this->map(Blocks::WEEPING_VINES(), function(NetherVines $block) : Writer{
return Writer::create(Ids::WEEPING_VINES)
@@ -1482,6 +1848,5 @@ private function registerSerializers() : void{
->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength());
});
$this->map(Blocks::WHEAT(), fn(Wheat $block) => Helper::encodeCrops($block, new Writer(Ids::WHEAT)));
- $this->map(Blocks::WHITE_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_WHITE));
}
}
diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php
index fab50717c44..fda0455aa38 100644
--- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php
+++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php
@@ -26,12 +26,10 @@
use pocketmine\block\Block;
use pocketmine\block\Button;
use pocketmine\block\Candle;
-use pocketmine\block\Copper;
-use pocketmine\block\CopperSlab;
-use pocketmine\block\CopperStairs;
use pocketmine\block\Crops;
use pocketmine\block\DaylightSensor;
use pocketmine\block\Door;
+use pocketmine\block\DoublePlant;
use pocketmine\block\FenceGate;
use pocketmine\block\FloorCoralFan;
use pocketmine\block\FloorSign;
@@ -41,15 +39,16 @@
use pocketmine\block\RedMushroomBlock;
use pocketmine\block\RedstoneComparator;
use pocketmine\block\RedstoneRepeater;
+use pocketmine\block\Sapling;
use pocketmine\block\SimplePressurePlate;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\Stem;
use pocketmine\block\Trapdoor;
+use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperOxidation;
-use pocketmine\block\VanillaBlocks;
+use pocketmine\block\utils\SlabType;
use pocketmine\block\Wall;
-use pocketmine\block\WallCoralFan;
use pocketmine\block\WallSign;
use pocketmine\block\WeightedPressurePlate;
use pocketmine\block\Wood;
@@ -57,7 +56,6 @@
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
use pocketmine\data\bedrock\block\BlockStateNames;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
-use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues;
use pocketmine\data\bedrock\MushroomBlockTypeIdMap;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
@@ -93,30 +91,30 @@ public static function decodeCrops(Crops $block, BlockStateReader $in) : Crops{
/** @throws BlockStateDeserializeException */
public static function decodeComparator(RedstoneComparator $block, BlockStateReader $in) : RedstoneComparator{
return $block
- ->setFacing($in->readLegacyHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setPowered($in->readBool(BlockStateNames::OUTPUT_LIT_BIT))
->setSubtractMode($in->readBool(BlockStateNames::OUTPUT_SUBTRACT_BIT));
}
/**
- * @phpstan-template TBlock of Copper|CopperSlab|CopperStairs
+ * @phpstan-template TBlock of CopperMaterial
*
* @phpstan-param TBlock $block
* @phpstan-return TBlock
*/
- public static function decodeCopper(Copper|CopperSlab|CopperStairs $block, CopperOxidation $oxidation) : Copper|CopperSlab|CopperStairs{
+ public static function decodeCopper(CopperMaterial $block, CopperOxidation $oxidation) : CopperMaterial{
$block->setOxidation($oxidation);
$block->setWaxed(false);
return $block;
}
/**
- * @phpstan-template TBlock of Copper|CopperSlab|CopperStairs
+ * @phpstan-template TBlock of CopperMaterial
*
* @phpstan-param TBlock $block
* @phpstan-return TBlock
*/
- public static function decodeWaxedCopper(Copper|CopperSlab|CopperStairs $block, CopperOxidation $oxidation) : Copper|CopperSlab|CopperStairs{
+ public static function decodeWaxedCopper(CopperMaterial $block, CopperOxidation $oxidation) : CopperMaterial{
$block->setOxidation($oxidation);
$block->setWaxed(true);
return $block;
@@ -138,6 +136,12 @@ public static function decodeDoor(Door $block, BlockStateReader $in) : Door{
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
}
+ /** @throws BlockStateDeserializeException */
+ public static function decodeDoublePlant(DoublePlant $block, BlockStateReader $in) : DoublePlant{
+ return $block
+ ->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT));
+ }
+
/** @throws BlockStateDeserializeException */
public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{
return $block
@@ -149,7 +153,6 @@ public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) :
/** @throws BlockStateDeserializeException */
public static function decodeFloorCoralFan(FloorCoralFan $block, BlockStateReader $in) : FloorCoralFan{
return $block
- ->setCoralType($in->readCoralType())
->setAxis(match($in->readBoundedInt(BlockStateNames::CORAL_FAN_DIRECTION, 0, 1)){
0 => Axis::X,
1 => Axis::Z,
@@ -204,8 +207,8 @@ public static function decodeLog(Wood $block, bool $stripped, BlockStateReader $
/** @throws BlockStateDeserializeException */
public static function decodeMushroomBlock(RedMushroomBlock $block, BlockStateReader $in) : Block{
switch($type = $in->readBoundedInt(BlockStateNames::HUGE_MUSHROOM_BITS, 0, 15)){
- case BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM: return VanillaBlocks::ALL_SIDED_MUSHROOM_STEM();
- case BlockLegacyMetadata::MUSHROOM_BLOCK_STEM: return VanillaBlocks::MUSHROOM_STEM();
+ case BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM:
+ case BlockLegacyMetadata::MUSHROOM_BLOCK_STEM: throw new BlockStateDeserializeException("This state does not exist");
default:
//invalid types get left as default
$type = MushroomBlockTypeIdMap::getInstance()->fromId($type);
@@ -216,10 +219,16 @@ public static function decodeMushroomBlock(RedMushroomBlock $block, BlockStateRe
/** @throws BlockStateDeserializeException */
public static function decodeRepeater(RedstoneRepeater $block, BlockStateReader $in) : RedstoneRepeater{
return $block
- ->setFacing($in->readLegacyHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setDelay($in->readBoundedInt(BlockStateNames::REPEATER_DELAY, 0, 3) + 1);
}
+ /** @throws BlockStateDeserializeException */
+ public static function decodeSapling(Sapling $block, BlockStateReader $in) : Sapling{
+ return $block
+ ->setReady($in->readBool(BlockStateNames::AGE_BIT));
+ }
+
/** @throws BlockStateDeserializeException */
public static function decodeSimplePressurePlate(SimplePressurePlate $block, BlockStateReader $in) : SimplePressurePlate{
//TODO: not sure what the deal is here ... seems like a mojang bug / artifact of bad implementation?
@@ -227,6 +236,17 @@ public static function decodeSimplePressurePlate(SimplePressurePlate $block, Blo
return $block->setPressed($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15) !== 0);
}
+ /** @throws BlockStateDeserializeException */
+ public static function decodeSingleSlab(Slab $block, BlockStateReader $in) : Slab{
+ return $block->setSlabType($in->readSlabPosition());
+ }
+
+ /** @throws BlockStateDeserializeException */
+ public static function decodeDoubleSlab(Slab $block, BlockStateReader $in) : Slab{
+ $in->ignored(StateNames::MC_VERTICAL_HALF);
+ return $block->setSlabType(SlabType::DOUBLE);
+ }
+
/** @throws BlockStateDeserializeException */
public static function decodeStairs(Stair $block, BlockStateReader $in) : Stair{
return $block
@@ -236,9 +256,12 @@ public static function decodeStairs(Stair $block, BlockStateReader $in) : Stair{
/** @throws BlockStateDeserializeException */
public static function decodeStem(Stem $block, BlockStateReader $in) : Stem{
- //TODO: our stems don't support facings yet (facing_direction)
- $in->todo(BlockStateNames::FACING_DIRECTION);
- return self::decodeCrops($block, $in);
+ //In PM, we use Facing::UP to indicate that the stem is not attached to a pumpkin/melon, since this makes the
+ //most intuitive sense (the stem is pointing at the sky). However, Bedrock uses the DOWN state for this, which
+ //is absurd, and I refuse to make our API similarly absurd.
+ $facing = $in->readFacingWithoutUp();
+ return self::decodeCrops($block, $in)
+ ->setFacing($facing === Facing::DOWN ? Facing::UP : $facing);
}
/** @throws BlockStateDeserializeException */
@@ -260,13 +283,6 @@ public static function decodeWall(Wall $block, BlockStateReader $in) : Wall{
return $block;
}
- /** @throws BlockStateDeserializeException */
- public static function decodeWallCoralFan(WallCoralFan $block, BlockStateReader $in) : WallCoralFan{
- return $block
- ->setDead($in->readBool(BlockStateNames::DEAD_BIT))
- ->setFacing($in->readCoralFacing());
- }
-
/** @throws BlockStateDeserializeException */
public static function decodeWallSign(WallSign $block, BlockStateReader $in) : WallSign{
return $block
@@ -277,100 +293,4 @@ public static function decodeWeightedPressurePlate(WeightedPressurePlate $block,
return $block
->setOutputSignalStrength($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15));
}
-
- /** @throws BlockStateDeserializeException */
- public static function mapLegacyWallType(BlockStateReader $in) : Wall{
- return self::decodeWall(match($type = $in->readString(BlockStateNames::WALL_BLOCK_TYPE)){
- StringValues::WALL_BLOCK_TYPE_ANDESITE => VanillaBlocks::ANDESITE_WALL(),
- StringValues::WALL_BLOCK_TYPE_BRICK => VanillaBlocks::BRICK_WALL(),
- StringValues::WALL_BLOCK_TYPE_COBBLESTONE => VanillaBlocks::COBBLESTONE_WALL(),
- StringValues::WALL_BLOCK_TYPE_DIORITE => VanillaBlocks::DIORITE_WALL(),
- StringValues::WALL_BLOCK_TYPE_END_BRICK => VanillaBlocks::END_STONE_BRICK_WALL(),
- StringValues::WALL_BLOCK_TYPE_GRANITE => VanillaBlocks::GRANITE_WALL(),
- StringValues::WALL_BLOCK_TYPE_MOSSY_COBBLESTONE => VanillaBlocks::MOSSY_COBBLESTONE_WALL(),
- StringValues::WALL_BLOCK_TYPE_MOSSY_STONE_BRICK => VanillaBlocks::MOSSY_STONE_BRICK_WALL(),
- StringValues::WALL_BLOCK_TYPE_NETHER_BRICK => VanillaBlocks::NETHER_BRICK_WALL(),
- StringValues::WALL_BLOCK_TYPE_PRISMARINE => VanillaBlocks::PRISMARINE_WALL(),
- StringValues::WALL_BLOCK_TYPE_RED_NETHER_BRICK => VanillaBlocks::RED_NETHER_BRICK_WALL(),
- StringValues::WALL_BLOCK_TYPE_RED_SANDSTONE => VanillaBlocks::RED_SANDSTONE_WALL(),
- StringValues::WALL_BLOCK_TYPE_SANDSTONE => VanillaBlocks::SANDSTONE_WALL(),
- StringValues::WALL_BLOCK_TYPE_STONE_BRICK => VanillaBlocks::STONE_BRICK_WALL(),
- default => throw $in->badValueException(BlockStateNames::WALL_BLOCK_TYPE, $type),
- }, $in);
- }
-
- /** @throws BlockStateDeserializeException */
- public static function mapStoneSlab1Type(BlockStateReader $in) : Slab{
- //* stone_slab_type (StringTag) = brick, cobblestone, nether_brick, quartz, sandstone, smooth_stone, stone_brick, wood
- return match($type = $in->readString(BlockStateNames::STONE_SLAB_TYPE)){
- StringValues::STONE_SLAB_TYPE_BRICK => VanillaBlocks::BRICK_SLAB(),
- StringValues::STONE_SLAB_TYPE_COBBLESTONE => VanillaBlocks::COBBLESTONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_NETHER_BRICK => VanillaBlocks::NETHER_BRICK_SLAB(),
- StringValues::STONE_SLAB_TYPE_QUARTZ => VanillaBlocks::QUARTZ_SLAB(),
- StringValues::STONE_SLAB_TYPE_SANDSTONE => VanillaBlocks::SANDSTONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_SMOOTH_STONE => VanillaBlocks::SMOOTH_STONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_STONE_BRICK => VanillaBlocks::STONE_BRICK_SLAB(),
- StringValues::STONE_SLAB_TYPE_WOOD => VanillaBlocks::FAKE_WOODEN_SLAB(),
- default => throw $in->badValueException(BlockStateNames::STONE_SLAB_TYPE, $type),
- };
- }
-
- /** @throws BlockStateDeserializeException */
- public static function mapStoneSlab2Type(BlockStateReader $in) : Slab{
- // * stone_slab_type_2 (StringTag) = mossy_cobblestone, prismarine_brick, prismarine_dark, prismarine_rough, purpur, red_nether_brick, red_sandstone, smooth_sandstone
- return match($type = $in->readString(BlockStateNames::STONE_SLAB_TYPE_2)){
- StringValues::STONE_SLAB_TYPE_2_MOSSY_COBBLESTONE => VanillaBlocks::MOSSY_COBBLESTONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_PRISMARINE_BRICK => VanillaBlocks::PRISMARINE_BRICKS_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_PRISMARINE_DARK => VanillaBlocks::DARK_PRISMARINE_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_PRISMARINE_ROUGH => VanillaBlocks::PRISMARINE_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_PURPUR => VanillaBlocks::PURPUR_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_RED_NETHER_BRICK => VanillaBlocks::RED_NETHER_BRICK_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_RED_SANDSTONE => VanillaBlocks::RED_SANDSTONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_2_SMOOTH_SANDSTONE => VanillaBlocks::SMOOTH_SANDSTONE_SLAB(),
- default => throw $in->badValueException(BlockStateNames::STONE_SLAB_TYPE_2, $type),
- };
- }
-
- /** @throws BlockStateDeserializeException */
- public static function mapStoneSlab3Type(BlockStateReader $in) : Slab{
- // * stone_slab_type_3 (StringTag) = andesite, diorite, end_stone_brick, granite, polished_andesite, polished_diorite, polished_granite, smooth_red_sandstone
- return match($type = $in->readString(BlockStateNames::STONE_SLAB_TYPE_3)){
- StringValues::STONE_SLAB_TYPE_3_ANDESITE => VanillaBlocks::ANDESITE_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_DIORITE => VanillaBlocks::DIORITE_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_END_STONE_BRICK => VanillaBlocks::END_STONE_BRICK_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_GRANITE => VanillaBlocks::GRANITE_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_POLISHED_ANDESITE => VanillaBlocks::POLISHED_ANDESITE_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_POLISHED_DIORITE => VanillaBlocks::POLISHED_DIORITE_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_POLISHED_GRANITE => VanillaBlocks::POLISHED_GRANITE_SLAB(),
- StringValues::STONE_SLAB_TYPE_3_SMOOTH_RED_SANDSTONE => VanillaBlocks::SMOOTH_RED_SANDSTONE_SLAB(),
- default => throw $in->badValueException(BlockStateNames::STONE_SLAB_TYPE_3, $type),
- };
- }
-
- /** @throws BlockStateDeserializeException */
- public static function mapStoneSlab4Type(BlockStateReader $in) : Slab{
- // * stone_slab_type_4 (StringTag) = cut_red_sandstone, cut_sandstone, mossy_stone_brick, smooth_quartz, stone
- return match($type = $in->readString(BlockStateNames::STONE_SLAB_TYPE_4)){
- StringValues::STONE_SLAB_TYPE_4_CUT_RED_SANDSTONE => VanillaBlocks::CUT_RED_SANDSTONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_4_CUT_SANDSTONE => VanillaBlocks::CUT_SANDSTONE_SLAB(),
- StringValues::STONE_SLAB_TYPE_4_MOSSY_STONE_BRICK => VanillaBlocks::MOSSY_STONE_BRICK_SLAB(),
- StringValues::STONE_SLAB_TYPE_4_SMOOTH_QUARTZ => VanillaBlocks::SMOOTH_QUARTZ_SLAB(),
- StringValues::STONE_SLAB_TYPE_4_STONE => VanillaBlocks::STONE_SLAB(),
- default => throw $in->badValueException(BlockStateNames::STONE_SLAB_TYPE_4, $type),
- };
- }
-
- /** @throws BlockStateDeserializeException */
- public static function mapWoodenSlabType(BlockStateReader $in) : Slab{
- // * wood_type (StringTag) = acacia, birch, dark_oak, jungle, oak, spruce
- return match($type = $in->readString(BlockStateNames::WOOD_TYPE)){
- StringValues::WOOD_TYPE_ACACIA => VanillaBlocks::ACACIA_SLAB(),
- StringValues::WOOD_TYPE_BIRCH => VanillaBlocks::BIRCH_SLAB(),
- StringValues::WOOD_TYPE_DARK_OAK => VanillaBlocks::DARK_OAK_SLAB(),
- StringValues::WOOD_TYPE_JUNGLE => VanillaBlocks::JUNGLE_SLAB(),
- StringValues::WOOD_TYPE_OAK => VanillaBlocks::OAK_SLAB(),
- StringValues::WOOD_TYPE_SPRUCE => VanillaBlocks::SPRUCE_SLAB(),
- default => throw $in->badValueException(BlockStateNames::WOOD_TYPE, $type),
- };
- }
}
diff --git a/src/data/bedrock/block/convert/BlockStateReader.php b/src/data/bedrock/block/convert/BlockStateReader.php
index 99f9c78009a..e3a02885f08 100644
--- a/src/data/bedrock/block/convert/BlockStateReader.php
+++ b/src/data/bedrock/block/convert/BlockStateReader.php
@@ -24,8 +24,6 @@
namespace pocketmine\data\bedrock\block\convert;
use pocketmine\block\utils\BellAttachmentType;
-use pocketmine\block\utils\CoralType;
-use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\WallConnectionType;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
@@ -39,20 +37,24 @@
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
-use pocketmine\utils\Utils;
+use function array_keys;
+use function count;
use function get_class;
+use function implode;
final class BlockStateReader{
/**
- * @var true[]
- * @phpstan-var array
+ * @var Tag[]
+ * @phpstan-var array
*/
- private array $usedStates = [];
+ private array $unusedStates;
public function __construct(
private BlockStateData $data
- ){}
+ ){
+ $this->unusedStates = $this->data->getStates();
+ }
public function missingOrWrongTypeException(string $name, ?Tag $tag) : BlockStateDeserializeException{
return new BlockStateDeserializeException("Property \"$name\" " . ($tag !== null ? "has unexpected type " . get_class($tag) : "is missing"));
@@ -67,7 +69,7 @@ public function badValueException(string $name, string $stringifiedValue, ?strin
/** @throws BlockStateDeserializeException */
public function readBool(string $name) : bool{
- $this->usedStates[$name] = true;
+ unset($this->unusedStates[$name]);
$tag = $this->data->getState($name);
if($tag instanceof ByteTag){
switch($tag->getValue()){
@@ -81,7 +83,7 @@ public function readBool(string $name) : bool{
/** @throws BlockStateDeserializeException */
public function readInt(string $name) : int{
- $this->usedStates[$name] = true;
+ unset($this->unusedStates[$name]);
$tag = $this->data->getState($name);
if($tag instanceof IntTag){
return $tag->getValue();
@@ -100,7 +102,7 @@ public function readBoundedInt(string $name, int $min, int $max) : int{
/** @throws BlockStateDeserializeException */
public function readString(string $name) : string{
- $this->usedStates[$name] = true;
+ unset($this->unusedStates[$name]);
//TODO: only allow a specific set of values (strings are primarily used for enums)
$tag = $this->data->getState($name);
if($tag instanceof StringTag){
@@ -135,6 +137,19 @@ public function readFacingDirection() : int{
]);
}
+ /** @throws BlockStateDeserializeException */
+ public function readBlockFace() : int{
+ return match($raw = $this->readString(BlockStateNames::MC_BLOCK_FACE)){
+ StringValues::MC_BLOCK_FACE_DOWN => Facing::DOWN,
+ StringValues::MC_BLOCK_FACE_UP => Facing::UP,
+ StringValues::MC_BLOCK_FACE_NORTH => Facing::NORTH,
+ StringValues::MC_BLOCK_FACE_SOUTH => Facing::SOUTH,
+ StringValues::MC_BLOCK_FACE_WEST => Facing::WEST,
+ StringValues::MC_BLOCK_FACE_EAST => Facing::EAST,
+ default => throw $this->badValueException(BlockStateNames::MC_BLOCK_FACE, $raw)
+ };
+ }
+
/**
* @return int[]
* @phpstan-return array
@@ -209,27 +224,17 @@ public function read5MinusHorizontalFacing() : int{
]);
}
- /** @throws BlockStateDeserializeException */
- public function readColor() : DyeColor{
- // * color (StringTag) = black, blue, brown, cyan, gray, green, light_blue, lime, magenta, orange, pink, purple, red, silver, white, yellow
- return match($color = $this->readString(BlockStateNames::COLOR)){
- StringValues::COLOR_BLACK => DyeColor::BLACK(),
- StringValues::COLOR_BLUE => DyeColor::BLUE(),
- StringValues::COLOR_BROWN => DyeColor::BROWN(),
- StringValues::COLOR_CYAN => DyeColor::CYAN(),
- StringValues::COLOR_GRAY => DyeColor::GRAY(),
- StringValues::COLOR_GREEN => DyeColor::GREEN(),
- StringValues::COLOR_LIGHT_BLUE => DyeColor::LIGHT_BLUE(),
- StringValues::COLOR_LIME => DyeColor::LIME(),
- StringValues::COLOR_MAGENTA => DyeColor::MAGENTA(),
- StringValues::COLOR_ORANGE => DyeColor::ORANGE(),
- StringValues::COLOR_PINK => DyeColor::PINK(),
- StringValues::COLOR_PURPLE => DyeColor::PURPLE(),
- StringValues::COLOR_RED => DyeColor::RED(),
- StringValues::COLOR_SILVER => DyeColor::LIGHT_GRAY(),
- StringValues::COLOR_WHITE => DyeColor::WHITE(),
- StringValues::COLOR_YELLOW => DyeColor::YELLOW(),
- default => throw $this->badValueException(BlockStateNames::COLOR, $color),
+ /**
+ * Used by pumpkins as of 1.20.0.23 beta
+ * @throws BlockStateDeserializeException
+ */
+ public function readCardinalHorizontalFacing() : int{
+ return match($raw = $this->readString(BlockStateNames::MC_CARDINAL_DIRECTION)){
+ StringValues::MC_CARDINAL_DIRECTION_NORTH => Facing::NORTH,
+ StringValues::MC_CARDINAL_DIRECTION_SOUTH => Facing::SOUTH,
+ StringValues::MC_CARDINAL_DIRECTION_WEST => Facing::WEST,
+ StringValues::MC_CARDINAL_DIRECTION_EAST => Facing::EAST,
+ default => throw $this->badValueException(BlockStateNames::MC_CARDINAL_DIRECTION, $raw)
};
}
@@ -279,7 +284,11 @@ public function readPillarAxis() : int{
/** @throws BlockStateDeserializeException */
public function readSlabPosition() : SlabType{
- return $this->readBool(BlockStateNames::TOP_SLOT_BIT) ? SlabType::TOP() : SlabType::BOTTOM();
+ return match($rawValue = $this->readString(BlockStateNames::MC_VERTICAL_HALF)){
+ StringValues::MC_VERTICAL_HALF_BOTTOM => SlabType::BOTTOM,
+ StringValues::MC_VERTICAL_HALF_TOP => SlabType::TOP,
+ default => throw $this->badValueException(BlockStateNames::MC_VERTICAL_HALF, $rawValue, "Invalid slab position"),
+ };
}
/**
@@ -299,25 +308,13 @@ public function readTorchFacing() : int{
};
}
- /** @throws BlockStateDeserializeException */
- public function readCoralType() : CoralType{
- return match($type = $this->readString(BlockStateNames::CORAL_COLOR)){
- StringValues::CORAL_COLOR_BLUE => CoralType::TUBE(),
- StringValues::CORAL_COLOR_PINK => CoralType::BRAIN(),
- StringValues::CORAL_COLOR_PURPLE => CoralType::BUBBLE(),
- StringValues::CORAL_COLOR_RED => CoralType::FIRE(),
- StringValues::CORAL_COLOR_YELLOW => CoralType::HORN(),
- default => throw $this->badValueException(BlockStateNames::CORAL_COLOR, $type),
- };
- }
-
/** @throws BlockStateDeserializeException */
public function readBellAttachmentType() : BellAttachmentType{
return match($type = $this->readString(BlockStateNames::ATTACHMENT)){
- StringValues::ATTACHMENT_HANGING => BellAttachmentType::CEILING(),
- StringValues::ATTACHMENT_STANDING => BellAttachmentType::FLOOR(),
- StringValues::ATTACHMENT_SIDE => BellAttachmentType::ONE_WALL(),
- StringValues::ATTACHMENT_MULTIPLE => BellAttachmentType::TWO_WALLS(),
+ StringValues::ATTACHMENT_HANGING => BellAttachmentType::CEILING,
+ StringValues::ATTACHMENT_STANDING => BellAttachmentType::FLOOR,
+ StringValues::ATTACHMENT_SIDE => BellAttachmentType::ONE_WALL,
+ StringValues::ATTACHMENT_MULTIPLE => BellAttachmentType::TWO_WALLS,
default => throw $this->badValueException(BlockStateNames::ATTACHMENT, $type),
};
}
@@ -329,8 +326,8 @@ public function readWallConnectionType(string $name) : ?WallConnectionType{
//we need to find a better way to auto-generate the constant names when they are reused
//for now, using these constants is better than nothing since it still gives static analysability
StringValues::WALL_CONNECTION_TYPE_EAST_NONE => null,
- StringValues::WALL_CONNECTION_TYPE_EAST_SHORT => WallConnectionType::SHORT(),
- StringValues::WALL_CONNECTION_TYPE_EAST_TALL => WallConnectionType::TALL(),
+ StringValues::WALL_CONNECTION_TYPE_EAST_SHORT => WallConnectionType::SHORT,
+ StringValues::WALL_CONNECTION_TYPE_EAST_TALL => WallConnectionType::TALL,
default => throw $this->badValueException($name, $type),
};
}
@@ -340,7 +337,7 @@ public function readWallConnectionType(string $name) : ?WallConnectionType{
*/
public function ignored(string $name) : void{
if($this->data->getState($name) !== null){
- $this->usedStates[$name] = true;
+ unset($this->unusedStates[$name]);
}else{
throw $this->missingOrWrongTypeException($name, null);
}
@@ -357,10 +354,8 @@ public function todo(string $name) : void{
* @throws BlockStateDeserializeException
*/
public function checkUnreadProperties() : void{
- foreach(Utils::stringifyKeys($this->data->getStates()) as $name => $tag){
- if(!isset($this->usedStates[$name])){
- throw new BlockStateDeserializeException("Unread property \"$name\"");
- }
+ if(count($this->unusedStates) > 0){
+ throw new BlockStateDeserializeException("Unread properties: " . implode(", ", array_keys($this->unusedStates)));
}
}
}
diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php
index 93e840343cf..3e221574616 100644
--- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php
+++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php
@@ -54,62 +54,50 @@
use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer;
use pocketmine\data\bedrock\MushroomBlockTypeIdMap;
use pocketmine\math\Facing;
-use pocketmine\utils\AssumptionFailedError;
final class BlockStateSerializerHelper{
-
- public static function encodeAllSidedLog(Wood $block) : BlockStateWriter{
- return BlockStateWriter::create(Ids::WOOD)
- ->writeBool(BlockStateNames::STRIPPED_BIT, $block->isStripped())
- ->writePillarAxis($block->getAxis())
- ->writeLegacyWoodType($block->getWoodType());
- }
-
- public static function encodeButton(Button $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeButton(Button $block, Writer $out) : Writer{
return $out
->writeFacingDirection($block->getFacing())
->writeBool(BlockStateNames::BUTTON_PRESSED_BIT, $block->isPressed());
}
- public static function encodeCandle(Candle $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeCandle(Candle $block, Writer $out) : Writer{
return $out
->writeBool(StateNames::LIT, $block->isLit())
->writeInt(StateNames::CANDLES, $block->getCount() - 1);
}
- public static function encodeChemistryTable(ChemistryTable $block, string $chemistryTableType, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeChemistryTable(ChemistryTable $block, Writer $out) : Writer{
return $out
- ->writeString(BlockStateNames::CHEMISTRY_TABLE_TYPE, $chemistryTableType)
->writeLegacyHorizontalFacing(Facing::opposite($block->getFacing()));
}
- public static function encodeCrops(Crops $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeCrops(Crops $block, Writer $out) : Writer{
return $out->writeInt(BlockStateNames::GROWTH, $block->getAge());
}
- public static function encodeColoredTorch(Torch $block, bool $highBit, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeTorch(Torch $block, Writer $out) : Writer{
return $out
- ->writeBool(BlockStateNames::COLOR_BIT, $highBit)
->writeTorchFacing($block->getFacing());
}
- public static function encodeCauldron(string $liquid, int $fillLevel, BlockStateWriter $out) : BlockStateWriter{
- return $out
+ public static function encodeCauldron(string $liquid, int $fillLevel) : Writer{
+ return Writer::create(Ids::CAULDRON)
->writeString(BlockStateNames::CAULDRON_LIQUID, $liquid)
->writeInt(BlockStateNames::FILL_LEVEL, $fillLevel);
}
public static function selectCopperId(CopperOxidation $oxidation, string $noneId, string $exposedId, string $weatheredId, string $oxidizedId) : string{
return match($oxidation){
- CopperOxidation::NONE() => $noneId,
- CopperOxidation::EXPOSED() => $exposedId,
- CopperOxidation::WEATHERED() => $weatheredId,
- CopperOxidation::OXIDIZED() => $oxidizedId,
- default => throw new AssumptionFailedError("Unhandled copper oxidation " . $oxidation->name())
+ CopperOxidation::NONE => $noneId,
+ CopperOxidation::EXPOSED => $exposedId,
+ CopperOxidation::WEATHERED => $weatheredId,
+ CopperOxidation::OXIDIZED => $oxidizedId,
};
}
- public static function encodeDoor(Door $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeDoor(Door $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop())
->writeLegacyHorizontalFacing(Facing::rotateY($block->getFacing(), true))
@@ -117,155 +105,116 @@ public static function encodeDoor(Door $block, BlockStateWriter $out) : BlockSta
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
}
- public static function encodeDoublePlant(DoublePlant $block, string $doublePlantType, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeDoublePlant(DoublePlant $block, Writer $out) : Writer{
return $out
- ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop())
- ->writeString(BlockStateNames::DOUBLE_PLANT_TYPE, $doublePlantType);
+ ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop());
}
- public static function encodeFenceGate(FenceGate $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{
return $out
->writeLegacyHorizontalFacing($block->getFacing())
->writeBool(BlockStateNames::IN_WALL_BIT, $block->isInWall())
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
}
- public static function encodeFloorSign(FloorSign $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeFloorSign(FloorSign $block, Writer $out) : Writer{
return $out
->writeInt(BlockStateNames::GROUND_SIGN_DIRECTION, $block->getRotation());
}
- public static function encodeFurnace(Furnace $block, string $unlitId, string $litId) : BlockStateWriter{
- return BlockStateWriter::create($block->isLit() ? $litId : $unlitId)
- ->writeHorizontalFacing($block->getFacing());
+ public static function encodeFurnace(Furnace $block, string $unlitId, string $litId) : Writer{
+ return Writer::create($block->isLit() ? $litId : $unlitId)
+ ->writeCardinalHorizontalFacing($block->getFacing());
}
- public static function encodeItemFrame(ItemFrame $block, string $id) : BlockStateWriter{
+ public static function encodeItemFrame(ItemFrame $block, string $id) : Writer{
return Writer::create($id)
->writeBool(StateNames::ITEM_FRAME_MAP_BIT, $block->hasMap())
->writeBool(StateNames::ITEM_FRAME_PHOTO_BIT, false)
->writeFacingDirection($block->getFacing());
}
- public static function encodeLeaves(Leaves $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeLeaves(Leaves $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::PERSISTENT_BIT, $block->isNoDecay())
->writeBool(BlockStateNames::UPDATE_BIT, $block->isCheckDecay());
}
- public static function encodeLeaves1(Leaves $block, string $type) : BlockStateWriter{
- return self::encodeLeaves($block, BlockStateWriter::create(Ids::LEAVES)
- ->writeString(BlockStateNames::OLD_LEAF_TYPE, $type));
- }
-
- public static function encodeLeaves2(Leaves $block, string $type) : BlockStateWriter{
- return self::encodeLeaves($block, BlockStateWriter::create(Ids::LEAVES2)
- ->writeString(BlockStateNames::NEW_LEAF_TYPE, $type));
- }
-
- public static function encodeLiquid(Liquid $block, string $stillId, string $flowingId) : BlockStateWriter{
- return BlockStateWriter::create($block->isStill() ? $stillId : $flowingId)
+ public static function encodeLiquid(Liquid $block, string $stillId, string $flowingId) : Writer{
+ return Writer::create($block->isStill() ? $stillId : $flowingId)
->writeInt(BlockStateNames::LIQUID_DEPTH, $block->getDecay() | ($block->isFalling() ? 0x8 : 0));
}
- public static function encodeLog(Wood $block, string $unstrippedId, string $strippedId) : BlockStateWriter{
+ public static function encodeLog(Wood $block, string $unstrippedId, string $strippedId) : Writer{
$out = $block->isStripped() ?
- BlockStateWriter::create($strippedId) :
- BlockStateWriter::create($unstrippedId);
+ Writer::create($strippedId) :
+ Writer::create($unstrippedId);
return $out
->writePillarAxis($block->getAxis());
}
- public static function encodeMushroomBlock(RedMushroomBlock $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeMushroomBlock(RedMushroomBlock $block, Writer $out) : Writer{
return $out
->writeInt(BlockStateNames::HUGE_MUSHROOM_BITS, MushroomBlockTypeIdMap::getInstance()->toId($block->getMushroomBlockType()));
}
- public static function encodeQuartz(string $type, int $axis) : BlockStateWriter{
- return BlockStateWriter::create(Ids::QUARTZ_BLOCK)
- ->writeString(BlockStateNames::CHISEL_TYPE, $type)
+ public static function encodeQuartz(int $axis, Writer $out) : Writer{
+ return $out
->writePillarAxis($axis); //this isn't needed for all types, but we have to write it anyway
}
- public static function encodeRedFlower(string $type) : BlockStateWriter{
- return BlockStateWriter::create(Ids::RED_FLOWER)->writeString(BlockStateNames::FLOWER_TYPE, $type);
- }
-
- public static function encodeSandstone(string $id, string $type) : BlockStateWriter{
- return BlockStateWriter::create($id)->writeString(BlockStateNames::SAND_STONE_TYPE, $type);
- }
-
- public static function encodeSapling(Sapling $block, string $type) : BlockStateWriter{
- return BlockStateWriter::create(Ids::SAPLING)
- ->writeBool(BlockStateNames::AGE_BIT, $block->isReady())
- ->writeString(BlockStateNames::SAPLING_TYPE, $type);
+ public static function encodeSapling(Sapling $block, Writer $out) : Writer{
+ return $out
+ ->writeBool(BlockStateNames::AGE_BIT, $block->isReady());
}
- public static function encodeSimplePressurePlate(SimplePressurePlate $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeSimplePressurePlate(SimplePressurePlate $block, Writer $out) : Writer{
//TODO: not sure what the deal is here ... seems like a mojang bug / artifact of bad implementation?
//best to keep this separate from weighted plates anyway...
return $out
->writeInt(BlockStateNames::REDSTONE_SIGNAL, $block->isPressed() ? 15 : 0);
}
- public static function encodeSlab(Slab $block, string $singleId, string $doubleId) : BlockStateWriter{
- $slabType = $block->getSlabType();
- return BlockStateWriter::create($slabType->equals(SlabType::DOUBLE()) ? $doubleId : $singleId)
+ private static function encodeSingleSlab(Slab $block, string $id) : Writer{
+ return Writer::create($id)
+ ->writeSlabPosition($block->getSlabType());
+ }
+ private static function encodeDoubleSlab(Slab $block, string $id) : Writer{
+ return Writer::create($id)
//this is (intentionally) also written for double slabs (as zero) to maintain bug parity with MCPE
- ->writeBool(BlockStateNames::TOP_SLOT_BIT, $slabType->equals(SlabType::TOP()));
+ ->writeSlabPosition(SlabType::BOTTOM);
}
- public static function encodeStairs(Stair $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeSlab(Slab $block, string $singleId, string $doubleId) : Writer{
+ return $block->getSlabType() === SlabType::DOUBLE ?
+ self::encodeDoubleSlab($block, $doubleId) :
+ self::encodeSingleSlab($block, $singleId);
+ }
+
+ public static function encodeStairs(Stair $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::UPSIDE_DOWN_BIT, $block->isUpsideDown())
->writeWeirdoHorizontalFacing($block->getFacing());
}
- public static function encodeStem(Stem $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeStem(Stem $block, Writer $out) : Writer{
+ //In PM, we use Facing::UP to indicate that the stem is not attached to a pumpkin/melon, since this makes the
+ //most intuitive sense (the stem is pointing at the sky). However, Bedrock uses the DOWN state for this, which
+ //is absurd, and I refuse to make our API similarly absurd.
+ $facing = $block->getFacing();
return self::encodeCrops($block, $out)
- ->writeHorizontalFacing(Facing::NORTH); //TODO: PM impl doesn't support this yet
- }
-
- public static function encodeStone(string $type) : BlockStateWriter{
- return BlockStateWriter::create(Ids::STONE)
- ->writeString(BlockStateNames::STONE_TYPE, $type);
- }
-
- public static function encodeStoneBricks(string $type) : BlockStateWriter{
- return BlockStateWriter::create(Ids::STONEBRICK)
- ->writeString(BlockStateNames::STONE_BRICK_TYPE, $type);
+ ->writeFacingWithoutUp($facing === Facing::UP ? Facing::DOWN : $facing);
}
- private static function encodeStoneSlab(Slab $block, string $singleId, string $doubleId, string $typeKey, string $typeValue) : BlockStateWriter{
- return self::encodeSlab($block, $singleId, $doubleId)
- ->writeString($typeKey, $typeValue);
- }
-
- public static function encodeStoneSlab1(Slab $block, string $typeValue) : BlockStateWriter{
- return self::encodeStoneSlab($block, Ids::STONE_BLOCK_SLAB, Ids::DOUBLE_STONE_BLOCK_SLAB, BlockStateNames::STONE_SLAB_TYPE, $typeValue);
- }
-
- public static function encodeStoneSlab2(Slab $block, string $typeValue) : BlockStateWriter{
- return self::encodeStoneSlab($block, Ids::STONE_BLOCK_SLAB2, Ids::DOUBLE_STONE_BLOCK_SLAB2, BlockStateNames::STONE_SLAB_TYPE_2, $typeValue);
- }
-
- public static function encodeStoneSlab3(Slab $block, string $typeValue) : BlockStateWriter{
- return self::encodeStoneSlab($block, Ids::STONE_BLOCK_SLAB3, Ids::DOUBLE_STONE_BLOCK_SLAB3, BlockStateNames::STONE_SLAB_TYPE_3, $typeValue);
- }
-
- public static function encodeStoneSlab4(Slab $block, string $typeValue) : BlockStateWriter{
- return self::encodeStoneSlab($block, Ids::STONE_BLOCK_SLAB4, Ids::DOUBLE_STONE_BLOCK_SLAB4, BlockStateNames::STONE_SLAB_TYPE_4, $typeValue);
- }
-
- public static function encodeTrapdoor(Trapdoor $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeTrapdoor(Trapdoor $block, Writer $out) : Writer{
return $out
->write5MinusHorizontalFacing($block->getFacing())
->writeBool(BlockStateNames::UPSIDE_DOWN_BIT, $block->isTop())
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
}
- public static function encodeWall(Wall $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeWall(Wall $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::WALL_POST_BIT, $block->isPost())
->writeWallConnectionType(BlockStateNames::WALL_CONNECTION_TYPE_EAST, $block->getConnection(Facing::EAST))
@@ -274,18 +223,8 @@ public static function encodeWall(Wall $block, BlockStateWriter $out) : BlockSta
->writeWallConnectionType(BlockStateNames::WALL_CONNECTION_TYPE_WEST, $block->getConnection(Facing::WEST));
}
- public static function encodeLegacyWall(Wall $block, string $type) : BlockStateWriter{
- return self::encodeWall($block, BlockStateWriter::create(Ids::COBBLESTONE_WALL))
- ->writeString(BlockStateNames::WALL_BLOCK_TYPE, $type);
- }
-
- public static function encodeWallSign(WallSign $block, BlockStateWriter $out) : BlockStateWriter{
+ public static function encodeWallSign(WallSign $block, Writer $out) : Writer{
return $out
->writeHorizontalFacing($block->getFacing());
}
-
- public static function encodeWoodenSlab(Slab $block, string $typeValue) : BlockStateWriter{
- return self::encodeSlab($block, Ids::WOODEN_SLAB, Ids::DOUBLE_WOODEN_SLAB)
- ->writeString(BlockStateNames::WOOD_TYPE, $typeValue);
- }
}
diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php
index 7a99387335b..5c0a427cc75 100644
--- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php
+++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php
@@ -23,22 +23,29 @@
namespace pocketmine\data\bedrock\block\convert;
+use pocketmine\block\AmethystCluster;
+use pocketmine\block\Anvil;
use pocketmine\block\Bamboo;
use pocketmine\block\Block;
use pocketmine\block\CaveVines;
use pocketmine\block\ChorusFlower;
-use pocketmine\block\Light;
+use pocketmine\block\DoublePitcherCrop;
+use pocketmine\block\Opaque;
+use pocketmine\block\PinkPetals;
+use pocketmine\block\PitcherCrop;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\SweetBerryBush;
use pocketmine\block\utils\BrewingStandSlot;
+use pocketmine\block\utils\ChiseledBookshelfSlot;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DirtType;
+use pocketmine\block\utils\DripleafState;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\FroglightType;
use pocketmine\block\utils\LeverFacing;
-use pocketmine\block\utils\SlabType;
+use pocketmine\block\utils\MobHeadType;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\block\Wood;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
@@ -73,8 +80,13 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
public function __construct(){
$this->registerCandleDeserializers();
$this->registerFlatColorBlockDeserializers();
+ $this->registerFlatCoralDeserializers();
$this->registerCauldronDeserializers();
- $this->registerWoodBlockDeserializers();
+ $this->registerFlatWoodBlockDeserializers();
+ $this->registerLeavesDeserializers();
+ $this->registerSaplingDeserializers();
+ $this->registerLightDeserializers();
+ $this->registerMobHeadDeserializers();
$this->registerSimpleDeserializers();
$this->registerDeserializers();
}
@@ -106,11 +118,8 @@ public function mapSimple(string $id, \Closure $getBlock) : void{
* @phpstan-param \Closure(Reader) : Slab $getBlock
*/
public function mapSlab(string $singleId, string $doubleId, \Closure $getBlock) : void{
- $this->map($singleId, fn(Reader $in) : Slab => $getBlock($in)->setSlabType($in->readSlabPosition()));
- $this->map($doubleId, function(Reader $in) use ($getBlock) : Slab{
- $in->ignored(StateNames::TOP_SLOT_BIT);
- return $getBlock($in)->setSlabType(SlabType::DOUBLE());
- });
+ $this->map($singleId, fn(Reader $in) => Helper::decodeSingleSlab($getBlock($in), $in));
+ $this->map($doubleId, fn(Reader $in) => Helper::decodeDoubleSlab($getBlock($in), $in));
}
/**
@@ -129,44 +138,44 @@ public function mapLog(string $unstrippedId, string $strippedId, \Closure $getBl
private function registerCandleDeserializers() : void{
$this->map(Ids::CANDLE, fn(Reader $in) => Helper::decodeCandle(Blocks::CANDLE(), $in));
foreach([
- Ids::BLACK_CANDLE => DyeColor::BLACK(),
- Ids::BLUE_CANDLE => DyeColor::BLUE(),
- Ids::BROWN_CANDLE => DyeColor::BROWN(),
- Ids::CYAN_CANDLE => DyeColor::CYAN(),
- Ids::GRAY_CANDLE => DyeColor::GRAY(),
- Ids::GREEN_CANDLE => DyeColor::GREEN(),
- Ids::LIGHT_BLUE_CANDLE => DyeColor::LIGHT_BLUE(),
- Ids::LIGHT_GRAY_CANDLE => DyeColor::LIGHT_GRAY(),
- Ids::LIME_CANDLE => DyeColor::LIME(),
- Ids::MAGENTA_CANDLE => DyeColor::MAGENTA(),
- Ids::ORANGE_CANDLE => DyeColor::ORANGE(),
- Ids::PINK_CANDLE => DyeColor::PINK(),
- Ids::PURPLE_CANDLE => DyeColor::PURPLE(),
- Ids::RED_CANDLE => DyeColor::RED(),
- Ids::WHITE_CANDLE => DyeColor::WHITE(),
- Ids::YELLOW_CANDLE => DyeColor::YELLOW(),
+ Ids::BLACK_CANDLE => DyeColor::BLACK,
+ Ids::BLUE_CANDLE => DyeColor::BLUE,
+ Ids::BROWN_CANDLE => DyeColor::BROWN,
+ Ids::CYAN_CANDLE => DyeColor::CYAN,
+ Ids::GRAY_CANDLE => DyeColor::GRAY,
+ Ids::GREEN_CANDLE => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_CANDLE => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_CANDLE => DyeColor::LIGHT_GRAY,
+ Ids::LIME_CANDLE => DyeColor::LIME,
+ Ids::MAGENTA_CANDLE => DyeColor::MAGENTA,
+ Ids::ORANGE_CANDLE => DyeColor::ORANGE,
+ Ids::PINK_CANDLE => DyeColor::PINK,
+ Ids::PURPLE_CANDLE => DyeColor::PURPLE,
+ Ids::RED_CANDLE => DyeColor::RED,
+ Ids::WHITE_CANDLE => DyeColor::WHITE,
+ Ids::YELLOW_CANDLE => DyeColor::YELLOW,
] as $id => $color){
$this->map($id, fn(Reader $in) => Helper::decodeCandle(Blocks::DYED_CANDLE()->setColor($color), $in));
}
$this->map(Ids::CANDLE_CAKE, fn(Reader $in) => Blocks::CAKE_WITH_CANDLE()->setLit($in->readBool(StateNames::LIT)));
foreach([
- Ids::BLACK_CANDLE_CAKE => DyeColor::BLACK(),
- Ids::BLUE_CANDLE_CAKE => DyeColor::BLUE(),
- Ids::BROWN_CANDLE_CAKE => DyeColor::BROWN(),
- Ids::CYAN_CANDLE_CAKE => DyeColor::CYAN(),
- Ids::GRAY_CANDLE_CAKE => DyeColor::GRAY(),
- Ids::GREEN_CANDLE_CAKE => DyeColor::GREEN(),
- Ids::LIGHT_BLUE_CANDLE_CAKE => DyeColor::LIGHT_BLUE(),
- Ids::LIGHT_GRAY_CANDLE_CAKE => DyeColor::LIGHT_GRAY(),
- Ids::LIME_CANDLE_CAKE => DyeColor::LIME(),
- Ids::MAGENTA_CANDLE_CAKE => DyeColor::MAGENTA(),
- Ids::ORANGE_CANDLE_CAKE => DyeColor::ORANGE(),
- Ids::PINK_CANDLE_CAKE => DyeColor::PINK(),
- Ids::PURPLE_CANDLE_CAKE => DyeColor::PURPLE(),
- Ids::RED_CANDLE_CAKE => DyeColor::RED(),
- Ids::WHITE_CANDLE_CAKE => DyeColor::WHITE(),
- Ids::YELLOW_CANDLE_CAKE => DyeColor::YELLOW(),
+ Ids::BLACK_CANDLE_CAKE => DyeColor::BLACK,
+ Ids::BLUE_CANDLE_CAKE => DyeColor::BLUE,
+ Ids::BROWN_CANDLE_CAKE => DyeColor::BROWN,
+ Ids::CYAN_CANDLE_CAKE => DyeColor::CYAN,
+ Ids::GRAY_CANDLE_CAKE => DyeColor::GRAY,
+ Ids::GREEN_CANDLE_CAKE => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_CANDLE_CAKE => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_CANDLE_CAKE => DyeColor::LIGHT_GRAY,
+ Ids::LIME_CANDLE_CAKE => DyeColor::LIME,
+ Ids::MAGENTA_CANDLE_CAKE => DyeColor::MAGENTA,
+ Ids::ORANGE_CANDLE_CAKE => DyeColor::ORANGE,
+ Ids::PINK_CANDLE_CAKE => DyeColor::PINK,
+ Ids::PURPLE_CANDLE_CAKE => DyeColor::PURPLE,
+ Ids::RED_CANDLE_CAKE => DyeColor::RED,
+ Ids::WHITE_CANDLE_CAKE => DyeColor::WHITE,
+ Ids::YELLOW_CANDLE_CAKE => DyeColor::YELLOW,
] as $id => $color){
$this->map($id, fn(Reader $in) => Blocks::CAKE_WITH_DYED_CANDLE()
->setColor($color)
@@ -177,22 +186,64 @@ private function registerCandleDeserializers() : void{
private function registerFlatColorBlockDeserializers() : void{
foreach([
- Ids::BLACK_GLAZED_TERRACOTTA => DyeColor::BLACK(),
- Ids::BLUE_GLAZED_TERRACOTTA => DyeColor::BLUE(),
- Ids::BROWN_GLAZED_TERRACOTTA => DyeColor::BROWN(),
- Ids::CYAN_GLAZED_TERRACOTTA => DyeColor::CYAN(),
- Ids::GRAY_GLAZED_TERRACOTTA => DyeColor::GRAY(),
- Ids::GREEN_GLAZED_TERRACOTTA => DyeColor::GREEN(),
- Ids::LIGHT_BLUE_GLAZED_TERRACOTTA => DyeColor::LIGHT_BLUE(),
- Ids::SILVER_GLAZED_TERRACOTTA => DyeColor::LIGHT_GRAY(),
- Ids::LIME_GLAZED_TERRACOTTA => DyeColor::LIME(),
- Ids::MAGENTA_GLAZED_TERRACOTTA => DyeColor::MAGENTA(),
- Ids::ORANGE_GLAZED_TERRACOTTA => DyeColor::ORANGE(),
- Ids::PINK_GLAZED_TERRACOTTA => DyeColor::PINK(),
- Ids::PURPLE_GLAZED_TERRACOTTA => DyeColor::PURPLE(),
- Ids::RED_GLAZED_TERRACOTTA => DyeColor::RED(),
- Ids::WHITE_GLAZED_TERRACOTTA => DyeColor::WHITE(),
- Ids::YELLOW_GLAZED_TERRACOTTA => DyeColor::YELLOW(),
+ Ids::HARD_BLACK_STAINED_GLASS => DyeColor::BLACK,
+ Ids::HARD_BLUE_STAINED_GLASS => DyeColor::BLUE,
+ Ids::HARD_BROWN_STAINED_GLASS => DyeColor::BROWN,
+ Ids::HARD_CYAN_STAINED_GLASS => DyeColor::CYAN,
+ Ids::HARD_GRAY_STAINED_GLASS => DyeColor::GRAY,
+ Ids::HARD_GREEN_STAINED_GLASS => DyeColor::GREEN,
+ Ids::HARD_LIGHT_BLUE_STAINED_GLASS => DyeColor::LIGHT_BLUE,
+ Ids::HARD_LIGHT_GRAY_STAINED_GLASS => DyeColor::LIGHT_GRAY,
+ Ids::HARD_LIME_STAINED_GLASS => DyeColor::LIME,
+ Ids::HARD_MAGENTA_STAINED_GLASS => DyeColor::MAGENTA,
+ Ids::HARD_ORANGE_STAINED_GLASS => DyeColor::ORANGE,
+ Ids::HARD_PINK_STAINED_GLASS => DyeColor::PINK,
+ Ids::HARD_PURPLE_STAINED_GLASS => DyeColor::PURPLE,
+ Ids::HARD_RED_STAINED_GLASS => DyeColor::RED,
+ Ids::HARD_WHITE_STAINED_GLASS => DyeColor::WHITE,
+ Ids::HARD_YELLOW_STAINED_GLASS => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->map($id, fn(Reader $in) => Blocks::STAINED_HARDENED_GLASS()->setColor($color));
+ }
+
+ foreach([
+ Ids::HARD_BLACK_STAINED_GLASS_PANE => DyeColor::BLACK,
+ Ids::HARD_BLUE_STAINED_GLASS_PANE => DyeColor::BLUE,
+ Ids::HARD_BROWN_STAINED_GLASS_PANE => DyeColor::BROWN,
+ Ids::HARD_CYAN_STAINED_GLASS_PANE => DyeColor::CYAN,
+ Ids::HARD_GRAY_STAINED_GLASS_PANE => DyeColor::GRAY,
+ Ids::HARD_GREEN_STAINED_GLASS_PANE => DyeColor::GREEN,
+ Ids::HARD_LIGHT_BLUE_STAINED_GLASS_PANE => DyeColor::LIGHT_BLUE,
+ Ids::HARD_LIGHT_GRAY_STAINED_GLASS_PANE => DyeColor::LIGHT_GRAY,
+ Ids::HARD_LIME_STAINED_GLASS_PANE => DyeColor::LIME,
+ Ids::HARD_MAGENTA_STAINED_GLASS_PANE => DyeColor::MAGENTA,
+ Ids::HARD_ORANGE_STAINED_GLASS_PANE => DyeColor::ORANGE,
+ Ids::HARD_PINK_STAINED_GLASS_PANE => DyeColor::PINK,
+ Ids::HARD_PURPLE_STAINED_GLASS_PANE => DyeColor::PURPLE,
+ Ids::HARD_RED_STAINED_GLASS_PANE => DyeColor::RED,
+ Ids::HARD_WHITE_STAINED_GLASS_PANE => DyeColor::WHITE,
+ Ids::HARD_YELLOW_STAINED_GLASS_PANE => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->map($id, fn(Reader $in) => Blocks::STAINED_HARDENED_GLASS_PANE()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_GLAZED_TERRACOTTA => DyeColor::BLACK,
+ Ids::BLUE_GLAZED_TERRACOTTA => DyeColor::BLUE,
+ Ids::BROWN_GLAZED_TERRACOTTA => DyeColor::BROWN,
+ Ids::CYAN_GLAZED_TERRACOTTA => DyeColor::CYAN,
+ Ids::GRAY_GLAZED_TERRACOTTA => DyeColor::GRAY,
+ Ids::GREEN_GLAZED_TERRACOTTA => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_GLAZED_TERRACOTTA => DyeColor::LIGHT_BLUE,
+ Ids::SILVER_GLAZED_TERRACOTTA => DyeColor::LIGHT_GRAY,
+ Ids::LIME_GLAZED_TERRACOTTA => DyeColor::LIME,
+ Ids::MAGENTA_GLAZED_TERRACOTTA => DyeColor::MAGENTA,
+ Ids::ORANGE_GLAZED_TERRACOTTA => DyeColor::ORANGE,
+ Ids::PINK_GLAZED_TERRACOTTA => DyeColor::PINK,
+ Ids::PURPLE_GLAZED_TERRACOTTA => DyeColor::PURPLE,
+ Ids::RED_GLAZED_TERRACOTTA => DyeColor::RED,
+ Ids::WHITE_GLAZED_TERRACOTTA => DyeColor::WHITE,
+ Ids::YELLOW_GLAZED_TERRACOTTA => DyeColor::YELLOW,
] as $id => $color){
$this->map($id, fn(Reader $in) => Blocks::GLAZED_TERRACOTTA()
->setColor($color)
@@ -201,25 +252,226 @@ private function registerFlatColorBlockDeserializers() : void{
}
foreach([
- Ids::BLACK_WOOL => DyeColor::BLACK(),
- Ids::BLUE_WOOL => DyeColor::BLUE(),
- Ids::BROWN_WOOL => DyeColor::BROWN(),
- Ids::CYAN_WOOL => DyeColor::CYAN(),
- Ids::GRAY_WOOL => DyeColor::GRAY(),
- Ids::GREEN_WOOL => DyeColor::GREEN(),
- Ids::LIGHT_BLUE_WOOL => DyeColor::LIGHT_BLUE(),
- Ids::LIGHT_GRAY_WOOL => DyeColor::LIGHT_GRAY(),
- Ids::LIME_WOOL => DyeColor::LIME(),
- Ids::MAGENTA_WOOL => DyeColor::MAGENTA(),
- Ids::ORANGE_WOOL => DyeColor::ORANGE(),
- Ids::PINK_WOOL => DyeColor::PINK(),
- Ids::PURPLE_WOOL => DyeColor::PURPLE(),
- Ids::RED_WOOL => DyeColor::RED(),
- Ids::WHITE_WOOL => DyeColor::WHITE(),
- Ids::YELLOW_WOOL => DyeColor::YELLOW(),
+ Ids::BLACK_WOOL => DyeColor::BLACK,
+ Ids::BLUE_WOOL => DyeColor::BLUE,
+ Ids::BROWN_WOOL => DyeColor::BROWN,
+ Ids::CYAN_WOOL => DyeColor::CYAN,
+ Ids::GRAY_WOOL => DyeColor::GRAY,
+ Ids::GREEN_WOOL => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_WOOL => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_WOOL => DyeColor::LIGHT_GRAY,
+ Ids::LIME_WOOL => DyeColor::LIME,
+ Ids::MAGENTA_WOOL => DyeColor::MAGENTA,
+ Ids::ORANGE_WOOL => DyeColor::ORANGE,
+ Ids::PINK_WOOL => DyeColor::PINK,
+ Ids::PURPLE_WOOL => DyeColor::PURPLE,
+ Ids::RED_WOOL => DyeColor::RED,
+ Ids::WHITE_WOOL => DyeColor::WHITE,
+ Ids::YELLOW_WOOL => DyeColor::YELLOW,
] as $id => $color){
$this->mapSimple($id, fn() => Blocks::WOOL()->setColor($color));
}
+
+ foreach([
+ Ids::BLACK_CARPET => DyeColor::BLACK,
+ Ids::BLUE_CARPET => DyeColor::BLUE,
+ Ids::BROWN_CARPET => DyeColor::BROWN,
+ Ids::CYAN_CARPET => DyeColor::CYAN,
+ Ids::GRAY_CARPET => DyeColor::GRAY,
+ Ids::GREEN_CARPET => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_CARPET => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_CARPET => DyeColor::LIGHT_GRAY,
+ Ids::LIME_CARPET => DyeColor::LIME,
+ Ids::MAGENTA_CARPET => DyeColor::MAGENTA,
+ Ids::ORANGE_CARPET => DyeColor::ORANGE,
+ Ids::PINK_CARPET => DyeColor::PINK,
+ Ids::PURPLE_CARPET => DyeColor::PURPLE,
+ Ids::RED_CARPET => DyeColor::RED,
+ Ids::WHITE_CARPET => DyeColor::WHITE,
+ Ids::YELLOW_CARPET => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::CARPET()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_SHULKER_BOX => DyeColor::BLACK,
+ Ids::BLUE_SHULKER_BOX => DyeColor::BLUE,
+ Ids::BROWN_SHULKER_BOX => DyeColor::BROWN,
+ Ids::CYAN_SHULKER_BOX => DyeColor::CYAN,
+ Ids::GRAY_SHULKER_BOX => DyeColor::GRAY,
+ Ids::GREEN_SHULKER_BOX => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_SHULKER_BOX => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_SHULKER_BOX => DyeColor::LIGHT_GRAY,
+ Ids::LIME_SHULKER_BOX => DyeColor::LIME,
+ Ids::MAGENTA_SHULKER_BOX => DyeColor::MAGENTA,
+ Ids::ORANGE_SHULKER_BOX => DyeColor::ORANGE,
+ Ids::PINK_SHULKER_BOX => DyeColor::PINK,
+ Ids::PURPLE_SHULKER_BOX => DyeColor::PURPLE,
+ Ids::RED_SHULKER_BOX => DyeColor::RED,
+ Ids::WHITE_SHULKER_BOX => DyeColor::WHITE,
+ Ids::YELLOW_SHULKER_BOX => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::DYED_SHULKER_BOX()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_CONCRETE => DyeColor::BLACK,
+ Ids::BLUE_CONCRETE => DyeColor::BLUE,
+ Ids::BROWN_CONCRETE => DyeColor::BROWN,
+ Ids::CYAN_CONCRETE => DyeColor::CYAN,
+ Ids::GRAY_CONCRETE => DyeColor::GRAY,
+ Ids::GREEN_CONCRETE => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_CONCRETE => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_CONCRETE => DyeColor::LIGHT_GRAY,
+ Ids::LIME_CONCRETE => DyeColor::LIME,
+ Ids::MAGENTA_CONCRETE => DyeColor::MAGENTA,
+ Ids::ORANGE_CONCRETE => DyeColor::ORANGE,
+ Ids::PINK_CONCRETE => DyeColor::PINK,
+ Ids::PURPLE_CONCRETE => DyeColor::PURPLE,
+ Ids::RED_CONCRETE => DyeColor::RED,
+ Ids::WHITE_CONCRETE => DyeColor::WHITE,
+ Ids::YELLOW_CONCRETE => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::CONCRETE()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_CONCRETE_POWDER => DyeColor::BLACK,
+ Ids::BLUE_CONCRETE_POWDER => DyeColor::BLUE,
+ Ids::BROWN_CONCRETE_POWDER => DyeColor::BROWN,
+ Ids::CYAN_CONCRETE_POWDER => DyeColor::CYAN,
+ Ids::GRAY_CONCRETE_POWDER => DyeColor::GRAY,
+ Ids::GREEN_CONCRETE_POWDER => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_CONCRETE_POWDER => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_CONCRETE_POWDER => DyeColor::LIGHT_GRAY,
+ Ids::LIME_CONCRETE_POWDER => DyeColor::LIME,
+ Ids::MAGENTA_CONCRETE_POWDER => DyeColor::MAGENTA,
+ Ids::ORANGE_CONCRETE_POWDER => DyeColor::ORANGE,
+ Ids::PINK_CONCRETE_POWDER => DyeColor::PINK,
+ Ids::PURPLE_CONCRETE_POWDER => DyeColor::PURPLE,
+ Ids::RED_CONCRETE_POWDER => DyeColor::RED,
+ Ids::WHITE_CONCRETE_POWDER => DyeColor::WHITE,
+ Ids::YELLOW_CONCRETE_POWDER => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::CONCRETE_POWDER()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_TERRACOTTA => DyeColor::BLACK,
+ Ids::BLUE_TERRACOTTA => DyeColor::BLUE,
+ Ids::BROWN_TERRACOTTA => DyeColor::BROWN,
+ Ids::CYAN_TERRACOTTA => DyeColor::CYAN,
+ Ids::GRAY_TERRACOTTA => DyeColor::GRAY,
+ Ids::GREEN_TERRACOTTA => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_TERRACOTTA => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_TERRACOTTA => DyeColor::LIGHT_GRAY,
+ Ids::LIME_TERRACOTTA => DyeColor::LIME,
+ Ids::MAGENTA_TERRACOTTA => DyeColor::MAGENTA,
+ Ids::ORANGE_TERRACOTTA => DyeColor::ORANGE,
+ Ids::PINK_TERRACOTTA => DyeColor::PINK,
+ Ids::PURPLE_TERRACOTTA => DyeColor::PURPLE,
+ Ids::RED_TERRACOTTA => DyeColor::RED,
+ Ids::WHITE_TERRACOTTA => DyeColor::WHITE,
+ Ids::YELLOW_TERRACOTTA => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::STAINED_CLAY()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_STAINED_GLASS => DyeColor::BLACK,
+ Ids::BLUE_STAINED_GLASS => DyeColor::BLUE,
+ Ids::BROWN_STAINED_GLASS => DyeColor::BROWN,
+ Ids::CYAN_STAINED_GLASS => DyeColor::CYAN,
+ Ids::GRAY_STAINED_GLASS => DyeColor::GRAY,
+ Ids::GREEN_STAINED_GLASS => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_STAINED_GLASS => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_STAINED_GLASS => DyeColor::LIGHT_GRAY,
+ Ids::LIME_STAINED_GLASS => DyeColor::LIME,
+ Ids::MAGENTA_STAINED_GLASS => DyeColor::MAGENTA,
+ Ids::ORANGE_STAINED_GLASS => DyeColor::ORANGE,
+ Ids::PINK_STAINED_GLASS => DyeColor::PINK,
+ Ids::PURPLE_STAINED_GLASS => DyeColor::PURPLE,
+ Ids::RED_STAINED_GLASS => DyeColor::RED,
+ Ids::WHITE_STAINED_GLASS => DyeColor::WHITE,
+ Ids::YELLOW_STAINED_GLASS => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::STAINED_GLASS()->setColor($color));
+ }
+
+ foreach([
+ Ids::BLACK_STAINED_GLASS_PANE => DyeColor::BLACK,
+ Ids::BLUE_STAINED_GLASS_PANE => DyeColor::BLUE,
+ Ids::BROWN_STAINED_GLASS_PANE => DyeColor::BROWN,
+ Ids::CYAN_STAINED_GLASS_PANE => DyeColor::CYAN,
+ Ids::GRAY_STAINED_GLASS_PANE => DyeColor::GRAY,
+ Ids::GREEN_STAINED_GLASS_PANE => DyeColor::GREEN,
+ Ids::LIGHT_BLUE_STAINED_GLASS_PANE => DyeColor::LIGHT_BLUE,
+ Ids::LIGHT_GRAY_STAINED_GLASS_PANE => DyeColor::LIGHT_GRAY,
+ Ids::LIME_STAINED_GLASS_PANE => DyeColor::LIME,
+ Ids::MAGENTA_STAINED_GLASS_PANE => DyeColor::MAGENTA,
+ Ids::ORANGE_STAINED_GLASS_PANE => DyeColor::ORANGE,
+ Ids::PINK_STAINED_GLASS_PANE => DyeColor::PINK,
+ Ids::PURPLE_STAINED_GLASS_PANE => DyeColor::PURPLE,
+ Ids::RED_STAINED_GLASS_PANE => DyeColor::RED,
+ Ids::WHITE_STAINED_GLASS_PANE => DyeColor::WHITE,
+ Ids::YELLOW_STAINED_GLASS_PANE => DyeColor::YELLOW,
+ ] as $id => $color){
+ $this->mapSimple($id, fn() => Blocks::STAINED_GLASS_PANE()->setColor($color));
+ }
+ }
+
+ private function registerFlatCoralDeserializers() : void{
+ foreach([
+ Ids::BRAIN_CORAL => CoralType::BRAIN,
+ Ids::BUBBLE_CORAL => CoralType::BUBBLE,
+ Ids::FIRE_CORAL => CoralType::FIRE,
+ Ids::HORN_CORAL => CoralType::HORN,
+ Ids::TUBE_CORAL => CoralType::TUBE,
+ ] as $id => $coralType){
+ $this->mapSimple($id, fn() => Blocks::CORAL()->setCoralType($coralType)->setDead(false));
+ }
+ foreach([
+ Ids::DEAD_BRAIN_CORAL => CoralType::BRAIN,
+ Ids::DEAD_BUBBLE_CORAL => CoralType::BUBBLE,
+ Ids::DEAD_FIRE_CORAL => CoralType::FIRE,
+ Ids::DEAD_HORN_CORAL => CoralType::HORN,
+ Ids::DEAD_TUBE_CORAL => CoralType::TUBE,
+ ] as $id => $coralType){
+ $this->mapSimple($id, fn() => Blocks::CORAL()->setCoralType($coralType)->setDead(true));
+ }
+
+ foreach([
+ [CoralType::BRAIN, Ids::BRAIN_CORAL_FAN, Ids::DEAD_BRAIN_CORAL_FAN],
+ [CoralType::BUBBLE, Ids::BUBBLE_CORAL_FAN, Ids::DEAD_BUBBLE_CORAL_FAN],
+ [CoralType::FIRE, Ids::FIRE_CORAL_FAN, Ids::DEAD_FIRE_CORAL_FAN],
+ [CoralType::HORN, Ids::HORN_CORAL_FAN, Ids::DEAD_HORN_CORAL_FAN],
+ [CoralType::TUBE, Ids::TUBE_CORAL_FAN, Ids::DEAD_TUBE_CORAL_FAN],
+ ] as [$coralType, $aliveId, $deadId]){
+ $this->map($aliveId, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN()->setCoralType($coralType)->setDead(false), $in));
+ $this->map($deadId, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN()->setCoralType($coralType)->setDead(true), $in));
+ }
+
+ foreach([
+ [CoralType::BRAIN, Ids::BRAIN_CORAL_BLOCK, Ids::DEAD_BRAIN_CORAL_BLOCK],
+ [CoralType::BUBBLE, Ids::BUBBLE_CORAL_BLOCK, Ids::DEAD_BUBBLE_CORAL_BLOCK],
+ [CoralType::FIRE, Ids::FIRE_CORAL_BLOCK, Ids::DEAD_FIRE_CORAL_BLOCK],
+ [CoralType::HORN, Ids::HORN_CORAL_BLOCK, Ids::DEAD_HORN_CORAL_BLOCK],
+ [CoralType::TUBE, Ids::TUBE_CORAL_BLOCK, Ids::DEAD_TUBE_CORAL_BLOCK],
+ ] as [$coralType, $aliveId, $deadId]){
+ $this->map($aliveId, fn(Reader $in) => Blocks::CORAL_BLOCK()->setCoralType($coralType)->setDead(false));
+ $this->map($deadId, fn(Reader $in) => Blocks::CORAL_BLOCK()->setCoralType($coralType)->setDead(true));
+ }
+
+ foreach([
+ [CoralType::BRAIN, Ids::BRAIN_CORAL_WALL_FAN, Ids::DEAD_BRAIN_CORAL_WALL_FAN],
+ [CoralType::BUBBLE, Ids::BUBBLE_CORAL_WALL_FAN, Ids::DEAD_BUBBLE_CORAL_WALL_FAN],
+ [CoralType::FIRE, Ids::FIRE_CORAL_WALL_FAN, Ids::DEAD_FIRE_CORAL_WALL_FAN],
+ [CoralType::HORN, Ids::HORN_CORAL_WALL_FAN, Ids::DEAD_HORN_CORAL_WALL_FAN],
+ [CoralType::TUBE, Ids::TUBE_CORAL_WALL_FAN, Ids::DEAD_TUBE_CORAL_WALL_FAN],
+ ] as [$coralType, $aliveId, $deadId]){
+ $this->map($aliveId, fn(Reader $in) => Blocks::WALL_CORAL_FAN()->setFacing($in->readCoralFacing())->setCoralType($coralType)->setDead(false));
+ $this->map($deadId, fn(Reader $in) => Blocks::WALL_CORAL_FAN()->setFacing($in->readCoralFacing())->setCoralType($coralType)->setDead(true));
+ }
}
private function registerCauldronDeserializers() : void{
@@ -238,29 +490,221 @@ private function registerCauldronDeserializers() : void{
})->setFillLevel($level);
};
$this->map(Ids::CAULDRON, $deserializer);
- $this->map(Ids::LAVA_CAULDRON, $deserializer);
}
- private function registerWoodBlockDeserializers() : void{
+ private function registerFlatWoodBlockDeserializers() : void{
+ $this->map(Ids::ACACIA_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::ACACIA_BUTTON(), $in));
+ $this->map(Ids::ACACIA_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::ACACIA_DOOR(), $in));
+ $this->map(Ids::ACACIA_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::ACACIA_FENCE_GATE(), $in));
+ $this->map(Ids::ACACIA_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::ACACIA_PRESSURE_PLATE(), $in));
+ $this->map(Ids::ACACIA_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::ACACIA_SIGN(), $in));
+ $this->map(Ids::ACACIA_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::ACACIA_TRAPDOOR(), $in));
+ $this->map(Ids::ACACIA_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::ACACIA_WALL_SIGN(), $in));
+ $this->mapLog(Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG, fn() => Blocks::ACACIA_LOG());
+ $this->mapLog(Ids::ACACIA_WOOD, Ids::STRIPPED_ACACIA_WOOD, fn() => Blocks::ACACIA_WOOD());
$this->mapSimple(Ids::ACACIA_FENCE, fn() => Blocks::ACACIA_FENCE());
- $this->mapSimple(Ids::BIRCH_FENCE, fn() => Blocks::BIRCH_FENCE());
- $this->mapSimple(Ids::DARK_OAK_FENCE, fn() => Blocks::DARK_OAK_FENCE());
- $this->mapSimple(Ids::JUNGLE_FENCE, fn() => Blocks::JUNGLE_FENCE());
- $this->mapSimple(Ids::OAK_FENCE, fn() => Blocks::OAK_FENCE());
- $this->mapSimple(Ids::SPRUCE_FENCE, fn() => Blocks::SPRUCE_FENCE());
+ $this->mapSimple(Ids::ACACIA_PLANKS, fn() => Blocks::ACACIA_PLANKS());
+ $this->mapSlab(Ids::ACACIA_SLAB, Ids::ACACIA_DOUBLE_SLAB, fn() => Blocks::ACACIA_SLAB());
+ $this->mapStairs(Ids::ACACIA_STAIRS, fn() => Blocks::ACACIA_STAIRS());
- $this->mapLog(Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG, fn() => Blocks::ACACIA_LOG());
+ $this->map(Ids::BIRCH_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::BIRCH_BUTTON(), $in));
+ $this->map(Ids::BIRCH_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::BIRCH_DOOR(), $in));
+ $this->map(Ids::BIRCH_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::BIRCH_FENCE_GATE(), $in));
+ $this->map(Ids::BIRCH_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::BIRCH_PRESSURE_PLATE(), $in));
+ $this->map(Ids::BIRCH_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::BIRCH_SIGN(), $in));
+ $this->map(Ids::BIRCH_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::BIRCH_TRAPDOOR(), $in));
+ $this->map(Ids::BIRCH_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::BIRCH_WALL_SIGN(), $in));
$this->mapLog(Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG, fn() => Blocks::BIRCH_LOG());
+ $this->mapLog(Ids::BIRCH_WOOD, Ids::STRIPPED_BIRCH_WOOD, fn() => Blocks::BIRCH_WOOD());
+ $this->mapSimple(Ids::BIRCH_FENCE, fn() => Blocks::BIRCH_FENCE());
+ $this->mapSimple(Ids::BIRCH_PLANKS, fn() => Blocks::BIRCH_PLANKS());
+ $this->mapSlab(Ids::BIRCH_SLAB, Ids::BIRCH_DOUBLE_SLAB, fn() => Blocks::BIRCH_SLAB());
+ $this->mapStairs(Ids::BIRCH_STAIRS, fn() => Blocks::BIRCH_STAIRS());
+
+ $this->map(Ids::CHERRY_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CHERRY_BUTTON(), $in));
+ $this->map(Ids::CHERRY_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CHERRY_DOOR(), $in));
+ $this->map(Ids::CHERRY_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CHERRY_FENCE_GATE(), $in));
+ $this->map(Ids::CHERRY_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CHERRY_PRESSURE_PLATE(), $in));
+ $this->map(Ids::CHERRY_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CHERRY_SIGN(), $in));
+ $this->map(Ids::CHERRY_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CHERRY_TRAPDOOR(), $in));
+ $this->map(Ids::CHERRY_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CHERRY_WALL_SIGN(), $in));
+ $this->mapLog(Ids::CHERRY_LOG, Ids::STRIPPED_CHERRY_LOG, fn() => Blocks::CHERRY_LOG());
+ $this->mapSimple(Ids::CHERRY_FENCE, fn() => Blocks::CHERRY_FENCE());
+ $this->mapSimple(Ids::CHERRY_PLANKS, fn() => Blocks::CHERRY_PLANKS());
+ $this->mapSlab(Ids::CHERRY_SLAB, Ids::CHERRY_DOUBLE_SLAB, fn() => Blocks::CHERRY_SLAB());
+ $this->mapStairs(Ids::CHERRY_STAIRS, fn() => Blocks::CHERRY_STAIRS());
+ $this->map(Ids::CHERRY_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::CHERRY_WOOD(), false, $in));
+ $this->map(Ids::STRIPPED_CHERRY_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::CHERRY_WOOD(), true, $in));
+
+ $this->map(Ids::CRIMSON_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CRIMSON_BUTTON(), $in));
+ $this->map(Ids::CRIMSON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CRIMSON_DOOR(), $in));
+ $this->map(Ids::CRIMSON_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CRIMSON_FENCE_GATE(), $in));
+ $this->map(Ids::CRIMSON_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CRIMSON_PRESSURE_PLATE(), $in));
+ $this->map(Ids::CRIMSON_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CRIMSON_SIGN(), $in));
+ $this->map(Ids::CRIMSON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CRIMSON_TRAPDOOR(), $in));
+ $this->map(Ids::CRIMSON_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CRIMSON_WALL_SIGN(), $in));
+ $this->mapLog(Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE, fn() => Blocks::CRIMSON_HYPHAE());
+ $this->mapLog(Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM, fn() => Blocks::CRIMSON_STEM());
+ $this->mapSimple(Ids::CRIMSON_FENCE, fn() => Blocks::CRIMSON_FENCE());
+ $this->mapSimple(Ids::CRIMSON_PLANKS, fn() => Blocks::CRIMSON_PLANKS());
+ $this->mapSlab(Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB, fn() => Blocks::CRIMSON_SLAB());
+ $this->mapStairs(Ids::CRIMSON_STAIRS, fn() => Blocks::CRIMSON_STAIRS());
+
+ $this->map(Ids::DARKOAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::DARK_OAK_SIGN(), $in));
+ $this->map(Ids::DARKOAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::DARK_OAK_WALL_SIGN(), $in));
+ $this->map(Ids::DARK_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::DARK_OAK_BUTTON(), $in));
+ $this->map(Ids::DARK_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::DARK_OAK_DOOR(), $in));
+ $this->map(Ids::DARK_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::DARK_OAK_FENCE_GATE(), $in));
+ $this->map(Ids::DARK_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::DARK_OAK_PRESSURE_PLATE(), $in));
+ $this->map(Ids::DARK_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::DARK_OAK_TRAPDOOR(), $in));
$this->mapLog(Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG, fn() => Blocks::DARK_OAK_LOG());
+ $this->mapLog(Ids::DARK_OAK_WOOD, Ids::STRIPPED_DARK_OAK_WOOD, fn() => Blocks::DARK_OAK_WOOD());
+ $this->mapSimple(Ids::DARK_OAK_FENCE, fn() => Blocks::DARK_OAK_FENCE());
+ $this->mapSimple(Ids::DARK_OAK_PLANKS, fn() => Blocks::DARK_OAK_PLANKS());
+ $this->mapSlab(Ids::DARK_OAK_SLAB, Ids::DARK_OAK_DOUBLE_SLAB, fn() => Blocks::DARK_OAK_SLAB());
+ $this->mapStairs(Ids::DARK_OAK_STAIRS, fn() => Blocks::DARK_OAK_STAIRS());
+
+ $this->map(Ids::JUNGLE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::JUNGLE_BUTTON(), $in));
+ $this->map(Ids::JUNGLE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::JUNGLE_DOOR(), $in));
+ $this->map(Ids::JUNGLE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::JUNGLE_FENCE_GATE(), $in));
+ $this->map(Ids::JUNGLE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::JUNGLE_PRESSURE_PLATE(), $in));
+ $this->map(Ids::JUNGLE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::JUNGLE_SIGN(), $in));
+ $this->map(Ids::JUNGLE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::JUNGLE_TRAPDOOR(), $in));
+ $this->map(Ids::JUNGLE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::JUNGLE_WALL_SIGN(), $in));
$this->mapLog(Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG, fn() => Blocks::JUNGLE_LOG());
+ $this->mapLog(Ids::JUNGLE_WOOD, Ids::STRIPPED_JUNGLE_WOOD, fn() => Blocks::JUNGLE_WOOD());
+ $this->mapSimple(Ids::JUNGLE_FENCE, fn() => Blocks::JUNGLE_FENCE());
+ $this->mapSimple(Ids::JUNGLE_PLANKS, fn() => Blocks::JUNGLE_PLANKS());
+ $this->mapSlab(Ids::JUNGLE_SLAB, Ids::JUNGLE_DOUBLE_SLAB, fn() => Blocks::JUNGLE_SLAB());
+ $this->mapStairs(Ids::JUNGLE_STAIRS, fn() => Blocks::JUNGLE_STAIRS());
+
+ $this->map(Ids::MANGROVE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::MANGROVE_BUTTON(), $in));
+ $this->map(Ids::MANGROVE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::MANGROVE_DOOR(), $in));
+ $this->map(Ids::MANGROVE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::MANGROVE_FENCE_GATE(), $in));
+ $this->map(Ids::MANGROVE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::MANGROVE_PRESSURE_PLATE(), $in));
+ $this->map(Ids::MANGROVE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::MANGROVE_SIGN(), $in));
+ $this->map(Ids::MANGROVE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::MANGROVE_TRAPDOOR(), $in));
+ $this->map(Ids::MANGROVE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::MANGROVE_WALL_SIGN(), $in));
+ $this->mapLog(Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG, fn() => Blocks::MANGROVE_LOG());
+ $this->mapSimple(Ids::MANGROVE_FENCE, fn() => Blocks::MANGROVE_FENCE());
+ $this->mapSimple(Ids::MANGROVE_PLANKS, fn() => Blocks::MANGROVE_PLANKS());
+ $this->mapSlab(Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB, fn() => Blocks::MANGROVE_SLAB());
+ $this->mapStairs(Ids::MANGROVE_STAIRS, fn() => Blocks::MANGROVE_STAIRS());
+ $this->map(Ids::MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), false, $in));
+ $this->map(Ids::STRIPPED_MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), true, $in));
+
+ //oak - due to age, many of these don't specify "oak", making for confusing reading
+ $this->map(Ids::WOODEN_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::OAK_BUTTON(), $in));
+ $this->map(Ids::WOODEN_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::OAK_DOOR(), $in));
+ $this->map(Ids::FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::OAK_FENCE_GATE(), $in));
+ $this->map(Ids::WOODEN_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::OAK_PRESSURE_PLATE(), $in));
+ $this->map(Ids::STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::OAK_SIGN(), $in));
+ $this->map(Ids::TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::OAK_TRAPDOOR(), $in));
+ $this->map(Ids::WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::OAK_WALL_SIGN(), $in));
$this->mapLog(Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG, fn() => Blocks::OAK_LOG());
+ $this->mapLog(Ids::OAK_WOOD, Ids::STRIPPED_OAK_WOOD, fn() => Blocks::OAK_WOOD());
+ $this->mapSimple(Ids::OAK_FENCE, fn() => Blocks::OAK_FENCE());
+ $this->mapSimple(Ids::OAK_PLANKS, fn() => Blocks::OAK_PLANKS());
+ $this->mapSlab(Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB, fn() => Blocks::OAK_SLAB());
+ $this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS());
+
+ $this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in));
+ $this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in));
+ $this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in));
+ $this->map(Ids::SPRUCE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::SPRUCE_PRESSURE_PLATE(), $in));
+ $this->map(Ids::SPRUCE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::SPRUCE_SIGN(), $in));
+ $this->map(Ids::SPRUCE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::SPRUCE_TRAPDOOR(), $in));
+ $this->map(Ids::SPRUCE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::SPRUCE_WALL_SIGN(), $in));
$this->mapLog(Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG, fn() => Blocks::SPRUCE_LOG());
+ $this->mapLog(Ids::SPRUCE_WOOD, Ids::STRIPPED_SPRUCE_WOOD, fn() => Blocks::SPRUCE_WOOD());
+ $this->mapSimple(Ids::SPRUCE_FENCE, fn() => Blocks::SPRUCE_FENCE());
+ $this->mapSimple(Ids::SPRUCE_PLANKS, fn() => Blocks::SPRUCE_PLANKS());
+ $this->mapSlab(Ids::SPRUCE_SLAB, Ids::SPRUCE_DOUBLE_SLAB, fn() => Blocks::SPRUCE_SLAB());
+ $this->mapStairs(Ids::SPRUCE_STAIRS, fn() => Blocks::SPRUCE_STAIRS());
+
+ $this->map(Ids::WARPED_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::WARPED_BUTTON(), $in));
+ $this->map(Ids::WARPED_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::WARPED_DOOR(), $in));
+ $this->map(Ids::WARPED_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::WARPED_FENCE_GATE(), $in));
+ $this->map(Ids::WARPED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::WARPED_PRESSURE_PLATE(), $in));
+ $this->map(Ids::WARPED_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::WARPED_SIGN(), $in));
+ $this->map(Ids::WARPED_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::WARPED_TRAPDOOR(), $in));
+ $this->map(Ids::WARPED_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::WARPED_WALL_SIGN(), $in));
+ $this->mapLog(Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE, fn() => Blocks::WARPED_HYPHAE());
+ $this->mapLog(Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM, fn() => Blocks::WARPED_STEM());
+ $this->mapSimple(Ids::WARPED_FENCE, fn() => Blocks::WARPED_FENCE());
+ $this->mapSimple(Ids::WARPED_PLANKS, fn() => Blocks::WARPED_PLANKS());
+ $this->mapSlab(Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB, fn() => Blocks::WARPED_SLAB());
+ $this->mapStairs(Ids::WARPED_STAIRS, fn() => Blocks::WARPED_STAIRS());
+ }
+
+ private function registerLeavesDeserializers() : void{
+ $this->map(Ids::ACACIA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::ACACIA_LEAVES(), $in));
+ $this->map(Ids::AZALEA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::AZALEA_LEAVES(), $in));
+ $this->map(Ids::AZALEA_LEAVES_FLOWERED, fn(Reader $in) => Helper::decodeLeaves(Blocks::FLOWERING_AZALEA_LEAVES(), $in));
+ $this->map(Ids::BIRCH_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::BIRCH_LEAVES(), $in));
+ $this->map(Ids::CHERRY_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::CHERRY_LEAVES(), $in));
+ $this->map(Ids::DARK_OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::DARK_OAK_LEAVES(), $in));
+ $this->map(Ids::JUNGLE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::JUNGLE_LEAVES(), $in));
+ $this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in));
+ $this->map(Ids::OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::OAK_LEAVES(), $in));
+ $this->map(Ids::SPRUCE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::SPRUCE_LEAVES(), $in));
+ }
+
+ private function registerSaplingDeserializers() : void{
+ foreach([
+ Ids::ACACIA_SAPLING => fn() => Blocks::ACACIA_SAPLING(),
+ Ids::BIRCH_SAPLING => fn() => Blocks::BIRCH_SAPLING(),
+ Ids::DARK_OAK_SAPLING => fn() => Blocks::DARK_OAK_SAPLING(),
+ Ids::JUNGLE_SAPLING => fn() => Blocks::JUNGLE_SAPLING(),
+ Ids::OAK_SAPLING => fn() => Blocks::OAK_SAPLING(),
+ Ids::SPRUCE_SAPLING => fn() => Blocks::SPRUCE_SAPLING(),
+ ] as $id => $getBlock){
+ $this->map($id, fn(Reader $in) => Helper::decodeSapling($getBlock(), $in));
+ }
+ }
+
+ private function registerLightDeserializers() : void{
+ foreach([
+ Ids::LIGHT_BLOCK_0 => 0,
+ Ids::LIGHT_BLOCK_1 => 1,
+ Ids::LIGHT_BLOCK_2 => 2,
+ Ids::LIGHT_BLOCK_3 => 3,
+ Ids::LIGHT_BLOCK_4 => 4,
+ Ids::LIGHT_BLOCK_5 => 5,
+ Ids::LIGHT_BLOCK_6 => 6,
+ Ids::LIGHT_BLOCK_7 => 7,
+ Ids::LIGHT_BLOCK_8 => 8,
+ Ids::LIGHT_BLOCK_9 => 9,
+ Ids::LIGHT_BLOCK_10 => 10,
+ Ids::LIGHT_BLOCK_11 => 11,
+ Ids::LIGHT_BLOCK_12 => 12,
+ Ids::LIGHT_BLOCK_13 => 13,
+ Ids::LIGHT_BLOCK_14 => 14,
+ Ids::LIGHT_BLOCK_15 => 15,
+ ] as $id => $level){
+ $this->mapSimple($id, fn() => Blocks::LIGHT()->setLightLevel($level));
+ }
+ }
+
+ private function registerMobHeadDeserializers() : void{
+ foreach([
+ Ids::CREEPER_HEAD => MobHeadType::CREEPER,
+ Ids::DRAGON_HEAD => MobHeadType::DRAGON,
+ Ids::PIGLIN_HEAD => MobHeadType::PIGLIN,
+ Ids::PLAYER_HEAD => MobHeadType::PLAYER,
+ Ids::SKELETON_SKULL => MobHeadType::SKELETON,
+ Ids::WITHER_SKELETON_SKULL => MobHeadType::WITHER_SKELETON,
+ Ids::ZOMBIE_HEAD => MobHeadType::ZOMBIE
+ ] as $id => $mobHeadType){
+ $this->map($id, fn(Reader $in) => Blocks::MOB_HEAD()->setMobHeadType($mobHeadType)->setFacing($in->readFacingWithoutDown()));
+ }
}
private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::AIR, fn() => Blocks::AIR());
$this->mapSimple(Ids::AMETHYST_BLOCK, fn() => Blocks::AMETHYST());
$this->mapSimple(Ids::ANCIENT_DEBRIS, fn() => Blocks::ANCIENT_DEBRIS());
+ $this->mapSimple(Ids::ANDESITE, fn() => Blocks::ANDESITE());
$this->mapSimple(Ids::BARRIER, fn() => Blocks::BARRIER());
$this->mapSimple(Ids::BEACON, fn() => Blocks::BEACON());
$this->mapSimple(Ids::BLACKSTONE, fn() => Blocks::BLACKSTONE());
@@ -268,12 +712,18 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::BOOKSHELF, fn() => Blocks::BOOKSHELF());
$this->mapSimple(Ids::BRICK_BLOCK, fn() => Blocks::BRICKS());
$this->mapSimple(Ids::BROWN_MUSHROOM, fn() => Blocks::BROWN_MUSHROOM());
+ $this->mapSimple(Ids::BUDDING_AMETHYST, fn() => Blocks::BUDDING_AMETHYST());
$this->mapSimple(Ids::CALCITE, fn() => Blocks::CALCITE());
$this->mapSimple(Ids::CARTOGRAPHY_TABLE, fn() => Blocks::CARTOGRAPHY_TABLE());
$this->mapSimple(Ids::CHEMICAL_HEAT, fn() => Blocks::CHEMICAL_HEAT());
$this->mapSimple(Ids::CHISELED_DEEPSLATE, fn() => Blocks::CHISELED_DEEPSLATE());
$this->mapSimple(Ids::CHISELED_NETHER_BRICKS, fn() => Blocks::CHISELED_NETHER_BRICKS());
$this->mapSimple(Ids::CHISELED_POLISHED_BLACKSTONE, fn() => Blocks::CHISELED_POLISHED_BLACKSTONE());
+ $this->mapSimple(Ids::CHISELED_RED_SANDSTONE, fn() => Blocks::CHISELED_RED_SANDSTONE());
+ $this->mapSimple(Ids::CHISELED_SANDSTONE, fn() => Blocks::CHISELED_SANDSTONE());
+ $this->mapSimple(Ids::CHISELED_STONE_BRICKS, fn() => Blocks::CHISELED_STONE_BRICKS());
+ $this->mapSimple(Ids::CHISELED_TUFF, fn() => Blocks::CHISELED_TUFF());
+ $this->mapSimple(Ids::CHISELED_TUFF_BRICKS, fn() => Blocks::CHISELED_TUFF_BRICKS());
$this->mapSimple(Ids::CHORUS_PLANT, fn() => Blocks::CHORUS_PLANT());
$this->mapSimple(Ids::CLAY, fn() => Blocks::CLAY());
$this->mapSimple(Ids::COAL_BLOCK, fn() => Blocks::COAL());
@@ -285,10 +735,13 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::CRACKED_DEEPSLATE_TILES, fn() => Blocks::CRACKED_DEEPSLATE_TILES());
$this->mapSimple(Ids::CRACKED_NETHER_BRICKS, fn() => Blocks::CRACKED_NETHER_BRICKS());
$this->mapSimple(Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS, fn() => Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS());
+ $this->mapSimple(Ids::CRACKED_STONE_BRICKS, fn() => Blocks::CRACKED_STONE_BRICKS());
$this->mapSimple(Ids::CRAFTING_TABLE, fn() => Blocks::CRAFTING_TABLE());
- $this->mapSimple(Ids::CRIMSON_FENCE, fn() => Blocks::CRIMSON_FENCE());
- $this->mapSimple(Ids::CRIMSON_PLANKS, fn() => Blocks::CRIMSON_PLANKS());
+ $this->mapSimple(Ids::CRIMSON_ROOTS, fn() => Blocks::CRIMSON_ROOTS());
$this->mapSimple(Ids::CRYING_OBSIDIAN, fn() => Blocks::CRYING_OBSIDIAN());
+ $this->mapSimple(Ids::CUT_RED_SANDSTONE, fn() => Blocks::CUT_RED_SANDSTONE());
+ $this->mapSimple(Ids::CUT_SANDSTONE, fn() => Blocks::CUT_SANDSTONE());
+ $this->mapSimple(Ids::DARK_PRISMARINE, fn() => Blocks::DARK_PRISMARINE());
$this->mapSimple(Ids::DEADBUSH, fn() => Blocks::DEAD_BUSH());
$this->mapSimple(Ids::DEEPSLATE_BRICKS, fn() => Blocks::DEEPSLATE_BRICKS());
$this->mapSimple(Ids::DEEPSLATE_COAL_ORE, fn() => Blocks::DEEPSLATE_COAL_ORE());
@@ -301,6 +754,7 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::DEEPSLATE_TILES, fn() => Blocks::DEEPSLATE_TILES());
$this->mapSimple(Ids::DIAMOND_BLOCK, fn() => Blocks::DIAMOND());
$this->mapSimple(Ids::DIAMOND_ORE, fn() => Blocks::DIAMOND_ORE());
+ $this->mapSimple(Ids::DIORITE, fn() => Blocks::DIORITE());
$this->mapSimple(Ids::DRAGON_EGG, fn() => Blocks::DRAGON_EGG());
$this->mapSimple(Ids::DRIED_KELP_BLOCK, fn() => Blocks::DRIED_KELP());
$this->mapSimple(Ids::ELEMENT_0, fn() => Blocks::ELEMENT_ZERO());
@@ -427,6 +881,7 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::ENCHANTING_TABLE, fn() => Blocks::ENCHANTING_TABLE());
$this->mapSimple(Ids::END_BRICKS, fn() => Blocks::END_STONE_BRICKS());
$this->mapSimple(Ids::END_STONE, fn() => Blocks::END_STONE());
+ $this->mapSimple(Ids::FERN, fn() => Blocks::FERN());
$this->mapSimple(Ids::FLETCHING_TABLE, fn() => Blocks::FLETCHING_TABLE());
$this->mapSimple(Ids::GILDED_BLACKSTONE, fn() => Blocks::GILDED_BLACKSTONE());
$this->mapSimple(Ids::GLASS, fn() => Blocks::GLASS());
@@ -435,7 +890,8 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::GLOWSTONE, fn() => Blocks::GLOWSTONE());
$this->mapSimple(Ids::GOLD_BLOCK, fn() => Blocks::GOLD());
$this->mapSimple(Ids::GOLD_ORE, fn() => Blocks::GOLD_ORE());
- $this->mapSimple(Ids::GRASS, fn() => Blocks::GRASS());
+ $this->mapSimple(Ids::GRANITE, fn() => Blocks::GRANITE());
+ $this->mapSimple(Ids::GRASS_BLOCK, fn() => Blocks::GRASS());
$this->mapSimple(Ids::GRASS_PATH, fn() => Blocks::GRASS_PATH());
$this->mapSimple(Ids::GRAVEL, fn() => Blocks::GRAVEL());
$this->mapSimple(Ids::HANGING_ROOTS, fn() => Blocks::HANGING_ROOTS());
@@ -444,6 +900,12 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::HARDENED_CLAY, fn() => Blocks::HARDENED_CLAY());
$this->mapSimple(Ids::HONEYCOMB_BLOCK, fn() => Blocks::HONEYCOMB());
$this->mapSimple(Ids::ICE, fn() => Blocks::ICE());
+ $this->mapSimple(Ids::INFESTED_CHISELED_STONE_BRICKS, fn() => Blocks::INFESTED_CHISELED_STONE_BRICK());
+ $this->mapSimple(Ids::INFESTED_COBBLESTONE, fn() => Blocks::INFESTED_COBBLESTONE());
+ $this->mapSimple(Ids::INFESTED_CRACKED_STONE_BRICKS, fn() => Blocks::INFESTED_CRACKED_STONE_BRICK());
+ $this->mapSimple(Ids::INFESTED_MOSSY_STONE_BRICKS, fn() => Blocks::INFESTED_MOSSY_STONE_BRICK());
+ $this->mapSimple(Ids::INFESTED_STONE, fn() => Blocks::INFESTED_STONE());
+ $this->mapSimple(Ids::INFESTED_STONE_BRICKS, fn() => Blocks::INFESTED_STONE_BRICK());
$this->mapSimple(Ids::INFO_UPDATE, fn() => Blocks::INFO_UPDATE());
$this->mapSimple(Ids::INFO_UPDATE2, fn() => Blocks::INFO_UPDATE2());
$this->mapSimple(Ids::INVISIBLE_BEDROCK, fn() => Blocks::INVISIBLE_BEDROCK());
@@ -454,12 +916,11 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::LAPIS_BLOCK, fn() => Blocks::LAPIS_LAZULI());
$this->mapSimple(Ids::LAPIS_ORE, fn() => Blocks::LAPIS_LAZULI_ORE());
$this->mapSimple(Ids::MAGMA, fn() => Blocks::MAGMA());
- $this->mapSimple(Ids::MANGROVE_FENCE, fn() => Blocks::MANGROVE_FENCE());
- $this->mapSimple(Ids::MANGROVE_PLANKS, fn() => Blocks::MANGROVE_PLANKS());
$this->mapSimple(Ids::MANGROVE_ROOTS, fn() => Blocks::MANGROVE_ROOTS());
$this->mapSimple(Ids::MELON_BLOCK, fn() => Blocks::MELON());
$this->mapSimple(Ids::MOB_SPAWNER, fn() => Blocks::MONSTER_SPAWNER());
$this->mapSimple(Ids::MOSSY_COBBLESTONE, fn() => Blocks::MOSSY_COBBLESTONE());
+ $this->mapSimple(Ids::MOSSY_STONE_BRICKS, fn() => Blocks::MOSSY_STONE_BRICKS());
$this->mapSimple(Ids::MUD, fn() => Blocks::MUD());
$this->mapSimple(Ids::MUD_BRICKS, fn() => Blocks::MUD_BRICKS());
$this->mapSimple(Ids::MYCELIUM, fn() => Blocks::MYCELIUM());
@@ -475,9 +936,15 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::PACKED_ICE, fn() => Blocks::PACKED_ICE());
$this->mapSimple(Ids::PACKED_MUD, fn() => Blocks::PACKED_MUD());
$this->mapSimple(Ids::PODZOL, fn() => Blocks::PODZOL());
+ $this->mapSimple(Ids::POLISHED_ANDESITE, fn() => Blocks::POLISHED_ANDESITE());
$this->mapSimple(Ids::POLISHED_BLACKSTONE, fn() => Blocks::POLISHED_BLACKSTONE());
$this->mapSimple(Ids::POLISHED_BLACKSTONE_BRICKS, fn() => Blocks::POLISHED_BLACKSTONE_BRICKS());
$this->mapSimple(Ids::POLISHED_DEEPSLATE, fn() => Blocks::POLISHED_DEEPSLATE());
+ $this->mapSimple(Ids::POLISHED_DIORITE, fn() => Blocks::POLISHED_DIORITE());
+ $this->mapSimple(Ids::POLISHED_GRANITE, fn() => Blocks::POLISHED_GRANITE());
+ $this->mapSimple(Ids::POLISHED_TUFF, fn() => Blocks::POLISHED_TUFF());
+ $this->mapSimple(Ids::PRISMARINE, fn() => Blocks::PRISMARINE());
+ $this->mapSimple(Ids::PRISMARINE_BRICKS, fn() => Blocks::PRISMARINE_BRICKS());
$this->mapSimple(Ids::QUARTZ_BRICKS, fn() => Blocks::QUARTZ_BRICKS());
$this->mapSimple(Ids::QUARTZ_ORE, fn() => Blocks::NETHER_QUARTZ_ORE());
$this->mapSimple(Ids::RAW_COPPER_BLOCK, fn() => Blocks::RAW_COPPER());
@@ -485,61 +952,86 @@ private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::RAW_IRON_BLOCK, fn() => Blocks::RAW_IRON());
$this->mapSimple(Ids::RED_MUSHROOM, fn() => Blocks::RED_MUSHROOM());
$this->mapSimple(Ids::RED_NETHER_BRICK, fn() => Blocks::RED_NETHER_BRICKS());
+ $this->mapSimple(Ids::RED_SAND, fn() => Blocks::RED_SAND());
+ $this->mapSimple(Ids::RED_SANDSTONE, fn() => Blocks::RED_SANDSTONE());
$this->mapSimple(Ids::REDSTONE_BLOCK, fn() => Blocks::REDSTONE());
$this->mapSimple(Ids::REINFORCED_DEEPSLATE, fn() => Blocks::REINFORCED_DEEPSLATE());
$this->mapSimple(Ids::RESERVED6, fn() => Blocks::RESERVED6());
+ $this->mapSimple(Ids::SAND, fn() => Blocks::SAND());
+ $this->mapSimple(Ids::SANDSTONE, fn() => Blocks::SANDSTONE());
$this->mapSimple(Ids::SCULK, fn() => Blocks::SCULK());
$this->mapSimple(Ids::SEA_LANTERN, fn() => Blocks::SEA_LANTERN());
+ $this->mapSimple(Ids::SHORT_GRASS, fn() => Blocks::TALL_GRASS()); //no, this is not a typo - tall_grass is now the double block, just to be confusing :(
$this->mapSimple(Ids::SHROOMLIGHT, fn() => Blocks::SHROOMLIGHT());
$this->mapSimple(Ids::SLIME, fn() => Blocks::SLIME());
$this->mapSimple(Ids::SMITHING_TABLE, fn() => Blocks::SMITHING_TABLE());
$this->mapSimple(Ids::SMOOTH_BASALT, fn() => Blocks::SMOOTH_BASALT());
+ $this->mapSimple(Ids::SMOOTH_RED_SANDSTONE, fn() => Blocks::SMOOTH_RED_SANDSTONE());
+ $this->mapSimple(Ids::SMOOTH_SANDSTONE, fn() => Blocks::SMOOTH_SANDSTONE());
$this->mapSimple(Ids::SMOOTH_STONE, fn() => Blocks::SMOOTH_STONE());
$this->mapSimple(Ids::SNOW, fn() => Blocks::SNOW());
$this->mapSimple(Ids::SOUL_SAND, fn() => Blocks::SOUL_SAND());
$this->mapSimple(Ids::SOUL_SOIL, fn() => Blocks::SOUL_SOIL());
$this->mapSimple(Ids::SPORE_BLOSSOM, fn() => Blocks::SPORE_BLOSSOM());
+ $this->mapSimple(Ids::SPONGE, fn() => Blocks::SPONGE());
+ $this->mapSimple(Ids::STONE, fn() => Blocks::STONE());
$this->mapSimple(Ids::STONECUTTER, fn() => Blocks::LEGACY_STONECUTTER());
+ $this->mapSimple(Ids::STONE_BRICKS, fn() => Blocks::STONE_BRICKS());
$this->mapSimple(Ids::TINTED_GLASS, fn() => Blocks::TINTED_GLASS());
+ $this->mapSimple(Ids::TORCHFLOWER, fn() => Blocks::TORCHFLOWER());
$this->mapSimple(Ids::TUFF, fn() => Blocks::TUFF());
+ $this->mapSimple(Ids::TUFF_BRICKS, fn() => Blocks::TUFF_BRICKS());
$this->mapSimple(Ids::UNDYED_SHULKER_BOX, fn() => Blocks::SHULKER_BOX());
- $this->mapSimple(Ids::WARPED_FENCE, fn() => Blocks::WARPED_FENCE());
- $this->mapSimple(Ids::WARPED_PLANKS, fn() => Blocks::WARPED_PLANKS());
$this->mapSimple(Ids::WARPED_WART_BLOCK, fn() => Blocks::WARPED_WART_BLOCK());
+ $this->mapSimple(Ids::WARPED_ROOTS, fn() => Blocks::WARPED_ROOTS());
$this->mapSimple(Ids::WATERLILY, fn() => Blocks::LILY_PAD());
$this->mapSimple(Ids::WEB, fn() => Blocks::COBWEB());
+ $this->mapSimple(Ids::WET_SPONGE, fn() => Blocks::SPONGE()->setWet(true));
$this->mapSimple(Ids::WITHER_ROSE, fn() => Blocks::WITHER_ROSE());
- $this->mapSimple(Ids::YELLOW_FLOWER, fn() => Blocks::DANDELION());
+ $this->mapSimple(Ids::DANDELION, fn() => Blocks::DANDELION());
+
+ $this->mapSimple(Ids::ALLIUM, fn() => Blocks::ALLIUM());
+ $this->mapSimple(Ids::CORNFLOWER, fn() => Blocks::CORNFLOWER());
+ $this->mapSimple(Ids::AZURE_BLUET, fn() => Blocks::AZURE_BLUET());
+ $this->mapSimple(Ids::LILY_OF_THE_VALLEY, fn() => Blocks::LILY_OF_THE_VALLEY());
+ $this->mapSimple(Ids::BLUE_ORCHID, fn() => Blocks::BLUE_ORCHID());
+ $this->mapSimple(Ids::OXEYE_DAISY, fn() => Blocks::OXEYE_DAISY());
+ $this->mapSimple(Ids::POPPY, fn() => Blocks::POPPY());
+ $this->mapSimple(Ids::ORANGE_TULIP, fn() => Blocks::ORANGE_TULIP());
+ $this->mapSimple(Ids::PINK_TULIP, fn() => Blocks::PINK_TULIP());
+ $this->mapSimple(Ids::RED_TULIP, fn() => Blocks::RED_TULIP());
+ $this->mapSimple(Ids::WHITE_TULIP, fn() => Blocks::WHITE_TULIP());
}
private function registerDeserializers() : void{
- $this->map(Ids::ACACIA_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::ACACIA_BUTTON(), $in));
- $this->map(Ids::ACACIA_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::ACACIA_DOOR(), $in));
- $this->map(Ids::ACACIA_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::ACACIA_FENCE_GATE(), $in));
- $this->map(Ids::ACACIA_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::ACACIA_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::ACACIA_STAIRS, fn() => Blocks::ACACIA_STAIRS());
- $this->map(Ids::ACACIA_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::ACACIA_SIGN(), $in));
- $this->map(Ids::ACACIA_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::ACACIA_TRAPDOOR(), $in));
- $this->map(Ids::ACACIA_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::ACACIA_WALL_SIGN(), $in));
$this->map(Ids::ACTIVATOR_RAIL, function(Reader $in) : Block{
return Blocks::ACTIVATOR_RAIL()
->setPowered($in->readBool(StateNames::RAIL_DATA_BIT))
->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 5));
});
+ $this->map(Ids::AMETHYST_CLUSTER, function(Reader $in) : Block{
+ return Blocks::AMETHYST_CLUSTER()
+ ->setStage(AmethystCluster::STAGE_CLUSTER)
+ ->setFacing($in->readBlockFace());
+ });
+ $this->mapSlab(Ids::ANDESITE_SLAB, Ids::ANDESITE_DOUBLE_SLAB, fn() => Blocks::ANDESITE_SLAB());
$this->mapStairs(Ids::ANDESITE_STAIRS, fn() => Blocks::ANDESITE_STAIRS());
+ $this->map(Ids::ANDESITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::ANDESITE_WALL(), $in));
$this->map(Ids::ANVIL, function(Reader $in) : Block{
return Blocks::ANVIL()
- ->setDamage(match($value = $in->readString(StateNames::DAMAGE)){
- StringValues::DAMAGE_UNDAMAGED => 0,
- StringValues::DAMAGE_SLIGHTLY_DAMAGED => 1,
- StringValues::DAMAGE_VERY_DAMAGED => 2,
- StringValues::DAMAGE_BROKEN => 0,
- default => throw $in->badValueException(StateNames::DAMAGE, $value),
- })
- ->setFacing($in->readLegacyHorizontalFacing());
+ ->setDamage(Anvil::UNDAMAGED)
+ ->setFacing($in->readCardinalHorizontalFacing());
+ });
+ $this->map(Ids::CHIPPED_ANVIL, function(Reader $in) : Block{
+ return Blocks::ANVIL()
+ ->setDamage(Anvil::SLIGHTLY_DAMAGED)
+ ->setFacing($in->readCardinalHorizontalFacing());
+ });
+ $this->map(Ids::DAMAGED_ANVIL, function(Reader $in) : Block{
+ return Blocks::ANVIL()
+ ->setDamage(Anvil::VERY_DAMAGED)
+ ->setFacing($in->readCardinalHorizontalFacing());
});
- $this->map(Ids::AZALEA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::AZALEA_LEAVES(), $in));
- $this->map(Ids::AZALEA_LEAVES_FLOWERED, fn(Reader $in) => Helper::decodeLeaves(Blocks::FLOWERING_AZALEA_LEAVES(), $in));
$this->map(Ids::BAMBOO, function(Reader $in) : Block{
return Blocks::BAMBOO()
->setLeafSize(match($value = $in->readString(StateNames::BAMBOO_LEAF_SIZE)){
@@ -556,7 +1048,6 @@ private function registerDeserializers() : void{
});
});
$this->map(Ids::BAMBOO_SAPLING, function(Reader $in) : Block{
- $in->ignored(StateNames::SAPLING_TYPE); //bug in MCPE
return Blocks::BAMBOO_SAPLING()->setReady($in->readBool(StateNames::AGE_BIT));
});
$this->map(Ids::BARREL, function(Reader $in) : Block{
@@ -585,20 +1076,28 @@ private function registerDeserializers() : void{
->setFacing($in->readLegacyHorizontalFacing())
->setAttachmentType($in->readBellAttachmentType());
});
- $this->map(Ids::BIRCH_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::BIRCH_BUTTON(), $in));
- $this->map(Ids::BIRCH_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::BIRCH_DOOR(), $in));
- $this->map(Ids::BIRCH_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::BIRCH_FENCE_GATE(), $in));
- $this->map(Ids::BIRCH_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::BIRCH_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::BIRCH_STAIRS, fn() => Blocks::BIRCH_STAIRS());
- $this->map(Ids::BIRCH_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::BIRCH_SIGN(), $in));
- $this->map(Ids::BIRCH_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::BIRCH_TRAPDOOR(), $in));
- $this->map(Ids::BIRCH_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::BIRCH_WALL_SIGN(), $in));
+ $this->map(Ids::BIG_DRIPLEAF, function(Reader $in) : Block{
+ if($in->readBool(StateNames::BIG_DRIPLEAF_HEAD)){
+ return Blocks::BIG_DRIPLEAF_HEAD()
+ ->setFacing($in->readCardinalHorizontalFacing())
+ ->setLeafState(match($type = $in->readString(StateNames::BIG_DRIPLEAF_TILT)){
+ StringValues::BIG_DRIPLEAF_TILT_NONE => DripleafState::STABLE,
+ StringValues::BIG_DRIPLEAF_TILT_UNSTABLE => DripleafState::UNSTABLE,
+ StringValues::BIG_DRIPLEAF_TILT_PARTIAL_TILT => DripleafState::PARTIAL_TILT,
+ StringValues::BIG_DRIPLEAF_TILT_FULL_TILT => DripleafState::FULL_TILT,
+ default => throw $in->badValueException(StateNames::BIG_DRIPLEAF_TILT, $type),
+ });
+ }else{
+ $in->ignored(StateNames::BIG_DRIPLEAF_TILT);
+ return Blocks::BIG_DRIPLEAF_STEM()->setFacing($in->readCardinalHorizontalFacing());
+ }
+ });
$this->mapSlab(Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB, fn() => Blocks::BLACKSTONE_SLAB());
$this->mapStairs(Ids::BLACKSTONE_STAIRS, fn() => Blocks::BLACKSTONE_STAIRS());
$this->map(Ids::BLACKSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::BLACKSTONE_WALL(), $in));
$this->map(Ids::BLAST_FURNACE, function(Reader $in) : Block{
return Blocks::BLAST_FURNACE()
- ->setFacing($in->readHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setLit(false);
});
$this->map(Ids::BONE_BLOCK, function(Reader $in) : Block{
@@ -607,11 +1106,18 @@ private function registerDeserializers() : void{
});
$this->map(Ids::BREWING_STAND, function(Reader $in) : Block{
return Blocks::BREWING_STAND()
- ->setSlot(BrewingStandSlot::EAST(), $in->readBool(StateNames::BREWING_STAND_SLOT_A_BIT))
- ->setSlot(BrewingStandSlot::SOUTHWEST(), $in->readBool(StateNames::BREWING_STAND_SLOT_B_BIT))
- ->setSlot(BrewingStandSlot::NORTHWEST(), $in->readBool(StateNames::BREWING_STAND_SLOT_C_BIT));
+ ->setSlot(BrewingStandSlot::EAST, $in->readBool(StateNames::BREWING_STAND_SLOT_A_BIT))
+ ->setSlot(BrewingStandSlot::SOUTHWEST, $in->readBool(StateNames::BREWING_STAND_SLOT_B_BIT))
+ ->setSlot(BrewingStandSlot::NORTHWEST, $in->readBool(StateNames::BREWING_STAND_SLOT_C_BIT));
});
+ $this->mapSlab(Ids::BRICK_SLAB, Ids::BRICK_DOUBLE_SLAB, fn() => Blocks::BRICK_SLAB());
$this->mapStairs(Ids::BRICK_STAIRS, fn() => Blocks::BRICK_STAIRS());
+ $this->map(Ids::BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::BRICK_WALL(), $in));
+ $this->map(Ids::MUSHROOM_STEM, fn(Reader $in) => match($in->readBoundedInt(StateNames::HUGE_MUSHROOM_BITS, 0, 15)){
+ BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM => Blocks::ALL_SIDED_MUSHROOM_STEM(),
+ BlockLegacyMetadata::MUSHROOM_BLOCK_STEM => Blocks::MUSHROOM_STEM(),
+ default => throw new BlockStateDeserializeException("This state does not exist"),
+ });
$this->map(Ids::BROWN_MUSHROOM_BLOCK, fn(Reader $in) => Helper::decodeMushroomBlock(Blocks::BROWN_MUSHROOM_BLOCK(), $in));
$this->map(Ids::CACTUS, function(Reader $in) : Block{
return Blocks::CACTUS()
@@ -621,14 +1127,15 @@ private function registerDeserializers() : void{
return Blocks::CAKE()
->setBites($in->readBoundedInt(StateNames::BITE_COUNTER, 0, 6));
});
- $this->map(Ids::CARPET, function(Reader $in) : Block{
- return Blocks::CARPET()
- ->setColor($in->readColor());
+ $this->map(Ids::CAMPFIRE, function(Reader $in) : Block{
+ return Blocks::CAMPFIRE()
+ ->setFacing($in->readCardinalHorizontalFacing())
+ ->setLit(!$in->readBool(StateNames::EXTINGUISHED));
});
$this->map(Ids::CARROTS, fn(Reader $in) => Helper::decodeCrops(Blocks::CARROTS(), $in));
$this->map(Ids::CARVED_PUMPKIN, function(Reader $in) : Block{
return Blocks::CARVED_PUMPKIN()
- ->setFacing($in->readLegacyHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
});
$this->map(Ids::CAVE_VINES, function(Reader $in) : CaveVines{
return Blocks::CAVE_VINES()
@@ -652,97 +1159,65 @@ private function registerDeserializers() : void{
return Blocks::CHAIN()
->setAxis($in->readPillarAxis());
});
- $this->map(Ids::CHEMISTRY_TABLE, function(Reader $in) : Block{
- return (match($type = $in->readString(StateNames::CHEMISTRY_TABLE_TYPE)){
- StringValues::CHEMISTRY_TABLE_TYPE_COMPOUND_CREATOR => Blocks::COMPOUND_CREATOR(),
- StringValues::CHEMISTRY_TABLE_TYPE_ELEMENT_CONSTRUCTOR => Blocks::ELEMENT_CONSTRUCTOR(),
- StringValues::CHEMISTRY_TABLE_TYPE_LAB_TABLE => Blocks::LAB_TABLE(),
- StringValues::CHEMISTRY_TABLE_TYPE_MATERIAL_REDUCER => Blocks::MATERIAL_REDUCER(),
- default => throw $in->badValueException(StateNames::CHEMISTRY_TABLE_TYPE, $type),
- })->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()));
+ $this->map(Ids::CHISELED_BOOKSHELF, function(Reader $in) : Block{
+ $block = Blocks::CHISELED_BOOKSHELF()
+ ->setFacing($in->readLegacyHorizontalFacing());
+
+ //we don't use API constant for bounds here as the data bounds might be different to what we support internally
+ $flags = $in->readBoundedInt(StateNames::BOOKS_STORED, 0, (1 << 6) - 1);
+ foreach(ChiseledBookshelfSlot::cases() as $slot){
+ $block->setSlot($slot, ($flags & (1 << $slot->value)) !== 0);
+ }
+
+ return $block;
+ });
+ $this->map(Ids::CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE));
+ $this->map(Ids::CHISELED_QUARTZ_BLOCK, function(Reader $in) : Block{
+ return Blocks::CHISELED_QUARTZ()
+ ->setAxis($in->readPillarAxis());
});
$this->map(Ids::CHEST, function(Reader $in) : Block{
return Blocks::CHEST()
- ->setFacing($in->readHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
});
$this->map(Ids::CHORUS_FLOWER, function(Reader $in) : Block{
return Blocks::CHORUS_FLOWER()
->setAge($in->readBoundedInt(StateNames::AGE, ChorusFlower::MIN_AGE, ChorusFlower::MAX_AGE));
});
+ $this->map(Ids::COARSE_DIRT, fn() => Blocks::DIRT()->setDirtType(DirtType::COARSE));
$this->mapSlab(Ids::COBBLED_DEEPSLATE_SLAB, Ids::COBBLED_DEEPSLATE_DOUBLE_SLAB, fn() => Blocks::COBBLED_DEEPSLATE_SLAB());
$this->mapStairs(Ids::COBBLED_DEEPSLATE_STAIRS, fn() => Blocks::COBBLED_DEEPSLATE_STAIRS());
$this->map(Ids::COBBLED_DEEPSLATE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::COBBLED_DEEPSLATE_WALL(), $in));
- $this->map(Ids::COBBLESTONE_WALL, fn(Reader $in) => Helper::mapLegacyWallType($in));
+ $this->mapSlab(Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB, fn() => Blocks::COBBLESTONE_SLAB());
+ $this->map(Ids::COBBLESTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::COBBLESTONE_WALL(), $in));
$this->map(Ids::COCOA, function(Reader $in) : Block{
return Blocks::COCOA_POD()
->setAge($in->readBoundedInt(StateNames::AGE, 0, 2))
->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()));
});
- $this->map(Ids::COLORED_TORCH_BP, function(Reader $in) : Block{
- return $in->readBool(StateNames::COLOR_BIT) ?
- Blocks::PURPLE_TORCH()->setFacing($in->readTorchFacing()) :
- Blocks::BLUE_TORCH()->setFacing($in->readTorchFacing());
- });
- $this->map(Ids::COLORED_TORCH_RG, function(Reader $in) : Block{
- return $in->readBool(StateNames::COLOR_BIT) ?
- Blocks::GREEN_TORCH()->setFacing($in->readTorchFacing()) :
- Blocks::RED_TORCH()->setFacing($in->readTorchFacing());
- });
- $this->map(Ids::CONCRETE, function(Reader $in) : Block{
- return Blocks::CONCRETE()
- ->setColor($in->readColor());
- });
- $this->map(Ids::CONCRETE_POWDER, function(Reader $in) : Block{
- return Blocks::CONCRETE_POWDER()
- ->setColor($in->readColor());
- });
- $this->map(Ids::COPPER_BLOCK, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::NONE()));
- $this->map(Ids::CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE()));
- $this->mapSlab(Ids::CUT_COPPER_SLAB, Ids::DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE()));
- $this->mapStairs(Ids::CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE()));
- $this->map(Ids::CORAL, function(Reader $in) : Block{
- return Blocks::CORAL()
- ->setCoralType($in->readCoralType())
- ->setDead($in->readBool(StateNames::DEAD_BIT));
- });
- $this->map(Ids::CORAL_BLOCK, function(Reader $in) : Block{
- return Blocks::CORAL_BLOCK()
- ->setCoralType($in->readCoralType())
- ->setDead($in->readBool(StateNames::DEAD_BIT));
- });
- $this->map(Ids::CORAL_FAN, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN(), $in)
- ->setDead(false));
- $this->map(Ids::CORAL_FAN_DEAD, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN(), $in)
- ->setDead(true));
- $this->map(Ids::CORAL_FAN_HANG, fn(Reader $in) => Helper::decodeWallCoralFan(Blocks::WALL_CORAL_FAN(), $in)
- ->setCoralType($in->readBool(StateNames::CORAL_HANG_TYPE_BIT) ? CoralType::BRAIN() : CoralType::TUBE()));
- $this->map(Ids::CORAL_FAN_HANG2, fn(Reader $in) => Helper::decodeWallCoralFan(Blocks::WALL_CORAL_FAN(), $in)
- ->setCoralType($in->readBool(StateNames::CORAL_HANG_TYPE_BIT) ? CoralType::FIRE() : CoralType::BUBBLE()));
- $this->map(Ids::CORAL_FAN_HANG3, function(Reader $in) : Block{
- $in->ignored(StateNames::CORAL_HANG_TYPE_BIT); //the game always writes this, even though it's not used
- return Helper::decodeWallCoralFan(Blocks::WALL_CORAL_FAN(), $in)
- ->setCoralType(CoralType::HORN());
+ $this->map(Ids::COLORED_TORCH_BLUE, fn(Reader $in) => Blocks::BLUE_TORCH()->setFacing($in->readTorchFacing()));
+ $this->map(Ids::COLORED_TORCH_GREEN, fn(Reader $in) => Blocks::GREEN_TORCH()->setFacing($in->readTorchFacing()));
+ $this->map(Ids::COLORED_TORCH_PURPLE, fn(Reader $in) => Blocks::PURPLE_TORCH()->setFacing($in->readTorchFacing()));
+ $this->map(Ids::COLORED_TORCH_RED, fn(Reader $in) => Blocks::RED_TORCH()->setFacing($in->readTorchFacing()));
+ $this->map(Ids::COMPOUND_CREATOR, fn(Reader $in) => Blocks::COMPOUND_CREATOR()
+ ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()))
+ );
+ $this->map(Ids::COPPER_BLOCK, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::NONE));
+ $this->map(Ids::COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
});
- $this->map(Ids::CRIMSON_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CRIMSON_BUTTON(), $in));
- $this->map(Ids::CRIMSON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CRIMSON_DOOR(), $in));
- $this->mapSlab(Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB, fn() => Blocks::CRIMSON_SLAB());
- $this->map(Ids::CRIMSON_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CRIMSON_FENCE_GATE(), $in));
- $this->map(Ids::CRIMSON_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_HYPHAE(), false, $in));
- $this->map(Ids::CRIMSON_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CRIMSON_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::CRIMSON_STAIRS, fn() => Blocks::CRIMSON_STAIRS());
- $this->map(Ids::CRIMSON_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CRIMSON_SIGN(), $in));
- $this->map(Ids::CRIMSON_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_STEM(), false, $in));
- $this->map(Ids::CRIMSON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CRIMSON_TRAPDOOR(), $in));
- $this->map(Ids::CRIMSON_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CRIMSON_WALL_SIGN(), $in));
- $this->map(Ids::DARK_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::DARK_OAK_BUTTON(), $in));
- $this->map(Ids::DARK_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::DARK_OAK_DOOR(), $in));
- $this->map(Ids::DARK_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::DARK_OAK_FENCE_GATE(), $in));
- $this->map(Ids::DARK_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::DARK_OAK_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::DARK_OAK_STAIRS, fn() => Blocks::DARK_OAK_STAIRS());
- $this->map(Ids::DARK_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::DARK_OAK_TRAPDOOR(), $in));
+ $this->map(Ids::COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in));
+ $this->map(Ids::COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE));
+ $this->map(Ids::COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in));
+ $this->map(Ids::CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE));
+ $this->mapSlab(Ids::CUT_COPPER_SLAB, Ids::DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE));
+ $this->mapStairs(Ids::CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE));
+ $this->mapSlab(Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_RED_SANDSTONE_SLAB());
+ $this->mapSlab(Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_SANDSTONE_SLAB());
+ $this->mapSlab(Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB, fn() => Blocks::DARK_PRISMARINE_SLAB());
$this->mapStairs(Ids::DARK_PRISMARINE_STAIRS, fn() => Blocks::DARK_PRISMARINE_STAIRS());
- $this->map(Ids::DARKOAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::DARK_OAK_SIGN(), $in));
- $this->map(Ids::DARKOAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::DARK_OAK_WALL_SIGN(), $in));
$this->map(Ids::DAYLIGHT_DETECTOR, fn(Reader $in) => Helper::decodeDaylightSensor(Blocks::DAYLIGHT_SENSOR(), $in)
->setInverted(false));
$this->map(Ids::DAYLIGHT_DETECTOR_INVERTED, fn(Reader $in) => Helper::decodeDaylightSensor(Blocks::DAYLIGHT_SENSOR(), $in)
@@ -763,50 +1238,53 @@ private function registerDeserializers() : void{
->setActivated($in->readBool(StateNames::RAIL_DATA_BIT))
->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 5));
});
+ $this->mapSlab(Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB, fn() => Blocks::DIORITE_SLAB());
$this->mapStairs(Ids::DIORITE_STAIRS, fn() => Blocks::DIORITE_STAIRS());
- $this->map(Ids::DIRT, function(Reader $in) : Block{
- return Blocks::DIRT()
- ->setDirtType(match($value = $in->readString(StateNames::DIRT_TYPE)){
- StringValues::DIRT_TYPE_NORMAL => DirtType::NORMAL(),
- StringValues::DIRT_TYPE_COARSE => DirtType::COARSE(),
- default => throw $in->badValueException(StateNames::DIRT_TYPE, $value),
- });
- });
- $this->map(Ids::DIRT_WITH_ROOTS, fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED()));
- $this->map(Ids::DOUBLE_PLANT, function(Reader $in) : Block{
- return (match($type = $in->readString(StateNames::DOUBLE_PLANT_TYPE)){
- StringValues::DOUBLE_PLANT_TYPE_FERN => Blocks::LARGE_FERN(),
- StringValues::DOUBLE_PLANT_TYPE_GRASS => Blocks::DOUBLE_TALLGRASS(),
- StringValues::DOUBLE_PLANT_TYPE_PAEONIA => Blocks::PEONY(),
- StringValues::DOUBLE_PLANT_TYPE_ROSE => Blocks::ROSE_BUSH(),
- StringValues::DOUBLE_PLANT_TYPE_SUNFLOWER => Blocks::SUNFLOWER(),
- StringValues::DOUBLE_PLANT_TYPE_SYRINGA => Blocks::LILAC(),
- default => throw $in->badValueException(StateNames::DOUBLE_PLANT_TYPE, $type),
- })->setTop($in->readBool(StateNames::UPPER_BLOCK_BIT));
- });
+ $this->map(Ids::DIORITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::DIORITE_WALL(), $in));
+ $this->map(Ids::DIRT, fn() => Blocks::DIRT()->setDirtType(DirtType::NORMAL));
+ $this->map(Ids::DIRT_WITH_ROOTS, fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED));
+ $this->map(Ids::LARGE_FERN, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::LARGE_FERN(), $in));
+ $this->map(Ids::TALL_GRASS, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::DOUBLE_TALLGRASS(), $in));
+ $this->map(Ids::PEONY, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::PEONY(), $in));
+ $this->map(Ids::ROSE_BUSH, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::ROSE_BUSH(), $in));
+ $this->map(Ids::SUNFLOWER, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::SUNFLOWER(), $in));
+ $this->map(Ids::LILAC, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::LILAC(), $in));
+ $this->map(Ids::ELEMENT_CONSTRUCTOR, fn(Reader $in) => Blocks::ELEMENT_CONSTRUCTOR()
+ ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()))
+ );
$this->mapStairs(Ids::END_BRICK_STAIRS, fn() => Blocks::END_STONE_BRICK_STAIRS());
+ $this->map(Ids::END_STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::END_STONE_BRICK_WALL(), $in));
$this->map(Ids::END_PORTAL_FRAME, function(Reader $in) : Block{
return Blocks::END_PORTAL_FRAME()
->setEye($in->readBool(StateNames::END_PORTAL_EYE_BIT))
- ->setFacing($in->readLegacyHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
});
$this->map(Ids::END_ROD, function(Reader $in) : Block{
return Blocks::END_ROD()
->setFacing($in->readEndRodFacingDirection());
});
+ $this->mapSlab(Ids::END_STONE_BRICK_SLAB, Ids::END_STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::END_STONE_BRICK_SLAB());
$this->map(Ids::ENDER_CHEST, function(Reader $in) : Block{
return Blocks::ENDER_CHEST()
- ->setFacing($in->readHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
+ });
+ $this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED));
+ $this->map(Ids::EXPOSED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED));
+ $this->map(Ids::EXPOSED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED));
+ $this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED));
+ $this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED));
+ $this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED));
+ $this->map(Ids::EXPOSED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
});
- $this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED()));
- $this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED()));
- $this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED()));
- $this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED()));
+ $this->map(Ids::EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in));
+ $this->map(Ids::EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in));
$this->map(Ids::FARMLAND, function(Reader $in) : Block{
return Blocks::FARMLAND()
->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7));
});
- $this->map(Ids::FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::OAK_FENCE_GATE(), $in));
$this->map(Ids::FIRE, function(Reader $in) : Block{
return Blocks::FIRE()
->setAge($in->readBoundedInt(StateNames::AGE, 0, 15));
@@ -824,7 +1302,7 @@ private function registerDeserializers() : void{
});
$this->map(Ids::FURNACE, function(Reader $in) : Block{
return Blocks::FURNACE()
- ->setFacing($in->readHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setLit(false);
});
$this->map(Ids::GLOW_LICHEN, fn(Reader $in) => Blocks::GLOW_LICHEN()->setFaces($in->readFacingFlags()));
@@ -834,15 +1312,9 @@ private function registerDeserializers() : void{
->setPowered($in->readBool(StateNames::RAIL_DATA_BIT))
->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 5));
});
+ $this->mapSlab(Ids::GRANITE_SLAB, Ids::GRANITE_DOUBLE_SLAB, fn() => Blocks::GRANITE_SLAB());
$this->mapStairs(Ids::GRANITE_STAIRS, fn() => Blocks::GRANITE_STAIRS());
- $this->map(Ids::HARD_STAINED_GLASS, function(Reader $in) : Block{
- return Blocks::STAINED_HARDENED_GLASS()
- ->setColor($in->readColor());
- });
- $this->map(Ids::HARD_STAINED_GLASS_PANE, function(Reader $in) : Block{
- return Blocks::STAINED_HARDENED_GLASS_PANE()
- ->setColor($in->readColor());
- });
+ $this->map(Ids::GRANITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::GRANITE_WALL(), $in));
$this->map(Ids::HAY_BLOCK, function(Reader $in) : Block{
$in->ignored(StateNames::DEPRECATED);
return Blocks::HAY_BALE()->setAxis($in->readPillarAxis());
@@ -855,14 +1327,9 @@ private function registerDeserializers() : void{
});
$this->map(Ids::IRON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::IRON_DOOR(), $in));
$this->map(Ids::IRON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::IRON_TRAPDOOR(), $in));
- $this->map(Ids::JUNGLE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::JUNGLE_BUTTON(), $in));
- $this->map(Ids::JUNGLE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::JUNGLE_DOOR(), $in));
- $this->map(Ids::JUNGLE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::JUNGLE_FENCE_GATE(), $in));
- $this->map(Ids::JUNGLE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::JUNGLE_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::JUNGLE_STAIRS, fn() => Blocks::JUNGLE_STAIRS());
- $this->map(Ids::JUNGLE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::JUNGLE_SIGN(), $in));
- $this->map(Ids::JUNGLE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::JUNGLE_TRAPDOOR(), $in));
- $this->map(Ids::JUNGLE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::JUNGLE_WALL_SIGN(), $in));
+ $this->map(Ids::LAB_TABLE, fn(Reader $in) => Blocks::LAB_TABLE()
+ ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()))
+ );
$this->map(Ids::LADDER, function(Reader $in) : Block{
return Blocks::LADDER()
->setFacing($in->readHorizontalFacing());
@@ -871,43 +1338,32 @@ private function registerDeserializers() : void{
return Blocks::LANTERN()
->setHanging($in->readBool(StateNames::HANGING));
});
+ $this->map(Ids::LARGE_AMETHYST_BUD, function(Reader $in) : Block{
+ return Blocks::AMETHYST_CLUSTER()
+ ->setStage(AmethystCluster::STAGE_LARGE_BUD)
+ ->setFacing($in->readBlockFace());
+ });
$this->map(Ids::LAVA, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::LAVA(), $in));
- $this->map(Ids::LEAVES, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::OLD_LEAF_TYPE)){
- StringValues::OLD_LEAF_TYPE_BIRCH => Blocks::BIRCH_LEAVES(),
- StringValues::OLD_LEAF_TYPE_JUNGLE => Blocks::JUNGLE_LEAVES(),
- StringValues::OLD_LEAF_TYPE_OAK => Blocks::OAK_LEAVES(),
- StringValues::OLD_LEAF_TYPE_SPRUCE => Blocks::SPRUCE_LEAVES(),
- default => throw $in->badValueException(StateNames::OLD_LEAF_TYPE, $type),
- }, $in));
- $this->map(Ids::LEAVES2, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::NEW_LEAF_TYPE)){
- StringValues::NEW_LEAF_TYPE_ACACIA => Blocks::ACACIA_LEAVES(),
- StringValues::NEW_LEAF_TYPE_DARK_OAK => Blocks::DARK_OAK_LEAVES(),
- default => throw $in->badValueException(StateNames::NEW_LEAF_TYPE, $type),
- }, $in));
$this->map(Ids::LECTERN, function(Reader $in) : Block{
return Blocks::LECTERN()
- ->setFacing($in->readLegacyHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setProducingSignal($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::LEVER, function(Reader $in) : Block{
return Blocks::LEVER()
->setActivated($in->readBool(StateNames::OPEN_BIT))
->setFacing(match($value = $in->readString(StateNames::LEVER_DIRECTION)){
- StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH => LeverFacing::DOWN_AXIS_Z(),
- StringValues::LEVER_DIRECTION_DOWN_EAST_WEST => LeverFacing::DOWN_AXIS_X(),
- StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH => LeverFacing::UP_AXIS_Z(),
- StringValues::LEVER_DIRECTION_UP_EAST_WEST => LeverFacing::UP_AXIS_X(),
- StringValues::LEVER_DIRECTION_NORTH => LeverFacing::NORTH(),
- StringValues::LEVER_DIRECTION_SOUTH => LeverFacing::SOUTH(),
- StringValues::LEVER_DIRECTION_WEST => LeverFacing::WEST(),
- StringValues::LEVER_DIRECTION_EAST => LeverFacing::EAST(),
+ StringValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH => LeverFacing::DOWN_AXIS_Z,
+ StringValues::LEVER_DIRECTION_DOWN_EAST_WEST => LeverFacing::DOWN_AXIS_X,
+ StringValues::LEVER_DIRECTION_UP_NORTH_SOUTH => LeverFacing::UP_AXIS_Z,
+ StringValues::LEVER_DIRECTION_UP_EAST_WEST => LeverFacing::UP_AXIS_X,
+ StringValues::LEVER_DIRECTION_NORTH => LeverFacing::NORTH,
+ StringValues::LEVER_DIRECTION_SOUTH => LeverFacing::SOUTH,
+ StringValues::LEVER_DIRECTION_WEST => LeverFacing::WEST,
+ StringValues::LEVER_DIRECTION_EAST => LeverFacing::EAST,
default => throw $in->badValueException(StateNames::LEVER_DIRECTION, $value),
});
});
- $this->map(Ids::LIGHT_BLOCK, function(Reader $in) : Block{
- return Blocks::LIGHT()
- ->setLightLevel($in->readBoundedInt(StateNames::BLOCK_LIGHT_LEVEL, Light::MIN_LIGHT_LEVEL, Light::MAX_LIGHT_LEVEL));
- });
$this->map(Ids::LIGHTNING_ROD, function(Reader $in) : Block{
return Blocks::LIGHTNING_ROD()
->setFacing($in->readFacingDirection());
@@ -915,18 +1371,18 @@ private function registerDeserializers() : void{
$this->map(Ids::LIGHT_WEIGHTED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeWeightedPressurePlate(Blocks::WEIGHTED_PRESSURE_PLATE_LIGHT(), $in));
$this->map(Ids::LIT_BLAST_FURNACE, function(Reader $in) : Block{
return Blocks::BLAST_FURNACE()
- ->setFacing($in->readHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setLit(true);
});
$this->map(Ids::LIT_DEEPSLATE_REDSTONE_ORE, fn() => Blocks::DEEPSLATE_REDSTONE_ORE()->setLit(true));
$this->map(Ids::LIT_FURNACE, function(Reader $in) : Block{
return Blocks::FURNACE()
- ->setFacing($in->readHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setLit(true);
});
$this->map(Ids::LIT_PUMPKIN, function(Reader $in) : Block{
return Blocks::LIT_PUMPKIN()
- ->setFacing($in->readLegacyHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
});
$this->map(Ids::LIT_REDSTONE_LAMP, function() : Block{
return Blocks::REDSTONE_LAMP()
@@ -938,42 +1394,28 @@ private function registerDeserializers() : void{
});
$this->map(Ids::LIT_SMOKER, function(Reader $in) : Block{
return Blocks::SMOKER()
- ->setFacing($in->readHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setLit(true);
});
$this->map(Ids::LOOM, function(Reader $in) : Block{
return Blocks::LOOM()
->setFacing($in->readLegacyHorizontalFacing());
});
- $this->map(Ids::MANGROVE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::MANGROVE_BUTTON(), $in));
- $this->map(Ids::MANGROVE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::MANGROVE_DOOR(), $in));
- $this->mapSlab(Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB, fn() => Blocks::MANGROVE_SLAB());
- $this->map(Ids::MANGROVE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::MANGROVE_FENCE_GATE(), $in));
- $this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in));
- $this->map(Ids::MANGROVE_LOG, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_LOG(), false, $in));
- $this->map(Ids::MANGROVE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::MANGROVE_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::MANGROVE_STAIRS, fn() => Blocks::MANGROVE_STAIRS());
- $this->map(Ids::MANGROVE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::MANGROVE_SIGN(), $in));
- $this->map(Ids::MANGROVE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::MANGROVE_TRAPDOOR(), $in));
- $this->map(Ids::MANGROVE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::MANGROVE_WALL_SIGN(), $in));
- $this->map(Ids::MANGROVE_WOOD, function(Reader $in){
- $in->ignored(StateNames::STRIPPED_BIT); //this is also ignored by vanilla
- return Helper::decodeLog(Blocks::MANGROVE_WOOD(), false, $in);
+ $this->map(Ids::MATERIAL_REDUCER, fn(Reader $in) => Blocks::MATERIAL_REDUCER()
+ ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()))
+ );
+ $this->map(Ids::MEDIUM_AMETHYST_BUD, function(Reader $in) : Block{
+ return Blocks::AMETHYST_CLUSTER()
+ ->setStage(AmethystCluster::STAGE_MEDIUM_BUD)
+ ->setFacing($in->readBlockFace());
});
$this->map(Ids::MELON_STEM, fn(Reader $in) => Helper::decodeStem(Blocks::MELON_STEM(), $in));
- $this->map(Ids::MONSTER_EGG, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::MONSTER_EGG_STONE_TYPE)){
- StringValues::MONSTER_EGG_STONE_TYPE_CHISELED_STONE_BRICK => Blocks::INFESTED_CHISELED_STONE_BRICK(),
- StringValues::MONSTER_EGG_STONE_TYPE_COBBLESTONE => Blocks::INFESTED_COBBLESTONE(),
- StringValues::MONSTER_EGG_STONE_TYPE_CRACKED_STONE_BRICK => Blocks::INFESTED_CRACKED_STONE_BRICK(),
- StringValues::MONSTER_EGG_STONE_TYPE_MOSSY_STONE_BRICK => Blocks::INFESTED_MOSSY_STONE_BRICK(),
- StringValues::MONSTER_EGG_STONE_TYPE_STONE => Blocks::INFESTED_STONE(),
- StringValues::MONSTER_EGG_STONE_TYPE_STONE_BRICK => Blocks::INFESTED_STONE_BRICK(),
- default => throw $in->badValueException(StateNames::MONSTER_EGG_STONE_TYPE, $type),
- };
- });
+ $this->mapSlab(Ids::MOSSY_COBBLESTONE_SLAB, Ids::MOSSY_COBBLESTONE_DOUBLE_SLAB, fn() => Blocks::MOSSY_COBBLESTONE_SLAB());
$this->mapStairs(Ids::MOSSY_COBBLESTONE_STAIRS, fn() => Blocks::MOSSY_COBBLESTONE_STAIRS());
+ $this->map(Ids::MOSSY_COBBLESTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MOSSY_COBBLESTONE_WALL(), $in));
+ $this->mapSlab(Ids::MOSSY_STONE_BRICK_SLAB, Ids::MOSSY_STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::MOSSY_STONE_BRICK_SLAB());
$this->mapStairs(Ids::MOSSY_STONE_BRICK_STAIRS, fn() => Blocks::MOSSY_STONE_BRICK_STAIRS());
+ $this->map(Ids::MOSSY_STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MOSSY_STONE_BRICK_WALL(), $in));
$this->mapSlab(Ids::MUD_BRICK_SLAB, Ids::MUD_BRICK_DOUBLE_SLAB, fn() => Blocks::MUD_BRICK_SLAB());
$this->mapStairs(Ids::MUD_BRICK_STAIRS, fn() => Blocks::MUD_BRICK_STAIRS());
$this->map(Ids::MUD_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MUD_BRICK_WALL(), $in));
@@ -981,30 +1423,55 @@ private function registerDeserializers() : void{
return Blocks::MUDDY_MANGROVE_ROOTS()
->setAxis($in->readPillarAxis());
});
+ $this->mapSlab(Ids::NETHER_BRICK_SLAB, Ids::NETHER_BRICK_DOUBLE_SLAB, fn() => Blocks::NETHER_BRICK_SLAB());
$this->mapStairs(Ids::NETHER_BRICK_STAIRS, fn() => Blocks::NETHER_BRICK_STAIRS());
+ $this->map(Ids::NETHER_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::NETHER_BRICK_WALL(), $in));
$this->map(Ids::NETHER_WART, function(Reader $in) : Block{
return Blocks::NETHER_WART()
->setAge($in->readBoundedInt(StateNames::AGE, 0, 3));
});
+ $this->mapSlab(Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB, fn() => Blocks::STONE_SLAB());
$this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS());
- $this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS());
- $this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE())->setAxis($in->readPillarAxis()));
- $this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED()));
- $this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED()));
- $this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED()));
- $this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED()));
- $this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT())->setAxis($in->readPillarAxis()));
- $this->map(Ids::PLANKS, function(Reader $in) : Block{
- return match($woodName = $in->readString(StateNames::WOOD_TYPE)){
- StringValues::WOOD_TYPE_OAK => Blocks::OAK_PLANKS(),
- StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_PLANKS(),
- StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_PLANKS(),
- StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_PLANKS(),
- StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_PLANKS(),
- StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_PLANKS(),
- default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodName),
- };
+ $this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis()));
+ $this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::OXIDIZED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED));
+ $this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED));
+ $this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::OXIDIZED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
});
+ $this->map(Ids::OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in));
+ $this->map(Ids::OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in));
+ $this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis()));
+ $this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB());
+ $this->map(Ids::PINK_PETALS, function(Reader $in) : Block{
+ //Pink petals only uses 0-3, but GROWTH state can go up to 7
+ $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7);
+ return Blocks::PINK_PETALS()
+ ->setFacing($in->readCardinalHorizontalFacing())
+ ->setCount(min($growth + 1, PinkPetals::MAX_COUNT));
+ });
+ $this->map(Ids::PITCHER_CROP, function(Reader $in) : Block{
+ $growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7);
+ $top = $in->readBool(StateNames::UPPER_BLOCK_BIT);
+ if($growth <= PitcherCrop::MAX_AGE){
+ //top pitcher crop with age 0-2 is an invalid state
+ //only the bottom half should exist in this case
+ return $top ? Blocks::AIR() : Blocks::PITCHER_CROP()->setAge($growth);
+ }
+ return Blocks::DOUBLE_PITCHER_CROP()
+ ->setAge(min($growth - PitcherCrop::MAX_AGE - 1, DoublePitcherCrop::MAX_AGE))
+ ->setTop($top);
+ });
+ $this->map(Ids::PITCHER_PLANT, function(Reader $in) : Block{
+ return Blocks::PITCHER_PLANT()
+ ->setTop($in->readBool(StateNames::UPPER_BLOCK_BIT));
+ });
+ $this->mapSlab(Ids::POLISHED_ANDESITE_SLAB, Ids::POLISHED_ANDESITE_DOUBLE_SLAB, fn() => Blocks::POLISHED_ANDESITE_SLAB());
$this->mapStairs(Ids::POLISHED_ANDESITE_STAIRS, fn() => Blocks::POLISHED_ANDESITE_STAIRS());
$this->map(Ids::POLISHED_BASALT, function(Reader $in) : Block{
return Blocks::POLISHED_BASALT()
@@ -1021,8 +1488,13 @@ private function registerDeserializers() : void{
$this->mapSlab(Ids::POLISHED_DEEPSLATE_SLAB, Ids::POLISHED_DEEPSLATE_DOUBLE_SLAB, fn() => Blocks::POLISHED_DEEPSLATE_SLAB());
$this->mapStairs(Ids::POLISHED_DEEPSLATE_STAIRS, fn() => Blocks::POLISHED_DEEPSLATE_STAIRS());
$this->map(Ids::POLISHED_DEEPSLATE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_DEEPSLATE_WALL(), $in));
+ $this->mapSlab(Ids::POLISHED_DIORITE_SLAB, Ids::POLISHED_DIORITE_DOUBLE_SLAB, fn() => Blocks::POLISHED_DIORITE_SLAB());
$this->mapStairs(Ids::POLISHED_DIORITE_STAIRS, fn() => Blocks::POLISHED_DIORITE_STAIRS());
+ $this->mapSlab(Ids::POLISHED_GRANITE_SLAB, Ids::POLISHED_GRANITE_DOUBLE_SLAB, fn() => Blocks::POLISHED_GRANITE_SLAB());
$this->mapStairs(Ids::POLISHED_GRANITE_STAIRS, fn() => Blocks::POLISHED_GRANITE_STAIRS());
+ $this->mapSlab(Ids::POLISHED_TUFF_SLAB, Ids::POLISHED_TUFF_DOUBLE_SLAB, fn() => Blocks::POLISHED_TUFF_SLAB());
+ $this->mapStairs(Ids::POLISHED_TUFF_STAIRS, fn() => Blocks::POLISHED_TUFF_STAIRS());
+ $this->map(Ids::POLISHED_TUFF_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_TUFF_WALL(), $in));
$this->map(Ids::PORTAL, function(Reader $in) : Block{
return Blocks::NETHER_PORTAL()
->setAxis(match($value = $in->readString(StateNames::PORTAL_AXIS)){
@@ -1036,85 +1508,44 @@ private function registerDeserializers() : void{
$this->map(Ids::POWERED_COMPARATOR, fn(Reader $in) => Helper::decodeComparator(Blocks::REDSTONE_COMPARATOR(), $in));
$this->map(Ids::POWERED_REPEATER, fn(Reader $in) => Helper::decodeRepeater(Blocks::REDSTONE_REPEATER(), $in)
->setPowered(true));
- $this->map(Ids::PRISMARINE, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::PRISMARINE_BLOCK_TYPE)){
- StringValues::PRISMARINE_BLOCK_TYPE_BRICKS => Blocks::PRISMARINE_BRICKS(),
- StringValues::PRISMARINE_BLOCK_TYPE_DARK => Blocks::DARK_PRISMARINE(),
- StringValues::PRISMARINE_BLOCK_TYPE_DEFAULT => Blocks::PRISMARINE(),
- default => throw $in->badValueException(StateNames::PRISMARINE_BLOCK_TYPE, $type),
- };
- });
+ $this->mapSlab(Ids::PRISMARINE_BRICK_SLAB, Ids::PRISMARINE_BRICK_DOUBLE_SLAB, fn() => Blocks::PRISMARINE_BRICKS_SLAB());
$this->mapStairs(Ids::PRISMARINE_BRICKS_STAIRS, fn() => Blocks::PRISMARINE_BRICKS_STAIRS());
+ $this->map(Ids::PRISMARINE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::PRISMARINE_WALL(), $in));
+ $this->mapSlab(Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB, fn() => Blocks::PRISMARINE_SLAB());
$this->mapStairs(Ids::PRISMARINE_STAIRS, fn() => Blocks::PRISMARINE_STAIRS());
$this->map(Ids::PUMPKIN, function(Reader $in) : Block{
- $in->ignored(StateNames::DIRECTION); //obsolete
+ $in->ignored(StateNames::MC_CARDINAL_DIRECTION); //obsolete
return Blocks::PUMPKIN();
});
$this->map(Ids::PUMPKIN_STEM, fn(Reader $in) => Helper::decodeStem(Blocks::PUMPKIN_STEM(), $in));
$this->map(Ids::PURPUR_BLOCK, function(Reader $in) : Block{
- $type = $in->readString(StateNames::CHISEL_TYPE);
- if($type === StringValues::CHISEL_TYPE_LINES){
- return Blocks::PURPUR_PILLAR()->setAxis($in->readPillarAxis());
- }else{
- $in->ignored(StateNames::PILLAR_AXIS); //axis only applies to pillars
- return match($type){
- StringValues::CHISEL_TYPE_CHISELED, //TODO: bug in MCPE
- StringValues::CHISEL_TYPE_SMOOTH, //TODO: bug in MCPE
- StringValues::CHISEL_TYPE_DEFAULT => Blocks::PURPUR(),
- default => throw $in->badValueException(StateNames::CHISEL_TYPE, $type),
- };
- }
+ $in->ignored(StateNames::PILLAR_AXIS); //???
+ return Blocks::PURPUR();
});
+ $this->map(Ids::PURPUR_PILLAR, fn(Reader $in) => Blocks::PURPUR_PILLAR()->setAxis($in->readPillarAxis()));
+ $this->mapSlab(Ids::PURPUR_SLAB, Ids::PURPUR_DOUBLE_SLAB, fn() => Blocks::PURPUR_SLAB());
$this->mapStairs(Ids::PURPUR_STAIRS, fn() => Blocks::PURPUR_STAIRS());
- $this->map(Ids::QUARTZ_BLOCK, function(Reader $in) : Block{
- switch($type = $in->readString(StateNames::CHISEL_TYPE)){
- case StringValues::CHISEL_TYPE_CHISELED:
- return Blocks::CHISELED_QUARTZ()->setAxis($in->readPillarAxis());
- case StringValues::CHISEL_TYPE_DEFAULT:
- $in->ignored(StateNames::PILLAR_AXIS);
- return Blocks::QUARTZ();
- case StringValues::CHISEL_TYPE_LINES:
- return Blocks::QUARTZ_PILLAR()->setAxis($in->readPillarAxis());
- case StringValues::CHISEL_TYPE_SMOOTH:
- $in->ignored(StateNames::PILLAR_AXIS);
- return Blocks::SMOOTH_QUARTZ();
- default:
- return throw $in->badValueException(StateNames::CHISEL_TYPE, $type);
- }
+ $this->map(Ids::QUARTZ_BLOCK, function(Reader $in) : Opaque{
+ $in->ignored(StateNames::PILLAR_AXIS);
+ return Blocks::QUARTZ();
+ });
+ $this->map(Ids::QUARTZ_PILLAR, function(Reader $in) : Block{
+ return Blocks::QUARTZ_PILLAR()
+ ->setAxis($in->readPillarAxis());
});
+ $this->mapSlab(Ids::QUARTZ_SLAB, Ids::QUARTZ_DOUBLE_SLAB, fn() => Blocks::QUARTZ_SLAB());
$this->mapStairs(Ids::QUARTZ_STAIRS, fn() => Blocks::QUARTZ_STAIRS());
$this->map(Ids::RAIL, function(Reader $in) : Block{
return Blocks::RAIL()
->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 9));
});
- $this->map(Ids::RED_FLOWER, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::FLOWER_TYPE)){
- StringValues::FLOWER_TYPE_ALLIUM => Blocks::ALLIUM(),
- StringValues::FLOWER_TYPE_CORNFLOWER => Blocks::CORNFLOWER(),
- StringValues::FLOWER_TYPE_HOUSTONIA => Blocks::AZURE_BLUET(), //wtf ???
- StringValues::FLOWER_TYPE_LILY_OF_THE_VALLEY => Blocks::LILY_OF_THE_VALLEY(),
- StringValues::FLOWER_TYPE_ORCHID => Blocks::BLUE_ORCHID(),
- StringValues::FLOWER_TYPE_OXEYE => Blocks::OXEYE_DAISY(),
- StringValues::FLOWER_TYPE_POPPY => Blocks::POPPY(),
- StringValues::FLOWER_TYPE_TULIP_ORANGE => Blocks::ORANGE_TULIP(),
- StringValues::FLOWER_TYPE_TULIP_PINK => Blocks::PINK_TULIP(),
- StringValues::FLOWER_TYPE_TULIP_RED => Blocks::RED_TULIP(),
- StringValues::FLOWER_TYPE_TULIP_WHITE => Blocks::WHITE_TULIP(),
- default => throw $in->badValueException(StateNames::FLOWER_TYPE, $type),
- };
- });
$this->map(Ids::RED_MUSHROOM_BLOCK, fn(Reader $in) => Helper::decodeMushroomBlock(Blocks::RED_MUSHROOM_BLOCK(), $in));
+ $this->mapSlab(Ids::RED_NETHER_BRICK_SLAB, Ids::RED_NETHER_BRICK_DOUBLE_SLAB, fn() => Blocks::RED_NETHER_BRICK_SLAB());
$this->mapStairs(Ids::RED_NETHER_BRICK_STAIRS, fn() => Blocks::RED_NETHER_BRICK_STAIRS());
- $this->map(Ids::RED_SANDSTONE, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::SAND_STONE_TYPE)){
- StringValues::SAND_STONE_TYPE_CUT => Blocks::CUT_RED_SANDSTONE(),
- StringValues::SAND_STONE_TYPE_DEFAULT => Blocks::RED_SANDSTONE(),
- StringValues::SAND_STONE_TYPE_HEIROGLYPHS => Blocks::CHISELED_RED_SANDSTONE(),
- StringValues::SAND_STONE_TYPE_SMOOTH => Blocks::SMOOTH_RED_SANDSTONE(),
- default => throw $in->badValueException(StateNames::SAND_STONE_TYPE, $type),
- };
- });
+ $this->map(Ids::RED_NETHER_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RED_NETHER_BRICK_WALL(), $in));
+ $this->mapSlab(Ids::RED_SANDSTONE_SLAB, Ids::RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::RED_SANDSTONE_SLAB());
$this->mapStairs(Ids::RED_SANDSTONE_STAIRS, fn() => Blocks::RED_SANDSTONE_STAIRS());
+ $this->map(Ids::RED_SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RED_SANDSTONE_WALL(), $in));
$this->map(Ids::REDSTONE_LAMP, function() : Block{
return Blocks::REDSTONE_LAMP()
->setPowered(false);
@@ -1136,60 +1567,49 @@ private function registerDeserializers() : void{
return Blocks::SUGARCANE()
->setAge($in->readBoundedInt(StateNames::AGE, 0, 15));
});
- $this->map(Ids::SAND, function(Reader $in) : Block{
- return match($value = $in->readString(StateNames::SAND_TYPE)){
- StringValues::SAND_TYPE_NORMAL => Blocks::SAND(),
- StringValues::SAND_TYPE_RED => Blocks::RED_SAND(),
- default => throw $in->badValueException(StateNames::SAND_TYPE, $value),
- };
- });
- $this->map(Ids::SANDSTONE, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::SAND_STONE_TYPE)){
- StringValues::SAND_STONE_TYPE_CUT => Blocks::CUT_SANDSTONE(),
- StringValues::SAND_STONE_TYPE_DEFAULT => Blocks::SANDSTONE(),
- StringValues::SAND_STONE_TYPE_HEIROGLYPHS => Blocks::CHISELED_SANDSTONE(),
- StringValues::SAND_STONE_TYPE_SMOOTH => Blocks::SMOOTH_SANDSTONE(),
- default => throw $in->badValueException(StateNames::SAND_STONE_TYPE, $type),
- };
- });
+ $this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB());
$this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
- $this->map(Ids::SAPLING, function(Reader $in) : Block{
- return (match($type = $in->readString(StateNames::SAPLING_TYPE)){
- StringValues::SAPLING_TYPE_ACACIA => Blocks::ACACIA_SAPLING(),
- StringValues::SAPLING_TYPE_BIRCH => Blocks::BIRCH_SAPLING(),
- StringValues::SAPLING_TYPE_DARK_OAK => Blocks::DARK_OAK_SAPLING(),
- StringValues::SAPLING_TYPE_JUNGLE => Blocks::JUNGLE_SAPLING(),
- StringValues::SAPLING_TYPE_OAK => Blocks::OAK_SAPLING(),
- StringValues::SAPLING_TYPE_SPRUCE => Blocks::SPRUCE_SAPLING(),
- default => throw $in->badValueException(StateNames::SAPLING_TYPE, $type),
- })
- ->setReady($in->readBool(StateNames::AGE_BIT));
- });
+ $this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in));
$this->map(Ids::SEA_PICKLE, function(Reader $in) : Block{
return Blocks::SEA_PICKLE()
->setCount($in->readBoundedInt(StateNames::CLUSTER_COUNT, 0, 3) + 1)
->setUnderwater(!$in->readBool(StateNames::DEAD_BIT));
});
- $this->map(Ids::SHULKER_BOX, function(Reader $in) : Block{
- return Blocks::DYED_SHULKER_BOX()
- ->setColor($in->readColor());
- });
- $this->map(Ids::SKULL, function(Reader $in) : Block{
- return Blocks::MOB_HEAD()
- ->setFacing($in->readFacingWithoutDown());
- });
$this->map(Ids::SMOKER, function(Reader $in) : Block{
return Blocks::SMOKER()
- ->setFacing($in->readHorizontalFacing())
+ ->setFacing($in->readCardinalHorizontalFacing())
->setLit(false);
});
+ $this->map(Ids::SMALL_AMETHYST_BUD, function(Reader $in) : Block{
+ return Blocks::AMETHYST_CLUSTER()
+ ->setStage(AmethystCluster::STAGE_SMALL_BUD)
+ ->setFacing($in->readBlockFace());
+ });
+ $this->map(Ids::SMALL_DRIPLEAF_BLOCK, function(Reader $in) : Block{
+ return Blocks::SMALL_DRIPLEAF()
+ ->setFacing($in->readCardinalHorizontalFacing())
+ ->setTop($in->readBool(StateNames::UPPER_BLOCK_BIT));
+ });
+ $this->map(Ids::SMOOTH_QUARTZ, function(Reader $in) : Block{
+ $in->ignored(StateNames::PILLAR_AXIS);
+ return Blocks::SMOOTH_QUARTZ();
+ });
+ $this->mapSlab(Ids::SMOOTH_QUARTZ_SLAB, Ids::SMOOTH_QUARTZ_DOUBLE_SLAB, fn() => Blocks::SMOOTH_QUARTZ_SLAB());
$this->mapStairs(Ids::SMOOTH_QUARTZ_STAIRS, fn() => Blocks::SMOOTH_QUARTZ_STAIRS());
+ $this->mapSlab(Ids::SMOOTH_RED_SANDSTONE_SLAB, Ids::SMOOTH_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SMOOTH_RED_SANDSTONE_SLAB());
$this->mapStairs(Ids::SMOOTH_RED_SANDSTONE_STAIRS, fn() => Blocks::SMOOTH_RED_SANDSTONE_STAIRS());
+ $this->mapSlab(Ids::SMOOTH_SANDSTONE_SLAB, Ids::SMOOTH_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SMOOTH_SANDSTONE_SLAB());
$this->mapStairs(Ids::SMOOTH_SANDSTONE_STAIRS, fn() => Blocks::SMOOTH_SANDSTONE_STAIRS());
+ $this->mapSlab(Ids::SMOOTH_STONE_SLAB, Ids::SMOOTH_STONE_DOUBLE_SLAB, fn() => Blocks::SMOOTH_STONE_SLAB());
$this->map(Ids::SNOW_LAYER, function(Reader $in) : Block{
$in->ignored(StateNames::COVERED_BIT); //seems to be useless
return Blocks::SNOW_LAYER()->setLayers($in->readBoundedInt(StateNames::HEIGHT, 0, 7) + 1);
});
+ $this->map(Ids::SOUL_CAMPFIRE, function(Reader $in) : Block{
+ return Blocks::SOUL_CAMPFIRE()
+ ->setFacing($in->readCardinalHorizontalFacing())
+ ->setLit(!$in->readBool(StateNames::EXTINGUISHED));
+ });
$this->map(Ids::SOUL_FIRE, function(Reader $in) : Block{
$in->ignored(StateNames::AGE); //this is useless for soul fire, since it doesn't have the logic associated
return Blocks::SOUL_FIRE();
@@ -1202,104 +1622,43 @@ private function registerDeserializers() : void{
return Blocks::SOUL_TORCH()
->setFacing($in->readTorchFacing());
});
- $this->map(Ids::SPONGE, function(Reader $in) : Block{
- return Blocks::SPONGE()->setWet(match($type = $in->readString(StateNames::SPONGE_TYPE)){
- StringValues::SPONGE_TYPE_DRY => false,
- StringValues::SPONGE_TYPE_WET => true,
- default => throw $in->badValueException(StateNames::SPONGE_TYPE, $type),
- });
- });
- $this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in));
- $this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in));
- $this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in));
- $this->map(Ids::SPRUCE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::SPRUCE_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::SPRUCE_STAIRS, fn() => Blocks::SPRUCE_STAIRS());
- $this->map(Ids::SPRUCE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::SPRUCE_SIGN(), $in));
- $this->map(Ids::SPRUCE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::SPRUCE_TRAPDOOR(), $in));
- $this->map(Ids::SPRUCE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::SPRUCE_WALL_SIGN(), $in));
- $this->map(Ids::STAINED_GLASS, function(Reader $in) : Block{
- return Blocks::STAINED_GLASS()
- ->setColor($in->readColor());
- });
- $this->map(Ids::STAINED_GLASS_PANE, function(Reader $in) : Block{
- return Blocks::STAINED_GLASS_PANE()
- ->setColor($in->readColor());
- });
- $this->map(Ids::STAINED_HARDENED_CLAY, function(Reader $in) : Block{
- return Blocks::STAINED_CLAY()
- ->setColor($in->readColor());
- });
$this->map(Ids::STANDING_BANNER, function(Reader $in) : Block{
return Blocks::BANNER()
->setRotation($in->readBoundedInt(StateNames::GROUND_SIGN_DIRECTION, 0, 15));
});
- $this->map(Ids::STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::OAK_SIGN(), $in));
- $this->map(Ids::STONE, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::STONE_TYPE)){
- StringValues::STONE_TYPE_ANDESITE => Blocks::ANDESITE(),
- StringValues::STONE_TYPE_ANDESITE_SMOOTH => Blocks::POLISHED_ANDESITE(),
- StringValues::STONE_TYPE_DIORITE => Blocks::DIORITE(),
- StringValues::STONE_TYPE_DIORITE_SMOOTH => Blocks::POLISHED_DIORITE(),
- StringValues::STONE_TYPE_GRANITE => Blocks::GRANITE(),
- StringValues::STONE_TYPE_GRANITE_SMOOTH => Blocks::POLISHED_GRANITE(),
- StringValues::STONE_TYPE_STONE => Blocks::STONE(),
- default => throw $in->badValueException(StateNames::STONE_TYPE, $type),
- };
- });
+ $this->mapSlab(Ids::STONE_BRICK_SLAB, Ids::STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::STONE_BRICK_SLAB());
$this->mapStairs(Ids::STONE_BRICK_STAIRS, fn() => Blocks::STONE_BRICK_STAIRS());
+ $this->map(Ids::STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::STONE_BRICK_WALL(), $in));
$this->map(Ids::STONE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::STONE_BUTTON(), $in));
$this->map(Ids::STONE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::STONE_PRESSURE_PLATE(), $in));
- $this->mapSlab(Ids::STONE_BLOCK_SLAB, Ids::DOUBLE_STONE_BLOCK_SLAB, fn(Reader $in) => Helper::mapStoneSlab1Type($in));
- $this->mapSlab(Ids::STONE_BLOCK_SLAB2, Ids::DOUBLE_STONE_BLOCK_SLAB2, fn(Reader $in) => Helper::mapStoneSlab2Type($in));
- $this->mapSlab(Ids::STONE_BLOCK_SLAB3, Ids::DOUBLE_STONE_BLOCK_SLAB3, fn(Reader $in) => Helper::mapStoneSlab3Type($in));
- $this->mapSlab(Ids::STONE_BLOCK_SLAB4, Ids::DOUBLE_STONE_BLOCK_SLAB4, fn(Reader $in) => Helper::mapStoneSlab4Type($in));
$this->mapStairs(Ids::STONE_STAIRS, fn() => Blocks::COBBLESTONE_STAIRS());
- $this->map(Ids::STONEBRICK, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::STONE_BRICK_TYPE)){
- StringValues::STONE_BRICK_TYPE_SMOOTH, //TODO: bug in vanilla
- StringValues::STONE_BRICK_TYPE_DEFAULT => Blocks::STONE_BRICKS(),
- StringValues::STONE_BRICK_TYPE_CHISELED => Blocks::CHISELED_STONE_BRICKS(),
- StringValues::STONE_BRICK_TYPE_CRACKED => Blocks::CRACKED_STONE_BRICKS(),
- StringValues::STONE_BRICK_TYPE_MOSSY => Blocks::MOSSY_STONE_BRICKS(),
- default => throw $in->badValueException(StateNames::STONE_BRICK_TYPE, $type),
- };
- });
$this->map(Ids::STONECUTTER_BLOCK, function(Reader $in) : Block{
return Blocks::STONECUTTER()
- ->setFacing($in->readHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
});
- $this->map(Ids::STRIPPED_CRIMSON_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_HYPHAE(), true, $in));
- $this->map(Ids::STRIPPED_CRIMSON_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_STEM(), true, $in));
- $this->map(Ids::STRIPPED_MANGROVE_LOG, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_LOG(), true, $in));
- $this->map(Ids::STRIPPED_MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), true, $in));
- $this->map(Ids::STRIPPED_WARPED_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_HYPHAE(), true, $in));
- $this->map(Ids::STRIPPED_WARPED_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_STEM(), true, $in));
$this->map(Ids::SWEET_BERRY_BUSH, function(Reader $in) : Block{
//berry bush only wants 0-3, but it can be bigger in MCPE due to misuse of GROWTH state which goes up to 7
$growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7);
return Blocks::SWEET_BERRY_BUSH()
->setAge(min($growth, SweetBerryBush::STAGE_MATURE));
});
- $this->map(Ids::TALLGRASS, function(Reader $in) : Block{
- return match($type = $in->readString(StateNames::TALL_GRASS_TYPE)){
- StringValues::TALL_GRASS_TYPE_DEFAULT, StringValues::TALL_GRASS_TYPE_SNOW, StringValues::TALL_GRASS_TYPE_TALL => Blocks::TALL_GRASS(),
- StringValues::TALL_GRASS_TYPE_FERN => Blocks::FERN(),
- default => throw $in->badValueException(StateNames::TALL_GRASS_TYPE, $type),
- };
- });
$this->map(Ids::TNT, function(Reader $in) : Block{
return Blocks::TNT()
->setUnstable($in->readBool(StateNames::EXPLODE_BIT))
- ->setWorksUnderwater($in->readBool(StateNames::ALLOW_UNDERWATER_BIT));
+ ->setWorksUnderwater(false);
});
$this->map(Ids::TORCH, function(Reader $in) : Block{
return Blocks::TORCH()
->setFacing($in->readTorchFacing());
});
- $this->map(Ids::TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::OAK_TRAPDOOR(), $in));
+ $this->map(Ids::TORCHFLOWER_CROP, function(Reader $in) : Block{
+ return Blocks::TORCHFLOWER_CROP()
+ //this property can have values 0-7, but only 0-1 are valid
+ ->setReady($in->readBoundedInt(StateNames::GROWTH, 0, 7) !== 0);
+ });
$this->map(Ids::TRAPPED_CHEST, function(Reader $in) : Block{
return Blocks::TRAPPED_CHEST()
- ->setFacing($in->readHorizontalFacing());
+ ->setFacing($in->readCardinalHorizontalFacing());
});
$this->map(Ids::TRIP_WIRE, function(Reader $in) : Block{
return Blocks::TRIPWIRE()
@@ -1314,10 +1673,21 @@ private function registerDeserializers() : void{
->setFacing($in->readLegacyHorizontalFacing())
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
+ $this->mapSlab(Ids::TUFF_BRICK_SLAB, Ids::TUFF_BRICK_DOUBLE_SLAB, fn() => Blocks::TUFF_BRICK_SLAB());
+ $this->mapStairs(Ids::TUFF_BRICK_STAIRS, fn() => Blocks::TUFF_BRICK_STAIRS());
+ $this->map(Ids::TUFF_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::TUFF_BRICK_WALL(), $in));
+ $this->mapSlab(Ids::TUFF_SLAB, Ids::TUFF_DOUBLE_SLAB, fn() => Blocks::TUFF_SLAB());
+ $this->mapStairs(Ids::TUFF_STAIRS, fn() => Blocks::TUFF_STAIRS());
+ $this->map(Ids::TUFF_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::TUFF_WALL(), $in));
$this->map(Ids::TWISTING_VINES, function(Reader $in) : Block{
return Blocks::TWISTING_VINES()
->setAge($in->readBoundedInt(StateNames::TWISTING_VINES_AGE, 0, 25));
});
+ $this->map(Ids::UNDERWATER_TNT, function(Reader $in) : Block{
+ return Blocks::TNT()
+ ->setUnstable($in->readBool(StateNames::EXPLODE_BIT))
+ ->setWorksUnderwater(true);
+ });
$this->map(Ids::UNDERWATER_TORCH, function(Reader $in) : Block{
return Blocks::UNDERWATER_TORCH()
->setFacing($in->readTorchFacing());
@@ -1330,7 +1700,7 @@ private function registerDeserializers() : void{
$this->map(Ids::UNPOWERED_COMPARATOR, fn(Reader $in) => Helper::decodeComparator(Blocks::REDSTONE_COMPARATOR(), $in));
$this->map(Ids::UNPOWERED_REPEATER, fn(Reader $in) => Helper::decodeRepeater(Blocks::REDSTONE_REPEATER(), $in)
->setPowered(false));
- $this->map(Ids::VERDANT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::VERDANT())->setAxis($in->readPillarAxis()));
+ $this->map(Ids::VERDANT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::VERDANT)->setAxis($in->readPillarAxis()));
$this->map(Ids::VINE, function(Reader $in) : Block{
$vineDirectionFlags = $in->readBoundedInt(StateNames::VINE_DIRECTION_BITS, 0, 15);
return Blocks::VINES()
@@ -1343,57 +1713,77 @@ private function registerDeserializers() : void{
return Blocks::WALL_BANNER()
->setFacing($in->readHorizontalFacing());
});
- $this->map(Ids::WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::OAK_WALL_SIGN(), $in));
- $this->map(Ids::WARPED_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::WARPED_BUTTON(), $in));
- $this->map(Ids::WARPED_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::WARPED_DOOR(), $in));
- $this->mapSlab(Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB, fn() => Blocks::WARPED_SLAB());
- $this->map(Ids::WARPED_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::WARPED_FENCE_GATE(), $in));
- $this->map(Ids::WARPED_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_HYPHAE(), false, $in));
- $this->map(Ids::WARPED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::WARPED_PRESSURE_PLATE(), $in));
- $this->mapStairs(Ids::WARPED_STAIRS, fn() => Blocks::WARPED_STAIRS());
- $this->map(Ids::WARPED_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::WARPED_SIGN(), $in));
- $this->map(Ids::WARPED_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_STEM(), false, $in));
- $this->map(Ids::WARPED_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::WARPED_TRAPDOOR(), $in));
- $this->map(Ids::WARPED_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::WARPED_WALL_SIGN(), $in));
$this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in));
- $this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE()));
- $this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE()));
- $this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE()));
- $this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE()));
- $this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED()));
- $this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED()));
- $this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED()));
- $this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED()));
- $this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED()));
- $this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED()));
- $this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED()));
- $this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED()));
- $this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED()));
- $this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED()));
- $this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED()));
- $this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED()));
- $this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED()));
- $this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED()));
- $this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED()));
- $this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED()));
+ $this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE));
+ $this->map(Ids::WAXED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE));
+ $this->map(Ids::WAXED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE));
+ $this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE));
+ $this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE));
+ $this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE));
+ $this->map(Ids::WAXED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
+ });
+ $this->map(Ids::WAXED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in));
+ $this->map(Ids::WAXED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in));
+ $this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED));
+ $this->map(Ids::WAXED_EXPOSED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED));
+ $this->map(Ids::WAXED_EXPOSED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED));
+ $this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED));
+ $this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED));
+ $this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED));
+ $this->map(Ids::WAXED_EXPOSED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
+ });
+ $this->map(Ids::WAXED_EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in));
+ $this->map(Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in));
+ $this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::WAXED_OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::WAXED_OXIDIZED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED));
+ $this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED));
+ $this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED));
+ $this->map(Ids::WAXED_OXIDIZED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
+ });
+ $this->map(Ids::WAXED_OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in));
+ $this->map(Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in));
+ $this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WAXED_WEATHERED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WAXED_WEATHERED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED));
+ $this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED));
+ $this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WAXED_WEATHERED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
+ });
+ $this->map(Ids::WAXED_WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in));
+ $this->map(Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in));
+ $this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WEATHERED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WEATHERED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED));
+ $this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED));
+ $this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED));
+ $this->map(Ids::WEATHERED_COPPER_BULB, function(Reader $in) : Block{
+ return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED)
+ ->setLit($in->readBool(StateNames::LIT))
+ ->setPowered($in->readBool(StateNames::POWERED_BIT));
+ });
+ $this->map(Ids::WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in));
+ $this->map(Ids::WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in));
$this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{
return Blocks::WEEPING_VINES()
->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25));
});
$this->map(Ids::WHEAT, fn(Reader $in) => Helper::decodeCrops(Blocks::WHEAT(), $in));
- $this->map(Ids::WOOD, fn(Reader $in) : Block => Helper::decodeLog(match($woodType = $in->readString(StateNames::WOOD_TYPE)){
- StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_WOOD(),
- StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_WOOD(),
- StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_WOOD(),
- StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_WOOD(),
- StringValues::WOOD_TYPE_OAK => Blocks::OAK_WOOD(),
- StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_WOOD(),
- default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodType),
- }, $in->readBool(StateNames::STRIPPED_BIT), $in));
- $this->map(Ids::WOODEN_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::OAK_BUTTON(), $in));
- $this->map(Ids::WOODEN_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::OAK_DOOR(), $in));
- $this->map(Ids::WOODEN_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::OAK_PRESSURE_PLATE(), $in));
- $this->mapSlab(Ids::WOODEN_SLAB, Ids::DOUBLE_WOODEN_SLAB, fn(Reader $in) => Helper::mapWoodenSlabType($in));
}
/** @throws BlockStateDeserializeException */
diff --git a/src/data/bedrock/block/convert/BlockStateWriter.php b/src/data/bedrock/block/convert/BlockStateWriter.php
index 0cfaa9d3dad..63af92d1009 100644
--- a/src/data/bedrock/block/convert/BlockStateWriter.php
+++ b/src/data/bedrock/block/convert/BlockStateWriter.php
@@ -24,11 +24,8 @@
namespace pocketmine\data\bedrock\block\convert;
use pocketmine\block\utils\BellAttachmentType;
-use pocketmine\block\utils\CoralType;
-use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\WallConnectionType;
-use pocketmine\block\utils\WoodType;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\BlockStateNames;
@@ -90,6 +87,20 @@ public function writeFacingDirection(int $value) : self{
return $this;
}
+ /** @return $this */
+ public function writeBlockFace(int $value) : self{
+ $this->writeString(BlockStateNames::MC_BLOCK_FACE, match($value){
+ Facing::DOWN => StringValues::MC_BLOCK_FACE_DOWN,
+ Facing::UP => StringValues::MC_BLOCK_FACE_UP,
+ Facing::NORTH => StringValues::MC_BLOCK_FACE_NORTH,
+ Facing::SOUTH => StringValues::MC_BLOCK_FACE_SOUTH,
+ Facing::WEST => StringValues::MC_BLOCK_FACE_WEST,
+ Facing::EAST => StringValues::MC_BLOCK_FACE_EAST,
+ default => throw new BlockStateSerializeException("Invalid Facing $value")
+ });
+ return $this;
+ }
+
/**
* @param int[] $faces
* @phpstan-param array $faces
@@ -165,28 +176,18 @@ public function write5MinusHorizontalFacing(int $value) : self{
});
}
- /** @return $this */
- public function writeColor(DyeColor $color) : self{
- $this->writeString(BlockStateNames::COLOR, match($color->id()){
- DyeColor::BLACK()->id() => StringValues::COLOR_BLACK,
- DyeColor::BLUE()->id() => StringValues::COLOR_BLUE,
- DyeColor::BROWN()->id() => StringValues::COLOR_BROWN,
- DyeColor::CYAN()->id() => StringValues::COLOR_CYAN,
- DyeColor::GRAY()->id() => StringValues::COLOR_GRAY,
- DyeColor::GREEN()->id() => StringValues::COLOR_GREEN,
- DyeColor::LIGHT_BLUE()->id() => StringValues::COLOR_LIGHT_BLUE,
- DyeColor::LIGHT_GRAY()->id() => StringValues::COLOR_SILVER,
- DyeColor::LIME()->id() => StringValues::COLOR_LIME,
- DyeColor::MAGENTA()->id() => StringValues::COLOR_MAGENTA,
- DyeColor::ORANGE()->id() => StringValues::COLOR_ORANGE,
- DyeColor::PINK()->id() => StringValues::COLOR_PINK,
- DyeColor::PURPLE()->id() => StringValues::COLOR_PURPLE,
- DyeColor::RED()->id() => StringValues::COLOR_RED,
- DyeColor::WHITE()->id() => StringValues::COLOR_WHITE,
- DyeColor::YELLOW()->id() => StringValues::COLOR_YELLOW,
- default => throw new BlockStateSerializeException("Invalid Color " . $color->name())
+ /**
+ * Used by pumpkins as of 1.20.0.23 beta
+ * @return $this
+ */
+ public function writeCardinalHorizontalFacing(int $value) : self{
+ return $this->writeString(BlockStateNames::MC_CARDINAL_DIRECTION, match($value){
+ Facing::SOUTH => StringValues::MC_CARDINAL_DIRECTION_SOUTH,
+ Facing::WEST => StringValues::MC_CARDINAL_DIRECTION_WEST,
+ Facing::NORTH => StringValues::MC_CARDINAL_DIRECTION_NORTH,
+ Facing::EAST => StringValues::MC_CARDINAL_DIRECTION_EAST,
+ default => throw new BlockStateSerializeException("Invalid horizontal facing $value")
});
- return $this;
}
/** @return $this */
@@ -232,10 +233,10 @@ public function writePillarAxis(int $axis) : self{
/** @return $this */
public function writeSlabPosition(SlabType $slabType) : self{
- $this->writeBool(BlockStateNames::TOP_SLOT_BIT, match($slabType->id()){
- SlabType::TOP()->id() => true,
- SlabType::BOTTOM()->id() => false,
- default => throw new BlockStateSerializeException("Invalid slab type " . $slabType->name())
+ $this->writeString(BlockStateNames::MC_VERTICAL_HALF, match($slabType){
+ SlabType::TOP => StringValues::MC_VERTICAL_HALF_TOP,
+ SlabType::BOTTOM => StringValues::MC_VERTICAL_HALF_BOTTOM,
+ default => throw new BlockStateSerializeException("Invalid slab type " . $slabType->name)
});
return $this;
}
@@ -254,41 +255,13 @@ public function writeTorchFacing(int $facing) : self{
return $this;
}
- /** @return $this */
- public function writeLegacyWoodType(WoodType $treeType) : self{
- $this->writeString(BlockStateNames::WOOD_TYPE, match($treeType->id()){
- WoodType::OAK()->id() => StringValues::WOOD_TYPE_OAK,
- WoodType::SPRUCE()->id() => StringValues::WOOD_TYPE_SPRUCE,
- WoodType::BIRCH()->id() => StringValues::WOOD_TYPE_BIRCH,
- WoodType::JUNGLE()->id() => StringValues::WOOD_TYPE_JUNGLE,
- WoodType::ACACIA()->id() => StringValues::WOOD_TYPE_ACACIA,
- WoodType::DARK_OAK()->id() => StringValues::WOOD_TYPE_DARK_OAK,
- default => throw new BlockStateSerializeException("Invalid Wood type " . $treeType->name())
- });
- return $this;
- }
-
- /** @return $this */
- public function writeCoralType(CoralType $coralType) : self{
- $this->writeString(BlockStateNames::CORAL_COLOR, match($coralType->id()){
- CoralType::TUBE()->id() => StringValues::CORAL_COLOR_BLUE,
- CoralType::BRAIN()->id() => StringValues::CORAL_COLOR_PINK,
- CoralType::BUBBLE()->id() => StringValues::CORAL_COLOR_PURPLE,
- CoralType::FIRE()->id() => StringValues::CORAL_COLOR_RED,
- CoralType::HORN()->id() => StringValues::CORAL_COLOR_YELLOW,
- default => throw new BlockStateSerializeException("Invalid Coral type " . $coralType->name())
- });
- return $this;
- }
-
/** @return $this */
public function writeBellAttachmentType(BellAttachmentType $attachmentType) : self{
- $this->writeString(BlockStateNames::ATTACHMENT, match($attachmentType->id()){
- BellAttachmentType::FLOOR()->id() => StringValues::ATTACHMENT_STANDING,
- BellAttachmentType::CEILING()->id() => StringValues::ATTACHMENT_HANGING,
- BellAttachmentType::ONE_WALL()->id() => StringValues::ATTACHMENT_SIDE,
- BellAttachmentType::TWO_WALLS()->id() => StringValues::ATTACHMENT_MULTIPLE,
- default => throw new BlockStateSerializeException("Invalid Bell attachment type " . $attachmentType->name())
+ $this->writeString(BlockStateNames::ATTACHMENT, match($attachmentType){
+ BellAttachmentType::FLOOR => StringValues::ATTACHMENT_STANDING,
+ BellAttachmentType::CEILING => StringValues::ATTACHMENT_HANGING,
+ BellAttachmentType::ONE_WALL => StringValues::ATTACHMENT_SIDE,
+ BellAttachmentType::TWO_WALLS => StringValues::ATTACHMENT_MULTIPLE,
});
return $this;
}
@@ -297,9 +270,8 @@ public function writeBellAttachmentType(BellAttachmentType $attachmentType) : se
public function writeWallConnectionType(string $name, ?WallConnectionType $wallConnectionType) : self{
$this->writeString($name, match($wallConnectionType){
null => StringValues::WALL_CONNECTION_TYPE_EAST_NONE,
- WallConnectionType::SHORT() => StringValues::WALL_CONNECTION_TYPE_EAST_SHORT,
- WallConnectionType::TALL() => StringValues::WALL_CONNECTION_TYPE_EAST_TALL,
- default => throw new BlockStateSerializeException("Invalid Wall connection type " . $wallConnectionType->name())
+ WallConnectionType::SHORT => StringValues::WALL_CONNECTION_TYPE_EAST_SHORT,
+ WallConnectionType::TALL => StringValues::WALL_CONNECTION_TYPE_EAST_TALL,
});
return $this;
}
diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php
index 36960383e2d..f8894cfd27c 100644
--- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php
+++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php
@@ -23,6 +23,7 @@
namespace pocketmine\data\bedrock\block\upgrade;
+use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenInfo as FlattenInfo;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap as ValueRemap;
use pocketmine\nbt\tag\Tag;
use function count;
@@ -58,26 +59,36 @@ final class BlockStateUpgradeSchema{
*/
public array $remappedPropertyValues = [];
+ /**
+ * @var FlattenInfo[]
+ * @phpstan-var array
+ */
+ public array $flattenedProperties = [];
+
/**
* @var BlockStateUpgradeSchemaBlockRemap[][]
* @phpstan-var array>
*/
public array $remappedStates = [];
+ public readonly int $versionId;
+
public function __construct(
- public int $maxVersionMajor,
- public int $maxVersionMinor,
- public int $maxVersionPatch,
- public int $maxVersionRevision,
+ public readonly int $maxVersionMajor,
+ public readonly int $maxVersionMinor,
+ public readonly int $maxVersionPatch,
+ public readonly int $maxVersionRevision,
private int $schemaId
- ){}
+ ){
+ $this->versionId = ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
+ }
/**
* @deprecated This is defined by Mojang, and therefore cannot be relied on. Use getSchemaId() instead for
* internal version management.
*/
public function getVersionId() : int{
- return ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
+ return $this->versionId;
}
public function getSchemaId() : int{ return $this->schemaId; }
@@ -89,6 +100,7 @@ public function isEmpty() : bool{
$this->removedProperties,
$this->renamedProperties,
$this->remappedPropertyValues,
+ $this->flattenedProperties,
$this->remappedStates,
] as $list){
if(count($list) !== 0){
diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php
index 7c57e1f1f97..676afbaf4e5 100644
--- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php
+++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php
@@ -24,6 +24,9 @@
namespace pocketmine\data\bedrock\block\upgrade;
use pocketmine\nbt\tag\Tag;
+use pocketmine\utils\Utils;
+use function array_diff;
+use function count;
final class BlockStateUpgradeSchemaBlockRemap{
/**
@@ -37,8 +40,43 @@ final class BlockStateUpgradeSchemaBlockRemap{
*/
public function __construct(
public array $oldState,
- public string $newName,
+ public string|BlockStateUpgradeSchemaFlattenInfo $newName,
public array $newState,
public array $copiedState
){}
+
+ public function equals(self $that) : bool{
+ $sameName = $this->newName === $that->newName ||
+ (
+ $this->newName instanceof BlockStateUpgradeSchemaFlattenInfo &&
+ $that->newName instanceof BlockStateUpgradeSchemaFlattenInfo &&
+ $this->newName->equals($that->newName)
+ );
+ if(!$sameName){
+ return false;
+ }
+
+ if(
+ count($this->oldState) !== count($that->oldState) ||
+ count($this->newState) !== count($that->newState) ||
+ count($this->copiedState) !== count($that->copiedState) ||
+ count(array_diff($this->copiedState, $that->copiedState)) !== 0
+ ){
+ return false;
+ }
+ foreach(Utils::stringifyKeys($this->oldState) as $propertyName => $propertyValue){
+ if(!isset($that->oldState[$propertyName]) || !$that->oldState[$propertyName]->equals($propertyValue)){
+ //different filter value
+ return false;
+ }
+ }
+ foreach(Utils::stringifyKeys($this->newState) as $propertyName => $propertyValue){
+ if(!isset($that->newState[$propertyName]) || !$that->newState[$propertyName]->equals($propertyValue)){
+ //different replacement value
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php
new file mode 100644
index 00000000000..4a14a1291f0
--- /dev/null
+++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php
@@ -0,0 +1,56 @@
+ $flattenedValueRemaps
+ * @phpstan-param ?class-string $flattenedPropertyType
+ */
+ public function __construct(
+ public string $prefix,
+ public string $flattenedProperty,
+ public string $suffix,
+ public array $flattenedValueRemaps,
+ public ?string $flattenedPropertyType = null
+ ){
+ ksort($this->flattenedValueRemaps, SORT_STRING);
+ }
+
+ public function equals(self $that) : bool{
+ return $this->prefix === $that->prefix &&
+ $this->flattenedProperty === $that->flattenedProperty &&
+ $this->suffix === $that->suffix &&
+ $this->flattenedValueRemaps === $that->flattenedValueRemaps &&
+ $this->flattenedPropertyType === $that->flattenedPropertyType;
+ }
+}
diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php
index c104b366822..08eba89785d 100644
--- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php
+++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php
@@ -25,6 +25,7 @@
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModel;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelBlockRemap;
+use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelFlattenInfo;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelTag;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelValueRemap;
use pocketmine\nbt\tag\ByteTag;
@@ -43,12 +44,14 @@
use function gettype;
use function implode;
use function is_object;
+use function is_string;
use function json_decode;
use function json_encode;
use function ksort;
use function sort;
use function str_pad;
use function strval;
+use function usort;
use const JSON_THROW_ON_ERROR;
use const SORT_NUMERIC;
use const STR_PAD_LEFT;
@@ -152,11 +155,24 @@ public static function fromJsonModel(BlockStateUpgradeSchemaModel $model, int $s
}
}
+ foreach(Utils::stringifyKeys($model->flattenedProperties ?? []) as $blockName => $flattenRule){
+ $result->flattenedProperties[$blockName] = self::jsonModelToFlattenRule($flattenRule);
+ }
+
foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){
foreach($remaps as $remap){
+ if(isset($remap->newName)){
+ $remapName = $remap->newName;
+ }elseif(isset($remap->newFlattenedName)){
+ $flattenRule = $remap->newFlattenedName;
+ $remapName = self::jsonModelToFlattenRule($flattenRule);
+ }else{
+ throw new \UnexpectedValueException("Expected exactly one of 'newName' or 'newFlattenedName' properties to be set");
+ }
+
$result->remappedStates[$oldBlockName][] = new BlockStateUpgradeSchemaBlockRemap(
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->oldState ?? []),
- $remap->newName,
+ $remapName,
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []),
$remap->copiedState ?? []
);
@@ -242,6 +258,36 @@ private static function buildRemappedValuesIndex(BlockStateUpgradeSchema $schema
$model->remappedPropertyValues = $modelDedupMapping;
}
+ private static function flattenRuleToJsonModel(BlockStateUpgradeSchemaFlattenInfo $flattenRule) : BlockStateUpgradeSchemaModelFlattenInfo{
+ return new BlockStateUpgradeSchemaModelFlattenInfo(
+ $flattenRule->prefix,
+ $flattenRule->flattenedProperty,
+ $flattenRule->suffix,
+ $flattenRule->flattenedValueRemaps,
+ match($flattenRule->flattenedPropertyType){
+ StringTag::class => null, //omit for TAG_String, as this is the common case
+ ByteTag::class => "byte",
+ IntTag::class => "int",
+ default => throw new \LogicException("Unexpected tag type " . $flattenRule->flattenedPropertyType . " in flattened property type")
+ }
+ );
+ }
+
+ private static function jsonModelToFlattenRule(BlockStateUpgradeSchemaModelFlattenInfo $flattenRule) : BlockStateUpgradeSchemaFlattenInfo{
+ return new BlockStateUpgradeSchemaFlattenInfo(
+ $flattenRule->prefix,
+ $flattenRule->flattenedProperty,
+ $flattenRule->suffix,
+ $flattenRule->flattenedValueRemaps ?? [],
+ match ($flattenRule->flattenedPropertyType) {
+ "string", null => StringTag::class,
+ "int" => IntTag::class,
+ "byte" => ByteTag::class,
+ default => throw new \UnexpectedValueException("Unexpected flattened property type $flattenRule->flattenedPropertyType, expected 'string', 'int' or 'byte'")
+ }
+ );
+ }
+
public static function toJsonModel(BlockStateUpgradeSchema $schema) : BlockStateUpgradeSchemaModel{
$result = new BlockStateUpgradeSchemaModel();
$result->maxVersionMajor = $schema->maxVersionMajor;
@@ -280,12 +326,19 @@ public static function toJsonModel(BlockStateUpgradeSchema $schema) : BlockState
self::buildRemappedValuesIndex($schema, $result);
+ foreach(Utils::stringifyKeys($schema->flattenedProperties) as $blockName => $flattenRule){
+ $result->flattenedProperties[$blockName] = self::flattenRuleToJsonModel($flattenRule);
+ }
+ if(isset($result->flattenedProperties)){
+ ksort($result->flattenedProperties);
+ }
+
foreach(Utils::stringifyKeys($schema->remappedStates) as $oldBlockName => $remaps){
$keyedRemaps = [];
foreach($remaps as $remap){
$modelRemap = new BlockStateUpgradeSchemaModelBlockRemap(
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->oldState),
- $remap->newName,
+ is_string($remap->newName) ? $remap->newName : self::flattenRuleToJsonModel($remap->newName),
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState),
$remap->copiedState
);
@@ -299,7 +352,15 @@ public static function toJsonModel(BlockStateUpgradeSchema $schema) : BlockState
}
$keyedRemaps[$key] = $modelRemap;
}
- ksort($keyedRemaps);
+ usort($keyedRemaps, function(BlockStateUpgradeSchemaModelBlockRemap $a, BlockStateUpgradeSchemaModelBlockRemap $b) : int{
+ //remaps with more specific criteria must come first
+ $filterSizeCompare = count($b->oldState ?? []) <=> count($a->oldState ?? []);
+ if($filterSizeCompare !== 0){
+ return $filterSizeCompare;
+ }
+ //remaps with the same number of criteria should be sorted alphabetically, but this is not strictly necessary
+ return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []);
+ });
$result->remappedStates[$oldBlockName] = array_values($keyedRemaps);
}
if(isset($result->remappedStates)){
@@ -364,6 +425,9 @@ public static function loadSchemaFromString(string $raw, int $schemaId) : BlockS
}
$jsonMapper = new \JsonMapper();
+ $jsonMapper->bExceptionOnMissingData = true;
+ $jsonMapper->bExceptionOnUndefinedProperty = true;
+ $jsonMapper->bStrictObjectTypeChecking = true;
try{
$model = $jsonMapper->map($json, new BlockStateUpgradeSchemaModel());
}catch(\JsonMapper_Exception $e){
diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php
index ab0424a3315..2dce762b8df 100644
--- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php
+++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php
@@ -24,17 +24,29 @@
namespace pocketmine\data\bedrock\block\upgrade;
use pocketmine\data\bedrock\block\BlockStateData;
+use pocketmine\nbt\tag\ByteTag;
+use pocketmine\nbt\tag\IntTag;
+use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
+use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function count;
+use function get_class;
+use function is_string;
use function ksort;
use function max;
+use function sprintf;
use const SORT_NUMERIC;
final class BlockStateUpgrader{
- /** @var BlockStateUpgradeSchema[] */
+ /**
+ * @var BlockStateUpgradeSchema[][] versionId => [schemaId => schema]
+ * @phpstan-var array>
+ */
private array $upgradeSchemas = [];
+ private int $outputVersion = 0;
+
/**
* @param BlockStateUpgradeSchema[] $upgradeSchemas
* @phpstan-param array $upgradeSchemas
@@ -47,73 +59,122 @@ public function __construct(array $upgradeSchemas){
public function addSchema(BlockStateUpgradeSchema $schema) : void{
$schemaId = $schema->getSchemaId();
- if(isset($this->upgradeSchemas[$schemaId])){
- throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID");
+ $versionId = $schema->getVersionId();
+ if(isset($this->upgradeSchemas[$versionId][$schemaId])){
+ throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID and version ID");
}
- $this->upgradeSchemas[$schemaId] = $schema;
+
+ //schema ID tells us the order when multiple schemas use the same version ID
+ $this->upgradeSchemas[$versionId][$schemaId] = $schema;
ksort($this->upgradeSchemas, SORT_NUMERIC);
+ ksort($this->upgradeSchemas[$versionId], SORT_NUMERIC);
+
+ $this->outputVersion = max($this->outputVersion, $schema->getVersionId());
}
public function upgrade(BlockStateData $blockStateData) : BlockStateData{
$version = $blockStateData->getVersion();
- $highestVersion = $version;
- foreach($this->upgradeSchemas as $schema){
- $resultVersion = $schema->getVersionId();
- $highestVersion = max($highestVersion, $resultVersion);
- if($version > $resultVersion){
- //even if this is actually the same version, we have to apply it anyway because mojang are dumb and
- //didn't always bump the blockstate version when changing it :(
+ foreach($this->upgradeSchemas as $resultVersion => $schemaList){
+ /*
+ * Sometimes Mojang made changes without bumping the version ID.
+ * A notable example is 0131_1.18.20.27_beta_to_1.18.30.json, which renamed a bunch of blockIDs.
+ * When this happens, all the schemas must be applied even if the version is the same, because the input
+ * version doesn't tell us which of the schemas have already been applied.
+ * If there's only one schema for a version (the norm), we can safely assume it's already been applied if
+ * the version is the same, and skip over it.
+ * TODO: this causes issues when testing isolated schemas since there will only be one schema for a version.
+ * The second check should be disabled for that case.
+ */
+ if($version > $resultVersion || (count($schemaList) === 1 && $version === $resultVersion)){
continue;
}
- $oldName = $blockStateData->getName();
- $oldState = $blockStateData->getStates();
- if(isset($schema->remappedStates[$oldName])){
- foreach($schema->remappedStates[$oldName] as $remap){
- if(count($remap->oldState) > count($oldState)){
- //match criteria has more requirements than we have state properties
- continue; //try next state
- }
- foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
- if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
- continue 2; //try next state
- }
- }
- $newState = $remap->newState;
- foreach($remap->copiedState as $stateName){
- if(isset($oldState[$stateName])){
- $newState[$stateName] = $oldState[$stateName];
- }
- }
- $blockStateData = new BlockStateData($remap->newName, $newState, $resultVersion);
- continue 2; //try next schema
- }
+ foreach($schemaList as $schema){
+ $blockStateData = $this->applySchema($schema, $blockStateData);
}
- $newName = $schema->renamedIds[$oldName] ?? null;
+ }
- $stateChanges = 0;
- $states = $blockStateData->getStates();
+ if($this->outputVersion > $version){
+ //always update the version number of the blockstate, even if it didn't change - this is needed for
+ //external tools
+ $blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $this->outputVersion);
+ }
+ return $blockStateData;
+ }
- $states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
- $states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
- $states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
- $states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
+ private function applySchema(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : BlockStateData{
+ $newStateData = $this->applyStateRemapped($schema, $blockStateData);
+ if($newStateData !== null){
+ return $newStateData;
+ }
- if($newName !== null || $stateChanges > 0){
- $blockStateData = new BlockStateData($newName ?? $oldName, $states, $resultVersion);
- //don't break out; we may need to further upgrade the state
- }
+ $oldName = $blockStateData->getName();
+ $states = $blockStateData->getStates();
+
+ if(isset($schema->renamedIds[$oldName]) && isset($schema->flattenedProperties[$oldName])){
+ //TODO: this probably ought to be validated when the schema is constructed
+ throw new AssumptionFailedError("Both renamedIds and flattenedProperties are set for the same block ID \"$oldName\" - don't know what to do");
+ }
+ if(isset($schema->renamedIds[$oldName])){
+ $newName = $schema->renamedIds[$oldName] ?? null;
+ }elseif(isset($schema->flattenedProperties[$oldName])){
+ [$newName, $states] = $this->applyPropertyFlattened($schema->flattenedProperties[$oldName], $oldName, $states);
+ }else{
+ $newName = null;
}
- if($highestVersion > $version){
- //always update the version number of the blockstate, even if it didn't change - this is needed for
- //external tools
- $blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $highestVersion);
+ $stateChanges = 0;
+
+ $states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
+ $states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
+ $states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
+ $states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
+
+ if($newName !== null || $stateChanges > 0){
+ return new BlockStateData($newName ?? $oldName, $states, $schema->getVersionId());
}
+
return $blockStateData;
}
+ private function applyStateRemapped(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : ?BlockStateData{
+ $oldName = $blockStateData->getName();
+ $oldState = $blockStateData->getStates();
+
+ if(isset($schema->remappedStates[$oldName])){
+ foreach($schema->remappedStates[$oldName] as $remap){
+ if(count($remap->oldState) > count($oldState)){
+ //match criteria has more requirements than we have state properties
+ continue; //try next state
+ }
+ foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
+ if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
+ continue 2; //try next state
+ }
+ }
+
+ if(is_string($remap->newName)){
+ $newName = $remap->newName;
+ }else{
+ //discard flatten modifications to state - the remap newState and copiedState will take care of it
+ [$newName, ] = $this->applyPropertyFlattened($remap->newName, $oldName, $oldState);
+ }
+
+ $newState = $remap->newState;
+ foreach($remap->copiedState as $stateName){
+ if(isset($oldState[$stateName])){
+ $newState[$stateName] = $oldState[$stateName];
+ }
+ }
+
+ return new BlockStateData($newName, $newState, $schema->getVersionId());
+ }
+ }
+
+ return null;
+ }
+
/**
* @param Tag[] $states
* @phpstan-param array $states
@@ -215,4 +276,32 @@ private function applyPropertyValueChanged(BlockStateUpgradeSchema $schema, stri
return $states;
}
+
+ /**
+ * @param Tag[] $states
+ * @phpstan-param array $states
+ *
+ * @return (string|Tag[])[]
+ * @phpstan-return array{0: string, 1: array}
+ */
+ private function applyPropertyFlattened(BlockStateUpgradeSchemaFlattenInfo $flattenInfo, string $oldName, array $states) : array{
+ $flattenedValue = $states[$flattenInfo->flattenedProperty] ?? null;
+ $expectedType = $flattenInfo->flattenedPropertyType;
+ if(!$flattenedValue instanceof $expectedType){
+ //flattened property is not of the expected type, so this transformation is not applicable
+ return [$oldName, $states];
+ }
+ $embedKey = match(get_class($flattenedValue)){
+ StringTag::class => $flattenedValue->getValue(),
+ ByteTag::class => (string) $flattenedValue->getValue(),
+ IntTag::class => (string) $flattenedValue->getValue(),
+ //flattenedPropertyType is always one of these three types, but PHPStan doesn't know that
+ default => throw new AssumptionFailedError("flattenedPropertyType should be one of these three types, but have " . get_class($flattenedValue)),
+ };
+ $embedValue = $flattenInfo->flattenedValueRemaps[$embedKey] ?? $embedKey;
+ $newName = sprintf("%s%s%s", $flattenInfo->prefix, $embedValue, $flattenInfo->suffix);
+ unset($states[$flattenInfo->flattenedProperty]);
+
+ return [$newName, $states];
+ }
}
diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php
index 1a4a14c87cf..7d91438e4f4 100644
--- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php
+++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php
@@ -75,6 +75,12 @@ final class BlockStateUpgradeSchemaModel implements \JsonSerializable{
*/
public array $remappedPropertyValuesIndex;
+ /**
+ * @var BlockStateUpgradeSchemaModelFlattenInfo[]
+ * @phpstan-var array
+ */
+ public array $flattenedProperties;
+
/**
* @var BlockStateUpgradeSchemaModelBlockRemap[][]
* @phpstan-var array>
diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php
index 49b2e0f2854..6accf1f0212 100644
--- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php
+++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php
@@ -34,12 +34,21 @@ final class BlockStateUpgradeSchemaModelBlockRemap{
*/
public ?array $oldState;
- /** @required */
+ /**
+ * Either this or newFlattenedName must be present
+ * Due to technical limitations of jsonmapper, we can't use a union type here
+ */
public string $newName;
+ /**
+ * Either this or newName must be present
+ * Due to technical limitations of jsonmapper, we can't use a union type here
+ */
+ public BlockStateUpgradeSchemaModelFlattenInfo $newFlattenedName;
/**
* @var BlockStateUpgradeSchemaModelTag[]|null
* @phpstan-var array|null
+ * @required
*/
public ?array $newState;
@@ -58,9 +67,13 @@ final class BlockStateUpgradeSchemaModelBlockRemap{
* @phpstan-param array $newState
* @phpstan-param list $copiedState
*/
- public function __construct(array $oldState, string $newName, array $newState, array $copiedState){
+ public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenInfo $newNameRule, array $newState, array $copiedState){
$this->oldState = count($oldState) === 0 ? null : $oldState;
- $this->newName = $newName;
+ if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenInfo){
+ $this->newFlattenedName = $newNameRule;
+ }else{
+ $this->newName = $newNameRule;
+ }
$this->newState = count($newState) === 0 ? null : $newState;
$this->copiedState = $copiedState;
}
diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php
new file mode 100644
index 00000000000..6da590287b9
--- /dev/null
+++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php
@@ -0,0 +1,68 @@
+
+ */
+ public array $flattenedValueRemaps;
+
+ /**
+ * @param string[] $flattenedValueRemaps
+ * @phpstan-param array $flattenedValueRemaps
+ */
+ public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps, ?string $flattenedPropertyType = null){
+ $this->prefix = $prefix;
+ $this->flattenedProperty = $flattenedProperty;
+ $this->suffix = $suffix;
+ $this->flattenedValueRemaps = $flattenedValueRemaps;
+ $this->flattenedPropertyType = $flattenedPropertyType;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize() : array{
+ $result = (array) $this;
+ if(count($this->flattenedValueRemaps) === 0){
+ unset($result["flattenedValueRemaps"]);
+ }
+ if($this->flattenedPropertyType === null){
+ unset($result["flattenedPropertyType"]);
+ }
+ return $result;
+ }
+}
diff --git a/src/data/bedrock/item/ItemSerializer.php b/src/data/bedrock/item/ItemSerializer.php
index 39aeb7bfb3b..845d5bc5ff6 100644
--- a/src/data/bedrock/item/ItemSerializer.php
+++ b/src/data/bedrock/item/ItemSerializer.php
@@ -177,6 +177,7 @@ private function standardBlock(Block $block) : Data{
throw new ItemTypeSerializeException($e->getMessage(), 0, $e);
}
+ //TODO: this really ought to throw if there's no blockitem ID
$itemNameId = BlockItemIdMap::getInstance()->lookupItemId($blockStateData->getName()) ?? $blockStateData->getName();
return new Data($itemNameId, 0, $blockStateData);
diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php
index 7e2e3bad007..7803cea5c71 100644
--- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php
+++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php
@@ -25,19 +25,21 @@
use pocketmine\block\Bed;
use pocketmine\block\Block;
-use pocketmine\block\MobHead;
+use pocketmine\block\CopperDoor;
+use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\data\bedrock\CompoundTypeIds;
use pocketmine\data\bedrock\DyeColorIdMap;
+use pocketmine\data\bedrock\GoatHornTypeIdMap;
use pocketmine\data\bedrock\item\ItemTypeNames as Ids;
use pocketmine\data\bedrock\item\SavedItemData as Data;
use pocketmine\data\bedrock\MedicineTypeIdMap;
-use pocketmine\data\bedrock\MobHeadTypeIdMap;
use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\SuspiciousStewTypeIdMap;
use pocketmine\item\Banner;
use pocketmine\item\Dye;
+use pocketmine\item\GoatHorn;
use pocketmine\item\Item;
use pocketmine\item\Medicine;
use pocketmine\item\Potion;
@@ -56,6 +58,7 @@ public function __construct(
$this->register1to1BlockWithMetaMappings();
$this->register1to1ItemWithMetaMappings();
$this->register1ToNItemMappings();
+ $this->registerMiscBlockMappings();
$this->registerMiscItemMappings();
}
@@ -133,8 +136,10 @@ private function register1to1BlockMappings() : void{
$this->map1to1Block(Ids::BIRCH_DOOR, Blocks::BIRCH_DOOR());
$this->map1to1Block(Ids::BREWING_STAND, Blocks::BREWING_STAND());
$this->map1to1Block(Ids::CAKE, Blocks::CAKE());
+ $this->map1to1Block(Ids::CAMPFIRE, Blocks::CAMPFIRE());
$this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON());
$this->map1to1Block(Ids::CHAIN, Blocks::CHAIN());
+ $this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR());
$this->map1to1Block(Ids::COMPARATOR, Blocks::REDSTONE_COMPARATOR());
$this->map1to1Block(Ids::CRIMSON_DOOR, Blocks::CRIMSON_DOOR());
$this->map1to1Block(Ids::DARK_OAK_DOOR, Blocks::DARK_OAK_DOOR());
@@ -147,6 +152,7 @@ private function register1to1BlockMappings() : void{
$this->map1to1Block(Ids::MANGROVE_DOOR, Blocks::MANGROVE_DOOR());
$this->map1to1Block(Ids::NETHER_WART, Blocks::NETHER_WART());
$this->map1to1Block(Ids::REPEATER, Blocks::REDSTONE_REPEATER());
+ $this->map1to1Block(Ids::SOUL_CAMPFIRE, Blocks::SOUL_CAMPFIRE());
$this->map1to1Block(Ids::SPRUCE_DOOR, Blocks::SPRUCE_DOOR());
$this->map1to1Block(Ids::SUGAR_CANE, Blocks::SUGARCANE());
$this->map1to1Block(Ids::WARPED_DOOR, Blocks::WARPED_DOOR());
@@ -185,11 +191,13 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::CHAINMAIL_HELMET, Items::CHAINMAIL_HELMET());
$this->map1to1Item(Ids::CHAINMAIL_LEGGINGS, Items::CHAINMAIL_LEGGINGS());
$this->map1to1Item(Ids::CHARCOAL, Items::CHARCOAL());
+ $this->map1to1Item(Ids::CHERRY_SIGN, Items::CHERRY_SIGN());
$this->map1to1Item(Ids::CHICKEN, Items::RAW_CHICKEN());
$this->map1to1Item(Ids::CHORUS_FRUIT, Items::CHORUS_FRUIT());
$this->map1to1Item(Ids::CLAY_BALL, Items::CLAY());
$this->map1to1Item(Ids::CLOCK, Items::CLOCK());
$this->map1to1Item(Ids::COAL, Items::COAL());
+ $this->map1to1Item(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::COAST_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::COCOA_BEANS, Items::COCOA_BEANS());
$this->map1to1Item(Ids::COD, Items::RAW_FISH());
$this->map1to1Item(Ids::COMPASS, Items::COMPASS());
@@ -218,12 +226,16 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::DISC_FRAGMENT_5, Items::DISC_FRAGMENT_5());
$this->map1to1Item(Ids::DRAGON_BREATH, Items::DRAGON_BREATH());
$this->map1to1Item(Ids::DRIED_KELP, Items::DRIED_KELP());
+ $this->map1to1Item(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ECHO_SHARD, Items::ECHO_SHARD());
$this->map1to1Item(Ids::EGG, Items::EGG());
$this->map1to1Item(Ids::EMERALD, Items::EMERALD());
+ $this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK());
$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE());
+ $this->map1to1Item(Ids::END_CRYSTAL, Items::END_CRYSTAL());
$this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL());
$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());
+ $this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::FEATHER, Items::FEATHER());
$this->map1to1Item(Ids::FERMENTED_SPIDER_EYE, Items::FERMENTED_SPIDER_EYE());
$this->map1to1Item(Ids::FIRE_CHARGE, Items::FIRE_CHARGE());
@@ -253,6 +265,7 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::HEART_OF_THE_SEA, Items::HEART_OF_THE_SEA());
$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
+ $this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::INK_SAC, Items::INK_SAC());
$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());
@@ -298,6 +311,7 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::MUSIC_DISC_WAIT, Items::RECORD_WAIT());
$this->map1to1Item(Ids::MUSIC_DISC_WARD, Items::RECORD_WARD());
$this->map1to1Item(Ids::MUTTON, Items::RAW_MUTTON());
+ $this->map1to1Item(Ids::NAME_TAG, Items::NAME_TAG());
$this->map1to1Item(Ids::NAUTILUS_SHELL, Items::NAUTILUS_SHELL());
$this->map1to1Item(Ids::NETHER_STAR, Items::NETHER_STAR());
$this->map1to1Item(Ids::NETHERBRICK, Items::NETHER_BRICK());
@@ -312,11 +326,13 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::NETHERITE_SCRAP, Items::NETHERITE_SCRAP());
$this->map1to1Item(Ids::NETHERITE_SHOVEL, Items::NETHERITE_SHOVEL());
$this->map1to1Item(Ids::NETHERITE_SWORD, Items::NETHERITE_SWORD());
+ $this->map1to1Item(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::OAK_BOAT, Items::OAK_BOAT());
$this->map1to1Item(Ids::OAK_SIGN, Items::OAK_SIGN());
$this->map1to1Item(Ids::PAINTING, Items::PAINTING());
$this->map1to1Item(Ids::PAPER, Items::PAPER());
$this->map1to1Item(Ids::PHANTOM_MEMBRANE, Items::PHANTOM_MEMBRANE());
+ $this->map1to1Item(Ids::PITCHER_POD, Items::PITCHER_POD());
$this->map1to1Item(Ids::POISONOUS_POTATO, Items::POISONOUS_POTATO());
$this->map1to1Item(Ids::POPPED_CHORUS_FRUIT, Items::POPPED_CHORUS_FRUIT());
$this->map1to1Item(Ids::PORKCHOP, Items::RAW_PORKCHOP());
@@ -331,18 +347,25 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::RABBIT_FOOT, Items::RABBIT_FOOT());
$this->map1to1Item(Ids::RABBIT_HIDE, Items::RABBIT_HIDE());
$this->map1to1Item(Ids::RABBIT_STEW, Items::RABBIT_STEW());
+ $this->map1to1Item(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE());
$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::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());
$this->map1to1Item(Ids::SALMON, Items::RAW_SALMON());
- $this->map1to1Item(Ids::SCUTE, Items::SCUTE());
+ $this->map1to1Item(Ids::TURTLE_SCUTE, Items::SCUTE());
+ $this->map1to1Item(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE());
+ $this->map1to1Item(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SHEARS, Items::SHEARS());
$this->map1to1Item(Ids::SHULKER_SHELL, Items::SHULKER_SHELL());
+ $this->map1to1Item(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SLIME_BALL, Items::SLIMEBALL());
+ $this->map1to1Item(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SNOWBALL, Items::SNOWBALL());
$this->map1to1Item(Ids::SPIDER_EYE, Items::SPIDER_EYE());
+ $this->map1to1Item(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SPRUCE_BOAT, Items::SPRUCE_BOAT());
$this->map1to1Item(Ids::SPRUCE_SIGN, Items::SPRUCE_SIGN());
$this->map1to1Item(Ids::SPYGLASS, Items::SPYGLASS());
@@ -356,14 +379,20 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::STRING, Items::STRING());
$this->map1to1Item(Ids::SUGAR, Items::SUGAR());
$this->map1to1Item(Ids::SWEET_BERRIES, Items::SWEET_BERRIES());
+ $this->map1to1Item(Ids::TORCHFLOWER_SEEDS, Items::TORCHFLOWER_SEEDS());
+ $this->map1to1Item(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::TOTEM_OF_UNDYING, Items::TOTEM());
$this->map1to1Item(Ids::TROPICAL_FISH, Items::CLOWNFISH());
$this->map1to1Item(Ids::TURTLE_HELMET, Items::TURTLE_HELMET());
+ $this->map1to1Item(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE, Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::VILLAGER_SPAWN_EGG, Items::VILLAGER_SPAWN_EGG());
+ $this->map1to1Item(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::WARPED_SIGN, Items::WARPED_SIGN());
$this->map1to1Item(Ids::WATER_BUCKET, Items::WATER_BUCKET());
+ $this->map1to1Item(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::WHEAT, Items::WHEAT());
$this->map1to1Item(Ids::WHEAT_SEEDS, Items::WHEAT_SEEDS());
+ $this->map1to1Item(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::WOODEN_AXE, Items::WOODEN_AXE());
$this->map1to1Item(Ids::WOODEN_HOE, Items::WOODEN_HOE());
$this->map1to1Item(Ids::WOODEN_PICKAXE, Items::WOODEN_PICKAXE());
@@ -441,14 +470,6 @@ function(Bed $block, int $meta) : void{
},
fn(Bed $block) => DyeColorIdMap::getInstance()->toId($block->getColor())
);
- $this->map1to1BlockWithMeta(
- Ids::SKULL,
- Blocks::MOB_HEAD(),
- function(MobHead $block, int $meta) : void{
- $block->setMobHeadType(MobHeadTypeIdMap::getInstance()->fromId($meta) ?? throw new ItemTypeDeserializeException("Unknown mob head type ID $meta"));
- },
- fn(MobHead $block) => MobHeadTypeIdMap::getInstance()->toId($block->getMobHeadType())
- );
}
/**
@@ -465,6 +486,14 @@ function(Banner $item, int $meta) : void{
},
fn(Banner $item) => DyeColorIdMap::getInstance()->toInvertedId($item->getColor())
);
+ $this->map1to1ItemWithMeta(
+ Ids::GOAT_HORN,
+ Items::GOAT_HORN(),
+ function(GoatHorn $item, int $meta) : void{
+ $item->setHornType(GoatHornTypeIdMap::getInstance()->fromId($meta) ?? throw new ItemTypeDeserializeException("Unknown goat horn type ID $meta"));
+ },
+ fn(GoatHorn $item) => GoatHornTypeIdMap::getInstance()->toId($item->getHornType())
+ );
$this->map1to1ItemWithMeta(
Ids::MEDICINE,
Items::MEDICINE(),
@@ -507,10 +536,35 @@ function(SuspiciousStew $item, int $meta) : void{
* complex to implement in a generic way.
*/
private function registerMiscItemMappings() : void{
- foreach(DyeColor::getAll() as $color){
+ foreach(DyeColor::cases() as $color){
$id = DyeColorIdMap::getInstance()->toItemId($color);
$this->deserializer?->map($id, fn() => Items::DYE()->setColor($color));
}
$this->serializer?->map(Items::DYE(), fn(Dye $item) => new Data(DyeColorIdMap::getInstance()->toItemId($item->getColor())));
}
+
+ /**
+ * Registers serializers and deserializers for PocketMine-MP blockitems that don't fit any other pattern.
+ * Ideally we want to get rid of this completely, if possible.
+ *
+ * Most of these are single PocketMine-MP blocks which map to multiple IDs depending on their properties, which is
+ * complex to implement in a generic way.
+ */
+ private function registerMiscBlockMappings() : void{
+ $copperDoorStateIdMap = [];
+ foreach ([
+ [Ids::COPPER_DOOR, CopperOxidation::NONE, false],
+ [Ids::EXPOSED_COPPER_DOOR, CopperOxidation::EXPOSED, false],
+ [Ids::WEATHERED_COPPER_DOOR, CopperOxidation::WEATHERED, false],
+ [Ids::OXIDIZED_COPPER_DOOR, CopperOxidation::OXIDIZED, false],
+ [Ids::WAXED_COPPER_DOOR, CopperOxidation::NONE, true],
+ [Ids::WAXED_EXPOSED_COPPER_DOOR, CopperOxidation::EXPOSED, true],
+ [Ids::WAXED_WEATHERED_COPPER_DOOR, CopperOxidation::WEATHERED, true],
+ [Ids::WAXED_OXIDIZED_COPPER_DOOR, CopperOxidation::OXIDIZED, true]
+ ] as [$id, $oxidation, $waxed]) {
+ $copperDoorStateIdMap[$oxidation->value][$waxed ? 1 : 0] = $id;
+ $this->deserializer?->mapBlock($id, fn() => Blocks::COPPER_DOOR()->setOxidation($oxidation)->setWaxed($waxed));
+ }
+ $this->serializer?->mapBlock(Blocks::COPPER_DOOR(), fn(CopperDoor $block) => new Data($copperDoorStateIdMap[$block->getOxidation()->value][$block->isWaxed() ? 1 : 0]));
+ }
}
diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php
index 2a808d41613..03b32d48269 100644
--- a/src/data/bedrock/item/ItemTypeNames.php
+++ b/src/data/bedrock/item/ItemTypeNames.php
@@ -30,20 +30,26 @@ final class ItemTypeNames{
public const ACACIA_BOAT = "minecraft:acacia_boat";
public const ACACIA_CHEST_BOAT = "minecraft:acacia_chest_boat";
public const ACACIA_DOOR = "minecraft:acacia_door";
+ public const ACACIA_HANGING_SIGN = "minecraft:acacia_hanging_sign";
public const ACACIA_SIGN = "minecraft:acacia_sign";
public const AGENT_SPAWN_EGG = "minecraft:agent_spawn_egg";
public const ALLAY_SPAWN_EGG = "minecraft:allay_spawn_egg";
public const AMETHYST_SHARD = "minecraft:amethyst_shard";
+ public const ANGLER_POTTERY_SHERD = "minecraft:angler_pottery_sherd";
public const APPLE = "minecraft:apple";
- public const ARCHER_POTTERY_SHARD = "minecraft:archer_pottery_shard";
+ public const ARCHER_POTTERY_SHERD = "minecraft:archer_pottery_sherd";
+ public const ARMADILLO_SCUTE = "minecraft:armadillo_scute";
+ public const ARMADILLO_SPAWN_EGG = "minecraft:armadillo_spawn_egg";
public const ARMOR_STAND = "minecraft:armor_stand";
- public const ARMS_UP_POTTERY_SHARD = "minecraft:arms_up_pottery_shard";
+ public const ARMS_UP_POTTERY_SHERD = "minecraft:arms_up_pottery_sherd";
public const ARROW = "minecraft:arrow";
public const AXOLOTL_BUCKET = "minecraft:axolotl_bucket";
public const AXOLOTL_SPAWN_EGG = "minecraft:axolotl_spawn_egg";
public const BAKED_POTATO = "minecraft:baked_potato";
public const BALLOON = "minecraft:balloon";
public const BAMBOO_CHEST_RAFT = "minecraft:bamboo_chest_raft";
+ public const BAMBOO_DOOR = "minecraft:bamboo_door";
+ public const BAMBOO_HANGING_SIGN = "minecraft:bamboo_hanging_sign";
public const BAMBOO_RAFT = "minecraft:bamboo_raft";
public const BAMBOO_SIGN = "minecraft:bamboo_sign";
public const BANNER = "minecraft:banner";
@@ -58,14 +64,20 @@ final class ItemTypeNames{
public const BIRCH_BOAT = "minecraft:birch_boat";
public const BIRCH_CHEST_BOAT = "minecraft:birch_chest_boat";
public const BIRCH_DOOR = "minecraft:birch_door";
+ public const BIRCH_HANGING_SIGN = "minecraft:birch_hanging_sign";
public const BIRCH_SIGN = "minecraft:birch_sign";
+ public const BLACK_BUNDLE = "minecraft:black_bundle";
public const BLACK_DYE = "minecraft:black_dye";
+ public const BLADE_POTTERY_SHERD = "minecraft:blade_pottery_sherd";
public const BLAZE_POWDER = "minecraft:blaze_powder";
public const BLAZE_ROD = "minecraft:blaze_rod";
public const BLAZE_SPAWN_EGG = "minecraft:blaze_spawn_egg";
public const BLEACH = "minecraft:bleach";
+ public const BLUE_BUNDLE = "minecraft:blue_bundle";
public const BLUE_DYE = "minecraft:blue_dye";
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";
public const BONE = "minecraft:bone";
public const BONE_MEAL = "minecraft:bone_meal";
public const BOOK = "minecraft:book";
@@ -73,15 +85,22 @@ final class ItemTypeNames{
public const BOW = "minecraft:bow";
public const BOWL = "minecraft:bowl";
public const BREAD = "minecraft:bread";
+ public const BREEZE_ROD = "minecraft:breeze_rod";
+ public const BREEZE_SPAWN_EGG = "minecraft:breeze_spawn_egg";
+ public const BREWER_POTTERY_SHERD = "minecraft:brewer_pottery_sherd";
public const BREWING_STAND = "minecraft:brewing_stand";
public const BRICK = "minecraft:brick";
+ public const BROWN_BUNDLE = "minecraft:brown_bundle";
public const BROWN_DYE = "minecraft:brown_dye";
public const BRUSH = "minecraft:brush";
public const BUCKET = "minecraft:bucket";
+ public const BUNDLE = "minecraft:bundle";
+ public const BURN_POTTERY_SHERD = "minecraft:burn_pottery_sherd";
public const CAKE = "minecraft:cake";
public const CAMEL_SPAWN_EGG = "minecraft:camel_spawn_egg";
public const CAMERA = "minecraft:camera";
public const CAMPFIRE = "minecraft:campfire";
+ public const CARPET = "minecraft:carpet";
public const CARROT = "minecraft:carrot";
public const CARROT_ON_A_STICK = "minecraft:carrot_on_a_stick";
public const CAT_SPAWN_EGG = "minecraft:cat_spawn_egg";
@@ -93,6 +112,12 @@ final class ItemTypeNames{
public const CHAINMAIL_HELMET = "minecraft:chainmail_helmet";
public const CHAINMAIL_LEGGINGS = "minecraft:chainmail_leggings";
public const CHARCOAL = "minecraft:charcoal";
+ public const CHEMISTRY_TABLE = "minecraft:chemistry_table";
+ public const CHERRY_BOAT = "minecraft:cherry_boat";
+ public const CHERRY_CHEST_BOAT = "minecraft:cherry_chest_boat";
+ public const CHERRY_DOOR = "minecraft:cherry_door";
+ public const CHERRY_HANGING_SIGN = "minecraft:cherry_hanging_sign";
+ public const CHERRY_SIGN = "minecraft:cherry_sign";
public const CHEST_BOAT = "minecraft:chest_boat";
public const CHEST_MINECART = "minecraft:chest_minecart";
public const CHICKEN = "minecraft:chicken";
@@ -101,14 +126,19 @@ final class ItemTypeNames{
public const CLAY_BALL = "minecraft:clay_ball";
public const CLOCK = "minecraft:clock";
public const COAL = "minecraft:coal";
+ public const COAST_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:coast_armor_trim_smithing_template";
public const COCOA_BEANS = "minecraft:cocoa_beans";
public const COD = "minecraft:cod";
public const COD_BUCKET = "minecraft:cod_bucket";
public const COD_SPAWN_EGG = "minecraft:cod_spawn_egg";
+ public const COLORED_TORCH_BP = "minecraft:colored_torch_bp";
+ public const COLORED_TORCH_RG = "minecraft:colored_torch_rg";
public const COMMAND_BLOCK_MINECART = "minecraft:command_block_minecart";
public const COMPARATOR = "minecraft:comparator";
public const COMPASS = "minecraft:compass";
public const COMPOUND = "minecraft:compound";
+ public const CONCRETE = "minecraft:concrete";
+ public const CONCRETE_POWDER = "minecraft:concrete_powder";
public const COOKED_BEEF = "minecraft:cooked_beef";
public const COOKED_CHICKEN = "minecraft:cooked_chicken";
public const COOKED_COD = "minecraft:cooked_cod";
@@ -117,17 +147,26 @@ final class ItemTypeNames{
public const COOKED_RABBIT = "minecraft:cooked_rabbit";
public const COOKED_SALMON = "minecraft:cooked_salmon";
public const COOKIE = "minecraft:cookie";
+ public const COPPER_DOOR = "minecraft:copper_door";
public const COPPER_INGOT = "minecraft:copper_ingot";
+ public const CORAL = "minecraft:coral";
+ public const CORAL_BLOCK = "minecraft:coral_block";
+ 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 CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
public const CRIMSON_DOOR = "minecraft:crimson_door";
+ public const CRIMSON_HANGING_SIGN = "minecraft:crimson_hanging_sign";
public const CRIMSON_SIGN = "minecraft:crimson_sign";
public const CROSSBOW = "minecraft:crossbow";
+ public const CYAN_BUNDLE = "minecraft:cyan_bundle";
public const CYAN_DYE = "minecraft:cyan_dye";
+ public const DANGER_POTTERY_SHERD = "minecraft:danger_pottery_sherd";
public const DARK_OAK_BOAT = "minecraft:dark_oak_boat";
public const DARK_OAK_CHEST_BOAT = "minecraft:dark_oak_chest_boat";
public const DARK_OAK_DOOR = "minecraft:dark_oak_door";
+ public const DARK_OAK_HANGING_SIGN = "minecraft:dark_oak_hanging_sign";
public const DARK_OAK_SIGN = "minecraft:dark_oak_sign";
public const DEBUG_STICK = "minecraft:debug_stick";
public const DIAMOND = "minecraft:diamond";
@@ -144,9 +183,15 @@ final class ItemTypeNames{
public const DISC_FRAGMENT_5 = "minecraft:disc_fragment_5";
public const DOLPHIN_SPAWN_EGG = "minecraft:dolphin_spawn_egg";
public const DONKEY_SPAWN_EGG = "minecraft:donkey_spawn_egg";
+ public const DOUBLE_PLANT = "minecraft:double_plant";
+ public const DOUBLE_STONE_BLOCK_SLAB = "minecraft:double_stone_block_slab";
+ public const DOUBLE_STONE_BLOCK_SLAB2 = "minecraft:double_stone_block_slab2";
+ public const DOUBLE_STONE_BLOCK_SLAB3 = "minecraft:double_stone_block_slab3";
+ public const DOUBLE_STONE_BLOCK_SLAB4 = "minecraft:double_stone_block_slab4";
public const DRAGON_BREATH = "minecraft:dragon_breath";
public const DRIED_KELP = "minecraft:dried_kelp";
public const DROWNED_SPAWN_EGG = "minecraft:drowned_spawn_egg";
+ public const DUNE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:dune_armor_trim_smithing_template";
public const DYE = "minecraft:dye";
public const ECHO_SHARD = "minecraft:echo_shard";
public const EGG = "minecraft:egg";
@@ -164,7 +209,11 @@ final class ItemTypeNames{
public const ENDERMITE_SPAWN_EGG = "minecraft:endermite_spawn_egg";
public const EVOKER_SPAWN_EGG = "minecraft:evoker_spawn_egg";
public const EXPERIENCE_BOTTLE = "minecraft:experience_bottle";
+ public const EXPLORER_POTTERY_SHERD = "minecraft:explorer_pottery_sherd";
+ public const EXPOSED_COPPER_DOOR = "minecraft:exposed_copper_door";
+ public const EYE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:eye_armor_trim_smithing_template";
public const FEATHER = "minecraft:feather";
+ public const FENCE = "minecraft:fence";
public const FERMENTED_SPIDER_EYE = "minecraft:fermented_spider_eye";
public const FIELD_MASONED_BANNER_PATTERN = "minecraft:field_masoned_banner_pattern";
public const FILLED_MAP = "minecraft:filled_map";
@@ -174,10 +223,14 @@ final class ItemTypeNames{
public const FISHING_ROD = "minecraft:fishing_rod";
public const FLINT = "minecraft:flint";
public const FLINT_AND_STEEL = "minecraft:flint_and_steel";
+ public const FLOW_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:flow_armor_trim_smithing_template";
+ public const FLOW_BANNER_PATTERN = "minecraft:flow_banner_pattern";
+ public const FLOW_POTTERY_SHERD = "minecraft:flow_pottery_sherd";
public const FLOWER_BANNER_PATTERN = "minecraft:flower_banner_pattern";
public const FLOWER_POT = "minecraft:flower_pot";
public const FOX_SPAWN_EGG = "minecraft:fox_spawn_egg";
public const FRAME = "minecraft:frame";
+ public const FRIEND_POTTERY_SHERD = "minecraft:friend_pottery_sherd";
public const FROG_SPAWN_EGG = "minecraft:frog_spawn_egg";
public const GHAST_SPAWN_EGG = "minecraft:ghast_spawn_egg";
public const GHAST_TEAR = "minecraft:ghast_tear";
@@ -206,17 +259,27 @@ final class ItemTypeNames{
public const GOLDEN_PICKAXE = "minecraft:golden_pickaxe";
public const GOLDEN_SHOVEL = "minecraft:golden_shovel";
public const GOLDEN_SWORD = "minecraft:golden_sword";
+ public const GRAY_BUNDLE = "minecraft:gray_bundle";
public const GRAY_DYE = "minecraft:gray_dye";
+ public const GREEN_BUNDLE = "minecraft:green_bundle";
public const GREEN_DYE = "minecraft:green_dye";
public const GUARDIAN_SPAWN_EGG = "minecraft:guardian_spawn_egg";
public const GUNPOWDER = "minecraft:gunpowder";
+ public const GUSTER_BANNER_PATTERN = "minecraft:guster_banner_pattern";
+ public const GUSTER_POTTERY_SHERD = "minecraft:guster_pottery_sherd";
+ public const HARD_STAINED_GLASS = "minecraft:hard_stained_glass";
+ public const HARD_STAINED_GLASS_PANE = "minecraft:hard_stained_glass_pane";
public const HEART_OF_THE_SEA = "minecraft:heart_of_the_sea";
+ public const HEART_POTTERY_SHERD = "minecraft:heart_pottery_sherd";
+ public const HEARTBREAK_POTTERY_SHERD = "minecraft:heartbreak_pottery_sherd";
public const HOGLIN_SPAWN_EGG = "minecraft:hoglin_spawn_egg";
public const HONEY_BOTTLE = "minecraft:honey_bottle";
public const HONEYCOMB = "minecraft:honeycomb";
public const HOPPER = "minecraft:hopper";
public const HOPPER_MINECART = "minecraft:hopper_minecart";
public const HORSE_SPAWN_EGG = "minecraft:horse_spawn_egg";
+ public const HOST_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:host_armor_trim_smithing_template";
+ public const HOWL_POTTERY_SHERD = "minecraft:howl_pottery_sherd";
public const HUSK_SPAWN_EGG = "minecraft:husk_spawn_egg";
public const ICE_BOMB = "minecraft:ice_bomb";
public const INK_SAC = "minecraft:ink_sac";
@@ -237,6 +300,7 @@ final class ItemTypeNames{
public const JUNGLE_BOAT = "minecraft:jungle_boat";
public const JUNGLE_CHEST_BOAT = "minecraft:jungle_chest_boat";
public const JUNGLE_DOOR = "minecraft:jungle_door";
+ public const JUNGLE_HANGING_SIGN = "minecraft:jungle_hanging_sign";
public const JUNGLE_SIGN = "minecraft:jungle_sign";
public const KELP = "minecraft:kelp";
public const LAPIS_LAZULI = "minecraft:lapis_lazuli";
@@ -248,26 +312,40 @@ final class ItemTypeNames{
public const LEATHER_HELMET = "minecraft:leather_helmet";
public const LEATHER_HORSE_ARMOR = "minecraft:leather_horse_armor";
public const LEATHER_LEGGINGS = "minecraft:leather_leggings";
+ public const LEAVES = "minecraft:leaves";
+ public const LEAVES2 = "minecraft:leaves2";
+ public const LIGHT_BLOCK = "minecraft:light_block";
+ public const LIGHT_BLUE_BUNDLE = "minecraft:light_blue_bundle";
public const LIGHT_BLUE_DYE = "minecraft:light_blue_dye";
+ public const LIGHT_GRAY_BUNDLE = "minecraft:light_gray_bundle";
public const LIGHT_GRAY_DYE = "minecraft:light_gray_dye";
+ public const LIME_BUNDLE = "minecraft:lime_bundle";
public const LIME_DYE = "minecraft:lime_dye";
public const LINGERING_POTION = "minecraft:lingering_potion";
public const LLAMA_SPAWN_EGG = "minecraft:llama_spawn_egg";
public const LODESTONE_COMPASS = "minecraft:lodestone_compass";
+ public const LOG = "minecraft:log";
+ public const LOG2 = "minecraft:log2";
+ public const MACE = "minecraft:mace";
+ public const MAGENTA_BUNDLE = "minecraft:magenta_bundle";
public const MAGENTA_DYE = "minecraft:magenta_dye";
public const MAGMA_CREAM = "minecraft:magma_cream";
public const MAGMA_CUBE_SPAWN_EGG = "minecraft:magma_cube_spawn_egg";
public const MANGROVE_BOAT = "minecraft:mangrove_boat";
public const MANGROVE_CHEST_BOAT = "minecraft:mangrove_chest_boat";
public const MANGROVE_DOOR = "minecraft:mangrove_door";
+ public const MANGROVE_HANGING_SIGN = "minecraft:mangrove_hanging_sign";
public const MANGROVE_SIGN = "minecraft:mangrove_sign";
public const MEDICINE = "minecraft:medicine";
public const MELON_SEEDS = "minecraft:melon_seeds";
public const MELON_SLICE = "minecraft:melon_slice";
public const MILK_BUCKET = "minecraft:milk_bucket";
public const MINECART = "minecraft:minecart";
+ public const MINER_POTTERY_SHERD = "minecraft:miner_pottery_sherd";
public const MOJANG_BANNER_PATTERN = "minecraft:mojang_banner_pattern";
+ public const MONSTER_EGG = "minecraft:monster_egg";
public const MOOSHROOM_SPAWN_EGG = "minecraft:mooshroom_spawn_egg";
+ public const MOURNER_POTTERY_SHERD = "minecraft:mourner_pottery_sherd";
public const MULE_SPAWN_EGG = "minecraft:mule_spawn_egg";
public const MUSHROOM_STEW = "minecraft:mushroom_stew";
public const MUSIC_DISC_11 = "minecraft:music_disc_11";
@@ -276,11 +354,15 @@ final class ItemTypeNames{
public const MUSIC_DISC_BLOCKS = "minecraft:music_disc_blocks";
public const MUSIC_DISC_CAT = "minecraft:music_disc_cat";
public const MUSIC_DISC_CHIRP = "minecraft:music_disc_chirp";
+ public const MUSIC_DISC_CREATOR = "minecraft:music_disc_creator";
+ public const MUSIC_DISC_CREATOR_MUSIC_BOX = "minecraft:music_disc_creator_music_box";
public const MUSIC_DISC_FAR = "minecraft:music_disc_far";
public const MUSIC_DISC_MALL = "minecraft:music_disc_mall";
public const MUSIC_DISC_MELLOHI = "minecraft:music_disc_mellohi";
public const MUSIC_DISC_OTHERSIDE = "minecraft:music_disc_otherside";
public const MUSIC_DISC_PIGSTEP = "minecraft:music_disc_pigstep";
+ public const MUSIC_DISC_PRECIPICE = "minecraft:music_disc_precipice";
+ public const MUSIC_DISC_RELIC = "minecraft:music_disc_relic";
public const MUSIC_DISC_STAL = "minecraft:music_disc_stal";
public const MUSIC_DISC_STRAD = "minecraft:music_disc_strad";
public const MUSIC_DISC_WAIT = "minecraft:music_disc_wait";
@@ -303,12 +385,18 @@ final class ItemTypeNames{
public const NETHERITE_SCRAP = "minecraft:netherite_scrap";
public const NETHERITE_SHOVEL = "minecraft:netherite_shovel";
public const NETHERITE_SWORD = "minecraft:netherite_sword";
+ public const NETHERITE_UPGRADE_SMITHING_TEMPLATE = "minecraft:netherite_upgrade_smithing_template";
public const NPC_SPAWN_EGG = "minecraft:npc_spawn_egg";
public const OAK_BOAT = "minecraft:oak_boat";
public const OAK_CHEST_BOAT = "minecraft:oak_chest_boat";
+ public const OAK_HANGING_SIGN = "minecraft:oak_hanging_sign";
public const OAK_SIGN = "minecraft:oak_sign";
public const OCELOT_SPAWN_EGG = "minecraft:ocelot_spawn_egg";
+ public const OMINOUS_BOTTLE = "minecraft:ominous_bottle";
+ public const OMINOUS_TRIAL_KEY = "minecraft:ominous_trial_key";
+ public const ORANGE_BUNDLE = "minecraft:orange_bundle";
public const ORANGE_DYE = "minecraft:orange_dye";
+ public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
public const PAINTING = "minecraft:painting";
public const PANDA_SPAWN_EGG = "minecraft:panda_spawn_egg";
public const PAPER = "minecraft:paper";
@@ -320,7 +408,11 @@ final class ItemTypeNames{
public const PIGLIN_BRUTE_SPAWN_EGG = "minecraft:piglin_brute_spawn_egg";
public const PIGLIN_SPAWN_EGG = "minecraft:piglin_spawn_egg";
public const PILLAGER_SPAWN_EGG = "minecraft:pillager_spawn_egg";
+ public const PINK_BUNDLE = "minecraft:pink_bundle";
public const PINK_DYE = "minecraft:pink_dye";
+ public const PITCHER_POD = "minecraft:pitcher_pod";
+ public const PLANKS = "minecraft:planks";
+ public const PLENTY_POTTERY_SHERD = "minecraft:plenty_pottery_sherd";
public const POISONOUS_POTATO = "minecraft:poisonous_potato";
public const POLAR_BEAR_SPAWN_EGG = "minecraft:polar_bear_spawn_egg";
public const POPPED_CHORUS_FRUIT = "minecraft:popped_chorus_fruit";
@@ -330,12 +422,13 @@ final class ItemTypeNames{
public const POWDER_SNOW_BUCKET = "minecraft:powder_snow_bucket";
public const PRISMARINE_CRYSTALS = "minecraft:prismarine_crystals";
public const PRISMARINE_SHARD = "minecraft:prismarine_shard";
- public const PRIZE_POTTERY_SHARD = "minecraft:prize_pottery_shard";
+ public const PRIZE_POTTERY_SHERD = "minecraft:prize_pottery_sherd";
public const PUFFERFISH = "minecraft:pufferfish";
public const PUFFERFISH_BUCKET = "minecraft:pufferfish_bucket";
public const PUFFERFISH_SPAWN_EGG = "minecraft:pufferfish_spawn_egg";
public const PUMPKIN_PIE = "minecraft:pumpkin_pie";
public const PUMPKIN_SEEDS = "minecraft:pumpkin_seeds";
+ public const PURPLE_BUNDLE = "minecraft:purple_bundle";
public const PURPLE_DYE = "minecraft:purple_dye";
public const QUARTZ = "minecraft:quartz";
public const RABBIT = "minecraft:rabbit";
@@ -343,35 +436,48 @@ final class ItemTypeNames{
public const RABBIT_HIDE = "minecraft:rabbit_hide";
public const RABBIT_SPAWN_EGG = "minecraft:rabbit_spawn_egg";
public const RABBIT_STEW = "minecraft:rabbit_stew";
+ public const RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:raiser_armor_trim_smithing_template";
public const RAPID_FERTILIZER = "minecraft:rapid_fertilizer";
public const RAVAGER_SPAWN_EGG = "minecraft:ravager_spawn_egg";
public const RAW_COPPER = "minecraft:raw_copper";
public const RAW_GOLD = "minecraft:raw_gold";
public const RAW_IRON = "minecraft:raw_iron";
public const RECOVERY_COMPASS = "minecraft:recovery_compass";
+ public const RED_BUNDLE = "minecraft:red_bundle";
public const RED_DYE = "minecraft:red_dye";
+ public const RED_FLOWER = "minecraft:red_flower";
public const REDSTONE = "minecraft:redstone";
public const REPEATER = "minecraft:repeater";
+ 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";
public const SALMON = "minecraft:salmon";
public const SALMON_BUCKET = "minecraft:salmon_bucket";
public const SALMON_SPAWN_EGG = "minecraft:salmon_spawn_egg";
- public const SCUTE = "minecraft:scute";
+ public const SAPLING = "minecraft:sapling";
+ public const SCRAPE_POTTERY_SHERD = "minecraft:scrape_pottery_sherd";
+ public const SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:sentry_armor_trim_smithing_template";
+ public const SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:shaper_armor_trim_smithing_template";
+ public const SHEAF_POTTERY_SHERD = "minecraft:sheaf_pottery_sherd";
public const SHEARS = "minecraft:shears";
public const SHEEP_SPAWN_EGG = "minecraft:sheep_spawn_egg";
+ public const SHELTER_POTTERY_SHERD = "minecraft:shelter_pottery_sherd";
public const SHIELD = "minecraft:shield";
+ public const SHULKER_BOX = "minecraft:shulker_box";
public const SHULKER_SHELL = "minecraft:shulker_shell";
public const SHULKER_SPAWN_EGG = "minecraft:shulker_spawn_egg";
+ public const SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:silence_armor_trim_smithing_template";
public const SILVERFISH_SPAWN_EGG = "minecraft:silverfish_spawn_egg";
public const SKELETON_HORSE_SPAWN_EGG = "minecraft:skeleton_horse_spawn_egg";
public const SKELETON_SPAWN_EGG = "minecraft:skeleton_spawn_egg";
public const SKULL = "minecraft:skull";
public const SKULL_BANNER_PATTERN = "minecraft:skull_banner_pattern";
- public const SKULL_POTTERY_SHARD = "minecraft:skull_pottery_shard";
+ public const SKULL_POTTERY_SHERD = "minecraft:skull_pottery_sherd";
public const SLIME_BALL = "minecraft:slime_ball";
public const SLIME_SPAWN_EGG = "minecraft:slime_spawn_egg";
public const SNIFFER_SPAWN_EGG = "minecraft:sniffer_spawn_egg";
+ public const SNORT_POTTERY_SHERD = "minecraft:snort_pottery_sherd";
+ public const SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:snout_armor_trim_smithing_template";
public const SNOW_GOLEM_SPAWN_EGG = "minecraft:snow_golem_spawn_egg";
public const SNOWBALL = "minecraft:snowball";
public const SOUL_CAMPFIRE = "minecraft:soul_campfire";
@@ -379,19 +485,29 @@ final class ItemTypeNames{
public const SPAWN_EGG = "minecraft:spawn_egg";
public const SPIDER_EYE = "minecraft:spider_eye";
public const SPIDER_SPAWN_EGG = "minecraft:spider_spawn_egg";
+ public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:spire_armor_trim_smithing_template";
public const SPLASH_POTION = "minecraft:splash_potion";
public const SPRUCE_BOAT = "minecraft:spruce_boat";
public const SPRUCE_CHEST_BOAT = "minecraft:spruce_chest_boat";
public const SPRUCE_DOOR = "minecraft:spruce_door";
+ public const SPRUCE_HANGING_SIGN = "minecraft:spruce_hanging_sign";
public const SPRUCE_SIGN = "minecraft:spruce_sign";
public const SPYGLASS = "minecraft:spyglass";
public const SQUID_SPAWN_EGG = "minecraft:squid_spawn_egg";
+ public const STAINED_GLASS = "minecraft:stained_glass";
+ public const STAINED_GLASS_PANE = "minecraft:stained_glass_pane";
+ public const STAINED_HARDENED_CLAY = "minecraft:stained_hardened_clay";
public const STICK = "minecraft:stick";
public const STONE_AXE = "minecraft:stone_axe";
+ public const STONE_BLOCK_SLAB = "minecraft:stone_block_slab";
+ public const STONE_BLOCK_SLAB2 = "minecraft:stone_block_slab2";
+ public const STONE_BLOCK_SLAB3 = "minecraft:stone_block_slab3";
+ public const STONE_BLOCK_SLAB4 = "minecraft:stone_block_slab4";
public const STONE_HOE = "minecraft:stone_hoe";
public const STONE_PICKAXE = "minecraft:stone_pickaxe";
public const STONE_SHOVEL = "minecraft:stone_shovel";
public const STONE_SWORD = "minecraft:stone_sword";
+ public const STONEBRICK = "minecraft:stonebrick";
public const STRAY_SPAWN_EGG = "minecraft:stray_spawn_egg";
public const STRIDER_SPAWN_EGG = "minecraft:strider_spawn_egg";
public const STRING = "minecraft:string";
@@ -401,41 +517,61 @@ final class ItemTypeNames{
public const SWEET_BERRIES = "minecraft:sweet_berries";
public const TADPOLE_BUCKET = "minecraft:tadpole_bucket";
public const TADPOLE_SPAWN_EGG = "minecraft:tadpole_spawn_egg";
+ public const TALLGRASS = "minecraft:tallgrass";
+ public const TIDE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:tide_armor_trim_smithing_template";
public const TNT_MINECART = "minecraft:tnt_minecart";
public const TORCHFLOWER_SEEDS = "minecraft:torchflower_seeds";
public const TOTEM_OF_UNDYING = "minecraft:totem_of_undying";
public const TRADER_LLAMA_SPAWN_EGG = "minecraft:trader_llama_spawn_egg";
+ public const TRIAL_KEY = "minecraft:trial_key";
public const TRIDENT = "minecraft:trident";
public const TROPICAL_FISH = "minecraft:tropical_fish";
public const TROPICAL_FISH_BUCKET = "minecraft:tropical_fish_bucket";
public const TROPICAL_FISH_SPAWN_EGG = "minecraft:tropical_fish_spawn_egg";
public const TURTLE_HELMET = "minecraft:turtle_helmet";
+ public const TURTLE_SCUTE = "minecraft:turtle_scute";
public const TURTLE_SPAWN_EGG = "minecraft:turtle_spawn_egg";
+ public const VEX_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:vex_armor_trim_smithing_template";
public const VEX_SPAWN_EGG = "minecraft:vex_spawn_egg";
public const VILLAGER_SPAWN_EGG = "minecraft:villager_spawn_egg";
public const VINDICATOR_SPAWN_EGG = "minecraft:vindicator_spawn_egg";
public const WANDERING_TRADER_SPAWN_EGG = "minecraft:wandering_trader_spawn_egg";
+ public const WARD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:ward_armor_trim_smithing_template";
public const WARDEN_SPAWN_EGG = "minecraft:warden_spawn_egg";
public const WARPED_DOOR = "minecraft:warped_door";
public const WARPED_FUNGUS_ON_A_STICK = "minecraft:warped_fungus_on_a_stick";
+ public const WARPED_HANGING_SIGN = "minecraft:warped_hanging_sign";
public const WARPED_SIGN = "minecraft:warped_sign";
public const WATER_BUCKET = "minecraft:water_bucket";
+ public const WAXED_COPPER_DOOR = "minecraft:waxed_copper_door";
+ public const WAXED_EXPOSED_COPPER_DOOR = "minecraft:waxed_exposed_copper_door";
+ public const WAXED_OXIDIZED_COPPER_DOOR = "minecraft:waxed_oxidized_copper_door";
+ public const WAXED_WEATHERED_COPPER_DOOR = "minecraft:waxed_weathered_copper_door";
+ public const WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wayfinder_armor_trim_smithing_template";
+ public const WEATHERED_COPPER_DOOR = "minecraft:weathered_copper_door";
public const WHEAT = "minecraft:wheat";
public const WHEAT_SEEDS = "minecraft:wheat_seeds";
+ public const WHITE_BUNDLE = "minecraft:white_bundle";
public const WHITE_DYE = "minecraft:white_dye";
+ public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wild_armor_trim_smithing_template";
+ public const WIND_CHARGE = "minecraft:wind_charge";
public const WITCH_SPAWN_EGG = "minecraft:witch_spawn_egg";
public const WITHER_SKELETON_SPAWN_EGG = "minecraft:wither_skeleton_spawn_egg";
public const WITHER_SPAWN_EGG = "minecraft:wither_spawn_egg";
+ public const WOLF_ARMOR = "minecraft:wolf_armor";
public const WOLF_SPAWN_EGG = "minecraft:wolf_spawn_egg";
+ public const WOOD = "minecraft:wood";
public const WOODEN_AXE = "minecraft:wooden_axe";
public const WOODEN_DOOR = "minecraft:wooden_door";
public const WOODEN_HOE = "minecraft:wooden_hoe";
public const WOODEN_PICKAXE = "minecraft:wooden_pickaxe";
public const WOODEN_SHOVEL = "minecraft:wooden_shovel";
+ public const WOODEN_SLAB = "minecraft:wooden_slab";
public const WOODEN_SWORD = "minecraft:wooden_sword";
public const WOOL = "minecraft:wool";
public const WRITABLE_BOOK = "minecraft:writable_book";
public const WRITTEN_BOOK = "minecraft:written_book";
+ public const YELLOW_BUNDLE = "minecraft:yellow_bundle";
public const YELLOW_DYE = "minecraft:yellow_dye";
public const ZOGLIN_SPAWN_EGG = "minecraft:zoglin_spawn_egg";
public const ZOMBIE_HORSE_SPAWN_EGG = "minecraft:zombie_horse_spawn_egg";
diff --git a/src/data/bedrock/item/upgrade/ItemDataUpgrader.php b/src/data/bedrock/item/upgrade/ItemDataUpgrader.php
index d7d097de823..7b34ffcb6e0 100644
--- a/src/data/bedrock/item/upgrade/ItemDataUpgrader.php
+++ b/src/data/bedrock/item/upgrade/ItemDataUpgrader.php
@@ -25,6 +25,7 @@
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
use pocketmine\data\bedrock\block\upgrade\BlockDataUpgrader;
+use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\data\bedrock\item\SavedItemStackData;
use pocketmine\data\SavedDataLoadingException;
@@ -35,6 +36,7 @@
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
+use pocketmine\network\mcpe\convert\BlockStateDictionary;
use pocketmine\utils\Binary;
use function assert;
@@ -46,6 +48,8 @@ public function __construct(
private LegacyItemIdToStringIdMap $legacyIntToStringIdMap,
private R12ItemIdToBlockIdMap $r12ItemIdToBlockIdMap,
private BlockDataUpgrader $blockDataUpgrader,
+ private BlockItemIdMap $blockItemIdMap,
+ private BlockStateDictionary $blockStateDictionary
){}
/**
@@ -148,6 +152,17 @@ private function upgradeItemTypeNbt(CompoundTag $tag) : ?SavedItemData{
[$newNameId, $newMeta] = $this->idMetaUpgrader->upgrade($rawNameId, $meta);
+ //TODO: Dirty hack to load old skulls from disk: Put this into item upgrade schema's before Mojang makes something with a non 0 default state
+ if($blockStateData === null && ($blockId = $this->blockItemIdMap->lookupBlockId($newNameId)) !== null){
+ $networkRuntimeId = $this->blockStateDictionary->lookupStateIdFromIdMeta($blockId, 0);
+
+ if($networkRuntimeId === null){
+ throw new SavedDataLoadingException("Failed to find blockstate for blockitem $newNameId");
+ }
+
+ $blockStateData = $this->blockStateDictionary->generateDataFromStateId($networkRuntimeId);
+ }
+
//TODO: this won't account for spawn eggs from before 1.16.100 - perhaps we're lucky and they just left the meta in there anyway?
//TODO: read version from VersionInfo::TAG_WORLD_DATA_VERSION - we may need it to fix up old items
diff --git a/src/data/bedrock/item/upgrade/ItemIdMetaUpgradeSchemaUtils.php b/src/data/bedrock/item/upgrade/ItemIdMetaUpgradeSchemaUtils.php
index ef1543a80a0..0fa10803a1e 100644
--- a/src/data/bedrock/item/upgrade/ItemIdMetaUpgradeSchemaUtils.php
+++ b/src/data/bedrock/item/upgrade/ItemIdMetaUpgradeSchemaUtils.php
@@ -88,6 +88,9 @@ public static function loadSchemaFromString(string $raw, int $schemaId) : ItemId
}
$jsonMapper = new \JsonMapper();
+ $jsonMapper->bExceptionOnMissingData = true;
+ $jsonMapper->bExceptionOnUndefinedProperty = true;
+ $jsonMapper->bStrictObjectTypeChecking = true;
try{
$model = $jsonMapper->map($json, new ItemIdMetaUpgradeSchemaModel());
}catch(\JsonMapper_Exception $e){
diff --git a/src/data/java/GameModeIdMap.php b/src/data/java/GameModeIdMap.php
index 348749b51fa..9262723ed87 100644
--- a/src/data/java/GameModeIdMap.php
+++ b/src/data/java/GameModeIdMap.php
@@ -26,6 +26,7 @@
use pocketmine\player\GameMode;
use pocketmine\utils\SingletonTrait;
use function array_key_exists;
+use function spl_object_id;
final class GameModeIdMap{
use SingletonTrait;
@@ -43,15 +44,19 @@ final class GameModeIdMap{
private array $enumToId = [];
public function __construct(){
- $this->register(0, GameMode::SURVIVAL());
- $this->register(1, GameMode::CREATIVE());
- $this->register(2, GameMode::ADVENTURE());
- $this->register(3, GameMode::SPECTATOR());
+ foreach(GameMode::cases() as $case){
+ $this->register(match($case){
+ GameMode::SURVIVAL => 0,
+ GameMode::CREATIVE => 1,
+ GameMode::ADVENTURE => 2,
+ GameMode::SPECTATOR => 3,
+ }, $case);
+ }
}
private function register(int $id, GameMode $type) : void{
$this->idToEnum[$id] = $type;
- $this->enumToId[$type->id()] = $id;
+ $this->enumToId[spl_object_id($type)] = $id;
}
public function fromId(int $id) : ?GameMode{
@@ -59,9 +64,10 @@ public function fromId(int $id) : ?GameMode{
}
public function toId(GameMode $type) : int{
- if(!array_key_exists($type->id(), $this->enumToId)){
- throw new \InvalidArgumentException("Game mode does not have a mapped ID"); //this should never happen
+ $k = spl_object_id($type);
+ if(!array_key_exists($k, $this->enumToId)){
+ throw new \InvalidArgumentException("Game mode $type->name does not have a mapped ID"); //this should never happen
}
- return $this->enumToId[$type->id()];
+ return $this->enumToId[$k];
}
}
diff --git a/src/data/runtime/RuntimeEnumSizeCalculatorTrait.php b/src/data/runtime/LegacyRuntimeEnumDescriberTrait.php
similarity index 72%
rename from src/data/runtime/RuntimeEnumSizeCalculatorTrait.php
rename to src/data/runtime/LegacyRuntimeEnumDescriberTrait.php
index 3c8d189e1cd..3c540f7fa75 100644
--- a/src/data/runtime/RuntimeEnumSizeCalculatorTrait.php
+++ b/src/data/runtime/LegacyRuntimeEnumDescriberTrait.php
@@ -24,63 +24,66 @@
namespace pocketmine\data\runtime;
/**
- * This class is auto-generated. Do not edit it manually.
- * @see build/generate-runtime-enum-serializers.php
+ * Provides backwards-compatible shims for the old codegen'd enum describer methods.
+ * This is kept for plugin backwards compatibility, but these functions should not be used in new code.
+ * @deprecated
*/
-trait RuntimeEnumSizeCalculatorTrait{
-
- abstract protected function addBits(int $bits) : void;
+trait LegacyRuntimeEnumDescriberTrait{
+ abstract protected function enum(\UnitEnum &$case) : void;
public function bellAttachmentType(\pocketmine\block\utils\BellAttachmentType &$value) : void{
- $this->addBits(2);
+ $this->enum($value);
}
public function copperOxidation(\pocketmine\block\utils\CopperOxidation &$value) : void{
- $this->addBits(2);
+ $this->enum($value);
}
public function coralType(\pocketmine\block\utils\CoralType &$value) : void{
- $this->addBits(3);
+ $this->enum($value);
}
public function dirtType(\pocketmine\block\utils\DirtType &$value) : void{
- $this->addBits(2);
+ $this->enum($value);
+ }
+
+ public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{
+ $this->enum($value);
}
public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{
- $this->addBits(4);
+ $this->enum($value);
}
public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void{
- $this->addBits(2);
+ $this->enum($value);
}
public function leverFacing(\pocketmine\block\utils\LeverFacing &$value) : void{
- $this->addBits(3);
+ $this->enum($value);
}
public function medicineType(\pocketmine\item\MedicineType &$value) : void{
- $this->addBits(2);
+ $this->enum($value);
}
public function mobHeadType(\pocketmine\block\utils\MobHeadType &$value) : void{
- $this->addBits(3);
+ $this->enum($value);
}
public function mushroomBlockType(\pocketmine\block\utils\MushroomBlockType &$value) : void{
- $this->addBits(4);
+ $this->enum($value);
}
public function potionType(\pocketmine\item\PotionType &$value) : void{
- $this->addBits(6);
+ $this->enum($value);
}
public function slabType(\pocketmine\block\utils\SlabType &$value) : void{
- $this->addBits(2);
+ $this->enum($value);
}
public function suspiciousStewType(\pocketmine\item\SuspiciousStewType &$value) : void{
- $this->addBits(4);
+ $this->enum($value);
}
-
}
diff --git a/src/data/runtime/RuntimeDataDescriber.php b/src/data/runtime/RuntimeDataDescriber.php
index 6be781a0f19..6eb552a7b64 100644
--- a/src/data/runtime/RuntimeDataDescriber.php
+++ b/src/data/runtime/RuntimeDataDescriber.php
@@ -27,11 +27,28 @@
use pocketmine\block\utils\WallConnectionType;
use pocketmine\math\Facing;
+/**
+ * Interface implemented by {@link RuntimeDataReader}, {@link RuntimeDataWriter} and {@link RuntimeDataSizeCalculator}.
+ * Used to describe the structure of runtime data to an implementation.
+ *
+ * This interface should be considered **sealed**.
+ * You may use it as a type for parameters and return values, but it should not be implemented outside of this package.
+ * New methods may be added without warning.
+ */
interface RuntimeDataDescriber extends RuntimeEnumDescriber{
public function int(int $bits, int &$value) : void;
+ /**
+ * @deprecated Use {@link RuntimeDataDescriber::boundedIntAuto()} instead.
+ */
public function boundedInt(int $bits, int $min, int $max, int &$value) : void;
+ /**
+ * Same as boundedInt() but automatically calculates the required number of bits from the range.
+ * The range bounds must be constant.
+ */
+ public function boundedIntAuto(int $min, int $max, int &$value) : void;
+
public function bool(bool &$value) : void;
public function horizontalFacing(int &$facing) : void;
@@ -63,10 +80,29 @@ public function wallConnections(array &$connections) : void;
/**
* @param BrewingStandSlot[] $slots
* @phpstan-param array $slots
+ *
+ * @deprecated Use {@link enumSet()} instead.
*/
public function brewingStandSlots(array &$slots) : void;
public function railShape(int &$railShape) : void;
public function straightOnlyRailShape(int &$railShape) : void;
+
+ /**
+ * @phpstan-template T of \UnitEnum
+ * @phpstan-param T &$case
+ * @phpstan-param-out T $case
+ */
+ public function enum(\UnitEnum &$case) : void;
+
+ /**
+ * @param \UnitEnum[] &$set
+ * @param \UnitEnum[] $allCases
+ *
+ * @phpstan-template T of \UnitEnum
+ * @phpstan-param array &$set
+ * @phpstan-param array $allCases
+ */
+ public function enumSet(array &$set, array $allCases) : void;
}
diff --git a/src/data/runtime/RuntimeDataReader.php b/src/data/runtime/RuntimeDataReader.php
index 8b0a4b30cde..84239f4113b 100644
--- a/src/data/runtime/RuntimeDataReader.php
+++ b/src/data/runtime/RuntimeDataReader.php
@@ -29,10 +29,13 @@
use pocketmine\math\Axis;
use pocketmine\math\Facing;
use pocketmine\utils\AssumptionFailedError;
+use function get_class;
use function intdiv;
+use function log;
+use function spl_object_id;
final class RuntimeDataReader implements RuntimeDataDescriber{
- use RuntimeEnumDeserializerTrait;
+ use LegacyRuntimeEnumDescriberTrait;
private int $offset = 0;
@@ -56,7 +59,20 @@ public function int(int $bits, int &$value) : void{
$value = $this->readInt($bits);
}
- protected function readBoundedInt(int $bits, int $min, int $max) : int{
+ /**
+ * @deprecated Use {@link self::boundedIntAuto()} instead.
+ */
+ public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
+ $offset = $this->offset;
+ $this->boundedIntAuto($min, $max, $value);
+ $actualBits = $this->offset - $offset;
+ if($this->offset !== $offset + $bits){
+ throw new \InvalidArgumentException("Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
+ }
+ }
+
+ private function readBoundedIntAuto(int $min, int $max) : int{
+ $bits = ((int) log($max - $min, 2)) + 1;
$result = $this->readInt($bits) + $min;
if($result < $min || $result > $max){
throw new InvalidSerializedRuntimeDataException("Value is outside the range $min - $max");
@@ -64,8 +80,8 @@ protected function readBoundedInt(int $bits, int $min, int $max) : int{
return $result;
}
- public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
- $value = $this->readBoundedInt($bits, $min, $max);
+ public function boundedIntAuto(int $min, int $max, int &$value) : void{
+ $value = $this->readBoundedIntAuto($min, $max);
}
protected function readBool() : bool{
@@ -160,13 +176,13 @@ public function horizontalAxis(int &$axis) : void{
public function wallConnections(array &$connections) : void{
$result = [];
$offset = 0;
- $packed = $this->readBoundedInt(7, 0, (3 ** 4) - 1);
+ $packed = $this->readBoundedIntAuto(0, (3 ** 4) - 1);
foreach(Facing::HORIZONTAL as $facing){
$type = intdiv($packed, (3 ** $offset)) % 3;
if($type !== 0){
$result[$facing] = match($type){
- 1 => WallConnectionType::SHORT(),
- 2 => WallConnectionType::TALL(),
+ 1 => WallConnectionType::SHORT,
+ 2 => WallConnectionType::TALL,
default => throw new AssumptionFailedError("Unreachable")
};
}
@@ -179,20 +195,11 @@ public function wallConnections(array &$connections) : void{
/**
* @param BrewingStandSlot[] $slots
* @phpstan-param array $slots
+ *
+ * @deprecated Use {@link enumSet()} instead.
*/
public function brewingStandSlots(array &$slots) : void{
- $result = [];
- foreach([
- BrewingStandSlot::EAST(),
- BrewingStandSlot::NORTHWEST(),
- BrewingStandSlot::SOUTHWEST(),
- ] as $member){
- if($this->readBool()){
- $result[$member->id()] = $member;
- }
- }
-
- $slots = $result;
+ $this->enumSet($slots, BrewingStandSlot::cases());
}
public function railShape(int &$railShape) : void{
@@ -213,5 +220,26 @@ public function straightOnlyRailShape(int &$railShape) : void{
$railShape = $result;
}
+ public function enum(\UnitEnum &$case) : void{
+ $metadata = RuntimeEnumMetadata::from($case);
+ $raw = $this->readInt($metadata->bits);
+ $result = $metadata->intToEnum($raw);
+ if($result === null){
+ throw new InvalidSerializedRuntimeDataException("Invalid serialized value $raw for " . get_class($case));
+ }
+
+ $case = $result;
+ }
+
+ public function enumSet(array &$set, array $allCases) : void{
+ $result = [];
+ foreach($allCases as $case){
+ if($this->readBool()){
+ $result[spl_object_id($case)] = $case;
+ }
+ }
+ $set = $result;
+ }
+
public function getOffset() : int{ return $this->offset; }
}
diff --git a/src/data/runtime/RuntimeDataSizeCalculator.php b/src/data/runtime/RuntimeDataSizeCalculator.php
index 87eec2ba849..18c703cb83d 100644
--- a/src/data/runtime/RuntimeDataSizeCalculator.php
+++ b/src/data/runtime/RuntimeDataSizeCalculator.php
@@ -26,9 +26,10 @@
use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\math\Facing;
use function count;
+use function log;
final class RuntimeDataSizeCalculator implements RuntimeDataDescriber{
- use RuntimeEnumSizeCalculatorTrait;
+ use LegacyRuntimeEnumDescriberTrait;
private int $bits = 0;
@@ -44,8 +45,20 @@ public function int(int $bits, int &$value) : void{
$this->addBits($bits);
}
+ /**
+ * @deprecated Use {@link self::boundedIntAuto()} instead.
+ */
public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
- $this->addBits($bits);
+ $currentBits = $this->bits;
+ $this->boundedIntAuto($min, $max, $value);
+ $actualBits = $this->bits - $currentBits;
+ if($actualBits !== $bits){
+ throw new \InvalidArgumentException("Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
+ }
+ }
+
+ public function boundedIntAuto(int $min, int $max, int &$value) : void{
+ $this->addBits(((int) log($max - $min, 2)) + 1);
}
public function bool(bool &$value) : void{
@@ -85,7 +98,7 @@ public function wallConnections(array &$connections) : void{
}
public function brewingStandSlots(array &$slots) : void{
- $this->addBits(count(BrewingStandSlot::getAll()));
+ $this->addBits(count(BrewingStandSlot::cases()));
}
public function railShape(int &$railShape) : void{
@@ -95,4 +108,13 @@ public function railShape(int &$railShape) : void{
public function straightOnlyRailShape(int &$railShape) : void{
$this->addBits(3);
}
+
+ public function enum(\UnitEnum &$case) : void{
+ $metadata = RuntimeEnumMetadata::from($case);
+ $this->addBits($metadata->bits);
+ }
+
+ public function enumSet(array &$set, array $allCases) : void{
+ $this->addBits(count($allCases));
+ }
}
diff --git a/src/data/runtime/RuntimeDataWriter.php b/src/data/runtime/RuntimeDataWriter.php
index e3029673834..0fbdd869f3b 100644
--- a/src/data/runtime/RuntimeDataWriter.php
+++ b/src/data/runtime/RuntimeDataWriter.php
@@ -27,11 +27,12 @@
use pocketmine\block\utils\WallConnectionType;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
-use pocketmine\utils\AssumptionFailedError;
use function array_flip;
+use function log;
+use function spl_object_id;
final class RuntimeDataWriter implements RuntimeDataDescriber{
- use RuntimeEnumSerializerTrait;
+ use LegacyRuntimeEnumDescriberTrait;
private int $value = 0;
private int $offset = 0;
@@ -56,15 +57,28 @@ public function int(int $bits, int &$value) : void{
$this->writeInt($bits, $value);
}
- protected function writeBoundedInt(int $bits, int $min, int $max, int $value) : void{
+ /**
+ * @deprecated Use {@link self::boundedIntAuto()} instead.
+ */
+ public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
+ $offset = $this->offset;
+ $this->writeBoundedIntAuto($min, $max, $value);
+ $actualBits = $this->offset - $offset;
+ if($actualBits !== $bits){
+ throw new \InvalidArgumentException("Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
+ }
+ }
+
+ private function writeBoundedIntAuto(int $min, int $max, int $value) : void{
if($value < $min || $value > $max){
throw new \InvalidArgumentException("Value $value is outside the range $min - $max");
}
+ $bits = ((int) log($max - $min, 2)) + 1;
$this->writeInt($bits, $value - $min);
}
- public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
- $this->writeBoundedInt($bits, $min, $max, $value);
+ public function boundedIntAuto(int $min, int $max, int &$value) : void{
+ $this->writeBoundedIntAuto($min, $max, $value);
}
protected function writeBool(bool $value) : void{
@@ -148,27 +162,22 @@ public function wallConnections(array &$connections) : void{
foreach(Facing::HORIZONTAL as $facing){
$packed += match($connections[$facing] ?? null){
null => 0,
- WallConnectionType::SHORT() => 1,
- WallConnectionType::TALL() => 2,
- default => throw new AssumptionFailedError("Unreachable")
+ WallConnectionType::SHORT => 1,
+ WallConnectionType::TALL => 2,
} * (3 ** $offset);
$offset++;
}
- $this->writeBoundedInt(7, 0, (3 ** 4) - 1, $packed);
+ $this->writeBoundedIntAuto(0, (3 ** 4) - 1, $packed);
}
/**
* @param BrewingStandSlot[] $slots
* @phpstan-param array $slots
+ *
+ * @deprecated Use {@link enumSet()} instead.
*/
public function brewingStandSlots(array &$slots) : void{
- foreach([
- BrewingStandSlot::EAST(),
- BrewingStandSlot::NORTHWEST(),
- BrewingStandSlot::SOUTHWEST(),
- ] as $member){
- $this->writeBool(isset($slots[$member->id()]));
- }
+ $this->enumSet($slots, BrewingStandSlot::cases());
}
public function railShape(int &$railShape) : void{
@@ -179,6 +188,17 @@ public function straightOnlyRailShape(int &$railShape) : void{
$this->int(3, $railShape);
}
+ public function enum(\UnitEnum &$case) : void{
+ $metadata = RuntimeEnumMetadata::from($case);
+ $this->writeInt($metadata->bits, $metadata->enumToInt($case));
+ }
+
+ public function enumSet(array &$set, array $allCases) : void{
+ foreach($allCases as $case){
+ $this->writeBool(isset($set[spl_object_id($case)]));
+ }
+ }
+
public function getValue() : int{ return $this->value; }
public function getOffset() : int{ return $this->offset; }
diff --git a/src/data/runtime/RuntimeEnumDescriber.php b/src/data/runtime/RuntimeEnumDescriber.php
index 643ecd3018c..79550d041bb 100644
--- a/src/data/runtime/RuntimeEnumDescriber.php
+++ b/src/data/runtime/RuntimeEnumDescriber.php
@@ -24,8 +24,9 @@
namespace pocketmine\data\runtime;
/**
- * This class is auto-generated. Do not edit it manually.
- * @see build/generate-runtime-enum-serializers.php
+ * Provides backwards-compatible shims for the old codegen'd enum describer methods.
+ * This is kept for plugin backwards compatibility, but these functions should not be used in new code.
+ * @deprecated
*/
interface RuntimeEnumDescriber{
@@ -37,6 +38,8 @@ public function coralType(\pocketmine\block\utils\CoralType &$value) : void;
public function dirtType(\pocketmine\block\utils\DirtType &$value) : void;
+ public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void;
+
public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void;
public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void;
diff --git a/src/data/runtime/RuntimeEnumDeserializerTrait.php b/src/data/runtime/RuntimeEnumDeserializerTrait.php
deleted file mode 100644
index 4ad50f06da9..00000000000
--- a/src/data/runtime/RuntimeEnumDeserializerTrait.php
+++ /dev/null
@@ -1,231 +0,0 @@
-readInt(2)){
- 0 => \pocketmine\block\utils\BellAttachmentType::CEILING(),
- 1 => \pocketmine\block\utils\BellAttachmentType::FLOOR(),
- 2 => \pocketmine\block\utils\BellAttachmentType::ONE_WALL(),
- 3 => \pocketmine\block\utils\BellAttachmentType::TWO_WALLS(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for BellAttachmentType")
- };
- }
-
- public function copperOxidation(\pocketmine\block\utils\CopperOxidation &$value) : void{
- $value = match($this->readInt(2)){
- 0 => \pocketmine\block\utils\CopperOxidation::EXPOSED(),
- 1 => \pocketmine\block\utils\CopperOxidation::NONE(),
- 2 => \pocketmine\block\utils\CopperOxidation::OXIDIZED(),
- 3 => \pocketmine\block\utils\CopperOxidation::WEATHERED(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for CopperOxidation")
- };
- }
-
- public function coralType(\pocketmine\block\utils\CoralType &$value) : void{
- $value = match($this->readInt(3)){
- 0 => \pocketmine\block\utils\CoralType::BRAIN(),
- 1 => \pocketmine\block\utils\CoralType::BUBBLE(),
- 2 => \pocketmine\block\utils\CoralType::FIRE(),
- 3 => \pocketmine\block\utils\CoralType::HORN(),
- 4 => \pocketmine\block\utils\CoralType::TUBE(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for CoralType")
- };
- }
-
- public function dirtType(\pocketmine\block\utils\DirtType &$value) : void{
- $value = match($this->readInt(2)){
- 0 => \pocketmine\block\utils\DirtType::COARSE(),
- 1 => \pocketmine\block\utils\DirtType::NORMAL(),
- 2 => \pocketmine\block\utils\DirtType::ROOTED(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for DirtType")
- };
- }
-
- public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{
- $value = match($this->readInt(4)){
- 0 => \pocketmine\block\utils\DyeColor::BLACK(),
- 1 => \pocketmine\block\utils\DyeColor::BLUE(),
- 2 => \pocketmine\block\utils\DyeColor::BROWN(),
- 3 => \pocketmine\block\utils\DyeColor::CYAN(),
- 4 => \pocketmine\block\utils\DyeColor::GRAY(),
- 5 => \pocketmine\block\utils\DyeColor::GREEN(),
- 6 => \pocketmine\block\utils\DyeColor::LIGHT_BLUE(),
- 7 => \pocketmine\block\utils\DyeColor::LIGHT_GRAY(),
- 8 => \pocketmine\block\utils\DyeColor::LIME(),
- 9 => \pocketmine\block\utils\DyeColor::MAGENTA(),
- 10 => \pocketmine\block\utils\DyeColor::ORANGE(),
- 11 => \pocketmine\block\utils\DyeColor::PINK(),
- 12 => \pocketmine\block\utils\DyeColor::PURPLE(),
- 13 => \pocketmine\block\utils\DyeColor::RED(),
- 14 => \pocketmine\block\utils\DyeColor::WHITE(),
- 15 => \pocketmine\block\utils\DyeColor::YELLOW(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for DyeColor")
- };
- }
-
- public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void{
- $value = match($this->readInt(2)){
- 0 => \pocketmine\block\utils\FroglightType::OCHRE(),
- 1 => \pocketmine\block\utils\FroglightType::PEARLESCENT(),
- 2 => \pocketmine\block\utils\FroglightType::VERDANT(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for FroglightType")
- };
- }
-
- public function leverFacing(\pocketmine\block\utils\LeverFacing &$value) : void{
- $value = match($this->readInt(3)){
- 0 => \pocketmine\block\utils\LeverFacing::DOWN_AXIS_X(),
- 1 => \pocketmine\block\utils\LeverFacing::DOWN_AXIS_Z(),
- 2 => \pocketmine\block\utils\LeverFacing::EAST(),
- 3 => \pocketmine\block\utils\LeverFacing::NORTH(),
- 4 => \pocketmine\block\utils\LeverFacing::SOUTH(),
- 5 => \pocketmine\block\utils\LeverFacing::UP_AXIS_X(),
- 6 => \pocketmine\block\utils\LeverFacing::UP_AXIS_Z(),
- 7 => \pocketmine\block\utils\LeverFacing::WEST(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for LeverFacing")
- };
- }
-
- public function medicineType(\pocketmine\item\MedicineType &$value) : void{
- $value = match($this->readInt(2)){
- 0 => \pocketmine\item\MedicineType::ANTIDOTE(),
- 1 => \pocketmine\item\MedicineType::ELIXIR(),
- 2 => \pocketmine\item\MedicineType::EYE_DROPS(),
- 3 => \pocketmine\item\MedicineType::TONIC(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MedicineType")
- };
- }
-
- public function mobHeadType(\pocketmine\block\utils\MobHeadType &$value) : void{
- $value = match($this->readInt(3)){
- 0 => \pocketmine\block\utils\MobHeadType::CREEPER(),
- 1 => \pocketmine\block\utils\MobHeadType::DRAGON(),
- 2 => \pocketmine\block\utils\MobHeadType::PLAYER(),
- 3 => \pocketmine\block\utils\MobHeadType::SKELETON(),
- 4 => \pocketmine\block\utils\MobHeadType::WITHER_SKELETON(),
- 5 => \pocketmine\block\utils\MobHeadType::ZOMBIE(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MobHeadType")
- };
- }
-
- public function mushroomBlockType(\pocketmine\block\utils\MushroomBlockType &$value) : void{
- $value = match($this->readInt(4)){
- 0 => \pocketmine\block\utils\MushroomBlockType::ALL_CAP(),
- 1 => \pocketmine\block\utils\MushroomBlockType::CAP_EAST(),
- 2 => \pocketmine\block\utils\MushroomBlockType::CAP_MIDDLE(),
- 3 => \pocketmine\block\utils\MushroomBlockType::CAP_NORTH(),
- 4 => \pocketmine\block\utils\MushroomBlockType::CAP_NORTHEAST(),
- 5 => \pocketmine\block\utils\MushroomBlockType::CAP_NORTHWEST(),
- 6 => \pocketmine\block\utils\MushroomBlockType::CAP_SOUTH(),
- 7 => \pocketmine\block\utils\MushroomBlockType::CAP_SOUTHEAST(),
- 8 => \pocketmine\block\utils\MushroomBlockType::CAP_SOUTHWEST(),
- 9 => \pocketmine\block\utils\MushroomBlockType::CAP_WEST(),
- 10 => \pocketmine\block\utils\MushroomBlockType::PORES(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MushroomBlockType")
- };
- }
-
- public function potionType(\pocketmine\item\PotionType &$value) : void{
- $value = match($this->readInt(6)){
- 0 => \pocketmine\item\PotionType::AWKWARD(),
- 1 => \pocketmine\item\PotionType::FIRE_RESISTANCE(),
- 2 => \pocketmine\item\PotionType::HARMING(),
- 3 => \pocketmine\item\PotionType::HEALING(),
- 4 => \pocketmine\item\PotionType::INVISIBILITY(),
- 5 => \pocketmine\item\PotionType::LEAPING(),
- 6 => \pocketmine\item\PotionType::LONG_FIRE_RESISTANCE(),
- 7 => \pocketmine\item\PotionType::LONG_INVISIBILITY(),
- 8 => \pocketmine\item\PotionType::LONG_LEAPING(),
- 9 => \pocketmine\item\PotionType::LONG_MUNDANE(),
- 10 => \pocketmine\item\PotionType::LONG_NIGHT_VISION(),
- 11 => \pocketmine\item\PotionType::LONG_POISON(),
- 12 => \pocketmine\item\PotionType::LONG_REGENERATION(),
- 13 => \pocketmine\item\PotionType::LONG_SLOWNESS(),
- 14 => \pocketmine\item\PotionType::LONG_SLOW_FALLING(),
- 15 => \pocketmine\item\PotionType::LONG_STRENGTH(),
- 16 => \pocketmine\item\PotionType::LONG_SWIFTNESS(),
- 17 => \pocketmine\item\PotionType::LONG_TURTLE_MASTER(),
- 18 => \pocketmine\item\PotionType::LONG_WATER_BREATHING(),
- 19 => \pocketmine\item\PotionType::LONG_WEAKNESS(),
- 20 => \pocketmine\item\PotionType::MUNDANE(),
- 21 => \pocketmine\item\PotionType::NIGHT_VISION(),
- 22 => \pocketmine\item\PotionType::POISON(),
- 23 => \pocketmine\item\PotionType::REGENERATION(),
- 24 => \pocketmine\item\PotionType::SLOWNESS(),
- 25 => \pocketmine\item\PotionType::SLOW_FALLING(),
- 26 => \pocketmine\item\PotionType::STRENGTH(),
- 27 => \pocketmine\item\PotionType::STRONG_HARMING(),
- 28 => \pocketmine\item\PotionType::STRONG_HEALING(),
- 29 => \pocketmine\item\PotionType::STRONG_LEAPING(),
- 30 => \pocketmine\item\PotionType::STRONG_POISON(),
- 31 => \pocketmine\item\PotionType::STRONG_REGENERATION(),
- 32 => \pocketmine\item\PotionType::STRONG_STRENGTH(),
- 33 => \pocketmine\item\PotionType::STRONG_SWIFTNESS(),
- 34 => \pocketmine\item\PotionType::STRONG_TURTLE_MASTER(),
- 35 => \pocketmine\item\PotionType::SWIFTNESS(),
- 36 => \pocketmine\item\PotionType::THICK(),
- 37 => \pocketmine\item\PotionType::TURTLE_MASTER(),
- 38 => \pocketmine\item\PotionType::WATER(),
- 39 => \pocketmine\item\PotionType::WATER_BREATHING(),
- 40 => \pocketmine\item\PotionType::WEAKNESS(),
- 41 => \pocketmine\item\PotionType::WITHER(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for PotionType")
- };
- }
-
- public function slabType(\pocketmine\block\utils\SlabType &$value) : void{
- $value = match($this->readInt(2)){
- 0 => \pocketmine\block\utils\SlabType::BOTTOM(),
- 1 => \pocketmine\block\utils\SlabType::DOUBLE(),
- 2 => \pocketmine\block\utils\SlabType::TOP(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for SlabType")
- };
- }
-
- public function suspiciousStewType(\pocketmine\item\SuspiciousStewType &$value) : void{
- $value = match($this->readInt(4)){
- 0 => \pocketmine\item\SuspiciousStewType::ALLIUM(),
- 1 => \pocketmine\item\SuspiciousStewType::AZURE_BLUET(),
- 2 => \pocketmine\item\SuspiciousStewType::BLUE_ORCHID(),
- 3 => \pocketmine\item\SuspiciousStewType::CORNFLOWER(),
- 4 => \pocketmine\item\SuspiciousStewType::DANDELION(),
- 5 => \pocketmine\item\SuspiciousStewType::LILY_OF_THE_VALLEY(),
- 6 => \pocketmine\item\SuspiciousStewType::OXEYE_DAISY(),
- 7 => \pocketmine\item\SuspiciousStewType::POPPY(),
- 8 => \pocketmine\item\SuspiciousStewType::TULIP(),
- 9 => \pocketmine\item\SuspiciousStewType::WITHER_ROSE(),
- default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for SuspiciousStewType")
- };
- }
-
-}
diff --git a/src/data/runtime/RuntimeEnumMetadata.php b/src/data/runtime/RuntimeEnumMetadata.php
new file mode 100644
index 00000000000..261b7a1bc3e
--- /dev/null
+++ b/src/data/runtime/RuntimeEnumMetadata.php
@@ -0,0 +1,114 @@
+
+ */
+ private readonly array $intToEnum;
+ /**
+ * @var int[]
+ * @phpstan-var array
+ */
+ private readonly array $enumToInt;
+
+ /**
+ * @param \UnitEnum[] $members
+ * @phpstan-param list $members
+ */
+ public function __construct(
+ array $members
+ ){
+ usort($members, fn(\UnitEnum $a, \UnitEnum $b) => $a->name <=> $b->name); //sort by name to ensure consistent ordering (and thus consistent bit assignments)
+
+ $this->bits = (int) ceil(log(count($members), 2));
+ $this->intToEnum = array_values($members);
+
+ $reversed = [];
+ foreach($this->intToEnum as $int => $enum){
+ $reversed[spl_object_id($enum)] = $int;
+ }
+
+ $this->enumToInt = $reversed;
+ }
+
+ /**
+ * @phpstan-return T|null
+ */
+ public function intToEnum(int $value) : ?object{
+ return $this->intToEnum[$value] ?? null;
+ }
+
+ /**
+ * @phpstan-param T $enum
+ */
+ public function enumToInt(object $enum) : int{
+ return $this->enumToInt[spl_object_id($enum)];
+ }
+
+ /**
+ * @var self[]
+ * @phpstan-var array
+ */
+ private static array $cache = [];
+
+ /**
+ * @phpstan-template TEnum of \UnitEnum
+ * @phpstan-param TEnum $case
+ *
+ * @phpstan-return self
+ */
+ public static function from(\UnitEnum $case) : self{
+ $class = $case::class;
+ /** @phpstan-var self|null $metadata */
+ $metadata = self::$cache[$class] ?? null;
+ if($metadata === null){
+ /**
+ * PHPStan can't infer this correctly :( https://github.com/phpstan/phpstan/issues/7162
+ * @phpstan-var list $cases
+ */
+ $cases = $case::cases();
+ self::$cache[$class] = $metadata = new self($cases);
+ }
+
+ return $metadata;
+ }
+}
diff --git a/src/data/runtime/RuntimeEnumSerializerTrait.php b/src/data/runtime/RuntimeEnumSerializerTrait.php
deleted file mode 100644
index 805723ac51f..00000000000
--- a/src/data/runtime/RuntimeEnumSerializerTrait.php
+++ /dev/null
@@ -1,231 +0,0 @@
-writeInt(2, match($value){
- \pocketmine\block\utils\BellAttachmentType::CEILING() => 0,
- \pocketmine\block\utils\BellAttachmentType::FLOOR() => 1,
- \pocketmine\block\utils\BellAttachmentType::ONE_WALL() => 2,
- \pocketmine\block\utils\BellAttachmentType::TWO_WALLS() => 3,
- default => throw new \pocketmine\utils\AssumptionFailedError("All BellAttachmentType cases should be covered")
- });
- }
-
- public function copperOxidation(\pocketmine\block\utils\CopperOxidation &$value) : void{
- $this->writeInt(2, match($value){
- \pocketmine\block\utils\CopperOxidation::EXPOSED() => 0,
- \pocketmine\block\utils\CopperOxidation::NONE() => 1,
- \pocketmine\block\utils\CopperOxidation::OXIDIZED() => 2,
- \pocketmine\block\utils\CopperOxidation::WEATHERED() => 3,
- default => throw new \pocketmine\utils\AssumptionFailedError("All CopperOxidation cases should be covered")
- });
- }
-
- public function coralType(\pocketmine\block\utils\CoralType &$value) : void{
- $this->writeInt(3, match($value){
- \pocketmine\block\utils\CoralType::BRAIN() => 0,
- \pocketmine\block\utils\CoralType::BUBBLE() => 1,
- \pocketmine\block\utils\CoralType::FIRE() => 2,
- \pocketmine\block\utils\CoralType::HORN() => 3,
- \pocketmine\block\utils\CoralType::TUBE() => 4,
- default => throw new \pocketmine\utils\AssumptionFailedError("All CoralType cases should be covered")
- });
- }
-
- public function dirtType(\pocketmine\block\utils\DirtType &$value) : void{
- $this->writeInt(2, match($value){
- \pocketmine\block\utils\DirtType::COARSE() => 0,
- \pocketmine\block\utils\DirtType::NORMAL() => 1,
- \pocketmine\block\utils\DirtType::ROOTED() => 2,
- default => throw new \pocketmine\utils\AssumptionFailedError("All DirtType cases should be covered")
- });
- }
-
- public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{
- $this->writeInt(4, match($value){
- \pocketmine\block\utils\DyeColor::BLACK() => 0,
- \pocketmine\block\utils\DyeColor::BLUE() => 1,
- \pocketmine\block\utils\DyeColor::BROWN() => 2,
- \pocketmine\block\utils\DyeColor::CYAN() => 3,
- \pocketmine\block\utils\DyeColor::GRAY() => 4,
- \pocketmine\block\utils\DyeColor::GREEN() => 5,
- \pocketmine\block\utils\DyeColor::LIGHT_BLUE() => 6,
- \pocketmine\block\utils\DyeColor::LIGHT_GRAY() => 7,
- \pocketmine\block\utils\DyeColor::LIME() => 8,
- \pocketmine\block\utils\DyeColor::MAGENTA() => 9,
- \pocketmine\block\utils\DyeColor::ORANGE() => 10,
- \pocketmine\block\utils\DyeColor::PINK() => 11,
- \pocketmine\block\utils\DyeColor::PURPLE() => 12,
- \pocketmine\block\utils\DyeColor::RED() => 13,
- \pocketmine\block\utils\DyeColor::WHITE() => 14,
- \pocketmine\block\utils\DyeColor::YELLOW() => 15,
- default => throw new \pocketmine\utils\AssumptionFailedError("All DyeColor cases should be covered")
- });
- }
-
- public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void{
- $this->writeInt(2, match($value){
- \pocketmine\block\utils\FroglightType::OCHRE() => 0,
- \pocketmine\block\utils\FroglightType::PEARLESCENT() => 1,
- \pocketmine\block\utils\FroglightType::VERDANT() => 2,
- default => throw new \pocketmine\utils\AssumptionFailedError("All FroglightType cases should be covered")
- });
- }
-
- public function leverFacing(\pocketmine\block\utils\LeverFacing &$value) : void{
- $this->writeInt(3, match($value){
- \pocketmine\block\utils\LeverFacing::DOWN_AXIS_X() => 0,
- \pocketmine\block\utils\LeverFacing::DOWN_AXIS_Z() => 1,
- \pocketmine\block\utils\LeverFacing::EAST() => 2,
- \pocketmine\block\utils\LeverFacing::NORTH() => 3,
- \pocketmine\block\utils\LeverFacing::SOUTH() => 4,
- \pocketmine\block\utils\LeverFacing::UP_AXIS_X() => 5,
- \pocketmine\block\utils\LeverFacing::UP_AXIS_Z() => 6,
- \pocketmine\block\utils\LeverFacing::WEST() => 7,
- default => throw new \pocketmine\utils\AssumptionFailedError("All LeverFacing cases should be covered")
- });
- }
-
- public function medicineType(\pocketmine\item\MedicineType &$value) : void{
- $this->writeInt(2, match($value){
- \pocketmine\item\MedicineType::ANTIDOTE() => 0,
- \pocketmine\item\MedicineType::ELIXIR() => 1,
- \pocketmine\item\MedicineType::EYE_DROPS() => 2,
- \pocketmine\item\MedicineType::TONIC() => 3,
- default => throw new \pocketmine\utils\AssumptionFailedError("All MedicineType cases should be covered")
- });
- }
-
- public function mobHeadType(\pocketmine\block\utils\MobHeadType &$value) : void{
- $this->writeInt(3, match($value){
- \pocketmine\block\utils\MobHeadType::CREEPER() => 0,
- \pocketmine\block\utils\MobHeadType::DRAGON() => 1,
- \pocketmine\block\utils\MobHeadType::PLAYER() => 2,
- \pocketmine\block\utils\MobHeadType::SKELETON() => 3,
- \pocketmine\block\utils\MobHeadType::WITHER_SKELETON() => 4,
- \pocketmine\block\utils\MobHeadType::ZOMBIE() => 5,
- default => throw new \pocketmine\utils\AssumptionFailedError("All MobHeadType cases should be covered")
- });
- }
-
- public function mushroomBlockType(\pocketmine\block\utils\MushroomBlockType &$value) : void{
- $this->writeInt(4, match($value){
- \pocketmine\block\utils\MushroomBlockType::ALL_CAP() => 0,
- \pocketmine\block\utils\MushroomBlockType::CAP_EAST() => 1,
- \pocketmine\block\utils\MushroomBlockType::CAP_MIDDLE() => 2,
- \pocketmine\block\utils\MushroomBlockType::CAP_NORTH() => 3,
- \pocketmine\block\utils\MushroomBlockType::CAP_NORTHEAST() => 4,
- \pocketmine\block\utils\MushroomBlockType::CAP_NORTHWEST() => 5,
- \pocketmine\block\utils\MushroomBlockType::CAP_SOUTH() => 6,
- \pocketmine\block\utils\MushroomBlockType::CAP_SOUTHEAST() => 7,
- \pocketmine\block\utils\MushroomBlockType::CAP_SOUTHWEST() => 8,
- \pocketmine\block\utils\MushroomBlockType::CAP_WEST() => 9,
- \pocketmine\block\utils\MushroomBlockType::PORES() => 10,
- default => throw new \pocketmine\utils\AssumptionFailedError("All MushroomBlockType cases should be covered")
- });
- }
-
- public function potionType(\pocketmine\item\PotionType &$value) : void{
- $this->writeInt(6, match($value){
- \pocketmine\item\PotionType::AWKWARD() => 0,
- \pocketmine\item\PotionType::FIRE_RESISTANCE() => 1,
- \pocketmine\item\PotionType::HARMING() => 2,
- \pocketmine\item\PotionType::HEALING() => 3,
- \pocketmine\item\PotionType::INVISIBILITY() => 4,
- \pocketmine\item\PotionType::LEAPING() => 5,
- \pocketmine\item\PotionType::LONG_FIRE_RESISTANCE() => 6,
- \pocketmine\item\PotionType::LONG_INVISIBILITY() => 7,
- \pocketmine\item\PotionType::LONG_LEAPING() => 8,
- \pocketmine\item\PotionType::LONG_MUNDANE() => 9,
- \pocketmine\item\PotionType::LONG_NIGHT_VISION() => 10,
- \pocketmine\item\PotionType::LONG_POISON() => 11,
- \pocketmine\item\PotionType::LONG_REGENERATION() => 12,
- \pocketmine\item\PotionType::LONG_SLOWNESS() => 13,
- \pocketmine\item\PotionType::LONG_SLOW_FALLING() => 14,
- \pocketmine\item\PotionType::LONG_STRENGTH() => 15,
- \pocketmine\item\PotionType::LONG_SWIFTNESS() => 16,
- \pocketmine\item\PotionType::LONG_TURTLE_MASTER() => 17,
- \pocketmine\item\PotionType::LONG_WATER_BREATHING() => 18,
- \pocketmine\item\PotionType::LONG_WEAKNESS() => 19,
- \pocketmine\item\PotionType::MUNDANE() => 20,
- \pocketmine\item\PotionType::NIGHT_VISION() => 21,
- \pocketmine\item\PotionType::POISON() => 22,
- \pocketmine\item\PotionType::REGENERATION() => 23,
- \pocketmine\item\PotionType::SLOWNESS() => 24,
- \pocketmine\item\PotionType::SLOW_FALLING() => 25,
- \pocketmine\item\PotionType::STRENGTH() => 26,
- \pocketmine\item\PotionType::STRONG_HARMING() => 27,
- \pocketmine\item\PotionType::STRONG_HEALING() => 28,
- \pocketmine\item\PotionType::STRONG_LEAPING() => 29,
- \pocketmine\item\PotionType::STRONG_POISON() => 30,
- \pocketmine\item\PotionType::STRONG_REGENERATION() => 31,
- \pocketmine\item\PotionType::STRONG_STRENGTH() => 32,
- \pocketmine\item\PotionType::STRONG_SWIFTNESS() => 33,
- \pocketmine\item\PotionType::STRONG_TURTLE_MASTER() => 34,
- \pocketmine\item\PotionType::SWIFTNESS() => 35,
- \pocketmine\item\PotionType::THICK() => 36,
- \pocketmine\item\PotionType::TURTLE_MASTER() => 37,
- \pocketmine\item\PotionType::WATER() => 38,
- \pocketmine\item\PotionType::WATER_BREATHING() => 39,
- \pocketmine\item\PotionType::WEAKNESS() => 40,
- \pocketmine\item\PotionType::WITHER() => 41,
- default => throw new \pocketmine\utils\AssumptionFailedError("All PotionType cases should be covered")
- });
- }
-
- public function slabType(\pocketmine\block\utils\SlabType &$value) : void{
- $this->writeInt(2, match($value){
- \pocketmine\block\utils\SlabType::BOTTOM() => 0,
- \pocketmine\block\utils\SlabType::DOUBLE() => 1,
- \pocketmine\block\utils\SlabType::TOP() => 2,
- default => throw new \pocketmine\utils\AssumptionFailedError("All SlabType cases should be covered")
- });
- }
-
- public function suspiciousStewType(\pocketmine\item\SuspiciousStewType &$value) : void{
- $this->writeInt(4, match($value){
- \pocketmine\item\SuspiciousStewType::ALLIUM() => 0,
- \pocketmine\item\SuspiciousStewType::AZURE_BLUET() => 1,
- \pocketmine\item\SuspiciousStewType::BLUE_ORCHID() => 2,
- \pocketmine\item\SuspiciousStewType::CORNFLOWER() => 3,
- \pocketmine\item\SuspiciousStewType::DANDELION() => 4,
- \pocketmine\item\SuspiciousStewType::LILY_OF_THE_VALLEY() => 5,
- \pocketmine\item\SuspiciousStewType::OXEYE_DAISY() => 6,
- \pocketmine\item\SuspiciousStewType::POPPY() => 7,
- \pocketmine\item\SuspiciousStewType::TULIP() => 8,
- \pocketmine\item\SuspiciousStewType::WITHER_ROSE() => 9,
- default => throw new \pocketmine\utils\AssumptionFailedError("All SuspiciousStewType cases should be covered")
- });
- }
-
-}
diff --git a/src/entity/Entity.php b/src/entity/Entity.php
index 095ab7ab334..68f5cbdd3f5 100644
--- a/src/entity/Entity.php
+++ b/src/entity/Entity.php
@@ -35,6 +35,7 @@
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\entity\EntitySpawnEvent;
use pocketmine\event\entity\EntityTeleportEvent;
+use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector2;
@@ -252,6 +253,14 @@ public function isNameTagAlwaysVisible() : bool{
return $this->alwaysShowNameTag;
}
+ /**
+ * Returns whether players can rename this entity using a name tag.
+ * Note that plugins can still name entities using setNameTag().
+ */
+ public function canBeRenamed() : bool{
+ return false;
+ }
+
public function setNameTag(string $name) : void{
$this->nameTag = $name;
$this->networkPropertiesDirty = true;
@@ -519,7 +528,12 @@ protected function addAttributes() : void{
}
public function attack(EntityDamageEvent $source) : void{
- if($this->isFireProof() && ($source->getCause() === EntityDamageEvent::CAUSE_FIRE || $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK)){
+ if($this->isFireProof() && (
+ $source->getCause() === EntityDamageEvent::CAUSE_FIRE ||
+ $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK ||
+ $source->getCause() === EntityDamageEvent::CAUSE_LAVA
+ )
+ ){
$source->cancel();
}
$source->call();
@@ -686,8 +700,10 @@ public function setFireTicks(int $fireTicks) : void{
if($fireTicks < 0 || $fireTicks > 0x7fff){
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
}
- $this->fireTicks = $fireTicks;
- $this->networkPropertiesDirty = true;
+ if(!$this->isFireProof()){
+ $this->fireTicks = $fireTicks;
+ $this->networkPropertiesDirty = true;
+ }
}
public function extinguish() : void{
@@ -700,12 +716,13 @@ public function isFireProof() : bool{
}
protected function doOnFireTick(int $tickDiff = 1) : bool{
- if($this->isFireProof() && $this->fireTicks > 1){
- $this->fireTicks = 1;
- }else{
- $this->fireTicks -= $tickDiff;
+ if($this->isFireProof() && $this->isOnFire()){
+ $this->extinguish();
+ return false;
}
+ $this->fireTicks -= $tickDiff;
+
if(($this->fireTicks % 20 === 0) || $tickDiff > 20){
$this->dealFireDamage();
}
@@ -766,33 +783,25 @@ public function getOffsetPosition(Vector3 $vector3) : Vector3{
}
protected function broadcastMovement(bool $teleport = false) : void{
- if($teleport){
- //TODO: HACK! workaround for https://github.com/pmmp/PocketMine-MP/issues/4394
- //this happens because MoveActor*Packet doesn't clear interpolation targets on the client, so the entity
- //snaps to the teleport position, but then lerps back to the original position if a normal movement for the
- //entity was recently broadcasted. This can be seen with players throwing ender pearls.
- //TODO: remove this if the bug ever gets fixed (lol)
- foreach($this->hasSpawned as $player){
- $this->despawnFrom($player);
- $this->spawnTo($player);
- }
- }else{
- NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
- $this->id,
- $this->getOffsetPosition($this->location),
- $this->location->pitch,
- $this->location->yaw,
- $this->location->yaw,
- (
- //TODO: if the above hack for #4394 gets removed, we should be setting FLAG_TELEPORT here
- ($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
- )
- )]);
- }
+ NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
+ $this->id,
+ $this->getOffsetPosition($this->location),
+ $this->location->pitch,
+ $this->location->yaw,
+ $this->location->yaw,
+ (
+ //TODO: We should be setting FLAG_TELEPORT here to disable client-side movement interpolation, but it
+ //breaks player teleporting (observers see the player rubberband back to the pre-teleport position while
+ //the teleported player sees themselves at the correct position), and does nothing whatsoever for
+ //non-player entities (movement is still interpolated). Both of these are client bugs.
+ //See https://github.com/pmmp/PocketMine-MP/issues/4394
+ ($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
+ )
+ )]);
}
protected function broadcastMotion() : void{
- NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]);
+ NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion(), tick: 0)]);
}
public function getGravity() : float{
@@ -842,7 +851,7 @@ protected function tryChangeMovement() : void{
protected function checkObstruction(float $x, float $y, float $z) : bool{
$world = $this->getWorld();
- if(count($world->getCollisionBoxes($this, $this->getBoundingBox(), false)) === 0){
+ if(count($world->getBlockCollisionBoxes($this->boundingBox)) === 0){
return false;
}
@@ -1144,7 +1153,7 @@ protected function move(float $dx, float $dy, float $dz) : void{
assert(abs($dx) <= 20 && abs($dy) <= 20 && abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
- $list = $this->getWorld()->getCollisionBoxes($this, $moveBB->addCoord($dx, $dy, $dz), false);
+ $list = $this->getWorld()->getBlockCollisionBoxes($moveBB->addCoord($dx, $dy, $dz));
foreach($list as $bb){
$dy = $bb->calculateYOffset($moveBB, $dy);
@@ -1176,7 +1185,7 @@ protected function move(float $dx, float $dy, float $dz) : void{
$stepBB = clone $this->boundingBox;
- $list = $this->getWorld()->getCollisionBoxes($this, $stepBB->addCoord($dx, $dy, $dz), false);
+ $list = $this->getWorld()->getBlockCollisionBoxes($stepBB->addCoord($dx, $dy, $dz));
foreach($list as $bb){
$dy = $bb->calculateYOffset($stepBB, $dy);
}
@@ -1552,6 +1561,13 @@ public function despawnFromAll() : void{
$this->hasSpawned = [];
}
+ /**
+ * Returns the item that players will equip when middle-clicking on this entity.
+ */
+ public function getPickedItem() : ?Item{
+ return null;
+ }
+
/**
* Flags the entity to be removed from the world on the next tick.
*/
@@ -1686,7 +1702,7 @@ public function broadcastAnimation(Animation $animation, ?array $targets = null)
*/
public function broadcastSound(Sound $sound, ?array $targets = null) : void{
if(!$this->silent){
- NetworkBroadcastUtils::broadcastPackets($targets ?? $this->getViewers(), $sound->encode($this->location));
+ $this->getWorld()->addSound($this->location->asVector3(), $sound, $targets ?? $this->getViewers());
}
}
diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php
index d8d189cffc1..3d53233ab71 100644
--- a/src/entity/EntityFactory.php
+++ b/src/entity/EntityFactory.php
@@ -32,6 +32,7 @@
use pocketmine\data\bedrock\PotionTypeIds;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\EntityDataHelper as Helper;
+use pocketmine\entity\object\EndCrystal;
use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\object\ItemEntity;
@@ -92,6 +93,10 @@ public function __construct(){
return new Egg(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['Egg', 'minecraft:egg']);
+ $this->register(EndCrystal::class, function(World $world, CompoundTag $nbt) : EndCrystal{
+ return new EndCrystal(Helper::parseLocation($nbt, $world), $nbt);
+ }, ['EnderCrystal', 'minecraft:ender_crystal']);
+
$this->register(EnderPearl::class, function(World $world, CompoundTag $nbt) : EnderPearl{
return new EnderPearl(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['ThrownEnderpearl', 'minecraft:ender_pearl']);
diff --git a/src/entity/Human.php b/src/entity/Human.php
index ae1aa246491..f2c4c7a7405 100644
--- a/src/entity/Human.php
+++ b/src/entity/Human.php
@@ -37,6 +37,7 @@
use pocketmine\inventory\PlayerEnderInventory;
use pocketmine\inventory\PlayerInventory;
use pocketmine\inventory\PlayerOffHandInventory;
+use pocketmine\item\enchantment\EnchantingHelper;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\Totem;
@@ -66,7 +67,6 @@
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\player\Player;
-use pocketmine\utils\Limits;
use pocketmine\world\sound\TotemUseSound;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
@@ -76,7 +76,6 @@
use function array_merge;
use function array_values;
use function min;
-use function random_int;
class Human extends Living implements ProjectileSource, InventoryHolder{
@@ -211,6 +210,18 @@ public function getXpManager() : ExperienceManager{
return $this->xpManager;
}
+ public function getEnchantmentSeed() : int{
+ return $this->xpSeed;
+ }
+
+ public function setEnchantmentSeed(int $seed) : void{
+ $this->xpSeed = $seed;
+ }
+
+ public function regenerateEnchantmentSeed() : void{
+ $this->xpSeed = EnchantingHelper::generateSeed();
+ }
+
public function getXpDropAmount() : int{
//this causes some XP to be lost on death when above level 1 (by design), dropping at most enough points for
//about 7.5 levels of XP.
@@ -334,7 +345,7 @@ function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{
if(($xpSeedTag = $nbt->getTag(self::TAG_XP_SEED)) instanceof IntTag){
$this->xpSeed = $xpSeedTag->getValue();
}else{
- $this->xpSeed = random_int(Limits::INT32_MIN, Limits::INT32_MAX);
+ $this->xpSeed = EnchantingHelper::generateSeed();
}
}
diff --git a/src/entity/HungerManager.php b/src/entity/HungerManager.php
index 304dcd30b45..a3185589187 100644
--- a/src/entity/HungerManager.php
+++ b/src/entity/HungerManager.php
@@ -133,14 +133,18 @@ public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CU
if(!$this->enabled){
return 0;
}
- $ev = new PlayerExhaustEvent($this->entity, $amount, $cause);
- $ev->call();
- if($ev->isCancelled()){
- return 0.0;
+ $evAmount = $amount;
+ if(PlayerExhaustEvent::hasHandlers()){
+ $ev = new PlayerExhaustEvent($this->entity, $amount, $cause);
+ $ev->call();
+ if($ev->isCancelled()){
+ return 0.0;
+ }
+ $evAmount = $ev->getAmount();
}
$exhaustion = $this->getExhaustion();
- $exhaustion += $ev->getAmount();
+ $exhaustion += $evAmount;
while($exhaustion >= 4.0){
$exhaustion -= 4.0;
@@ -159,7 +163,7 @@ public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CU
}
$this->setExhaustion($exhaustion);
- return $ev->getAmount();
+ return $evAmount;
}
public function getFoodTickTimer() : int{
diff --git a/src/entity/Living.php b/src/entity/Living.php
index 29a8ceae8ff..81f46424f17 100644
--- a/src/entity/Living.php
+++ b/src/entity/Living.php
@@ -68,6 +68,7 @@
use function ceil;
use function count;
use function floor;
+use function ksort;
use function lcg_value;
use function max;
use function min;
@@ -76,10 +77,22 @@
use function round;
use function sqrt;
use const M_PI;
+use const SORT_NUMERIC;
abstract class Living extends Entity{
protected const DEFAULT_BREATH_TICKS = 300;
+ /**
+ * The default knockback multiplier when an entity is hit by another entity.
+ * Larger values knock the entity back with increased velocity.
+ */
+ public const DEFAULT_KNOCKBACK_FORCE = 0.4;
+ /**
+ * Limit of an entity's vertical knockback velocity when hit by another entity. Without this limit, the entity
+ * may be knocked far up into the air with large knockback forces.
+ */
+ public const DEFAULT_KNOCKBACK_VERTICAL_LIMIT = 0.4;
+
private const TAG_LEGACY_HEALTH = "HealF"; //TAG_Float
private const TAG_HEALTH = "Health"; //TAG_Float
private const TAG_BREATH_TICKS = "Air"; //TAG_Short
@@ -121,6 +134,10 @@ protected function getInitialGravity() : float{ return 0.08; }
abstract public function getName() : string;
+ public function canBeRenamed() : bool{
+ return true;
+ }
+
protected function initEntity(CompoundTag $nbt) : void{
parent::initEntity($nbt);
@@ -217,6 +234,7 @@ public function isSneaking() : bool{
public function setSneaking(bool $value = true) : void{
$this->sneaking = $value;
$this->networkPropertiesDirty = true;
+ $this->recalculateSize();
}
public function isSprinting() : bool{
@@ -258,6 +276,8 @@ private function recalculateSize() : void{
if($this->isSwimming() || $this->isGliding()){
$width = $size->getWidth();
$this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale()));
+ }elseif($this->isSneaking()){
+ $this->setSize((new EntitySizeInfo(3 / 4 * $size->getHeight(), $size->getWidth(), 3 / 4 * $size->getEyeHeight()))->scale($this->getScale()));
}else{
$this->setSize($size->scale($this->getScale()));
}
@@ -484,14 +504,16 @@ protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
public function damageArmor(float $damage) : void{
$durabilityRemoved = (int) max(floor($damage / 4), 1);
- $armor = $this->armorInventory->getContents(true);
- foreach($armor as $item){
+ $armor = $this->armorInventory->getContents();
+ foreach($armor as $slotId => $item){
if($item instanceof Armor){
+ $oldItem = clone $item;
$this->damageItem($item, $durabilityRemoved);
+ if(!$item->equalsExact($oldItem)){
+ $this->armorInventory->setItem($slotId, $item);
+ }
}
}
-
- $this->armorInventory->setContents($armor);
}
private function damageItem(Durable $item, int $durabilityRemoved) : void{
@@ -502,7 +524,7 @@ private function damageItem(Durable $item, int $durabilityRemoved) : void{
}
public function attack(EntityDamageEvent $source) : void{
- if($this->noDamageTicks > 0){
+ if($this->noDamageTicks > 0 && $source->getCause() !== EntityDamageEvent::CAUSE_SUICIDE){
$source->cancel();
}
@@ -515,7 +537,9 @@ public function attack(EntityDamageEvent $source) : void{
$source->cancel();
}
- $this->applyDamageModifiers($source);
+ if($source->getCause() !== EntityDamageEvent::CAUSE_SUICIDE){
+ $this->applyDamageModifiers($source);
+ }
if($source instanceof EntityDamageByEntityEvent && (
$source->getCause() === EntityDamageEvent::CAUSE_BLOCK_EXPLOSION ||
@@ -533,26 +557,33 @@ public function attack(EntityDamageEvent $source) : void{
return;
}
- $this->attackTime = $source->getAttackCooldown();
+ if($this->attackTime <= 0){
+ //this logic only applies if the entity was cold attacked
+
+ $this->attackTime = $source->getAttackCooldown();
- if($source instanceof EntityDamageByChildEntityEvent){
- $e = $source->getChild();
- if($e !== null){
- $motion = $e->getMotion();
- $this->knockBack($motion->x, $motion->z, $source->getKnockBack());
+ if($source instanceof EntityDamageByChildEntityEvent){
+ $e = $source->getChild();
+ if($e !== null){
+ $motion = $e->getMotion();
+ $this->knockBack($motion->x, $motion->z, $source->getKnockBack(), $source->getVerticalKnockBackLimit());
+ }
+ }elseif($source instanceof EntityDamageByEntityEvent){
+ $e = $source->getDamager();
+ if($e !== null){
+ $deltaX = $this->location->x - $e->location->x;
+ $deltaZ = $this->location->z - $e->location->z;
+ $this->knockBack($deltaX, $deltaZ, $source->getKnockBack(), $source->getVerticalKnockBackLimit());
+ }
}
- }elseif($source instanceof EntityDamageByEntityEvent){
- $e = $source->getDamager();
- if($e !== null){
- $deltaX = $this->location->x - $e->location->x;
- $deltaZ = $this->location->z - $e->location->z;
- $this->knockBack($deltaX, $deltaZ, $source->getKnockBack());
+
+ if($this->isAlive()){
+ $this->doHitAnimation();
}
}
if($this->isAlive()){
$this->applyPostDamageEffects($source);
- $this->doHitAnimation();
}
}
@@ -560,7 +591,7 @@ protected function doHitAnimation() : void{
$this->broadcastAnimation(new HurtAnimation($this));
}
- public function knockBack(float $x, float $z, float $force = 0.4, ?float $verticalLimit = 0.4) : void{
+ public function knockBack(float $x, float $z, float $force = self::DEFAULT_KNOCKBACK_FORCE, ?float $verticalLimit = self::DEFAULT_KNOCKBACK_VERTICAL_LIMIT) : void{
$f = sqrt($x * $x + $z * $z);
if($f <= 0){
return;
@@ -637,9 +668,12 @@ protected function entityBaseTick(int $tickDiff = 1) : bool{
}
foreach($this->armorInventory->getContents() as $index => $item){
+ $oldItem = clone $item;
if($item->onTickWorn($this)){
$hasUpdate = true;
- $this->armorInventory->setItem($index, $item);
+ if(!$item->equalsExact($oldItem)){
+ $this->armorInventory->setItem($index, $item);
+ }
}
}
}
@@ -851,8 +885,30 @@ protected function sendSpawnPacket(Player $player) : void{
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
- $properties->setByte(EntityMetadataProperties::POTION_AMBIENT, $this->effectManager->hasOnlyAmbientEffects() ? 1 : 0);
- $properties->setInt(EntityMetadataProperties::POTION_COLOR, Binary::signInt($this->effectManager->getBubbleColor()->toARGB()));
+ $visibleEffects = [];
+ foreach ($this->effectManager->all() as $effect) {
+ if (!$effect->isVisible() || !$effect->getType()->hasBubbles()) {
+ continue;
+ }
+ $visibleEffects[EffectIdMap::getInstance()->toId($effect->getType())] = $effect->isAmbient();
+ }
+
+ //TODO: HACK! the client may not be able to identify effects if they are not sorted.
+ ksort($visibleEffects, SORT_NUMERIC);
+
+ $effectsData = 0;
+ $packedEffectsCount = 0;
+ foreach ($visibleEffects as $effectId => $isAmbient) {
+ $effectsData = ($effectsData << 7) |
+ (($effectId & 0x3f) << 1) | //Why not use 7 bits instead of only 6? mojang...
+ ($isAmbient ? 1 : 0);
+
+ if (++$packedEffectsCount >= 8) {
+ break;
+ }
+ }
+ $properties->setLong(EntityMetadataProperties::VISIBLE_MOB_EFFECTS, $effectsData);
+
$properties->setShort(EntityMetadataProperties::AIR, $this->breathTicks);
$properties->setShort(EntityMetadataProperties::MAX_AIR, $this->maxBreathTicks);
diff --git a/src/entity/Squid.php b/src/entity/Squid.php
index 75c50061b98..a0f3fef48b9 100644
--- a/src/entity/Squid.php
+++ b/src/entity/Squid.php
@@ -26,6 +26,7 @@
use pocketmine\entity\animation\SquidInkCloudAnimation;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
+use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
@@ -124,4 +125,8 @@ public function getDrops() : array{
VanillaItems::INK_SAC()->setCount(mt_rand(1, 3))
];
}
+
+ public function getPickedItem() : ?Item{
+ return VanillaItems::SQUID_SPAWN_EGG();
+ }
}
diff --git a/src/entity/Villager.php b/src/entity/Villager.php
index 376401a5df2..fee65b01437 100644
--- a/src/entity/Villager.php
+++ b/src/entity/Villager.php
@@ -23,6 +23,8 @@
namespace pocketmine\entity;
+use pocketmine\item\Item;
+use pocketmine\item\VanillaItems;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
@@ -87,6 +89,10 @@ public function isBaby() : bool{
return $this->baby;
}
+ public function getPickedItem() : ?Item{
+ return VanillaItems::VILLAGER_SPAWN_EGG();
+ }
+
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
$properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby);
diff --git a/src/entity/Zombie.php b/src/entity/Zombie.php
index 18fc2207e7f..159a2dd2586 100644
--- a/src/entity/Zombie.php
+++ b/src/entity/Zombie.php
@@ -23,6 +23,7 @@
namespace pocketmine\entity;
+use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use function mt_rand;
@@ -65,4 +66,8 @@ public function getXpDropAmount() : int{
//TODO: check for equipment and whether it's a baby
return 5;
}
+
+ public function getPickedItem() : ?Item{
+ return VanillaItems::ZOMBIE_SPAWN_EGG();
+ }
}
diff --git a/src/entity/animation/ConsumingItemAnimation.php b/src/entity/animation/ConsumingItemAnimation.php
index aa6152a57e2..49c8a0a516a 100644
--- a/src/entity/animation/ConsumingItemAnimation.php
+++ b/src/entity/animation/ConsumingItemAnimation.php
@@ -23,7 +23,7 @@
namespace pocketmine\entity\animation;
-use pocketmine\entity\Human;
+use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
@@ -32,7 +32,7 @@
final class ConsumingItemAnimation implements Animation{
public function __construct(
- private Human $human, //TODO: maybe this can be expanded to more than just player entities?
+ private Living $entity,
private Item $item
){}
@@ -40,7 +40,7 @@ public function encode() : array{
[$netId, $netData] = TypeConverter::getInstance()->getItemTranslator()->toNetworkId($this->item);
return [
//TODO: need to check the data values
- ActorEventPacket::create($this->human->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData)
+ ActorEventPacket::create($this->entity->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData)
];
}
}
diff --git a/src/entity/object/EndCrystal.php b/src/entity/object/EndCrystal.php
new file mode 100644
index 00000000000..afaeb676938
--- /dev/null
+++ b/src/entity/object/EndCrystal.php
@@ -0,0 +1,146 @@
+showBase;
+ }
+
+ public function setShowBase(bool $showBase) : void{
+ $this->showBase = $showBase;
+ $this->networkPropertiesDirty = true;
+ }
+
+ public function getBeamTarget() : ?Vector3{
+ return $this->beamTarget;
+ }
+
+ public function setBeamTarget(?Vector3 $beamTarget) : void{
+ $this->beamTarget = $beamTarget;
+ $this->networkPropertiesDirty = true;
+ }
+
+ public function attack(EntityDamageEvent $source) : void{
+ parent::attack($source);
+ if(
+ $source->getCause() !== EntityDamageEvent::CAUSE_VOID &&
+ !$this->isFlaggedForDespawn() &&
+ !$source->isCancelled()
+ ){
+ $this->flagForDespawn();
+ $this->explode();
+ }
+ }
+
+ protected function initEntity(CompoundTag $nbt) : void{
+ parent::initEntity($nbt);
+
+ $this->setMaxHealth(1);
+ $this->setHealth(1);
+
+ $this->setShowBase($nbt->getByte(self::TAG_SHOWBASE, 0) === 1);
+
+ if(
+ ($beamXTag = $nbt->getTag(self::TAG_BLOCKTARGET_X)) instanceof IntTag &&
+ ($beamYTag = $nbt->getTag(self::TAG_BLOCKTARGET_Y)) instanceof IntTag &&
+ ($beamZTag = $nbt->getTag(self::TAG_BLOCKTARGET_Z)) instanceof IntTag
+ ){
+ $this->setBeamTarget(new Vector3($beamXTag->getValue(), $beamYTag->getValue(), $beamZTag->getValue()));
+ }
+ }
+
+ public function saveNBT() : CompoundTag{
+ $nbt = parent::saveNBT();
+
+ $nbt->setByte(self::TAG_SHOWBASE, $this->showBase ? 1 : 0);
+ if($this->beamTarget !== null){
+ $nbt->setInt(self::TAG_BLOCKTARGET_X, $this->beamTarget->getFloorX());
+ $nbt->setInt(self::TAG_BLOCKTARGET_Y, $this->beamTarget->getFloorY());
+ $nbt->setInt(self::TAG_BLOCKTARGET_Z, $this->beamTarget->getFloorZ());
+ }
+ return $nbt;
+ }
+
+ public function explode() : void{
+ $ev = new EntityPreExplodeEvent($this, 6);
+ $ev->call();
+ if(!$ev->isCancelled()){
+ $explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this);
+ if($ev->isBlockBreaking()){
+ $explosion->explodeA();
+ }
+ $explosion->explodeB();
+ }
+ }
+
+ protected function syncNetworkData(EntityMetadataCollection $properties) : void{
+ parent::syncNetworkData($properties);
+
+ $properties->setGenericFlag(EntityMetadataFlags::SHOWBASE, $this->showBase);
+ $properties->setBlockPos(EntityMetadataProperties::BLOCK_TARGET, BlockPosition::fromVector3($this->beamTarget ?? Vector3::zero()));
+ }
+}
diff --git a/src/entity/object/FallingBlock.php b/src/entity/object/FallingBlock.php
index 9d8af893470..66d4049f88a 100644
--- a/src/entity/object/FallingBlock.php
+++ b/src/entity/object/FallingBlock.php
@@ -35,6 +35,7 @@
use pocketmine\event\entity\EntityBlockChangeEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
+use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
@@ -194,6 +195,10 @@ public function saveNBT() : CompoundTag{
return $nbt;
}
+ public function getPickedItem() : ?Item{
+ return $this->block->asItem();
+ }
+
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php
index 6e9fdcdcf35..90eeece67c7 100644
--- a/src/entity/object/ItemEntity.php
+++ b/src/entity/object/ItemEntity.php
@@ -123,7 +123,7 @@ protected function entityBaseTick(int $tickDiff = 1) : bool{
}
}
- if($this->hasMovementUpdate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){
+ if($this->hasMovementUpdate() && $this->isMergeCandidate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){
$mergeable = [$this]; //in case the merge target ends up not being this
$mergeTarget = $this;
foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(0.5, 0.5, 0.5), $this) as $entity){
@@ -165,12 +165,19 @@ protected function entityBaseTick(int $tickDiff = 1) : bool{
}
}
+ private function isMergeCandidate() : bool{
+ return $this->pickupDelay !== self::NEVER_DESPAWN && $this->item->getCount() < $this->item->getMaxStackSize();
+ }
+
/**
* Returns whether this item entity can merge with the given one.
*/
public function isMergeable(ItemEntity $entity) : bool{
+ if(!$this->isMergeCandidate() || !$entity->isMergeCandidate()){
+ return false;
+ }
$item = $entity->item;
- return $entity !== $this && $entity->pickupDelay !== self::NEVER_DESPAWN && $item->canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->getMaxStackSize();
+ return $entity !== $this && $item->canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->getMaxStackSize();
}
/**
diff --git a/src/entity/object/Painting.php b/src/entity/object/Painting.php
index f6449883c7d..641d040510d 100644
--- a/src/entity/object/Painting.php
+++ b/src/entity/object/Painting.php
@@ -28,6 +28,7 @@
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\event\entity\EntityDamageByEntityEvent;
+use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
@@ -165,6 +166,10 @@ protected function sendSpawnPacket(Player $player) : void{
));
}
+ public function getPickedItem() : ?Item{
+ return VanillaItems::PAINTING();
+ }
+
/**
* Returns the painting motive (which image is displayed on the painting)
*/
diff --git a/src/entity/object/PaintingMotive.php b/src/entity/object/PaintingMotive.php
index a456630fca3..00114c508be 100644
--- a/src/entity/object/PaintingMotive.php
+++ b/src/entity/object/PaintingMotive.php
@@ -37,9 +37,11 @@ public static function init() : void{
new PaintingMotive(1, 1, "Aztec2"),
new PaintingMotive(1, 1, "Bomb"),
new PaintingMotive(1, 1, "Kebab"),
+ new PaintingMotive(1, 1, "meditative"),
new PaintingMotive(1, 1, "Plant"),
new PaintingMotive(1, 1, "Wasteland"),
new PaintingMotive(1, 2, "Graham"),
+ new PaintingMotive(1, 2, "prairie_ride"),
new PaintingMotive(1, 2, "Wanderer"),
new PaintingMotive(2, 1, "Courbet"),
new PaintingMotive(2, 1, "Creebet"),
@@ -47,8 +49,10 @@ public static function init() : void{
new PaintingMotive(2, 1, "Sea"),
new PaintingMotive(2, 1, "Sunset"),
new PaintingMotive(2, 2, "Bust"),
+ new PaintingMotive(2, 2, "baroque"),
new PaintingMotive(2, 2, "Earth"),
new PaintingMotive(2, 2, "Fire"),
+ new PaintingMotive(2, 2, "humble"),
new PaintingMotive(2, 2, "Match"),
new PaintingMotive(2, 2, "SkullAndRoses"),
new PaintingMotive(2, 2, "Stage"),
@@ -56,12 +60,28 @@ public static function init() : void{
new PaintingMotive(2, 2, "Water"),
new PaintingMotive(2, 2, "Wind"),
new PaintingMotive(2, 2, "Wither"),
+ new PaintingMotive(3, 3, "bouquet"),
+ new PaintingMotive(3, 3, "cavebird"),
+ new PaintingMotive(3, 3, "cotan"),
+ new PaintingMotive(3, 3, "endboss"),
+ new PaintingMotive(3, 3, "fern"),
+ new PaintingMotive(3, 3, "owlemons"),
+ new PaintingMotive(3, 3, "sunflowers"),
+ new PaintingMotive(3, 3, "tides"),
+ new PaintingMotive(3, 4, "backyard"),
+ new PaintingMotive(3, 4, "pond"),
+ new PaintingMotive(4, 2, "changing"),
new PaintingMotive(4, 2, "Fighters"),
+ new PaintingMotive(4, 2, "finding"),
+ new PaintingMotive(4, 2, "lowmist"),
+ new PaintingMotive(4, 2, "passage"),
new PaintingMotive(4, 3, "DonkeyKong"),
new PaintingMotive(4, 3, "Skeleton"),
new PaintingMotive(4, 4, "BurningSkull"),
+ new PaintingMotive(4, 4, "orb"),
new PaintingMotive(4, 4, "Pigscene"),
- new PaintingMotive(4, 4, "Pointer")
+ new PaintingMotive(4, 4, "Pointer"),
+ new PaintingMotive(4, 4, "unpacked")
] as $motive){
self::registerMotive($motive);
}
diff --git a/src/entity/object/PrimedTNT.php b/src/entity/object/PrimedTNT.php
index ec621adfb80..af3c9792280 100644
--- a/src/entity/object/PrimedTNT.php
+++ b/src/entity/object/PrimedTNT.php
@@ -23,11 +23,13 @@
namespace pocketmine\entity\object;
+use pocketmine\block\VanillaBlocks;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Explosive;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityPreExplodeEvent;
+use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@@ -127,6 +129,10 @@ public function explode() : void{
}
}
+ public function getPickedItem() : ?Item{
+ return VanillaBlocks::TNT()->setWorksUnderwater($this->worksUnderwater)->asItem();
+ }
+
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php
index 0507ebdb625..0abc274b5dd 100644
--- a/src/entity/projectile/Projectile.php
+++ b/src/entity/projectile/Projectile.php
@@ -28,6 +28,7 @@
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\entity\Location;
+use pocketmine\entity\object\EndCrystal;
use pocketmine\event\entity\EntityCombustByEntityEvent;
use pocketmine\event\entity\EntityDamageByChildEntityEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
@@ -96,7 +97,7 @@ protected function initEntity(CompoundTag $nbt) : void{
}
public function canCollideWith(Entity $entity) : bool{
- return $entity instanceof Living && !$this->onGround;
+ return ($entity instanceof Living || $entity instanceof EndCrystal) && !$this->onGround;
}
public function canBeCollidedWith() : bool{
@@ -173,8 +174,9 @@ protected function move(float $dx, float $dy, float $dz) : void{
$entityHit = null;
$hitResult = null;
+ $world = $this->getWorld();
foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
- $block = $this->getWorld()->getBlockAt($vector3->x, $vector3->y, $vector3->z);
+ $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
$blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
if($blockHitResult !== null){
@@ -188,7 +190,7 @@ protected function move(float $dx, float $dy, float $dz) : void{
$entityDistance = PHP_INT_MAX;
$newDiff = $end->subtractVector($start);
- foreach($this->getWorld()->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){
+ foreach($world->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){
if($entity->getId() === $this->getOwningEntityId() && $this->ticksLived < 5){
continue;
}
@@ -256,7 +258,7 @@ protected function move(float $dx, float $dy, float $dz) : void{
);
}
- $this->getWorld()->onEntityMoved($this);
+ $world->onEntityMoved($this);
$this->checkBlockIntersections();
Timings::$projectileMove->stopTiming();
diff --git a/src/entity/projectile/Snowball.php b/src/entity/projectile/Snowball.php
index cf9d7c68949..30a0ba6e254 100644
--- a/src/entity/projectile/Snowball.php
+++ b/src/entity/projectile/Snowball.php
@@ -31,8 +31,9 @@ class Snowball extends Throwable{
public static function getNetworkTypeId() : string{ return EntityIds::SNOWBALL; }
protected function onHit(ProjectileHitEvent $event) : void{
+ $world = $this->getWorld();
for($i = 0; $i < 6; ++$i){
- $this->getWorld()->addParticle($this->location, new SnowballPoofParticle());
+ $world->addParticle($this->location, new SnowballPoofParticle());
}
}
}
diff --git a/src/entity/projectile/SplashPotion.php b/src/entity/projectile/SplashPotion.php
index 17e4d8af71a..f4635cad77c 100644
--- a/src/entity/projectile/SplashPotion.php
+++ b/src/entity/projectile/SplashPotion.php
@@ -129,7 +129,7 @@ protected function onHit(ProjectileHitEvent $event) : void{
}else{
//TODO: lingering potions
}
- }elseif($event instanceof ProjectileHitBlockEvent && $this->getPotionType()->equals(PotionType::WATER())){
+ }elseif($event instanceof ProjectileHitBlockEvent && $this->getPotionType() === PotionType::WATER){
$blockIn = $event->getBlockHit()->getSide($event->getRayTraceResult()->getHitFace());
if($blockIn->hasTypeTag(BlockTypeTags::FIRE)){
diff --git a/src/event/Event.php b/src/event/Event.php
index 5488285b2bc..21b8ae36a5d 100644
--- a/src/event/Event.php
+++ b/src/event/Event.php
@@ -27,6 +27,7 @@
namespace pocketmine\event;
use pocketmine\timings\Timings;
+use function count;
use function get_class;
abstract class Event{
@@ -54,11 +55,11 @@ public function call() : void{
$timings = Timings::getEventTimings($this);
$timings->startTiming();
- $handlerList = HandlerListManager::global()->getListFor(get_class($this));
+ $handlers = HandlerListManager::global()->getHandlersFor(static::class);
++self::$eventCallDepth;
try{
- foreach($handlerList->getListenerList() as $registration){
+ foreach($handlers as $registration){
$registration->callEvent($this);
}
}finally{
@@ -66,4 +67,14 @@ public function call() : void{
$timings->stopTiming();
}
}
+
+ /**
+ * Returns whether the current class context has any registered global handlers.
+ * This can be used in hot code paths to avoid unnecessary event object creation.
+ *
+ * Usage: SomeEventClass::hasHandlers()
+ */
+ public static function hasHandlers() : bool{
+ return count(HandlerListManager::global()->getHandlersFor(static::class)) > 0;
+ }
}
diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php
index 37811e9592a..2072cd5226f 100644
--- a/src/event/HandlerList.php
+++ b/src/event/HandlerList.php
@@ -33,20 +33,17 @@ class HandlerList{
/** @var RegisteredListener[][] */
private array $handlerSlots = [];
- private RegisteredListenerCache $handlerCache;
-
/** @var RegisteredListenerCache[] */
private array $affectedHandlerCaches = [];
/**
- * @phpstan-template TEvent of Event
- * @phpstan-param class-string $class
+ * @phpstan-param class-string $class
*/
public function __construct(
private string $class,
- private ?HandlerList $parentList
+ private ?HandlerList $parentList,
+ private RegisteredListenerCache $handlerCache = new RegisteredListenerCache()
){
- $this->handlerCache = new RegisteredListenerCache();
for($list = $this; $list !== null; $list = $list->parentList){
$list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache;
}
diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php
index ab94674cfa3..605a3874789 100644
--- a/src/event/HandlerListManager.php
+++ b/src/event/HandlerListManager.php
@@ -36,6 +36,11 @@ public static function global() : self{
/** @var HandlerList[] classname => HandlerList */
private array $allLists = [];
+ /**
+ * @var RegisteredListenerCache[] event class name => cache
+ * @phpstan-var array, RegisteredListenerCache>
+ */
+ private array $handlerCaches = [];
/**
* Unregisters all the listeners
@@ -81,8 +86,7 @@ private static function resolveNearestHandleableParent(\ReflectionClass $class)
*
* Calling this method also lazily initializes the $classMap inheritance tree of handler lists.
*
- * @phpstan-template TEvent of Event
- * @phpstan-param class-string $event
+ * @phpstan-param class-string $event
*
* @throws \ReflectionException
* @throws \InvalidArgumentException
@@ -98,7 +102,24 @@ public function getListFor(string $event) : HandlerList{
}
$parent = self::resolveNearestHandleableParent($class);
- return $this->allLists[$event] = new HandlerList($event, $parent !== null ? $this->getListFor($parent->getName()) : null);
+ $cache = new RegisteredListenerCache();
+ $this->handlerCaches[$event] = $cache;
+ return $this->allLists[$event] = new HandlerList(
+ $event,
+ parentList: $parent !== null ? $this->getListFor($parent->getName()) : null,
+ handlerCache: $cache
+ );
+ }
+
+ /**
+ * @phpstan-param class-string $event
+ *
+ * @return RegisteredListener[]
+ */
+ public function getHandlersFor(string $event) : array{
+ $cache = $this->handlerCaches[$event] ?? null;
+ //getListFor() will populate the cache for the next call
+ return $cache?->list ?? $this->getListFor($event)->getListenerList();
}
/**
diff --git a/src/event/block/BlockPlaceEvent.php b/src/event/block/BlockPlaceEvent.php
index b92569fc163..3572cb5e090 100644
--- a/src/event/block/BlockPlaceEvent.php
+++ b/src/event/block/BlockPlaceEvent.php
@@ -43,7 +43,12 @@ public function __construct(
protected BlockTransaction $transaction,
protected Block $blockAgainst,
protected Item $item
- ){}
+ ){
+ $world = $this->blockAgainst->getPosition()->getWorld();
+ foreach($this->transaction->getBlocks() as [$x, $y, $z, $block]){
+ $block->position($world, $x, $y, $z);
+ }
+ }
/**
* Returns the player who is placing the block.
diff --git a/src/event/block/CampfireCookEvent.php b/src/event/block/CampfireCookEvent.php
new file mode 100644
index 00000000000..3762f5848f1
--- /dev/null
+++ b/src/event/block/CampfireCookEvent.php
@@ -0,0 +1,63 @@
+input = clone $input;
+ }
+
+ public function getCampfire() : Campfire{
+ return $this->campfire;
+ }
+
+ public function getSlot() : int{
+ return $this->slot;
+ }
+
+ public function getInput() : Item{
+ return $this->input;
+ }
+
+ public function getResult() : Item{
+ return $this->result;
+ }
+
+ public function setResult(Item $result) : void{
+ $this->result = $result;
+ }
+}
diff --git a/src/event/block/FarmlandHydrationChangeEvent.php b/src/event/block/FarmlandHydrationChangeEvent.php
new file mode 100644
index 00000000000..4df5c1a4ef5
--- /dev/null
+++ b/src/event/block/FarmlandHydrationChangeEvent.php
@@ -0,0 +1,59 @@
+oldHydration;
+ }
+
+ public function getNewHydration() : int{
+ return $this->newHydration;
+ }
+
+ public function setNewHydration(int $hydration) : void{
+ if($hydration < 0 || $hydration > Farmland::MAX_WETNESS){
+ throw new \InvalidArgumentException("Hydration must be in range 0 ... " . Farmland::MAX_WETNESS);
+ }
+ $this->newHydration = $hydration;
+ }
+}
diff --git a/src/event/block/PressurePlateUpdateEvent.php b/src/event/block/PressurePlateUpdateEvent.php
new file mode 100644
index 00000000000..e6f2e92ba48
--- /dev/null
+++ b/src/event/block/PressurePlateUpdateEvent.php
@@ -0,0 +1,52 @@
+activatingEntities; }
+}
diff --git a/src/event/entity/EntityDamageByEntityEvent.php b/src/event/entity/EntityDamageByEntityEvent.php
index 6264375bb6c..052be9a1596 100644
--- a/src/event/entity/EntityDamageByEntityEvent.php
+++ b/src/event/entity/EntityDamageByEntityEvent.php
@@ -36,7 +36,15 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
/**
* @param float[] $modifiers
*/
- public function __construct(Entity $damager, Entity $entity, int $cause, float $damage, array $modifiers = [], private float $knockBack = 0.4){
+ public function __construct(
+ Entity $damager,
+ Entity $entity,
+ int $cause,
+ float $damage,
+ array $modifiers = [],
+ private float $knockBack = Living::DEFAULT_KNOCKBACK_FORCE,
+ private float $verticalKnockBackLimit = Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT
+ ){
$this->damagerEntityId = $damager->getId();
parent::__construct($entity, $cause, $damage, $modifiers);
$this->addAttackerModifiers($damager);
@@ -49,7 +57,7 @@ protected function addAttackerModifiers(Entity $damager) : void{
$this->setModifier($this->getBaseDamage() * 0.3 * $strength->getEffectLevel(), self::MODIFIER_STRENGTH);
}
- if(($weakness = $effects->get(VanillaEffects::WEAKNESS())) !== null){
+ if(($weakness = $effects->get(VanillaEffects::WEAKNESS())) !== null && $this->getCause() === EntityDamageEvent::CAUSE_ENTITY_ATTACK){
$this->setModifier(-($this->getBaseDamage() * 0.2 * $weakness->getEffectLevel()), self::MODIFIER_WEAKNESS);
}
}
@@ -62,11 +70,39 @@ public function getDamager() : ?Entity{
return $this->getEntity()->getWorld()->getServer()->getWorldManager()->findEntity($this->damagerEntityId);
}
+ /**
+ * Returns the force with which the victim will be knocked back from the attacking entity.
+ *
+ * @see Living::DEFAULT_KNOCKBACK_FORCE
+ */
public function getKnockBack() : float{
return $this->knockBack;
}
+ /**
+ * Sets the force with which the victim will be knocked back from the attacking entity.
+ * Larger values will knock the victim back further.
+ * Negative values will pull the victim towards the attacker.
+ */
public function setKnockBack(float $knockBack) : void{
$this->knockBack = $knockBack;
}
+
+ /**
+ * Returns the maximum upwards velocity the victim may have after being knocked back.
+ * This ensures that the victim doesn't fly up into the sky when high levels of knockback are applied.
+ *
+ * @see Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT
+ */
+ public function getVerticalKnockBackLimit() : float{
+ return $this->verticalKnockBackLimit;
+ }
+
+ /**
+ * Sets the maximum upwards velocity the victim may have after being knocked back.
+ * Larger values will allow the victim to fly higher if the knockback force is also large.
+ */
+ public function setVerticalKnockBackLimit(float $verticalKnockBackLimit) : void{
+ $this->verticalKnockBackLimit = $verticalKnockBackLimit;
+ }
}
diff --git a/src/event/inventory/CraftItemEvent.php b/src/event/inventory/CraftItemEvent.php
index 64f285f1553..35409f8286b 100644
--- a/src/event/inventory/CraftItemEvent.php
+++ b/src/event/inventory/CraftItemEvent.php
@@ -30,6 +30,7 @@
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\item\Item;
use pocketmine\player\Player;
+use pocketmine\utils\Utils;
class CraftItemEvent extends Event implements Cancellable{
use CancellableTrait;
@@ -74,7 +75,7 @@ public function getRepetitions() : int{
* @return Item[]
*/
public function getInputs() : array{
- return $this->inputs;
+ return Utils::cloneObjectArray($this->inputs);
}
/**
@@ -83,7 +84,7 @@ public function getInputs() : array{
* @return Item[]
*/
public function getOutputs() : array{
- return $this->outputs;
+ return Utils::cloneObjectArray($this->outputs);
}
public function getPlayer() : Player{
diff --git a/src/event/player/PlayerEnchantingOptionsRequestEvent.php b/src/event/player/PlayerEnchantingOptionsRequestEvent.php
new file mode 100644
index 00000000000..833185f7605
--- /dev/null
+++ b/src/event/player/PlayerEnchantingOptionsRequestEvent.php
@@ -0,0 +1,75 @@
+player = $player;
+ }
+
+ public function getInventory() : EnchantInventory{
+ return $this->inventory;
+ }
+
+ /**
+ * @return EnchantingOption[]
+ */
+ public function getOptions() : array{
+ return $this->options;
+ }
+
+ /**
+ * @param EnchantingOption[] $options
+ */
+ public function setOptions(array $options) : void{
+ Utils::validateArrayValueType($options, function(EnchantingOption $_) : void{ });
+ if(($optionCount = count($options)) > 3){
+ throw new \LogicException("The maximum number of options for an enchanting table is 3, but $optionCount have been passed");
+ }
+
+ $this->options = $options;
+ }
+}
diff --git a/src/event/player/PlayerEntityPickEvent.php b/src/event/player/PlayerEntityPickEvent.php
new file mode 100644
index 00000000000..3c742d6c489
--- /dev/null
+++ b/src/event/player/PlayerEntityPickEvent.php
@@ -0,0 +1,53 @@
+player = $player;
+ }
+
+ public function getEntity() : Entity{
+ return $this->entityClicked;
+ }
+
+ public function getResultItem() : Item{
+ return $this->resultItem;
+ }
+}
diff --git a/src/event/player/PlayerInteractEvent.php b/src/event/player/PlayerInteractEvent.php
index 6119d5e99cb..46daf70813d 100644
--- a/src/event/player/PlayerInteractEvent.php
+++ b/src/event/player/PlayerInteractEvent.php
@@ -42,6 +42,9 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
protected Vector3 $touchVector;
+ protected bool $useItem = true;
+ protected bool $useBlock = true;
+
public function __construct(
Player $player,
protected Item $item,
@@ -73,4 +76,28 @@ public function getTouchVector() : Vector3{
public function getFace() : int{
return $this->blockFace;
}
+
+ /**
+ * Returns whether the item may react to the interaction. If disabled, items such as spawn eggs will not activate.
+ * This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
+ */
+ public function useItem() : bool{ return $this->useItem; }
+
+ /**
+ * Sets whether the used item may react to the interaction. If false, items such as spawn eggs will not activate.
+ * This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
+ */
+ public function setUseItem(bool $useItem) : void{ $this->useItem = $useItem; }
+
+ /**
+ * Returns whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
+ * respond, containers will not open, etc.
+ */
+ public function useBlock() : bool{ return $this->useBlock; }
+
+ /**
+ * Sets whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
+ * respond, containers will not open, etc.
+ */
+ public function setUseBlock(bool $useBlock) : void{ $this->useBlock = $useBlock; }
}
diff --git a/src/event/player/PlayerItemEnchantEvent.php b/src/event/player/PlayerItemEnchantEvent.php
new file mode 100644
index 00000000000..76151384d3f
--- /dev/null
+++ b/src/event/player/PlayerItemEnchantEvent.php
@@ -0,0 +1,85 @@
+player = $player;
+ }
+
+ /**
+ * Returns the inventory transaction involved in this enchant event.
+ */
+ public function getTransaction() : EnchantingTransaction{
+ return $this->transaction;
+ }
+
+ /**
+ * Returns the enchantment option used.
+ */
+ public function getOption() : EnchantingOption{
+ return $this->option;
+ }
+
+ /**
+ * Returns the item to be enchanted.
+ */
+ public function getInputItem() : Item{
+ return clone $this->inputItem;
+ }
+
+ /**
+ * Returns the enchanted item.
+ */
+ public function getOutputItem() : Item{
+ return clone $this->outputItem;
+ }
+
+ /**
+ * Returns the number of XP levels and lapis that will be subtracted after enchanting
+ * if the player is not in creative mode.
+ */
+ public function getCost() : int{
+ return $this->cost;
+ }
+}
diff --git a/src/event/player/PlayerMissSwingEvent.php b/src/event/player/PlayerMissSwingEvent.php
new file mode 100644
index 00000000000..2f7ffda9cfc
--- /dev/null
+++ b/src/event/player/PlayerMissSwingEvent.php
@@ -0,0 +1,39 @@
+player = $player;
+ }
+}
diff --git a/src/event/player/PlayerResourcePackOfferEvent.php b/src/event/player/PlayerResourcePackOfferEvent.php
new file mode 100644
index 00000000000..df00cf474ef
--- /dev/null
+++ b/src/event/player/PlayerResourcePackOfferEvent.php
@@ -0,0 +1,106 @@
+ key, leave unset for any packs that are not encrypted
+ *
+ * @phpstan-param list $resourcePacks
+ * @phpstan-param array $encryptionKeys
+ */
+ public function __construct(
+ private readonly PlayerInfo $playerInfo,
+ private array $resourcePacks,
+ private array $encryptionKeys,
+ private bool $mustAccept
+ ){}
+
+ public function getPlayerInfo() : PlayerInfo{
+ return $this->playerInfo;
+ }
+
+ /**
+ * Adds a resource pack to the top of the stack.
+ * The resources in this pack will be applied over the top of any existing packs.
+ */
+ public function addResourcePack(ResourcePack $entry, ?string $encryptionKey = null) : void{
+ array_unshift($this->resourcePacks, $entry);
+ if($encryptionKey !== null){
+ $this->encryptionKeys[$entry->getPackId()] = $encryptionKey;
+ }
+ }
+
+ /**
+ * Sets the resource packs to offer. Packs are applied from the highest key to the lowest, with each pack
+ * overwriting any resources from the previous pack. This means that the pack at index 0 gets the final say on which
+ * resources are used.
+ *
+ * @param ResourcePack[] $resourcePacks
+ * @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
+ *
+ * @phpstan-param list $resourcePacks
+ * @phpstan-param array $encryptionKeys
+ */
+ public function setResourcePacks(array $resourcePacks, array $encryptionKeys) : void{
+ $this->resourcePacks = $resourcePacks;
+ $this->encryptionKeys = $encryptionKeys;
+ }
+
+ /**
+ * @return ResourcePack[]
+ * @phpstan-return list
+ */
+ public function getResourcePacks() : array{
+ return $this->resourcePacks;
+ }
+
+ /**
+ * @return string[]
+ * @phpstan-return array
+ */
+ public function getEncryptionKeys() : array{
+ return $this->encryptionKeys;
+ }
+
+ public function setMustAccept(bool $mustAccept) : void{
+ $this->mustAccept = $mustAccept;
+ }
+
+ public function mustAccept() : bool{
+ return $this->mustAccept;
+ }
+}
diff --git a/src/event/world/WorldDifficultyChangeEvent.php b/src/event/world/WorldDifficultyChangeEvent.php
new file mode 100644
index 00000000000..90cee376a5c
--- /dev/null
+++ b/src/event/world/WorldDifficultyChangeEvent.php
@@ -0,0 +1,44 @@
+oldDifficulty; }
+
+ public function getNewDifficulty() : int{ return $this->newDifficulty; }
+}
diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php
index dcb3c04cb2e..8591cc65bf3 100644
--- a/src/inventory/ArmorInventory.php
+++ b/src/inventory/ArmorInventory.php
@@ -23,8 +23,13 @@
namespace pocketmine\inventory;
+use pocketmine\block\BlockTypeIds;
use pocketmine\entity\Living;
+use pocketmine\inventory\transaction\action\validator\CallbackSlotValidator;
+use pocketmine\inventory\transaction\TransactionValidationException;
+use pocketmine\item\Armor;
use pocketmine\item\Item;
+use pocketmine\item\ItemBlock;
class ArmorInventory extends SimpleInventory{
public const SLOT_HEAD = 0;
@@ -36,6 +41,8 @@ public function __construct(
protected Living $holder
){
parent::__construct(4);
+
+ $this->validators->add(new CallbackSlotValidator(self::validate(...)));
}
public function getHolder() : Living{
@@ -73,4 +80,20 @@ public function setLeggings(Item $leggings) : void{
public function setBoots(Item $boots) : void{
$this->setItem(self::SLOT_FEET, $boots);
}
+
+ private static function validate(Inventory $inventory, Item $item, int $slot) : ?TransactionValidationException{
+ if($item instanceof Armor){
+ if($item->getArmorSlot() !== $slot){
+ return new TransactionValidationException("Armor item is in wrong slot");
+ }
+ }else{
+ if(!($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && (
+ $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN ||
+ $item->getBlock()->getTypeId() === BlockTypeIds::MOB_HEAD
+ ))){
+ return new TransactionValidationException("Item is not accepted in an armor slot");
+ }
+ }
+ return null;
+ }
}
diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php
index 254e44b1ea0..522c827a4b2 100644
--- a/src/inventory/BaseInventory.php
+++ b/src/inventory/BaseInventory.php
@@ -36,8 +36,10 @@
/**
* This class provides everything needed to implement an inventory, minus the underlying storage system.
+ *
+ * @phpstan-import-type SlotValidators from SlotValidatedInventory
*/
-abstract class BaseInventory implements Inventory{
+abstract class BaseInventory implements Inventory, SlotValidatedInventory{
protected int $maxStackSize = Inventory::MAX_STACK;
/** @var Player[] */
protected array $viewers = [];
@@ -46,9 +48,12 @@ abstract class BaseInventory implements Inventory{
* @phpstan-var ObjectSet
*/
protected ObjectSet $listeners;
+ /** @phpstan-var SlotValidators */
+ protected ObjectSet $validators;
public function __construct(){
$this->listeners = new ObjectSet();
+ $this->validators = new ObjectSet();
}
public function getMaxStackSize() : int{
@@ -398,4 +403,8 @@ public function slotExists(int $slot) : bool{
public function getListeners() : ObjectSet{
return $this->listeners;
}
+
+ public function getSlotValidators() : ObjectSet{
+ return $this->validators;
+ }
}
diff --git a/src/inventory/SlotValidatedInventory.php b/src/inventory/SlotValidatedInventory.php
new file mode 100644
index 00000000000..f30ebf8a011
--- /dev/null
+++ b/src/inventory/SlotValidatedInventory.php
@@ -0,0 +1,46 @@
+
+ */
+interface SlotValidatedInventory{
+ /**
+ * Returns a set of validators that will be used to determine whether an item can be placed in a particular slot.
+ * All validators need to return null for the transaction to be allowed.
+ * If one of the validators returns an exception, the transaction will be cancelled.
+ *
+ * There is no guarantee that the validators will be called in any particular order.
+ *
+ * @phpstan-return SlotValidators
+ */
+ public function getSlotValidators() : ObjectSet;
+}
diff --git a/src/inventory/transaction/EnchantingTransaction.php b/src/inventory/transaction/EnchantingTransaction.php
new file mode 100644
index 00000000000..d00df97e21d
--- /dev/null
+++ b/src/inventory/transaction/EnchantingTransaction.php
@@ -0,0 +1,134 @@
+inputItem === null || $this->outputItem === null){
+ throw new AssumptionFailedError("Expected that inputItem and outputItem are not null before validating output");
+ }
+
+ $enchantedInput = EnchantingHelper::enchantItem($this->inputItem, $this->option->getEnchantments());
+ if(!$this->outputItem->equalsExact($enchantedInput)){
+ throw new TransactionValidationException("Invalid output item");
+ }
+ }
+
+ private function validateFiniteResources(int $lapisSpent) : void{
+ if($lapisSpent !== $this->cost){
+ throw new TransactionValidationException("Expected the amount of lapis lazuli spent to be $this->cost, but received $lapisSpent");
+ }
+
+ $xpLevel = $this->source->getXpManager()->getXpLevel();
+ $requiredXpLevel = $this->option->getRequiredXpLevel();
+
+ if($xpLevel < $requiredXpLevel){
+ throw new TransactionValidationException("Player's XP level $xpLevel is less than the required XP level $requiredXpLevel");
+ }
+ //XP level cost is intentionally not checked here, as the required level may be lower than the cost, allowing
+ //the option to be used with less XP than the cost - in this case, as much XP as possible will be deducted.
+ }
+
+ public function validate() : void{
+ if(count($this->actions) < 1){
+ throw new TransactionValidationException("Transaction must have at least one action to be executable");
+ }
+
+ /** @var Item[] $inputs */
+ $inputs = [];
+ /** @var Item[] $outputs */
+ $outputs = [];
+ $this->matchItems($outputs, $inputs);
+
+ $lapisSpent = 0;
+ foreach($inputs as $input){
+ if($input->getTypeId() === ItemTypeIds::LAPIS_LAZULI){
+ $lapisSpent = $input->getCount();
+ }else{
+ if($this->inputItem !== null){
+ throw new TransactionValidationException("Received more than 1 items to enchant");
+ }
+ $this->inputItem = $input;
+ }
+ }
+
+ if($this->inputItem === null){
+ throw new TransactionValidationException("No item to enchant received");
+ }
+
+ if(($outputCount = count($outputs)) !== 1){
+ throw new TransactionValidationException("Expected 1 output item, but received $outputCount");
+ }
+ $this->outputItem = $outputs[0];
+
+ $this->validateOutput();
+
+ if($this->source->hasFiniteResources()){
+ $this->validateFiniteResources($lapisSpent);
+ }
+ }
+
+ public function execute() : void{
+ parent::execute();
+
+ if($this->source->hasFiniteResources()){
+ //If the required XP level is less than the XP cost, the option can be selected with less XP than the cost.
+ //In this case, as much XP as possible will be taken.
+ $this->source->getXpManager()->subtractXpLevels(min($this->cost, $this->source->getXpManager()->getXpLevel()));
+ }
+ $this->source->regenerateEnchantmentSeed();
+ }
+
+ protected function callExecuteEvent() : bool{
+ if($this->inputItem === null || $this->outputItem === null){
+ throw new AssumptionFailedError("Expected that inputItem and outputItem are not null before executing the event");
+ }
+
+ $event = new PlayerItemEnchantEvent($this->source, $this, $this->option, $this->inputItem, $this->outputItem, $this->cost);
+ $event->call();
+ return !$event->isCancelled();
+ }
+}
diff --git a/src/inventory/transaction/action/CreateItemAction.php b/src/inventory/transaction/action/CreateItemAction.php
index 99605bf9633..3ebda690040 100644
--- a/src/inventory/transaction/action/CreateItemAction.php
+++ b/src/inventory/transaction/action/CreateItemAction.php
@@ -23,7 +23,6 @@
namespace pocketmine\inventory\transaction\action;
-use pocketmine\inventory\CreativeInventory;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@@ -43,7 +42,7 @@ public function validate(Player $source) : void{
if($source->hasFiniteResources()){
throw new TransactionValidationException("Player has finite resources, cannot create items");
}
- if(!CreativeInventory::getInstance()->contains($this->sourceItem)){
+ if(!$source->getCreativeInventory()->contains($this->sourceItem)){
throw new TransactionValidationException("Creative inventory does not contain requested item");
}
}
diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php
index 453f0c4d228..68c3dba1b32 100644
--- a/src/inventory/transaction/action/SlotChangeAction.php
+++ b/src/inventory/transaction/action/SlotChangeAction.php
@@ -24,6 +24,7 @@
namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\Inventory;
+use pocketmine\inventory\SlotValidatedInventory;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item;
@@ -74,6 +75,14 @@ public function validate(Player $source) : void{
if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){
throw new TransactionValidationException("Target item exceeds inventory max stack size");
}
+ if($this->inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){
+ foreach($this->inventory->getSlotValidators() as $validator){
+ $ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot);
+ if($ret !== null){
+ throw new TransactionValidationException("Target item is not accepted by the inventory at slot #" . $this->inventorySlot . ": " . $ret->getMessage(), 0, $ret);
+ }
+ }
+ }
}
/**
diff --git a/src/inventory/transaction/action/validator/CallbackSlotValidator.php b/src/inventory/transaction/action/validator/CallbackSlotValidator.php
new file mode 100644
index 00000000000..1670dc6232a
--- /dev/null
+++ b/src/inventory/transaction/action/validator/CallbackSlotValidator.php
@@ -0,0 +1,44 @@
+validate)($inventory, $item, $slot);
+ }
+}
diff --git a/src/inventory/transaction/action/validator/SlotValidator.php b/src/inventory/transaction/action/validator/SlotValidator.php
new file mode 100644
index 00000000000..1b78c91f7f4
--- /dev/null
+++ b/src/inventory/transaction/action/validator/SlotValidator.php
@@ -0,0 +1,38 @@
+armorInfo = $info;
}
@@ -72,6 +75,14 @@ public function isFireProof() : bool{
return $this->armorInfo->isFireProof();
}
+ public function getMaterial() : ArmorMaterial{
+ return $this->armorInfo->getMaterial();
+ }
+
+ public function getEnchantability() : int{
+ return $this->armorInfo->getMaterial()->getEnchantability();
+ }
+
/**
* Returns the dyed colour of this armour piece. This generally only applies to leather armour.
*/
@@ -135,11 +146,15 @@ public function onClickAir(Player $player, Vector3 $directionVector, array &$ret
$new = $thisCopy->pop();
$player->getArmorInventory()->setItem($this->getArmorSlot(), $new);
$player->getInventory()->setItemInHand($existing);
+ $sound = $new->getMaterial()->getEquipSound();
+ if($sound !== null){
+ $player->broadcastSound($sound);
+ }
if(!$thisCopy->isNull()){
//if the stack size was bigger than 1 (usually won't happen, but might be caused by plugins)
$returnedItems[] = $thisCopy;
}
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
protected function deserializeCompoundTag(CompoundTag $tag) : void{
diff --git a/src/item/ArmorMaterial.php b/src/item/ArmorMaterial.php
new file mode 100644
index 00000000000..d0ea33feb4e
--- /dev/null
+++ b/src/item/ArmorMaterial.php
@@ -0,0 +1,52 @@
+enchantability;
+ }
+
+ /**
+ * Returns the sound that plays when equipping the armor
+ */
+ public function getEquipSound() : ?Sound{
+ return $this->equipSound;
+ }
+}
diff --git a/src/item/ArmorTypeInfo.php b/src/item/ArmorTypeInfo.php
index 580b73df338..dbb4ed06dfe 100644
--- a/src/item/ArmorTypeInfo.php
+++ b/src/item/ArmorTypeInfo.php
@@ -24,13 +24,18 @@
namespace pocketmine\item;
class ArmorTypeInfo{
+ private ArmorMaterial $material;
+
public function __construct(
private int $defensePoints,
private int $maxDurability,
private int $armorSlot,
private int $toughness = 0,
- private bool $fireProof = false
- ){}
+ private bool $fireProof = false,
+ ?ArmorMaterial $material = null
+ ){
+ $this->material = $material ?? VanillaArmorMaterials::LEATHER();
+ }
public function getDefensePoints() : int{
return $this->defensePoints;
@@ -51,4 +56,8 @@ public function getToughness() : int{
public function isFireProof() : bool{
return $this->fireProof;
}
+
+ public function getMaterial() : ArmorMaterial{
+ return $this->material;
+ }
}
diff --git a/src/item/Banner.php b/src/item/Banner.php
index 250f2099efa..2fc53f5ae60 100644
--- a/src/item/Banner.php
+++ b/src/item/Banner.php
@@ -23,7 +23,6 @@
namespace pocketmine\item;
-use pocketmine\block\Block;
use pocketmine\block\tile\Banner as TileBanner;
use pocketmine\block\utils\BannerPatternLayer;
use pocketmine\block\utils\DyeColor;
@@ -40,7 +39,7 @@ class Banner extends ItemBlockWallOrFloor{
public const TAG_PATTERN_COLOR = TileBanner::TAG_PATTERN_COLOR;
public const TAG_PATTERN_NAME = TileBanner::TAG_PATTERN_NAME;
- private DyeColor $color;
+ private DyeColor $color = DyeColor::BLACK;
/**
* @var BannerPatternLayer[]
@@ -48,11 +47,6 @@ class Banner extends ItemBlockWallOrFloor{
*/
private array $patterns = [];
- public function __construct(ItemIdentifier $identifier, Block $floorVariant, Block $wallVariant){
- parent::__construct($identifier, $floorVariant, $wallVariant);
- $this->color = DyeColor::BLACK();
- }
-
public function getColor() : DyeColor{
return $this->color;
}
@@ -64,7 +58,7 @@ public function setColor(DyeColor $color) : self{
}
protected function describeState(RuntimeDataDescriber $w) : void{
- $w->dyeColor($this->color);
+ $w->enum($this->color);
}
/**
@@ -102,7 +96,7 @@ protected function deserializeCompoundTag(CompoundTag $tag) : void{
if($patterns !== null && $patterns->getTagType() === NBT::TAG_Compound){
/** @var CompoundTag $t */
foreach($patterns as $t){
- $patternColor = $colorIdMap->fromInvertedId($t->getInt(self::TAG_PATTERN_COLOR)) ?? DyeColor::BLACK(); //TODO: missing pattern colour should be an error
+ $patternColor = $colorIdMap->fromInvertedId($t->getInt(self::TAG_PATTERN_COLOR)) ?? DyeColor::BLACK; //TODO: missing pattern colour should be an error
$patternType = $patternIdMap->fromId($t->getString(self::TAG_PATTERN_NAME));
if($patternType === null){
continue; //TODO: this should be an error
diff --git a/src/item/BoatType.php b/src/item/BoatType.php
index 5ef0c9255b4..518a1324e7d 100644
--- a/src/item/BoatType.php
+++ b/src/item/BoatType.php
@@ -24,13 +24,11 @@
namespace pocketmine\item;
use pocketmine\block\utils\WoodType;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static BoatType ACACIA()
* @method static BoatType BIRCH()
@@ -40,33 +38,30 @@
* @method static BoatType OAK()
* @method static BoatType SPRUCE()
*/
-final class BoatType{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum BoatType{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("oak", WoodType::OAK()),
- new self("spruce", WoodType::SPRUCE()),
- new self("birch", WoodType::BIRCH()),
- new self("jungle", WoodType::JUNGLE()),
- new self("acacia", WoodType::ACACIA()),
- new self("dark_oak", WoodType::DARK_OAK()),
- new self("mangrove", WoodType::MANGROVE()),
- );
- }
+ case OAK;
+ case SPRUCE;
+ case BIRCH;
+ case JUNGLE;
+ case ACACIA;
+ case DARK_OAK;
+ case MANGROVE;
- private function __construct(
- string $enumName,
- private WoodType $woodType,
- ){
- $this->Enum___construct($enumName);
+ public function getWoodType() : WoodType{
+ return match($this){
+ self::OAK => WoodType::OAK,
+ self::SPRUCE => WoodType::SPRUCE,
+ self::BIRCH => WoodType::BIRCH,
+ self::JUNGLE => WoodType::JUNGLE,
+ self::ACACIA => WoodType::ACACIA,
+ self::DARK_OAK => WoodType::DARK_OAK,
+ self::MANGROVE => WoodType::MANGROVE,
+ };
}
- public function getWoodType() : WoodType{ return $this->woodType; }
-
public function getDisplayName() : string{
- return $this->woodType->getDisplayName();
+ return $this->getWoodType()->getDisplayName();
}
}
diff --git a/src/item/Bow.php b/src/item/Bow.php
index 3c1320f037e..1b25312a8e2 100644
--- a/src/item/Bow.php
+++ b/src/item/Bow.php
@@ -53,7 +53,7 @@ public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseR
};
if($player->hasFiniteResources() && $inventory === null){
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
$location = $player->getLocation();
@@ -95,7 +95,7 @@ public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseR
if($ev->isCancelled()){
$entity->flagForDespawn();
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
$entity->setMotion($entity->getMotion()->multiply($ev->getForce()));
@@ -105,7 +105,7 @@ public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseR
$projectileEv->call();
if($projectileEv->isCancelled()){
$ev->getProjectile()->flagForDespawn();
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
$ev->getProjectile()->spawnToAll();
@@ -121,7 +121,7 @@ public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseR
$this->applyDamage(1);
}
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
public function canStartUsingItem(Player $player) : bool{
diff --git a/src/item/Bowl.php b/src/item/Bowl.php
index d83044307d7..217346df54c 100644
--- a/src/item/Bowl.php
+++ b/src/item/Bowl.php
@@ -25,5 +25,7 @@
class Bowl extends Item{
- //TODO: check fuel
+ public function getFuelTime() : int{
+ return 200;
+ }
}
diff --git a/src/item/Bucket.php b/src/item/Bucket.php
index b788a302267..ee69a0a8a5e 100644
--- a/src/item/Bucket.php
+++ b/src/item/Bucket.php
@@ -49,7 +49,7 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
default => null
};
if($resultItem === null){
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
$ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem);
@@ -60,12 +60,12 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
$this->pop();
$returnedItems[] = $ev->getItem();
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
}
diff --git a/src/item/ChorusFruit.php b/src/item/ChorusFruit.php
index 80f99d5d81a..e10c519576b 100644
--- a/src/item/ChorusFruit.php
+++ b/src/item/ChorusFruit.php
@@ -56,15 +56,17 @@ public function onConsume(Living $consumer) : void{
$maxY = $minY + 16;
$maxZ = $minZ + 16;
+ $worldMinY = $world->getMinY();
+
for($attempts = 0; $attempts < 16; ++$attempts){
$x = mt_rand($minX, $maxX);
$y = mt_rand($minY, $maxY);
$z = mt_rand($minZ, $maxZ);
- while($y >= 0 && !$world->getBlockAt($x, $y, $z)->isSolid()){
+ while($y >= $worldMinY && !$world->getBlockAt($x, $y, $z)->isSolid()){
$y--;
}
- if($y < 0){
+ if($y < $worldMinY){
continue;
}
@@ -86,4 +88,8 @@ public function onConsume(Living $consumer) : void{
public function getCooldownTicks() : int{
return 20;
}
+
+ public function getCooldownTag() : ?string{
+ return ItemCooldownTags::CHORUS_FRUIT;
+ }
}
diff --git a/src/item/CoralFan.php b/src/item/CoralFan.php
index 294eb6d6e2a..7fdfc9114b9 100644
--- a/src/item/CoralFan.php
+++ b/src/item/CoralFan.php
@@ -24,7 +24,6 @@
namespace pocketmine\item;
use pocketmine\block\Block;
-use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\block\VanillaBlocks;
use pocketmine\data\runtime\RuntimeDataDescriber;
@@ -37,7 +36,6 @@ final class CoralFan extends Item{
}
public function __construct(ItemIdentifier $identifier){
- $this->coralType = CoralType::TUBE();
parent::__construct($identifier, VanillaBlocks::CORAL_FAN()->getName());
}
diff --git a/src/item/Dye.php b/src/item/Dye.php
index f71b44e3a96..9fdfb96710b 100644
--- a/src/item/Dye.php
+++ b/src/item/Dye.php
@@ -27,15 +27,10 @@
use pocketmine\data\runtime\RuntimeDataDescriber;
class Dye extends Item{
- private DyeColor $color;
-
- public function __construct(ItemIdentifier $identifier, string $name){
- $this->color = DyeColor::BLACK();
- parent::__construct($identifier, $name);
- }
+ private DyeColor $color = DyeColor::BLACK;
protected function describeState(RuntimeDataDescriber $w) : void{
- $w->dyeColor($this->color);
+ $w->enum($this->color);
}
public function getColor() : DyeColor{
diff --git a/src/item/EnchantedBook.php b/src/item/EnchantedBook.php
new file mode 100644
index 00000000000..5660de6f60a
--- /dev/null
+++ b/src/item/EnchantedBook.php
@@ -0,0 +1,30 @@
+getTypeId() === BlockTypeIds::OBSIDIAN || $blockClicked->getTypeId() === BlockTypeIds::BEDROCK){
+ $pos = $blockClicked->getPosition();
+ $world = $pos->getWorld();
+ $bb = AxisAlignedBB::one()
+ ->offset($pos->getX(), $pos->getY(), $pos->getZ())
+ ->extend(Facing::UP, 1);
+ if(
+ count($world->getNearbyEntities($bb)) === 0 &&
+ $blockClicked->getSide(Facing::UP)->getTypeId() === BlockTypeIds::AIR &&
+ $blockClicked->getSide(Facing::UP, 2)->getTypeId() === BlockTypeIds::AIR
+ ){
+ $crystal = new EntityEndCrystal(Location::fromObject($pos->add(0.5, 1, 0.5), $world));
+ $crystal->spawnToAll();
+
+ $this->pop();
+ return ItemUseResult::SUCCESS;
+ }
+ }
+ return ItemUseResult::NONE;
+ }
+}
diff --git a/src/item/EnderPearl.php b/src/item/EnderPearl.php
index 76bcb358ea4..7109d3ae066 100644
--- a/src/item/EnderPearl.php
+++ b/src/item/EnderPearl.php
@@ -45,4 +45,8 @@ public function getThrowForce() : float{
public function getCooldownTicks() : int{
return 20;
}
+
+ public function getCooldownTag() : ?string{
+ return ItemCooldownTags::ENDER_PEARL;
+ }
}
diff --git a/src/item/FireCharge.php b/src/item/FireCharge.php
index 20e7e4f89e1..a612e0897de 100644
--- a/src/item/FireCharge.php
+++ b/src/item/FireCharge.php
@@ -40,9 +40,9 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
$this->pop();
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
}
diff --git a/src/item/FlintSteel.php b/src/item/FlintSteel.php
index 0ff2e754cd9..3e694eb0dbe 100644
--- a/src/item/FlintSteel.php
+++ b/src/item/FlintSteel.php
@@ -40,10 +40,10 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
$this->applyDamage(1);
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
public function getMaxDurability() : int{
diff --git a/src/item/GlassBottle.php b/src/item/GlassBottle.php
index bda6d132ebc..c638b109f7c 100644
--- a/src/item/GlassBottle.php
+++ b/src/item/GlassBottle.php
@@ -33,11 +33,11 @@ class GlassBottle extends Item{
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
if($blockClicked->getTypeId() === BlockTypeIds::WATER){
$this->pop();
- $returnedItems[] = VanillaItems::POTION()->setType(PotionType::WATER());
+ $returnedItems[] = VanillaItems::POTION()->setType(PotionType::WATER);
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
}
diff --git a/src/item/GoatHorn.php b/src/item/GoatHorn.php
new file mode 100644
index 00000000000..088701e3986
--- /dev/null
+++ b/src/item/GoatHorn.php
@@ -0,0 +1,71 @@
+enum($this->goatHornType);
+ }
+
+ public function getHornType() : GoatHornType{ return $this->goatHornType; }
+
+ /**
+ * @return $this
+ */
+ public function setHornType(GoatHornType $type) : self{
+ $this->goatHornType = $type;
+ return $this;
+ }
+
+ public function getMaxStackSize() : int{
+ return 1;
+ }
+
+ public function getCooldownTicks() : int{
+ return 140;
+ }
+
+ public function getCooldownTag() : ?string{
+ return ItemCooldownTags::GOAT_HORN;
+ }
+
+ public function canStartUsingItem(Player $player) : bool{
+ return true;
+ }
+
+ public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
+ $position = $player->getPosition();
+ $position->getWorld()->addSound($position, new GoatHornSound($this->goatHornType));
+
+ return ItemUseResult::SUCCESS;
+ }
+}
diff --git a/src/item/GoatHornType.php b/src/item/GoatHornType.php
new file mode 100644
index 00000000000..6c0c3b2f7fc
--- /dev/null
+++ b/src/item/GoatHornType.php
@@ -0,0 +1,36 @@
+nbt = new CompoundTag();
}
@@ -455,6 +458,29 @@ public function getVanillaName() : string{
return $this->name;
}
+ /**
+ * Returns tags that represent the type of item being enchanted and are used to determine
+ * what enchantments can be applied to this item during in-game enchanting (enchanting table, anvil, fishing, etc.).
+ * @see ItemEnchantmentTags
+ * @see ItemEnchantmentTagRegistry
+ * @see AvailableEnchantmentRegistry
+ *
+ * @return string[]
+ */
+ public function getEnchantmentTags() : array{
+ return $this->enchantmentTags;
+ }
+
+ /**
+ * Returns the value that defines how enchantable the item is.
+ *
+ * The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
+ * or multiple enchantments upon being enchanted in an enchanting table.
+ */
+ public function getEnchantability() : int{
+ return 1;
+ }
+
final public function canBePlaced() : bool{
return $this->getBlock()->canBePlaced();
}
@@ -562,7 +588,7 @@ public function getMiningEfficiency(bool $isCorrectTool) : float{
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
*/
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
/**
@@ -572,7 +598,7 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
*/
public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
/**
@@ -582,7 +608,7 @@ public function onClickAir(Player $player, Vector3 $directionVector, array &$ret
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
*/
public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseResult{
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
/**
@@ -628,6 +654,20 @@ public function getCooldownTicks() : int{
return 0;
}
+ /**
+ * Returns a tag that identifies a group of items that should have cooldown at the same time
+ * regardless of their state or type.
+ * When cooldown starts, any other items with the same cooldown tag can't be used until the cooldown expires.
+ * Such behaviour can be seen in goat horns and shields.
+ *
+ * If tag is null, item state id will be used to store cooldown.
+ *
+ * @see ItemCooldownTags
+ */
+ public function getCooldownTag() : ?string{
+ return null;
+ }
+
/**
* Compares an Item to this Item and check if they match.
*
diff --git a/src/item/ItemBlock.php b/src/item/ItemBlock.php
index fbbe2efeb45..015c7847140 100644
--- a/src/item/ItemBlock.php
+++ b/src/item/ItemBlock.php
@@ -24,6 +24,7 @@
namespace pocketmine\item;
use pocketmine\block\Block;
+use pocketmine\block\BlockTypeIds;
use pocketmine\data\runtime\RuntimeDataDescriber;
/**
@@ -36,7 +37,7 @@ final class ItemBlock extends Item{
public function __construct(
private Block $block
){
- parent::__construct(ItemIdentifier::fromBlock($block), $block->getName());
+ parent::__construct(ItemIdentifier::fromBlock($block), $block->getName(), $block->getEnchantmentTags());
}
protected function describeState(RuntimeDataDescriber $w) : void{
@@ -58,4 +59,12 @@ public function isFireProof() : bool{
public function getMaxStackSize() : int{
return $this->block->getMaxStackSize();
}
+
+ public function isNull() : bool{
+ //TODO: we really shouldn't need to treat air as a special case here
+ //this is needed because the "null" empty slot item is represented by an air block, but there's no real reason
+ //why air should be needed at all. A separate special item type (or actual null) should be used instead, but
+ //this would cause a lot of BC breaks, so we can't do it yet.
+ return parent::isNull() || $this->block->getTypeId() === BlockTypeIds::AIR;
+ }
}
diff --git a/src/item/ItemCooldownTags.php b/src/item/ItemCooldownTags.php
new file mode 100644
index 00000000000..f0ef6d1699d
--- /dev/null
+++ b/src/item/ItemCooldownTags.php
@@ -0,0 +1,45 @@
+canBeReplaced()){
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
//TODO: move this to generic placement logic
@@ -70,10 +70,10 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
$this->pop();
$returnedItems[] = $ev->getItem();
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
public function getLiquid() : Liquid{
diff --git a/src/item/Medicine.php b/src/item/Medicine.php
index a15ac03535b..bd2f724649d 100644
--- a/src/item/Medicine.php
+++ b/src/item/Medicine.php
@@ -29,15 +29,10 @@
class Medicine extends Item implements ConsumableItem{
- private MedicineType $medicineType;
-
- public function __construct(ItemIdentifier $identifier, string $name){
- $this->medicineType = MedicineType::EYE_DROPS();
- parent::__construct($identifier, $name);
- }
+ private MedicineType $medicineType = MedicineType::EYE_DROPS;
protected function describeState(RuntimeDataDescriber $w) : void{
- $w->medicineType($this->medicineType);
+ $w->enum($this->medicineType);
}
public function getType() : MedicineType{ return $this->medicineType; }
diff --git a/src/item/MedicineType.php b/src/item/MedicineType.php
index f7ce2b816db..ea99bb75dcf 100644
--- a/src/item/MedicineType.php
+++ b/src/item/MedicineType.php
@@ -25,42 +25,39 @@
use pocketmine\entity\effect\Effect;
use pocketmine\entity\effect\VanillaEffects;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static MedicineType ANTIDOTE()
* @method static MedicineType ELIXIR()
* @method static MedicineType EYE_DROPS()
* @method static MedicineType TONIC()
*/
-final class MedicineType{
- use EnumTrait {
- __construct as Enum___construct;
+enum MedicineType{
+ use LegacyEnumShimTrait;
+
+ case ANTIDOTE;
+ case ELIXIR;
+ case EYE_DROPS;
+ case TONIC;
+
+ /**
+ * @phpstan-return array{0: string, 1: Effect}
+ */
+ private function getMetadata() : array{
+ //cache not required here - VanillaEffects always returns the same object
+ return match($this){
+ self::ANTIDOTE => ['Antidote', VanillaEffects::POISON()],
+ self::ELIXIR => ['Elixir', VanillaEffects::WEAKNESS()],
+ self::EYE_DROPS => ['Eye Drops', VanillaEffects::BLINDNESS()],
+ self::TONIC => ['Tonic', VanillaEffects::NAUSEA()]
+ };
}
- protected static function setup() : void{
- self::registerAll(
- new self('antidote', 'Antidote', VanillaEffects::POISON()),
- new self('elixir', 'Elixir', VanillaEffects::WEAKNESS()),
- new self('eye_drops', 'Eye Drops', VanillaEffects::BLINDNESS()),
- new self('tonic', 'Tonic', VanillaEffects::NAUSEA())
- );
- }
-
- private function __construct(
- string $enumName,
- private string $displayName,
- private Effect $curedEffect
- ){
- $this->Enum___construct($enumName);
- }
-
- public function getDisplayName() : string{ return $this->displayName; }
+ public function getDisplayName() : string{ return $this->getMetadata()[0]; }
- public function getCuredEffect() : Effect{ return $this->curedEffect; }
+ public function getCuredEffect() : Effect{ return $this->getMetadata()[1]; }
}
diff --git a/src/item/NameTag.php b/src/item/NameTag.php
new file mode 100644
index 00000000000..8c7113e1dee
--- /dev/null
+++ b/src/item/NameTag.php
@@ -0,0 +1,40 @@
+canBeRenamed() && $this->hasCustomName()){
+ $entity->setNameTag($this->getCustomName());
+ $this->pop();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/item/PaintingItem.php b/src/item/PaintingItem.php
index f3821d0b26a..a83c8dba8fe 100644
--- a/src/item/PaintingItem.php
+++ b/src/item/PaintingItem.php
@@ -39,7 +39,7 @@ class PaintingItem extends Item{
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
if(Facing::axis($face) === Axis::Y){
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
$motives = [];
@@ -67,7 +67,7 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
}
if(count($motives) === 0){ //No space available
- return ItemUseResult::NONE();
+ return ItemUseResult::NONE;
}
/** @var PaintingMotive $motive */
@@ -81,6 +81,6 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
$entity->spawnToAll();
$player->getWorld()->addSound($replacePos->add(0.5, 0.5, 0.5), new PaintingPlaceSound());
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
}
diff --git a/src/item/PitcherPod.php b/src/item/PitcherPod.php
new file mode 100644
index 00000000000..be9393515dc
--- /dev/null
+++ b/src/item/PitcherPod.php
@@ -0,0 +1,34 @@
+potionType = PotionType::WATER();
- parent::__construct($identifier, $name);
- }
+ private PotionType $potionType = PotionType::WATER;
protected function describeState(RuntimeDataDescriber $w) : void{
- $w->potionType($this->potionType);
+ $w->enum($this->potionType);
}
public function getType() : PotionType{ return $this->potionType; }
@@ -55,7 +51,7 @@ public function getMaxStackSize() : int{
}
public function onConsume(Living $consumer) : void{
-
+ $consumer->broadcastSound(new BottleEmptySound());
}
public function getAdditionalEffects() : array{
diff --git a/src/item/PotionType.php b/src/item/PotionType.php
index 7ec0f387615..3f4773e6de5 100644
--- a/src/item/PotionType.php
+++ b/src/item/PotionType.php
@@ -25,13 +25,12 @@
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
+use function spl_object_id;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static PotionType AWKWARD()
* @method static PotionType FIRE_RESISTANCE()
@@ -65,6 +64,7 @@
* @method static PotionType STRONG_LEAPING()
* @method static PotionType STRONG_POISON()
* @method static PotionType STRONG_REGENERATION()
+ * @method static PotionType STRONG_SLOWNESS()
* @method static PotionType STRONG_STRENGTH()
* @method static PotionType STRONG_SWIFTNESS()
* @method static PotionType STRONG_TURTLE_MASTER()
@@ -75,154 +75,196 @@
* @method static PotionType WATER_BREATHING()
* @method static PotionType WEAKNESS()
* @method static PotionType WITHER()
+ *
+ * @phpstan-type TMetadata array{0: string, 1: \Closure() : list}
*/
-final class PotionType{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum PotionType{
+ use LegacyEnumShimTrait;
+
+ case WATER;
+ case MUNDANE;
+ case LONG_MUNDANE;
+ case THICK;
+ case AWKWARD;
+ case NIGHT_VISION;
+ case LONG_NIGHT_VISION;
+ case INVISIBILITY;
+ case LONG_INVISIBILITY;
+ case LEAPING;
+ case LONG_LEAPING;
+ case STRONG_LEAPING;
+ case FIRE_RESISTANCE;
+ case LONG_FIRE_RESISTANCE;
+ case SWIFTNESS;
+ case LONG_SWIFTNESS;
+ case STRONG_SWIFTNESS;
+ case SLOWNESS;
+ case LONG_SLOWNESS;
+ case WATER_BREATHING;
+ case LONG_WATER_BREATHING;
+ case HEALING;
+ case STRONG_HEALING;
+ case HARMING;
+ case STRONG_HARMING;
+ case POISON;
+ case LONG_POISON;
+ case STRONG_POISON;
+ case REGENERATION;
+ case LONG_REGENERATION;
+ case STRONG_REGENERATION;
+ case STRENGTH;
+ case LONG_STRENGTH;
+ case STRONG_STRENGTH;
+ case WEAKNESS;
+ case LONG_WEAKNESS;
+ case WITHER;
+ case TURTLE_MASTER;
+ case LONG_TURTLE_MASTER;
+ case STRONG_TURTLE_MASTER;
+ case SLOW_FALLING;
+ case LONG_SLOW_FALLING;
+ case STRONG_SLOWNESS;
+
+ /**
+ * @phpstan-return TMetadata
+ */
+ private function getMetadata() : array{
+ /** @phpstan-var array $cache */
+ static $cache = [];
- protected static function setup() : void{
- self::registerAll(
- new self("water", "Water", fn() => []),
- new self("mundane", "Mundane", fn() => []),
- new self("long_mundane", "Long Mundane", fn() => []),
- new self("thick", "Thick", fn() => []),
- new self("awkward", "Awkward", fn() => []),
- new self("night_vision", "Night Vision", fn() => [
+ return $cache[spl_object_id($this)] ??= match($this){
+ self::WATER => ["Water", fn() => []],
+ self::MUNDANE => ["Mundane", fn() => []],
+ self::LONG_MUNDANE => ["Long Mundane", fn() => []],
+ self::THICK => ["Thick", fn() => []],
+ self::AWKWARD => ["Awkward", fn() => []],
+ self::NIGHT_VISION => ["Night Vision", fn() => [
new EffectInstance(VanillaEffects::NIGHT_VISION(), 3600)
- ]),
- new self("long_night_vision", "Long Night Vision", fn() => [
+ ]],
+ self::LONG_NIGHT_VISION => ["Long Night Vision", fn() => [
new EffectInstance(VanillaEffects::NIGHT_VISION(), 9600)
- ]),
- new self("invisibility", "Invisibility", fn() => [
+ ]],
+ self::INVISIBILITY => ["Invisibility", fn() => [
new EffectInstance(VanillaEffects::INVISIBILITY(), 3600)
- ]),
- new self("long_invisibility", "Long Invisibility", fn() => [
+ ]],
+ self::LONG_INVISIBILITY => ["Long Invisibility", fn() => [
new EffectInstance(VanillaEffects::INVISIBILITY(), 9600)
- ]),
- new self("leaping", "Leaping", fn() => [
+ ]],
+ self::LEAPING => ["Leaping", fn() => [
new EffectInstance(VanillaEffects::JUMP_BOOST(), 3600)
- ]),
- new self("long_leaping", "Long Leaping", fn() => [
+ ]],
+ self::LONG_LEAPING => ["Long Leaping", fn() => [
new EffectInstance(VanillaEffects::JUMP_BOOST(), 9600)
- ]),
- new self("strong_leaping", "Strong Leaping", fn() => [
+ ]],
+ self::STRONG_LEAPING => ["Strong Leaping", fn() => [
new EffectInstance(VanillaEffects::JUMP_BOOST(), 1800, 1)
- ]),
- new self("fire_resistance", "Fire Resistance", fn() => [
+ ]],
+ self::FIRE_RESISTANCE => ["Fire Resistance", fn() => [
new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 3600)
- ]),
- new self("long_fire_resistance", "Long Fire Resistance", fn() => [
+ ]],
+ self::LONG_FIRE_RESISTANCE => ["Long Fire Resistance", fn() => [
new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 9600)
- ]),
- new self("swiftness", "Swiftness", fn() => [
+ ]],
+ self::SWIFTNESS => ["Swiftness", fn() => [
new EffectInstance(VanillaEffects::SPEED(), 3600)
- ]),
- new self("long_swiftness", "Long Swiftness", fn() => [
+ ]],
+ self::LONG_SWIFTNESS => ["Long Swiftness", fn() => [
new EffectInstance(VanillaEffects::SPEED(), 9600)
- ]),
- new self("strong_swiftness", "Strong Swiftness", fn() => [
+ ]],
+ self::STRONG_SWIFTNESS => ["Strong Swiftness", fn() => [
new EffectInstance(VanillaEffects::SPEED(), 1800, 1)
- ]),
- new self("slowness", "Slowness", fn() => [
+ ]],
+ self::SLOWNESS => ["Slowness", fn() => [
new EffectInstance(VanillaEffects::SLOWNESS(), 1800)
- ]),
- new self("long_slowness", "Long Slowness", fn() => [
+ ]],
+ self::LONG_SLOWNESS => ["Long Slowness", fn() => [
new EffectInstance(VanillaEffects::SLOWNESS(), 4800)
- ]),
- new self("water_breathing", "Water Breathing", fn() => [
+ ]],
+ self::WATER_BREATHING => ["Water Breathing", fn() => [
new EffectInstance(VanillaEffects::WATER_BREATHING(), 3600)
- ]),
- new self("long_water_breathing", "Long Water Breathing", fn() => [
+ ]],
+ self::LONG_WATER_BREATHING => ["Long Water Breathing", fn() => [
new EffectInstance(VanillaEffects::WATER_BREATHING(), 9600)
- ]),
- new self("healing", "Healing", fn() => [
+ ]],
+ self::HEALING => ["Healing", fn() => [
new EffectInstance(VanillaEffects::INSTANT_HEALTH())
- ]),
- new self("strong_healing", "Strong Healing", fn() => [
+ ]],
+ self::STRONG_HEALING => ["Strong Healing", fn() => [
new EffectInstance(VanillaEffects::INSTANT_HEALTH(), null, 1)
- ]),
- new self("harming", "Harming", fn() => [
+ ]],
+ self::HARMING => ["Harming", fn() => [
new EffectInstance(VanillaEffects::INSTANT_DAMAGE())
- ]),
- new self("strong_harming", "Strong Harming", fn() => [
+ ]],
+ self::STRONG_HARMING => ["Strong Harming", fn() => [
new EffectInstance(VanillaEffects::INSTANT_DAMAGE(), null, 1)
- ]),
- new self("poison", "Poison", fn() => [
+ ]],
+ self::POISON => ["Poison", fn() => [
new EffectInstance(VanillaEffects::POISON(), 900)
- ]),
- new self("long_poison", "Long Poison", fn() => [
+ ]],
+ self::LONG_POISON => ["Long Poison", fn() => [
new EffectInstance(VanillaEffects::POISON(), 2400)
- ]),
- new self("strong_poison", "Strong Poison", fn() => [
+ ]],
+ self::STRONG_POISON => ["Strong Poison", fn() => [
new EffectInstance(VanillaEffects::POISON(), 440, 1)
- ]),
- new self("regeneration", "Regeneration", fn() => [
+ ]],
+ self::REGENERATION => ["Regeneration", fn() => [
new EffectInstance(VanillaEffects::REGENERATION(), 900)
- ]),
- new self("long_regeneration", "Long Regeneration", fn() => [
+ ]],
+ self::LONG_REGENERATION => ["Long Regeneration", fn() => [
new EffectInstance(VanillaEffects::REGENERATION(), 2400)
- ]),
- new self("strong_regeneration", "Strong Regeneration", fn() => [
+ ]],
+ self::STRONG_REGENERATION => ["Strong Regeneration", fn() => [
new EffectInstance(VanillaEffects::REGENERATION(), 440, 1)
- ]),
- new self("strength", "Strength", fn() => [
+ ]],
+ self::STRENGTH => ["Strength", fn() => [
new EffectInstance(VanillaEffects::STRENGTH(), 3600)
- ]),
- new self("long_strength", "Long Strength", fn() => [
+ ]],
+ self::LONG_STRENGTH => ["Long Strength", fn() => [
new EffectInstance(VanillaEffects::STRENGTH(), 9600)
- ]),
- new self("strong_strength", "Strong Strength", fn() => [
+ ]],
+ self::STRONG_STRENGTH => ["Strong Strength", fn() => [
new EffectInstance(VanillaEffects::STRENGTH(), 1800, 1)
- ]),
- new self("weakness", "Weakness", fn() => [
+ ]],
+ self::WEAKNESS => ["Weakness", fn() => [
new EffectInstance(VanillaEffects::WEAKNESS(), 1800)
- ]),
- new self("long_weakness", "Long Weakness", fn() => [
+ ]],
+ self::LONG_WEAKNESS => ["Long Weakness", fn() => [
new EffectInstance(VanillaEffects::WEAKNESS(), 4800)
- ]),
- new self("wither", "Wither", fn() => [
+ ]],
+ self::WITHER => ["Wither", fn() => [
new EffectInstance(VanillaEffects::WITHER(), 800, 1)
- ]),
- new self("turtle_master", "Turtle Master", fn() => [
+ ]],
+ self::TURTLE_MASTER => ["Turtle Master", fn() => [
new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 3),
new EffectInstance(VanillaEffects::RESISTANCE(), 20 * 20, 2),
- ]),
- new self("long_turtle_master", "Long Turtle Master", fn() => [
+ ]],
+ self::LONG_TURTLE_MASTER => ["Long Turtle Master", fn() => [
new EffectInstance(VanillaEffects::SLOWNESS(), 40 * 20, 3),
new EffectInstance(VanillaEffects::RESISTANCE(), 40 * 20, 2),
- ]),
- new self("strong_turtle_master", "Strong Turtle Master", fn() => [
+ ]],
+ self::STRONG_TURTLE_MASTER => ["Strong Turtle Master", fn() => [
new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 5),
new EffectInstance(VanillaEffects::RESISTANCE(), 20 * 20, 3),
- ]),
- new self("slow_falling", "Slow Falling", fn() => [
+ ]],
+ self::SLOW_FALLING => ["Slow Falling", fn() => [
//TODO
- ]),
- new self("long_slow_falling", "Long Slow Falling", fn() => [
+ ]],
+ self::LONG_SLOW_FALLING => ["Long Slow Falling", fn() => [
//TODO
- ])
- );
- }
-
- /**
- * @phpstan-param \Closure() : list $effectsGetter
- */
- private function __construct(
- string $enumName,
- private string $displayName,
- private \Closure $effectsGetter
- ){
- $this->Enum___construct($enumName);
+ ]],
+ self::STRONG_SLOWNESS => ["Strong Slowness", fn() => [
+ new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 3)
+ ]]
+ };
}
- public function getDisplayName() : string{ return $this->displayName; }
+ public function getDisplayName() : string{ return $this->getMetadata()[0]; }
/**
* @return EffectInstance[]
* @phpstan-return list
*/
public function getEffects() : array{
- return ($this->effectsGetter)();
+ return ($this->getMetadata()[1])();
}
}
diff --git a/src/item/ProjectileItem.php b/src/item/ProjectileItem.php
index 8056af505a5..47c4583e046 100644
--- a/src/item/ProjectileItem.php
+++ b/src/item/ProjectileItem.php
@@ -46,7 +46,7 @@ public function onClickAir(Player $player, Vector3 $directionVector, array &$ret
$projectileEv->call();
if($projectileEv->isCancelled()){
$projectile->flagForDespawn();
- return ItemUseResult::FAIL();
+ return ItemUseResult::FAIL;
}
$projectile->spawnToAll();
@@ -55,6 +55,6 @@ public function onClickAir(Player $player, Vector3 $directionVector, array &$ret
$this->pop();
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
}
diff --git a/src/item/SpawnEgg.php b/src/item/SpawnEgg.php
index 1147a951ddb..51dcceebd2a 100644
--- a/src/item/SpawnEgg.php
+++ b/src/item/SpawnEgg.php
@@ -43,6 +43,6 @@ public function onInteractBlock(Player $player, Block $blockReplace, Block $bloc
$this->pop();
$entity->spawnToAll();
//TODO: what if the entity was marked for deletion?
- return ItemUseResult::SUCCESS();
+ return ItemUseResult::SUCCESS;
}
}
diff --git a/src/item/SplashPotion.php b/src/item/SplashPotion.php
index c54562f2b05..e1c9167ac34 100644
--- a/src/item/SplashPotion.php
+++ b/src/item/SplashPotion.php
@@ -31,15 +31,10 @@
class SplashPotion extends ProjectileItem{
- private PotionType $potionType;
-
- public function __construct(ItemIdentifier $identifier, string $name){
- $this->potionType = PotionType::WATER();
- parent::__construct($identifier, $name);
- }
+ private PotionType $potionType = PotionType::WATER;
protected function describeState(RuntimeDataDescriber $w) : void{
- $w->potionType($this->potionType);
+ $w->enum($this->potionType);
}
public function getType() : PotionType{ return $this->potionType; }
diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php
index 4bd761f591f..4dae231acab 100644
--- a/src/item/StringToItemParser.php
+++ b/src/item/StringToItemParser.php
@@ -23,6 +23,7 @@
namespace pocketmine\item;
+use pocketmine\block\AmethystCluster;
use pocketmine\block\Block;
use pocketmine\block\Light;
use pocketmine\block\utils\CopperOxidation;
@@ -37,6 +38,7 @@
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\StringToTParser;
use function array_keys;
+use function strtolower;
/**
* Handles parsing items from strings. This is used to interpret names from the /give command (and others).
@@ -58,8 +60,8 @@ private static function make() : self{
}
private static function registerDynamicBlocks(self $result) : void{
- foreach(DyeColor::getAll() as $color){
- $register = fn(string $name, \Closure $callback) => $result->registerBlock($color->name() . "_" . $name, $callback);
+ foreach(DyeColor::cases() as $color){
+ $register = fn(string $name, \Closure $callback) => $result->registerBlock(strtolower($color->name) . "_" . $name, $callback);
//wall and floor banner are the same item
$register("banner", fn() => Blocks::BANNER()->setColor($color));
$register("bed", fn() => Blocks::BED()->setColor($color));
@@ -77,8 +79,8 @@ private static function registerDynamicBlocks(self $result) : void{
$register("shulker_box", fn() => Blocks::DYED_SHULKER_BOX()->setColor($color));
}
- foreach(CoralType::getAll() as $coralType){
- $register = fn(string $name, \Closure $callback) => $result->registerBlock($coralType->name() . "_" . $name, $callback);
+ foreach(CoralType::cases() as $coralType){
+ $register = fn(string $name, \Closure $callback) => $result->registerBlock(strtolower($coralType->name) . "_" . $name, $callback);
$register("coral", fn() => Blocks::CORAL()->setCoralType($coralType));
$register("coral_block", fn() => Blocks::CORAL_BLOCK()->setCoralType($coralType));
//wall and floor coral fans are the same item
@@ -90,20 +92,25 @@ private static function registerDynamicBlocks(self $result) : void{
$result->registerBlock("light_block_$i", fn() => Blocks::LIGHT()->setLightLevel($i));
}
- foreach(CopperOxidation::getAll() as $oxidation){
- $oxPrefix = $oxidation->equals(CopperOxidation::NONE()) ? "" : $oxidation->name() . "_";
+ foreach(CopperOxidation::cases() as $oxidation){
+ $oxPrefix = $oxidation === CopperOxidation::NONE ? "" : strtolower($oxidation->name) . "_";
foreach(["" => false, "waxed_" => true] as $waxedPrefix => $waxed){
$register = fn(string $name, \Closure $callback) => $result->registerBlock($waxedPrefix . $oxPrefix . $name, $callback);
$register("copper_block", fn() => Blocks::COPPER()->setOxidation($oxidation)->setWaxed($waxed));
+ $register("chiseled_copper", fn() => Blocks::CHISELED_COPPER()->setOxidation($oxidation)->setWaxed($waxed));
+ $register("copper_grate", fn() => Blocks::COPPER_GRATE()->setOxidation($oxidation)->setWaxed($waxed));
$register("cut_copper_block", fn() => Blocks::CUT_COPPER()->setOxidation($oxidation)->setWaxed($waxed));
$register("cut_copper_stairs", fn() => Blocks::CUT_COPPER_STAIRS()->setOxidation($oxidation)->setWaxed($waxed));
$register("cut_copper_slab", fn() => Blocks::CUT_COPPER_SLAB()->setOxidation($oxidation)->setWaxed($waxed));
+ $register("copper_bulb", fn() => Blocks::COPPER_BULB()->setOxidation($oxidation)->setWaxed($waxed));
+ $register("copper_door", fn() => Blocks::COPPER_DOOR()->setOxidation($oxidation)->setWaxed($waxed));
+ $register("copper_trapdoor", fn() => Blocks::COPPER_TRAPDOOR()->setOxidation($oxidation)->setWaxed($waxed));
}
}
- foreach(FroglightType::getAll() as $froglightType){
- $result->registerBlock($froglightType->name() . "_froglight", fn() => Blocks::FROGLIGHT()->setFroglightType($froglightType));
+ foreach(FroglightType::cases() as $froglightType){
+ $result->registerBlock(strtolower($froglightType->name) . "_froglight", fn() => Blocks::FROGLIGHT()->setFroglightType($froglightType));
}
}
@@ -133,6 +140,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("all_sided_mushroom_stem", fn() => Blocks::ALL_SIDED_MUSHROOM_STEM());
$result->registerBlock("allium", fn() => Blocks::ALLIUM());
$result->registerBlock("amethyst_block", fn() => Blocks::AMETHYST());
+ $result->registerBlock("amethyst_cluster", fn() => Blocks::AMETHYST_CLUSTER());
$result->registerBlock("ancient_debris", fn() => Blocks::ANCIENT_DEBRIS());
$result->registerBlock("andesite", fn() => Blocks::ANDESITE());
$result->registerBlock("andesite_slab", fn() => Blocks::ANDESITE_SLAB());
@@ -155,6 +163,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("beetroot_block", fn() => Blocks::BEETROOTS());
$result->registerBlock("beetroots", fn() => Blocks::BEETROOTS());
$result->registerBlock("bell", fn() => Blocks::BELL());
+ $result->registerBlock("big_dripleaf", fn() => Blocks::BIG_DRIPLEAF_HEAD());
$result->registerBlock("birch_button", fn() => Blocks::BIRCH_BUTTON());
$result->registerBlock("birch_door", fn() => Blocks::BIRCH_DOOR());
$result->registerBlock("birch_door_block", fn() => Blocks::BIRCH_DOOR());
@@ -194,24 +203,41 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("bricks_block", fn() => Blocks::BRICKS());
$result->registerBlock("brown_mushroom", fn() => Blocks::BROWN_MUSHROOM());
$result->registerBlock("brown_mushroom_block", fn() => Blocks::BROWN_MUSHROOM_BLOCK());
+ $result->registerBlock("budding_amethyst", fn() => Blocks::BUDDING_AMETHYST());
$result->registerBlock("burning_furnace", fn() => Blocks::FURNACE());
$result->registerBlock("bush", fn() => Blocks::DEAD_BUSH());
$result->registerBlock("cactus", fn() => Blocks::CACTUS());
$result->registerBlock("cake", fn() => Blocks::CAKE());
$result->registerBlock("cake_block", fn() => Blocks::CAKE());
$result->registerBlock("calcite", fn() => Blocks::CALCITE());
+ $result->registerBlock("campfire", fn() => Blocks::CAMPFIRE());
$result->registerBlock("candle", fn() => Blocks::CANDLE());
$result->registerBlock("carpet", fn() => Blocks::CARPET());
$result->registerBlock("carrot_block", fn() => Blocks::CARROTS());
$result->registerBlock("carrots", fn() => Blocks::CARROTS());
+ $result->registerBlock("cartography_table", fn() => Blocks::CARTOGRAPHY_TABLE());
$result->registerBlock("carved_pumpkin", fn() => Blocks::CARVED_PUMPKIN());
$result->registerBlock("cauldron", fn() => Blocks::CAULDRON());
$result->registerBlock("cave_vines", fn() => Blocks::CAVE_VINES());
$result->registerBlock("chain", fn() => Blocks::CHAIN());
+ $result->registerBlock("cherry_button", fn() => Blocks::CHERRY_BUTTON());
+ $result->registerBlock("cherry_door", fn() => Blocks::CHERRY_DOOR());
+ $result->registerBlock("cherry_fence", fn() => Blocks::CHERRY_FENCE());
+ $result->registerBlock("cherry_fence_gate", fn() => Blocks::CHERRY_FENCE_GATE());
+ $result->registerBlock("cherry_leaves", fn() => Blocks::CHERRY_LEAVES());
+ $result->registerBlock("cherry_log", fn() => Blocks::CHERRY_LOG());
+ $result->registerBlock("cherry_planks", fn() => Blocks::CHERRY_PLANKS());
+ $result->registerBlock("cherry_pressure_plate", fn() => Blocks::CHERRY_PRESSURE_PLATE());
+ $result->registerBlock("cherry_sign", fn() => Blocks::CHERRY_SIGN());
+ $result->registerBlock("cherry_slab", fn() => Blocks::CHERRY_SLAB());
+ $result->registerBlock("cherry_stairs", fn() => Blocks::CHERRY_STAIRS());
+ $result->registerBlock("cherry_trapdoor", fn() => Blocks::CHERRY_TRAPDOOR());
+ $result->registerBlock("cherry_wood", fn() => Blocks::CHERRY_WOOD());
$result->registerBlock("chemical_heat", fn() => Blocks::CHEMICAL_HEAT());
$result->registerBlock("chemistry_table", fn() => Blocks::COMPOUND_CREATOR());
$result->registerBlock("chest", fn() => Blocks::CHEST());
$result->registerBlock("chipped_anvil", fn() => Blocks::ANVIL()->setDamage(1));
+ $result->registerBlock("chiseled_bookshelf", fn() => Blocks::CHISELED_BOOKSHELF());
$result->registerBlock("chiseled_deepslate", fn() => Blocks::CHISELED_DEEPSLATE());
$result->registerBlock("chiseled_nether_bricks", fn() => Blocks::CHISELED_NETHER_BRICKS());
$result->registerBlock("chiseled_polished_blackstone", fn() => Blocks::CHISELED_POLISHED_BLACKSTONE());
@@ -219,12 +245,14 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("chiseled_red_sandstone", fn() => Blocks::CHISELED_RED_SANDSTONE());
$result->registerBlock("chiseled_sandstone", fn() => Blocks::CHISELED_SANDSTONE());
$result->registerBlock("chiseled_stone_bricks", fn() => Blocks::CHISELED_STONE_BRICKS());
+ $result->registerBlock("chiseled_tuff", fn() => Blocks::CHISELED_TUFF());
+ $result->registerBlock("chiseled_tuff_bricks", fn() => Blocks::CHISELED_TUFF_BRICKS());
$result->registerBlock("chorus_flower", fn() => Blocks::CHORUS_FLOWER());
$result->registerBlock("chorus_plant", fn() => Blocks::CHORUS_PLANT());
$result->registerBlock("clay_block", fn() => Blocks::CLAY());
$result->registerBlock("coal_block", fn() => Blocks::COAL());
$result->registerBlock("coal_ore", fn() => Blocks::COAL_ORE());
- $result->registerBlock("coarse_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::COARSE()));
+ $result->registerBlock("coarse_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::COARSE));
$result->registerBlock("cobble", fn() => Blocks::COBBLESTONE());
$result->registerBlock("cobble_stairs", fn() => Blocks::COBBLESTONE_STAIRS());
$result->registerBlock("cobble_wall", fn() => Blocks::COBBLESTONE_WALL());
@@ -253,10 +281,10 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("coral", fn() => Blocks::CORAL());
$result->registerBlock("coral_block", fn() => Blocks::CORAL_BLOCK());
$result->registerBlock("coral_fan", fn() => Blocks::CORAL_FAN());
- $result->registerBlock("coral_fan_dead", fn() => Blocks::CORAL_FAN()->setCoralType(CoralType::TUBE())->setDead(true));
+ $result->registerBlock("coral_fan_dead", fn() => Blocks::CORAL_FAN()->setCoralType(CoralType::TUBE)->setDead(true));
$result->registerBlock("coral_fan_hang", fn() => Blocks::WALL_CORAL_FAN());
- $result->registerBlock("coral_fan_hang2", fn() => Blocks::WALL_CORAL_FAN()->setCoralType(CoralType::BUBBLE()));
- $result->registerBlock("coral_fan_hang3", fn() => Blocks::WALL_CORAL_FAN()->setCoralType(CoralType::HORN()));
+ $result->registerBlock("coral_fan_hang2", fn() => Blocks::WALL_CORAL_FAN()->setCoralType(CoralType::BUBBLE));
+ $result->registerBlock("coral_fan_hang3", fn() => Blocks::WALL_CORAL_FAN()->setCoralType(CoralType::HORN));
$result->registerBlock("cornflower", fn() => Blocks::CORNFLOWER());
$result->registerBlock("cracked_deepslate_bricks", fn() => Blocks::CRACKED_DEEPSLATE_BRICKS());
$result->registerBlock("cracked_deepslate_tiles", fn() => Blocks::CRACKED_DEEPSLATE_TILES());
@@ -264,7 +292,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("cracked_polished_blackstone_bricks", fn() => Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS());
$result->registerBlock("cracked_stone_bricks", fn() => Blocks::CRACKED_STONE_BRICKS());
$result->registerBlock("crafting_table", fn() => Blocks::CRAFTING_TABLE());
- $result->registerBlock("creeper_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::CREEPER()));
+ $result->registerBlock("creeper_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::CREEPER));
$result->registerBlock("crimson_button", fn() => Blocks::CRIMSON_BUTTON());
$result->registerBlock("crimson_door", fn() => Blocks::CRIMSON_DOOR());
$result->registerBlock("crimson_fence", fn() => Blocks::CRIMSON_FENCE());
@@ -272,6 +300,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("crimson_hyphae", fn() => Blocks::CRIMSON_HYPHAE()->setStripped(false));
$result->registerBlock("crimson_planks", fn() => Blocks::CRIMSON_PLANKS());
$result->registerBlock("crimson_pressure_plate", fn() => Blocks::CRIMSON_PRESSURE_PLATE());
+ $result->registerBlock("crimson_roots", fn() => Blocks::CRIMSON_ROOTS());
$result->registerBlock("crimson_sign", fn() => Blocks::CRIMSON_SIGN());
$result->registerBlock("crimson_slab", fn() => Blocks::CRIMSON_SLAB());
$result->registerBlock("crimson_stairs", fn() => Blocks::CRIMSON_STAIRS());
@@ -339,24 +368,24 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("diorite_slab", fn() => Blocks::DIORITE_SLAB());
$result->registerBlock("diorite_stairs", fn() => Blocks::DIORITE_STAIRS());
$result->registerBlock("diorite_wall", fn() => Blocks::DIORITE_WALL());
- $result->registerBlock("dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::NORMAL()));
- $result->registerBlock("dirt_with_roots", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED()));
+ $result->registerBlock("dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::NORMAL));
+ $result->registerBlock("dirt_with_roots", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED));
$result->registerBlock("door_block", fn() => Blocks::OAK_DOOR());
$result->registerBlock("double_plant", fn() => Blocks::SUNFLOWER());
- $result->registerBlock("double_red_sandstone_slab", fn() => Blocks::RED_SANDSTONE_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_slab", fn() => Blocks::STONE_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_slabs", fn() => Blocks::STONE_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_stone_slab", fn() => Blocks::STONE_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_stone_slab2", fn() => Blocks::RED_SANDSTONE_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_stone_slab3", fn() => Blocks::END_STONE_BRICK_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_stone_slab4", fn() => Blocks::MOSSY_STONE_BRICK_SLAB()->setSlabType(SlabType::DOUBLE()));
+ $result->registerBlock("double_red_sandstone_slab", fn() => Blocks::RED_SANDSTONE_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_slab", fn() => Blocks::STONE_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_slabs", fn() => Blocks::STONE_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_stone_slab", fn() => Blocks::STONE_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_stone_slab2", fn() => Blocks::RED_SANDSTONE_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_stone_slab3", fn() => Blocks::END_STONE_BRICK_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_stone_slab4", fn() => Blocks::MOSSY_STONE_BRICK_SLAB()->setSlabType(SlabType::DOUBLE));
$result->registerBlock("double_tallgrass", fn() => Blocks::DOUBLE_TALLGRASS());
- $result->registerBlock("double_wood_slab", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_wood_slabs", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_wooden_slab", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE()));
- $result->registerBlock("double_wooden_slabs", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE()));
+ $result->registerBlock("double_wood_slab", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_wood_slabs", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_wooden_slab", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE));
+ $result->registerBlock("double_wooden_slabs", fn() => Blocks::OAK_SLAB()->setSlabType(SlabType::DOUBLE));
$result->registerBlock("dragon_egg", fn() => Blocks::DRAGON_EGG());
- $result->registerBlock("dragon_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::DRAGON()));
+ $result->registerBlock("dragon_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::DRAGON));
$result->registerBlock("dried_kelp_block", fn() => Blocks::DRIED_KELP());
$result->registerBlock("dyed_shulker_box", fn() => Blocks::DYED_SHULKER_BOX());
$result->registerBlock("element_0", fn() => Blocks::ELEMENT_ZERO());
@@ -728,6 +757,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("lapis_lazuli_block", fn() => Blocks::LAPIS_LAZULI());
$result->registerBlock("lapis_lazuli_ore", fn() => Blocks::LAPIS_LAZULI_ORE());
$result->registerBlock("lapis_ore", fn() => Blocks::LAPIS_LAZULI_ORE());
+ $result->registerBlock("large_amethyst_bud", fn() => Blocks::AMETHYST_CLUSTER()->setStage(AmethystCluster::STAGE_LARGE_BUD));
$result->registerBlock("large_fern", fn() => Blocks::LARGE_FERN());
$result->registerBlock("lava", fn() => Blocks::LAVA());
$result->registerBlock("leave", fn() => Blocks::OAK_LEAVES());
@@ -770,6 +800,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("mangrove_trapdoor", fn() => Blocks::MANGROVE_TRAPDOOR());
$result->registerBlock("mangrove_wood", fn() => Blocks::MANGROVE_WOOD()->setStripped(false));
$result->registerBlock("material_reducer", fn() => Blocks::MATERIAL_REDUCER());
+ $result->registerBlock("medium_amethyst_bud", fn() => Blocks::AMETHYST_CLUSTER()->setStage(AmethystCluster::STAGE_MEDIUM_BUD));
$result->registerBlock("melon_block", fn() => Blocks::MELON());
$result->registerBlock("melon_stem", fn() => Blocks::MELON_STEM());
$result->registerBlock("mob_head", fn() => Blocks::MOB_HEAD());
@@ -842,10 +873,13 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("packed_ice", fn() => Blocks::PACKED_ICE());
$result->registerBlock("packed_mud", fn() => Blocks::PACKED_MUD());
$result->registerBlock("peony", fn() => Blocks::PEONY());
+ $result->registerBlock("pink_petals", fn() => Blocks::PINK_PETALS());
$result->registerBlock("pink_tulip", fn() => Blocks::PINK_TULIP());
+ $result->registerBlock("piglin_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PIGLIN));
+ $result->registerBlock("pitcher_plant", fn() => Blocks::PITCHER_PLANT());
$result->registerBlock("plank", fn() => Blocks::OAK_PLANKS());
$result->registerBlock("planks", fn() => Blocks::OAK_PLANKS());
- $result->registerBlock("player_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PLAYER()));
+ $result->registerBlock("player_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PLAYER));
$result->registerBlock("podzol", fn() => Blocks::PODZOL());
$result->registerBlock("polished_andesite", fn() => Blocks::POLISHED_ANDESITE());
$result->registerBlock("polished_andesite_slab", fn() => Blocks::POLISHED_ANDESITE_SLAB());
@@ -871,6 +905,10 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("polished_granite", fn() => Blocks::POLISHED_GRANITE());
$result->registerBlock("polished_granite_slab", fn() => Blocks::POLISHED_GRANITE_SLAB());
$result->registerBlock("polished_granite_stairs", fn() => Blocks::POLISHED_GRANITE_STAIRS());
+ $result->registerBlock("polished_tuff", fn() => Blocks::POLISHED_TUFF());
+ $result->registerBlock("polished_tuff_slab", fn() => Blocks::POLISHED_TUFF_SLAB());
+ $result->registerBlock("polished_tuff_stairs", fn() => Blocks::POLISHED_TUFF_STAIRS());
+ $result->registerBlock("polished_tuff_wall", fn() => Blocks::POLISHED_TUFF_WALL());
$result->registerBlock("poppy", fn() => Blocks::POPPY());
$result->registerBlock("portal", fn() => Blocks::NETHER_PORTAL());
$result->registerBlock("portal_block", fn() => Blocks::NETHER_PORTAL());
@@ -934,7 +972,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("repeater", fn() => Blocks::REDSTONE_REPEATER());
$result->registerBlock("repeater_block", fn() => Blocks::REDSTONE_REPEATER());
$result->registerBlock("reserved6", fn() => Blocks::RESERVED6());
- $result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED()));
+ $result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED));
$result->registerBlock("rose", fn() => Blocks::POPPY());
$result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH());
$result->registerBlock("sand", fn() => Blocks::SAND());
@@ -951,13 +989,16 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("shulker_box", fn() => Blocks::SHULKER_BOX());
$result->registerBlock("sign", fn() => Blocks::OAK_SIGN());
$result->registerBlock("sign_post", fn() => Blocks::OAK_SIGN());
- $result->registerBlock("skeleton_skull", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::SKELETON()));
- $result->registerBlock("skull", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::SKELETON()));
+ $result->registerBlock("skeleton_skull", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::SKELETON));
+ $result->registerBlock("skull", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::SKELETON));
$result->registerBlock("skull_block", fn() => Blocks::MOB_HEAD());
$result->registerBlock("slab", fn() => Blocks::SMOOTH_STONE_SLAB());
$result->registerBlock("slabs", fn() => Blocks::SMOOTH_STONE_SLAB());
$result->registerBlock("slime", fn() => Blocks::SLIME());
$result->registerBlock("slime_block", fn() => Blocks::SLIME());
+ $result->registerBlock("small_amethyst_bud", fn() => Blocks::AMETHYST_CLUSTER()->setStage(AmethystCluster::STAGE_SMALL_BUD));
+ $result->registerBlock("small_dripleaf", fn() => Blocks::SMALL_DRIPLEAF());
+ $result->registerBlock("smithing_table", fn() => Blocks::SMITHING_TABLE());
$result->registerBlock("smoker", fn() => Blocks::SMOKER());
$result->registerBlock("smooth_basalt", fn() => Blocks::SMOOTH_BASALT());
$result->registerBlock("smooth_quartz", fn() => Blocks::SMOOTH_QUARTZ());
@@ -974,6 +1015,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("snow", fn() => Blocks::SNOW());
$result->registerBlock("snow_block", fn() => Blocks::SNOW());
$result->registerBlock("snow_layer", fn() => Blocks::SNOW_LAYER());
+ $result->registerBlock("soul_campfire", fn() => Blocks::SOUL_CAMPFIRE());
$result->registerBlock("soul_lantern", fn() => Blocks::SOUL_LANTERN());
$result->registerBlock("soul_sand", fn() => Blocks::SOUL_SAND());
$result->registerBlock("soul_soil", fn() => Blocks::SOUL_SOIL());
@@ -1030,6 +1072,8 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("stripped_acacia_wood", fn() => Blocks::ACACIA_WOOD()->setStripped(true));
$result->registerBlock("stripped_birch_log", fn() => Blocks::BIRCH_LOG()->setStripped(true));
$result->registerBlock("stripped_birch_wood", fn() => Blocks::BIRCH_WOOD()->setStripped(true));
+ $result->registerBlock("stripped_cherry_log", fn() => Blocks::CHERRY_LOG()->setStripped(true));
+ $result->registerBlock("stripped_cherry_wood", fn() => Blocks::CHERRY_WOOD()->setStripped(true));
$result->registerBlock("stripped_crimson_hyphae", fn() => Blocks::CRIMSON_HYPHAE()->setStripped(true));
$result->registerBlock("stripped_crimson_stem", fn() => Blocks::CRIMSON_STEM()->setStripped(true));
$result->registerBlock("stripped_dark_oak_log", fn() => Blocks::DARK_OAK_LOG()->setStripped(true));
@@ -1056,6 +1100,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("tinted_glass", fn() => Blocks::TINTED_GLASS());
$result->registerBlock("tnt", fn() => Blocks::TNT());
$result->registerBlock("torch", fn() => Blocks::TORCH());
+ $result->registerBlock("torchflower", fn() => Blocks::TORCHFLOWER());
$result->registerBlock("trapdoor", fn() => Blocks::OAK_TRAPDOOR());
$result->registerBlock("trapped_chest", fn() => Blocks::TRAPPED_CHEST());
$result->registerBlock("trip_wire", fn() => Blocks::TRIPWIRE());
@@ -1064,7 +1109,15 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("trunk", fn() => Blocks::OAK_PLANKS());
$result->registerBlock("trunk2", fn() => Blocks::ACACIA_LOG()->setStripped(false));
$result->registerBlock("tuff", fn() => Blocks::TUFF());
+ $result->registerBlock("tuff_bricks", fn() => Blocks::TUFF_BRICKS());
+ $result->registerBlock("tuff_brick_slab", fn() => Blocks::TUFF_BRICK_SLAB());
+ $result->registerBlock("tuff_brick_stairs", fn() => Blocks::TUFF_BRICK_STAIRS());
+ $result->registerBlock("tuff_brick_wall", fn() => Blocks::TUFF_BRICK_WALL());
+ $result->registerBlock("tuff_slab", fn() => Blocks::TUFF_SLAB());
+ $result->registerBlock("tuff_stairs", fn() => Blocks::TUFF_STAIRS());
+ $result->registerBlock("tuff_wall", fn() => Blocks::TUFF_WALL());
$result->registerBlock("twisting_vines", fn() => Blocks::TWISTING_VINES());
+ $result->registerBlock("underwater_tnt", fn() => Blocks::TNT()->setWorksUnderwater(true));
$result->registerBlock("underwater_torch", fn() => Blocks::UNDERWATER_TORCH());
$result->registerBlock("undyed_shulker_box", fn() => Blocks::SHULKER_BOX());
$result->registerBlock("unlit_redstone_torch", fn() => Blocks::REDSTONE_TORCH());
@@ -1085,6 +1138,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("warped_hyphae", fn() => Blocks::WARPED_HYPHAE()->setStripped(false));
$result->registerBlock("warped_planks", fn() => Blocks::WARPED_PLANKS());
$result->registerBlock("warped_pressure_plate", fn() => Blocks::WARPED_PRESSURE_PLATE());
+ $result->registerBlock("warped_roots", fn() => Blocks::WARPED_ROOTS());
$result->registerBlock("warped_sign", fn() => Blocks::WARPED_SIGN());
$result->registerBlock("warped_slab", fn() => Blocks::WARPED_SLAB());
$result->registerBlock("warped_stairs", fn() => Blocks::WARPED_STAIRS());
@@ -1101,7 +1155,7 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("wheat_block", fn() => Blocks::WHEAT());
$result->registerBlock("white_tulip", fn() => Blocks::WHITE_TULIP());
$result->registerBlock("wither_rose", fn() => Blocks::WITHER_ROSE());
- $result->registerBlock("wither_skeleton_skull", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::WITHER_SKELETON()));
+ $result->registerBlock("wither_skeleton_skull", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::WITHER_SKELETON));
$result->registerBlock("wood", fn() => Blocks::OAK_LOG()->setStripped(false));
$result->registerBlock("wood2", fn() => Blocks::ACACIA_LOG()->setStripped(false));
$result->registerBlock("wood_door_block", fn() => Blocks::OAK_DOOR());
@@ -1121,33 +1175,45 @@ private static function registerBlocks(self $result) : void{
$result->registerBlock("wool", fn() => Blocks::WOOL());
$result->registerBlock("workbench", fn() => Blocks::CRAFTING_TABLE());
$result->registerBlock("yellow_flower", fn() => Blocks::DANDELION());
- $result->registerBlock("zombie_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::ZOMBIE()));
+ $result->registerBlock("zombie_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::ZOMBIE));
}
private static function registerDynamicItems(self $result) : void{
- foreach(DyeColor::getAll() as $color){
- $prefix = fn(string $name) => $color->name() . "_" . $name;
+ foreach(DyeColor::cases() as $color){
+ $prefix = fn(string $name) => strtolower($color->name) . "_" . $name;
$result->register($prefix("dye"), fn() => Items::DYE()->setColor($color));
}
- foreach(SuspiciousStewType::getAll() as $suspiciousStewType){
- $prefix = fn(string $name) => $suspiciousStewType->name() . "_" . $name;
+
+ foreach(GoatHornType::cases() as $goatHornType){
+ $prefix = fn(string $name) => strtolower($goatHornType->name) . "_" . $name;
+
+ $result->register($prefix("goat_horn"), fn() => Items::GOAT_HORN()->setHornType($goatHornType));
+ }
+
+ foreach(SuspiciousStewType::cases() as $suspiciousStewType){
+ $prefix = fn(string $name) => strtolower($suspiciousStewType->name) . "_" . $name;
$result->register($prefix("suspicious_stew"), fn() => Items::SUSPICIOUS_STEW()->setType($suspiciousStewType));
}
+
+ foreach(PotionType::cases() as $potionType){
+ $prefix = fn(string $name) => strtolower($potionType->name) . "_" . $name;
+
+ $result->register($prefix("potion"), fn() => Items::POTION()->setType($potionType));
+ $result->register($prefix("splash_potion"), fn() => Items::SPLASH_POTION()->setType($potionType));
+ }
}
private static function registerItems(self $result) : void{
$result->register("acacia_boat", fn() => Items::ACACIA_BOAT());
$result->register("amethyst_shard", fn() => Items::AMETHYST_SHARD());
- $result->register("antidote", fn() => Items::MEDICINE()->setType(MedicineType::ANTIDOTE()));
+ $result->register("antidote", fn() => Items::MEDICINE()->setType(MedicineType::ANTIDOTE));
$result->register("apple", fn() => Items::APPLE());
$result->register("apple_enchanted", fn() => Items::ENCHANTED_GOLDEN_APPLE());
$result->register("appleenchanted", fn() => Items::ENCHANTED_GOLDEN_APPLE());
$result->register("arrow", fn() => Items::ARROW());
- $result->register("awkward_potion", fn() => Items::POTION()->setType(PotionType::AWKWARD()));
- $result->register("awkward_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::AWKWARD()));
$result->register("baked_potato", fn() => Items::BAKED_POTATO());
$result->register("baked_potatoes", fn() => Items::BAKED_POTATO());
$result->register("beef", fn() => Items::RAW_BEEF());
@@ -1226,6 +1292,7 @@ private static function registerItems(self $result) : void{
$result->register("clown_fish", fn() => Items::CLOWNFISH());
$result->register("clownfish", fn() => Items::CLOWNFISH());
$result->register("coal", fn() => Items::COAL());
+ $result->register("coast_armor_trim_smithing_template", fn() => Items::COAST_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("cocoa_beans", fn() => Items::COCOA_BEANS());
$result->register("cod", fn() => Items::RAW_FISH());
$result->register("compass", fn() => Items::COMPASS());
@@ -1254,21 +1321,23 @@ private static function registerItems(self $result) : void{
$result->register("disc_fragment_5", fn() => Items::DISC_FRAGMENT_5());
$result->register("dragon_breath", fn() => Items::DRAGON_BREATH());
$result->register("dried_kelp", fn() => Items::DRIED_KELP());
+ $result->register("dune_armor_trim_smithing_template", fn() => Items::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("dye", fn() => Items::INK_SAC());
$result->register("echo_shard", fn() => Items::ECHO_SHARD());
$result->register("egg", fn() => Items::EGG());
- $result->register("elixir", fn() => Items::MEDICINE()->setType(MedicineType::ELIXIR()));
+ $result->register("elixir", fn() => Items::MEDICINE()->setType(MedicineType::ELIXIR));
$result->register("emerald", fn() => Items::EMERALD());
+ $result->register("enchanted_book", fn() => Items::ENCHANTED_BOOK());
$result->register("enchanted_golden_apple", fn() => Items::ENCHANTED_GOLDEN_APPLE());
$result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE());
+ $result->register("end_crystal", fn() => Items::END_CRYSTAL());
$result->register("ender_pearl", fn() => Items::ENDER_PEARL());
$result->register("experience_bottle", fn() => Items::EXPERIENCE_BOTTLE());
- $result->register("eye_drops", fn() => Items::MEDICINE()->setType(MedicineType::EYE_DROPS()));
+ $result->register("eye_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
+ $result->register("eye_drops", fn() => Items::MEDICINE()->setType(MedicineType::EYE_DROPS));
$result->register("feather", fn() => Items::FEATHER());
$result->register("fermented_spider_eye", fn() => Items::FERMENTED_SPIDER_EYE());
$result->register("fire_charge", fn() => Items::FIRE_CHARGE());
- $result->register("fire_resistance_potion", fn() => Items::POTION()->setType(PotionType::FIRE_RESISTANCE()));
- $result->register("fire_resistance_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::FIRE_RESISTANCE()));
$result->register("fish", fn() => Items::RAW_FISH());
$result->register("fishing_rod", fn() => Items::FISHING_ROD());
$result->register("flint", fn() => Items::FLINT());
@@ -1280,6 +1349,7 @@ private static function registerItems(self $result) : void{
$result->register("glow_berries", fn() => Items::GLOW_BERRIES());
$result->register("glow_ink_sac", fn() => Items::GLOW_INK_SAC());
$result->register("glowstone_dust", fn() => Items::GLOWSTONE_DUST());
+ $result->register("goat_horn", fn() => Items::GOAT_HORN());
$result->register("gold_axe", fn() => Items::GOLDEN_AXE());
$result->register("gold_boots", fn() => Items::GOLDEN_BOOTS());
$result->register("gold_chestplate", fn() => Items::GOLDEN_CHESTPLATE());
@@ -1304,16 +1374,11 @@ private static function registerItems(self $result) : void{
$result->register("golden_shovel", fn() => Items::GOLDEN_SHOVEL());
$result->register("golden_sword", fn() => Items::GOLDEN_SWORD());
$result->register("gunpowder", fn() => Items::GUNPOWDER());
- $result->register("harming_potion", fn() => Items::POTION()->setType(PotionType::HARMING()));
- $result->register("harming_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::HARMING()));
- $result->register("healing_potion", fn() => Items::POTION()->setType(PotionType::HEALING()));
- $result->register("healing_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::HEALING()));
$result->register("heart_of_the_sea", fn() => Items::HEART_OF_THE_SEA());
$result->register("honey_bottle", fn() => Items::HONEY_BOTTLE());
+ $result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("honeycomb", fn() => Items::HONEYCOMB());
$result->register("ink_sac", fn() => Items::INK_SAC());
- $result->register("invisibility_potion", fn() => Items::POTION()->setType(PotionType::INVISIBILITY()));
- $result->register("invisibility_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::INVISIBILITY()));
$result->register("iron_axe", fn() => Items::IRON_AXE());
$result->register("iron_boots", fn() => Items::IRON_BOOTS());
$result->register("iron_chestplate", fn() => Items::IRON_CHESTPLATE());
@@ -1328,8 +1393,6 @@ private static function registerItems(self $result) : void{
$result->register("jungle_boat", fn() => Items::JUNGLE_BOAT());
$result->register("lapis_lazuli", fn() => Items::LAPIS_LAZULI());
$result->register("lava_bucket", fn() => Items::LAVA_BUCKET());
- $result->register("leaping_potion", fn() => Items::POTION()->setType(PotionType::LEAPING()));
- $result->register("leaping_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LEAPING()));
$result->register("leather", fn() => Items::LEATHER());
$result->register("leather_boots", fn() => Items::LEATHER_BOOTS());
$result->register("leather_cap", fn() => Items::LEATHER_CAP());
@@ -1338,48 +1401,19 @@ private static function registerItems(self $result) : void{
$result->register("leather_leggings", fn() => Items::LEATHER_PANTS());
$result->register("leather_pants", fn() => Items::LEATHER_PANTS());
$result->register("leather_tunic", fn() => Items::LEATHER_TUNIC());
- $result->register("long_fire_resistance_potion", fn() => Items::POTION()->setType(PotionType::LONG_FIRE_RESISTANCE()));
- $result->register("long_fire_resistance_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_FIRE_RESISTANCE()));
- $result->register("long_invisibility_potion", fn() => Items::POTION()->setType(PotionType::LONG_INVISIBILITY()));
- $result->register("long_invisibility_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_INVISIBILITY()));
- $result->register("long_leaping_potion", fn() => Items::POTION()->setType(PotionType::LONG_LEAPING()));
- $result->register("long_leaping_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_LEAPING()));
- $result->register("long_mundane_potion", fn() => Items::POTION()->setType(PotionType::LONG_MUNDANE()));
- $result->register("long_mundane_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_MUNDANE()));
- $result->register("long_night_vision_potion", fn() => Items::POTION()->setType(PotionType::LONG_NIGHT_VISION()));
- $result->register("long_night_vision_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_NIGHT_VISION()));
- $result->register("long_poison_potion", fn() => Items::POTION()->setType(PotionType::LONG_POISON()));
- $result->register("long_poison_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_POISON()));
- $result->register("long_regeneration_potion", fn() => Items::POTION()->setType(PotionType::LONG_REGENERATION()));
- $result->register("long_regeneration_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_REGENERATION()));
- $result->register("long_slow_falling_potion", fn() => Items::POTION()->setType(PotionType::LONG_SLOW_FALLING()));
- $result->register("long_slow_falling_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_SLOW_FALLING()));
- $result->register("long_slowness_potion", fn() => Items::POTION()->setType(PotionType::LONG_SLOWNESS()));
- $result->register("long_slowness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_SLOWNESS()));
- $result->register("long_strength_potion", fn() => Items::POTION()->setType(PotionType::LONG_STRENGTH()));
- $result->register("long_strength_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_STRENGTH()));
- $result->register("long_swiftness_potion", fn() => Items::POTION()->setType(PotionType::LONG_SWIFTNESS()));
- $result->register("long_swiftness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_SWIFTNESS()));
- $result->register("long_turtle_master_potion", fn() => Items::POTION()->setType(PotionType::LONG_TURTLE_MASTER()));
- $result->register("long_turtle_master_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_TURTLE_MASTER()));
- $result->register("long_water_breathing_potion", fn() => Items::POTION()->setType(PotionType::LONG_WATER_BREATHING()));
- $result->register("long_water_breathing_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_WATER_BREATHING()));
- $result->register("long_weakness_potion", fn() => Items::POTION()->setType(PotionType::LONG_WEAKNESS()));
- $result->register("long_weakness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::LONG_WEAKNESS()));
$result->register("magma_cream", fn() => Items::MAGMA_CREAM());
$result->register("melon", fn() => Items::MELON());
$result->register("melon_seeds", fn() => Items::MELON_SEEDS());
$result->register("melon_slice", fn() => Items::MELON());
$result->register("milk_bucket", fn() => Items::MILK_BUCKET());
$result->register("minecart", fn() => Items::MINECART());
- $result->register("mundane_potion", fn() => Items::POTION()->setType(PotionType::MUNDANE()));
- $result->register("mundane_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::MUNDANE()));
$result->register("mushroom_stew", fn() => Items::MUSHROOM_STEW());
$result->register("mutton", fn() => Items::RAW_MUTTON());
$result->register("mutton_cooked", fn() => Items::COOKED_MUTTON());
$result->register("mutton_raw", fn() => Items::RAW_MUTTON());
$result->register("muttoncooked", fn() => Items::COOKED_MUTTON());
$result->register("muttonraw", fn() => Items::RAW_MUTTON());
+ $result->register("name_tag", fn() => Items::NAME_TAG());
$result->register("nautilus_shell", fn() => Items::NAUTILUS_SHELL());
$result->register("nether_brick", fn() => Items::NETHER_BRICK());
$result->register("nether_quartz", fn() => Items::NETHER_QUARTZ());
@@ -1397,14 +1431,12 @@ private static function registerItems(self $result) : void{
$result->register("netherite_shovel", fn() => Items::NETHERITE_SHOVEL());
$result->register("netherite_sword", fn() => Items::NETHERITE_SWORD());
$result->register("netherstar", fn() => Items::NETHER_STAR());
- $result->register("night_vision_potion", fn() => Items::POTION()->setType(PotionType::NIGHT_VISION()));
- $result->register("night_vision_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::NIGHT_VISION()));
+ $result->register("netherite_upgrade_smithing_template", fn() => Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE());
$result->register("oak_boat", fn() => Items::OAK_BOAT());
$result->register("painting", fn() => Items::PAINTING());
$result->register("paper", fn() => Items::PAPER());
$result->register("phantom_membrane", fn() => Items::PHANTOM_MEMBRANE());
- $result->register("poison_potion", fn() => Items::POTION()->setType(PotionType::POISON()));
- $result->register("poison_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::POISON()));
+ $result->register("pitcher_pod", fn() => Items::PITCHER_POD());
$result->register("poisonous_potato", fn() => Items::POISONOUS_POTATO());
$result->register("popped_chorus_fruit", fn() => Items::POPPED_CHORUS_FRUIT());
$result->register("porkchop", fn() => Items::RAW_PORKCHOP());
@@ -1421,6 +1453,7 @@ private static function registerItems(self $result) : void{
$result->register("rabbit_foot", fn() => Items::RABBIT_FOOT());
$result->register("rabbit_hide", fn() => Items::RABBIT_HIDE());
$result->register("rabbit_stew", fn() => Items::RABBIT_STEW());
+ $result->register("raiser_armor_trim_smithing_template", fn() => Items::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("raw_beef", fn() => Items::RAW_BEEF());
$result->register("raw_cod", fn() => Items::RAW_FISH());
$result->register("raw_copper", fn() => Items::RAW_COPPER());
@@ -1449,23 +1482,23 @@ private static function registerItems(self $result) : void{
$result->register("record_ward", fn() => Items::RECORD_WARD());
$result->register("redstone", fn() => Items::REDSTONE_DUST());
$result->register("redstone_dust", fn() => Items::REDSTONE_DUST());
- $result->register("regeneration_potion", fn() => Items::POTION()->setType(PotionType::REGENERATION()));
- $result->register("regeneration_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::REGENERATION()));
+ $result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("rotten_flesh", fn() => Items::ROTTEN_FLESH());
$result->register("salmon", fn() => Items::RAW_SALMON());
$result->register("scute", fn() => Items::SCUTE());
+ $result->register("sentry_armor_trim_smithing_template", fn() => Items::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE());
+ $result->register("shaper_armor_trim_smithing_template", fn() => Items::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("seeds", fn() => Items::WHEAT_SEEDS());
$result->register("shears", fn() => Items::SHEARS());
$result->register("shulker_shell", fn() => Items::SHULKER_SHELL());
+ $result->register("silence_armor_trim_smithing_template", fn() => Items::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("slime_ball", fn() => Items::SLIMEBALL());
+ $result->register("snout_armor_trim_smithing_template", fn() => Items::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("slimeball", fn() => Items::SLIMEBALL());
- $result->register("slow_falling_potion", fn() => Items::POTION()->setType(PotionType::SLOW_FALLING()));
- $result->register("slow_falling_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::SLOW_FALLING()));
- $result->register("slowness_potion", fn() => Items::POTION()->setType(PotionType::SLOWNESS()));
- $result->register("slowness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::SLOWNESS()));
$result->register("snowball", fn() => Items::SNOWBALL());
$result->register("speckled_melon", fn() => Items::GLISTERING_MELON());
$result->register("spider_eye", fn() => Items::SPIDER_EYE());
+ $result->register("spire_armor_trim_smithing_template", fn() => Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("splash_potion", fn() => Items::SPLASH_POTION());
$result->register("spruce_boat", fn() => Items::SPRUCE_BOAT());
$result->register("spyglass", fn() => Items::SPYGLASS());
@@ -1478,50 +1511,24 @@ private static function registerItems(self $result) : void{
$result->register("stone_pickaxe", fn() => Items::STONE_PICKAXE());
$result->register("stone_shovel", fn() => Items::STONE_SHOVEL());
$result->register("stone_sword", fn() => Items::STONE_SWORD());
- $result->register("strength_potion", fn() => Items::POTION()->setType(PotionType::STRENGTH()));
- $result->register("strength_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRENGTH()));
$result->register("string", fn() => Items::STRING());
- $result->register("strong_harming_potion", fn() => Items::POTION()->setType(PotionType::STRONG_HARMING()));
- $result->register("strong_harming_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_HARMING()));
- $result->register("strong_healing_potion", fn() => Items::POTION()->setType(PotionType::STRONG_HEALING()));
- $result->register("strong_healing_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_HEALING()));
- $result->register("strong_leaping_potion", fn() => Items::POTION()->setType(PotionType::STRONG_LEAPING()));
- $result->register("strong_leaping_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_LEAPING()));
- $result->register("strong_poison_potion", fn() => Items::POTION()->setType(PotionType::STRONG_POISON()));
- $result->register("strong_poison_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_POISON()));
- $result->register("strong_regeneration_potion", fn() => Items::POTION()->setType(PotionType::STRONG_REGENERATION()));
- $result->register("strong_regeneration_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_REGENERATION()));
- $result->register("strong_strength_potion", fn() => Items::POTION()->setType(PotionType::STRONG_STRENGTH()));
- $result->register("strong_strength_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_STRENGTH()));
- $result->register("strong_swiftness_potion", fn() => Items::POTION()->setType(PotionType::STRONG_SWIFTNESS()));
- $result->register("strong_swiftness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_SWIFTNESS()));
- $result->register("strong_turtle_master_potion", fn() => Items::POTION()->setType(PotionType::STRONG_TURTLE_MASTER()));
- $result->register("strong_turtle_master_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::STRONG_TURTLE_MASTER()));
$result->register("sugar", fn() => Items::SUGAR());
$result->register("suspicious_stew", fn() => Items::SUSPICIOUS_STEW());
$result->register("sweet_berries", fn() => Items::SWEET_BERRIES());
- $result->register("swiftness_potion", fn() => Items::POTION()->setType(PotionType::SWIFTNESS()));
- $result->register("swiftness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::SWIFTNESS()));
- $result->register("thick_potion", fn() => Items::POTION()->setType(PotionType::THICK()));
- $result->register("thick_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::THICK()));
- $result->register("tonic", fn() => Items::MEDICINE()->setType(MedicineType::TONIC()));
+ $result->register("tonic", fn() => Items::MEDICINE()->setType(MedicineType::TONIC));
+ $result->register("torchflower_seeds", fn() => Items::TORCHFLOWER_SEEDS());
+ $result->register("tide_armor_trim_smithing_template", fn() => Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("totem", fn() => Items::TOTEM());
$result->register("turtle_helmet", fn() => Items::TURTLE_HELMET());
- $result->register("turtle_master_potion", fn() => Items::POTION()->setType(PotionType::TURTLE_MASTER()));
- $result->register("turtle_master_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::TURTLE_MASTER()));
+ $result->register("vex_armor_trim_smithing_template", fn() => Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("turtle_shell_piece", fn() => Items::SCUTE());
$result->register("villager_spawn_egg", fn() => Items::VILLAGER_SPAWN_EGG());
- $result->register("water_breathing_potion", fn() => Items::POTION()->setType(PotionType::WATER_BREATHING()));
- $result->register("water_breathing_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::WATER_BREATHING()));
+ $result->register("ward_armor_trim_smithing_template", fn() => Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("water_bucket", fn() => Items::WATER_BUCKET());
- $result->register("water_potion", fn() => Items::POTION()->setType(PotionType::WATER()));
- $result->register("water_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::WATER()));
- $result->register("weakness_potion", fn() => Items::POTION()->setType(PotionType::WEAKNESS()));
- $result->register("weakness_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::WEAKNESS()));
+ $result->register("wayfinder_armor_trim_smithing_template", fn() => Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("wheat", fn() => Items::WHEAT());
$result->register("wheat_seeds", fn() => Items::WHEAT_SEEDS());
- $result->register("wither_potion", fn() => Items::POTION()->setType(PotionType::WITHER()));
- $result->register("wither_splash_potion", fn() => Items::SPLASH_POTION()->setType(PotionType::WITHER()));
+ $result->register("wild_armor_trim_smithing_template", fn() => Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("wooden_axe", fn() => Items::WOODEN_AXE());
$result->register("wooden_hoe", fn() => Items::WOODEN_HOE());
$result->register("wooden_pickaxe", fn() => Items::WOODEN_PICKAXE());
diff --git a/src/item/SuspiciousStew.php b/src/item/SuspiciousStew.php
index a2adc0b6176..7d1d30cc091 100644
--- a/src/item/SuspiciousStew.php
+++ b/src/item/SuspiciousStew.php
@@ -27,15 +27,10 @@
class SuspiciousStew extends Food{
- private SuspiciousStewType $suspiciousStewType;
-
- public function __construct(ItemIdentifier $identifier, string $name){
- $this->suspiciousStewType = SuspiciousStewType::POPPY();
- parent::__construct($identifier, $name);
- }
+ private SuspiciousStewType $suspiciousStewType = SuspiciousStewType::POPPY;
protected function describeState(RuntimeDataDescriber $w) : void{
- $w->suspiciousStewType($this->suspiciousStewType);
+ $w->enum($this->suspiciousStewType);
}
public function getType() : SuspiciousStewType{ return $this->suspiciousStewType; }
diff --git a/src/item/SuspiciousStewType.php b/src/item/SuspiciousStewType.php
index 27209b5731e..d53b82a3fab 100644
--- a/src/item/SuspiciousStewType.php
+++ b/src/item/SuspiciousStewType.php
@@ -25,13 +25,11 @@
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static SuspiciousStewType ALLIUM()
* @method static SuspiciousStewType AZURE_BLUET()
@@ -44,61 +42,36 @@
* @method static SuspiciousStewType TULIP()
* @method static SuspiciousStewType WITHER_ROSE()
*/
-final class SuspiciousStewType{
- use EnumTrait {
- __construct as Enum___construct;
- }
-
- protected static function setup() : void{
- self::registerAll(
- new self("poppy", fn() => [
- new EffectInstance(VanillaEffects::NIGHT_VISION(), 80)
- ]),
- new self("cornflower", fn() => [
- new EffectInstance(VanillaEffects::JUMP_BOOST(), 80)
- ]),
- new self("tulip", fn() => [
- new EffectInstance(VanillaEffects::WEAKNESS(), 140)
- ]),
- new self("azure_bluet", fn() => [
- new EffectInstance(VanillaEffects::BLINDNESS(), 120)
- ]),
- new self("lily_of_the_valley", fn() => [
- new EffectInstance(VanillaEffects::POISON(), 200)
- ]),
- new self("dandelion", fn() => [
- new EffectInstance(VanillaEffects::SATURATION(), 6)
- ]),
- new self("blue_orchid", fn() => [
- new EffectInstance(VanillaEffects::SATURATION(), 6)
- ]),
- new self("allium", fn() => [
- new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 40)
- ]),
- new self("oxeye_daisy", fn() => [
- new EffectInstance(VanillaEffects::REGENERATION(), 120)
- ]),
- new self("wither_rose", fn() => [
- new EffectInstance(VanillaEffects::WITHER(), 120)
- ])
- );
- }
+enum SuspiciousStewType{
+ use LegacyEnumShimTrait;
- /**
- * @phpstan-param \Closure() : list $effectsGetter
- */
- private function __construct(
- string $enumName,
- private \Closure $effectsGetter
- ){
- $this->Enum___construct($enumName);
- }
+ case POPPY;
+ case CORNFLOWER;
+ case TULIP;
+ case AZURE_BLUET;
+ case LILY_OF_THE_VALLEY;
+ case DANDELION;
+ case BLUE_ORCHID;
+ case ALLIUM;
+ case OXEYE_DAISY;
+ case WITHER_ROSE;
/**
* @return EffectInstance[]
* @phpstan-return list
*/
public function getEffects() : array{
- return ($this->effectsGetter)();
+ return match($this){
+ self::POPPY => [new EffectInstance(VanillaEffects::NIGHT_VISION(), 80)],
+ self::CORNFLOWER => [new EffectInstance(VanillaEffects::JUMP_BOOST(), 80)],
+ self::TULIP => [new EffectInstance(VanillaEffects::WEAKNESS(), 140)],
+ self::AZURE_BLUET => [new EffectInstance(VanillaEffects::BLINDNESS(), 120)],
+ self::LILY_OF_THE_VALLEY => [new EffectInstance(VanillaEffects::POISON(), 200)],
+ self::DANDELION,
+ self::BLUE_ORCHID => [new EffectInstance(VanillaEffects::SATURATION(), 6)],
+ self::ALLIUM => [new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 40)],
+ self::OXEYE_DAISY => [new EffectInstance(VanillaEffects::REGENERATION(), 120)],
+ self::WITHER_ROSE => [new EffectInstance(VanillaEffects::WITHER(), 120)]
+ };
}
}
diff --git a/src/item/TieredTool.php b/src/item/TieredTool.php
index e7d4f220153..20b40bbcb40 100644
--- a/src/item/TieredTool.php
+++ b/src/item/TieredTool.php
@@ -26,8 +26,11 @@
abstract class TieredTool extends Tool{
protected ToolTier $tier;
- public function __construct(ItemIdentifier $identifier, string $name, ToolTier $tier){
- parent::__construct($identifier, $name);
+ /**
+ * @param string[] $enchantmentTags
+ */
+ public function __construct(ItemIdentifier $identifier, string $name, ToolTier $tier, array $enchantmentTags = []){
+ parent::__construct($identifier, $name, $enchantmentTags);
$this->tier = $tier;
}
@@ -43,8 +46,12 @@ protected function getBaseMiningEfficiency() : float{
return $this->tier->getBaseEfficiency();
}
+ public function getEnchantability() : int{
+ return $this->tier->getEnchantability();
+ }
+
public function getFuelTime() : int{
- if($this->tier->equals(ToolTier::WOOD())){
+ if($this->tier === ToolTier::WOOD){
return 200;
}
@@ -52,6 +59,6 @@ public function getFuelTime() : int{
}
public function isFireProof() : bool{
- return $this->tier->equals(ToolTier::NETHERITE());
+ return $this->tier === ToolTier::NETHERITE;
}
}
diff --git a/src/item/ToolTier.php b/src/item/ToolTier.php
index 231e233c334..8469bc7e5f4 100644
--- a/src/item/ToolTier.php
+++ b/src/item/ToolTier.php
@@ -23,13 +23,11 @@
namespace pocketmine\item;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static ToolTier DIAMOND()
* @method static ToolTier GOLD()
@@ -37,46 +35,64 @@
* @method static ToolTier NETHERITE()
* @method static ToolTier STONE()
* @method static ToolTier WOOD()
+ *
+ * @phpstan-type TMetadata array{0: int, 1: int, 2: int, 3: int, 4: int}
*/
-final class ToolTier{
- use EnumTrait {
- __construct as Enum___construct;
- }
+enum ToolTier{
+ use LegacyEnumShimTrait;
+
+ case WOOD;
+ case GOLD;
+ case STONE;
+ case IRON;
+ case DIAMOND;
+ case NETHERITE;
- protected static function setup() : void{
- self::registerAll(
- new self("wood", 1, 60, 5, 2),
- new self("gold", 2, 33, 5, 12),
- new self("stone", 3, 132, 6, 4),
- new self("iron", 4, 251, 7, 6),
- new self("diamond", 5, 1562, 8, 8),
- new self("netherite", 6, 2032, 9, 9)
- );
+ /**
+ * This function exists only to permit the use of named arguments and to make the code easier to read in PhpStorm.
+ * @phpstan-return TMetadata
+ */
+ private static function meta(int $harvestLevel, int $maxDurability, int $baseAttackPoints, int $baseEfficiency, int $enchantability) : array{
+ return [$harvestLevel, $maxDurability, $baseAttackPoints, $baseEfficiency, $enchantability];
}
- private function __construct(
- string $name,
- private int $harvestLevel,
- private int $maxDurability,
- private int $baseAttackPoints,
- private int $baseEfficiency
- ){
- $this->Enum___construct($name);
+ /**
+ * @phpstan-return TMetadata
+ */
+ private function getMetadata() : array{
+ return match($this){
+ self::WOOD => self::meta(1, 60, 5, 2, 15),
+ self::GOLD => self::meta(2, 33, 5, 12, 22),
+ self::STONE => self::meta(3, 132, 6, 4, 5),
+ self::IRON => self::meta(4, 251, 7, 6, 14),
+ self::DIAMOND => self::meta(5, 1562, 8, 8, 10),
+ self::NETHERITE => self::meta(6, 2032, 9, 9, 15)
+ };
}
public function getHarvestLevel() : int{
- return $this->harvestLevel;
+ return $this->getMetadata()[0];
}
public function getMaxDurability() : int{
- return $this->maxDurability;
+ return $this->getMetadata()[1];
}
public function getBaseAttackPoints() : int{
- return $this->baseAttackPoints;
+ return $this->getMetadata()[2];
}
public function getBaseEfficiency() : int{
- return $this->baseEfficiency;
+ return $this->getMetadata()[3];
+ }
+
+ /**
+ * Returns the value that defines how enchantable the item is.
+ *
+ * The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
+ * or multiple enchantments upon being enchanted in an enchanting table.
+ */
+ public function getEnchantability() : int{
+ return $this->getMetadata()[4];
}
}
diff --git a/src/item/TorchflowerSeeds.php b/src/item/TorchflowerSeeds.php
new file mode 100644
index 00000000000..123af35a090
--- /dev/null
+++ b/src/item/TorchflowerSeeds.php
@@ -0,0 +1,34 @@
+
+ */
+ public static function getAll() : array{
+ // phpstan doesn't support generic traits yet :(
+ /** @var ArmorMaterial[] $result */
+ $result = self::_registryGetAll();
+ return $result;
+ }
+
+ protected static function setup() : void{
+ self::register("leather", new ArmorMaterial(15, new ArmorEquipLeatherSound()));
+ self::register("chainmail", new ArmorMaterial(12, new ArmorEquipChainSound()));
+ self::register("iron", new ArmorMaterial(9, new ArmorEquipIronSound()));
+ self::register("turtle", new ArmorMaterial(9, new ArmorEquipGenericSound()));
+ self::register("gold", new ArmorMaterial(25, new ArmorEquipGoldSound()));
+ self::register("diamond", new ArmorMaterial(10, new ArmorEquipDiamondSound()));
+ self::register("netherite", new ArmorMaterial(15, new ArmorEquipNetheriteSound()));
+ }
+}
diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php
index 7e94dd4494a..5899b63576f 100644
--- a/src/item/VanillaItems.php
+++ b/src/item/VanillaItems.php
@@ -24,7 +24,6 @@
namespace pocketmine\item;
use pocketmine\block\utils\RecordType;
-use pocketmine\block\VanillaBlocks;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\entity\Entity;
use pocketmine\entity\Location;
@@ -32,12 +31,15 @@
use pocketmine\entity\Villager;
use pocketmine\entity\Zombie;
use pocketmine\inventory\ArmorInventory;
+use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\ItemIdentifier as IID;
-use pocketmine\item\ItemTypeIds as Ids;
+use pocketmine\item\VanillaArmorMaterials as ArmorMaterials;
use pocketmine\math\Vector3;
-use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\CloningRegistryTrait;
use pocketmine\world\World;
+use function is_int;
+use function mb_strtoupper;
+use function strtolower;
/**
* This doc-block is generated automatically, do not modify it manually.
@@ -114,11 +116,13 @@
* @method static Item CHEMICAL_SULPHATE()
* @method static Item CHEMICAL_TUNGSTEN_CHLORIDE()
* @method static Item CHEMICAL_WATER()
+ * @method static ItemBlockWallOrFloor CHERRY_SIGN()
* @method static ChorusFruit CHORUS_FRUIT()
* @method static Item CLAY()
* @method static Clock CLOCK()
* @method static Clownfish CLOWNFISH()
* @method static Coal COAL()
+ * @method static Item COAST_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static CocoaBeans COCOA_BEANS()
* @method static Compass COMPASS()
* @method static CookedChicken COOKED_CHICKEN()
@@ -146,13 +150,17 @@
* @method static Item DISC_FRAGMENT_5()
* @method static Item DRAGON_BREATH()
* @method static DriedKelp DRIED_KELP()
+ * @method static Item DUNE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Dye DYE()
* @method static Item ECHO_SHARD()
* @method static Egg EGG()
* @method static Item EMERALD()
+ * @method static EnchantedBook ENCHANTED_BOOK()
* @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE()
* @method static EnderPearl ENDER_PEARL()
+ * @method static EndCrystal END_CRYSTAL()
* @method static ExperienceBottle EXPERIENCE_BOTTLE()
+ * @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item FEATHER()
* @method static Item FERMENTED_SPIDER_EYE()
* @method static FireCharge FIRE_CHARGE()
@@ -165,6 +173,7 @@
* @method static Item GLOWSTONE_DUST()
* @method static GlowBerries GLOW_BERRIES()
* @method static Item GLOW_INK_SAC()
+ * @method static GoatHorn GOAT_HORN()
* @method static GoldenApple GOLDEN_APPLE()
* @method static Axe GOLDEN_AXE()
* @method static Armor GOLDEN_BOOTS()
@@ -182,6 +191,7 @@
* @method static Item HEART_OF_THE_SEA()
* @method static Item HONEYCOMB()
* @method static HoneyBottle HONEY_BOTTLE()
+ * @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item INK_SAC()
* @method static Axe IRON_AXE()
* @method static Armor IRON_BOOTS()
@@ -212,6 +222,7 @@
* @method static MilkBucket MILK_BUCKET()
* @method static Minecart MINECART()
* @method static MushroomStew MUSHROOM_STEW()
+ * @method static NameTag NAME_TAG()
* @method static Item NAUTILUS_SHELL()
* @method static Axe NETHERITE_AXE()
* @method static Armor NETHERITE_BOOTS()
@@ -224,6 +235,7 @@
* @method static Item NETHERITE_SCRAP()
* @method static Shovel NETHERITE_SHOVEL()
* @method static Sword NETHERITE_SWORD()
+ * @method static Item NETHERITE_UPGRADE_SMITHING_TEMPLATE()
* @method static Item NETHER_BRICK()
* @method static Item NETHER_QUARTZ()
* @method static Item NETHER_STAR()
@@ -232,6 +244,7 @@
* @method static PaintingItem PAINTING()
* @method static Item PAPER()
* @method static Item PHANTOM_MEMBRANE()
+ * @method static PitcherPod PITCHER_POD()
* @method static PoisonousPotato POISONOUS_POTATO()
* @method static Item POPPED_CHORUS_FRUIT()
* @method static Potato POTATO()
@@ -244,6 +257,7 @@
* @method static Item RABBIT_FOOT()
* @method static Item RABBIT_HIDE()
* @method static RabbitStew RABBIT_STEW()
+ * @method static Item RAISER_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static RawBeef RAW_BEEF()
* @method static RawChicken RAW_CHICKEN()
* @method static Item RAW_COPPER()
@@ -270,13 +284,19 @@
* @method static Record RECORD_WAIT()
* @method static Record RECORD_WARD()
* @method static Redstone REDSTONE_DUST()
+ * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static RottenFlesh ROTTEN_FLESH()
* @method static Item SCUTE()
+ * @method static Item SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE()
+ * @method static Item SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Shears SHEARS()
* @method static Item SHULKER_SHELL()
+ * @method static Item SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item SLIMEBALL()
+ * @method static Item SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Snowball SNOWBALL()
* @method static SpiderEye SPIDER_EYE()
+ * @method static Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static SplashPotion SPLASH_POTION()
* @method static Boat SPRUCE_BOAT()
* @method static ItemBlockWallOrFloor SPRUCE_SIGN()
@@ -293,13 +313,19 @@
* @method static Item SUGAR()
* @method static SuspiciousStew SUSPICIOUS_STEW()
* @method static SweetBerries SWEET_BERRIES()
+ * @method static Item TIDE_ARMOR_TRIM_SMITHING_TEMPLATE()
+ * @method static TorchflowerSeeds TORCHFLOWER_SEEDS()
* @method static Totem TOTEM()
* @method static TurtleHelmet TURTLE_HELMET()
+ * @method static Item VEX_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static SpawnEgg VILLAGER_SPAWN_EGG()
+ * @method static Item WARD_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static ItemBlockWallOrFloor WARPED_SIGN()
* @method static LiquidBucket WATER_BUCKET()
+ * @method static Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item WHEAT()
* @method static WheatSeeds WHEAT_SEEDS()
+ * @method static Item WILD_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Axe WOODEN_AXE()
* @method static Hoe WOODEN_HOE()
* @method static Pickaxe WOODEN_PICKAXE()
@@ -316,8 +342,29 @@ private function __construct(){
//NOOP
}
- protected static function register(string $name, Item $item) : void{
+ /**
+ * @phpstan-template TItem of Item
+ * @phpstan-param \Closure(IID) : TItem $createItem
+ * @phpstan-return TItem
+ */
+ protected static function register(string $name, \Closure $createItem) : Item{
+ //this sketchy hack allows us to avoid manually writing the constants inline
+ //since type IDs are generated from this class anyway, I'm OK with this hack
+ //nonetheless, we should try to get rid of it in a future major version (e.g by using string type IDs)
+ $reflect = new \ReflectionClass(ItemTypeIds::class);
+ $typeId = $reflect->getConstant(mb_strtoupper($name));
+ if(!is_int($typeId)){
+ //this allows registering new stuff without adding new type ID constants
+ //this reduces the number of mandatory steps to test new features in local development
+ \GlobalLogger::get()->error(self::class . ": No constant type ID found for $name, generating a new one");
+ $typeId = ItemTypeIds::newId();
+ }
+
+ $item = $createItem(new IID($typeId));
+
self::_registryRegister($name, $item);
+
+ return $item;
}
/**
@@ -335,307 +382,328 @@ protected static function setup() : void{
self::registerArmorItems();
self::registerSpawnEggs();
self::registerTierToolItems();
+ self::registerSmithingTemplates();
- self::register("air", VanillaBlocks::AIR()->asItem()->setCount(0));
+ //this doesn't use the regular register() because it doesn't have an item typeID
+ //in the future we'll probably want to dissociate this from the air block and make a proper null item
+ self::_registryRegister("air", Blocks::AIR()->asItem()->setCount(0));
- self::register("acacia_sign", new ItemBlockWallOrFloor(new IID(Ids::ACACIA_SIGN), Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
- self::register("amethyst_shard", new Item(new IID(Ids::AMETHYST_SHARD), "Amethyst Shard"));
- self::register("apple", new Apple(new IID(Ids::APPLE), "Apple"));
- self::register("arrow", new Arrow(new IID(Ids::ARROW), "Arrow"));
- self::register("baked_potato", new BakedPotato(new IID(Ids::BAKED_POTATO), "Baked Potato"));
- self::register("bamboo", new Bamboo(new IID(Ids::BAMBOO), "Bamboo"));
- self::register("banner", new Banner(new IID(Ids::BANNER), Blocks::BANNER(), Blocks::WALL_BANNER()));
- self::register("beetroot", new Beetroot(new IID(Ids::BEETROOT), "Beetroot"));
- self::register("beetroot_seeds", new BeetrootSeeds(new IID(Ids::BEETROOT_SEEDS), "Beetroot Seeds"));
- self::register("beetroot_soup", new BeetrootSoup(new IID(Ids::BEETROOT_SOUP), "Beetroot Soup"));
- self::register("birch_sign", new ItemBlockWallOrFloor(new IID(Ids::BIRCH_SIGN), Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
- self::register("blaze_powder", new Item(new IID(Ids::BLAZE_POWDER), "Blaze Powder"));
- self::register("blaze_rod", new BlazeRod(new IID(Ids::BLAZE_ROD), "Blaze Rod"));
- self::register("bleach", new Item(new IID(Ids::BLEACH), "Bleach"));
- self::register("bone", new Item(new IID(Ids::BONE), "Bone"));
- self::register("bone_meal", new Fertilizer(new IID(Ids::BONE_MEAL), "Bone Meal"));
- self::register("book", new Book(new IID(Ids::BOOK), "Book"));
- self::register("bow", new Bow(new IID(Ids::BOW), "Bow"));
- self::register("bowl", new Bowl(new IID(Ids::BOWL), "Bowl"));
- self::register("bread", new Bread(new IID(Ids::BREAD), "Bread"));
- self::register("brick", new Item(new IID(Ids::BRICK), "Brick"));
- self::register("bucket", new Bucket(new IID(Ids::BUCKET), "Bucket"));
- self::register("carrot", new Carrot(new IID(Ids::CARROT), "Carrot"));
- self::register("charcoal", new Coal(new IID(Ids::CHARCOAL), "Charcoal"));
- self::register("chemical_aluminium_oxide", new Item(new IID(Ids::CHEMICAL_ALUMINIUM_OXIDE), "Aluminium Oxide"));
- self::register("chemical_ammonia", new Item(new IID(Ids::CHEMICAL_AMMONIA), "Ammonia"));
- self::register("chemical_barium_sulphate", new Item(new IID(Ids::CHEMICAL_BARIUM_SULPHATE), "Barium Sulphate"));
- self::register("chemical_benzene", new Item(new IID(Ids::CHEMICAL_BENZENE), "Benzene"));
- self::register("chemical_boron_trioxide", new Item(new IID(Ids::CHEMICAL_BORON_TRIOXIDE), "Boron Trioxide"));
- self::register("chemical_calcium_bromide", new Item(new IID(Ids::CHEMICAL_CALCIUM_BROMIDE), "Calcium Bromide"));
- self::register("chemical_calcium_chloride", new Item(new IID(Ids::CHEMICAL_CALCIUM_CHLORIDE), "Calcium Chloride"));
- self::register("chemical_cerium_chloride", new Item(new IID(Ids::CHEMICAL_CERIUM_CHLORIDE), "Cerium Chloride"));
- self::register("chemical_charcoal", new Item(new IID(Ids::CHEMICAL_CHARCOAL), "Charcoal"));
- self::register("chemical_crude_oil", new Item(new IID(Ids::CHEMICAL_CRUDE_OIL), "Crude Oil"));
- self::register("chemical_glue", new Item(new IID(Ids::CHEMICAL_GLUE), "Glue"));
- self::register("chemical_hydrogen_peroxide", new Item(new IID(Ids::CHEMICAL_HYDROGEN_PEROXIDE), "Hydrogen Peroxide"));
- self::register("chemical_hypochlorite", new Item(new IID(Ids::CHEMICAL_HYPOCHLORITE), "Hypochlorite"));
- self::register("chemical_ink", new Item(new IID(Ids::CHEMICAL_INK), "Ink"));
- self::register("chemical_iron_sulphide", new Item(new IID(Ids::CHEMICAL_IRON_SULPHIDE), "Iron Sulphide"));
- self::register("chemical_latex", new Item(new IID(Ids::CHEMICAL_LATEX), "Latex"));
- self::register("chemical_lithium_hydride", new Item(new IID(Ids::CHEMICAL_LITHIUM_HYDRIDE), "Lithium Hydride"));
- self::register("chemical_luminol", new Item(new IID(Ids::CHEMICAL_LUMINOL), "Luminol"));
- self::register("chemical_magnesium_nitrate", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_NITRATE), "Magnesium Nitrate"));
- self::register("chemical_magnesium_oxide", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_OXIDE), "Magnesium Oxide"));
- self::register("chemical_magnesium_salts", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_SALTS), "Magnesium Salts"));
- self::register("chemical_mercuric_chloride", new Item(new IID(Ids::CHEMICAL_MERCURIC_CHLORIDE), "Mercuric Chloride"));
- self::register("chemical_polyethylene", new Item(new IID(Ids::CHEMICAL_POLYETHYLENE), "Polyethylene"));
- self::register("chemical_potassium_chloride", new Item(new IID(Ids::CHEMICAL_POTASSIUM_CHLORIDE), "Potassium Chloride"));
- self::register("chemical_potassium_iodide", new Item(new IID(Ids::CHEMICAL_POTASSIUM_IODIDE), "Potassium Iodide"));
- self::register("chemical_rubbish", new Item(new IID(Ids::CHEMICAL_RUBBISH), "Rubbish"));
- self::register("chemical_salt", new Item(new IID(Ids::CHEMICAL_SALT), "Salt"));
- self::register("chemical_soap", new Item(new IID(Ids::CHEMICAL_SOAP), "Soap"));
- self::register("chemical_sodium_acetate", new Item(new IID(Ids::CHEMICAL_SODIUM_ACETATE), "Sodium Acetate"));
- self::register("chemical_sodium_fluoride", new Item(new IID(Ids::CHEMICAL_SODIUM_FLUORIDE), "Sodium Fluoride"));
- self::register("chemical_sodium_hydride", new Item(new IID(Ids::CHEMICAL_SODIUM_HYDRIDE), "Sodium Hydride"));
- self::register("chemical_sodium_hydroxide", new Item(new IID(Ids::CHEMICAL_SODIUM_HYDROXIDE), "Sodium Hydroxide"));
- self::register("chemical_sodium_hypochlorite", new Item(new IID(Ids::CHEMICAL_SODIUM_HYPOCHLORITE), "Sodium Hypochlorite"));
- self::register("chemical_sodium_oxide", new Item(new IID(Ids::CHEMICAL_SODIUM_OXIDE), "Sodium Oxide"));
- self::register("chemical_sugar", new Item(new IID(Ids::CHEMICAL_SUGAR), "Sugar"));
- self::register("chemical_sulphate", new Item(new IID(Ids::CHEMICAL_SULPHATE), "Sulphate"));
- self::register("chemical_tungsten_chloride", new Item(new IID(Ids::CHEMICAL_TUNGSTEN_CHLORIDE), "Tungsten Chloride"));
- self::register("chemical_water", new Item(new IID(Ids::CHEMICAL_WATER), "Water"));
- self::register("chorus_fruit", new ChorusFruit(new IID(Ids::CHORUS_FRUIT), "Chorus Fruit"));
- self::register("clay", new Item(new IID(Ids::CLAY), "Clay"));
- self::register("clock", new Clock(new IID(Ids::CLOCK), "Clock"));
- self::register("clownfish", new Clownfish(new IID(Ids::CLOWNFISH), "Clownfish"));
- self::register("coal", new Coal(new IID(Ids::COAL), "Coal"));
- self::register("cocoa_beans", new CocoaBeans(new IID(Ids::COCOA_BEANS), "Cocoa Beans"));
- self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass"));
- self::register("cooked_chicken", new CookedChicken(new IID(Ids::COOKED_CHICKEN), "Cooked Chicken"));
- self::register("cooked_fish", new CookedFish(new IID(Ids::COOKED_FISH), "Cooked Fish"));
- self::register("cooked_mutton", new CookedMutton(new IID(Ids::COOKED_MUTTON), "Cooked Mutton"));
- self::register("cooked_porkchop", new CookedPorkchop(new IID(Ids::COOKED_PORKCHOP), "Cooked Porkchop"));
- self::register("cooked_rabbit", new CookedRabbit(new IID(Ids::COOKED_RABBIT), "Cooked Rabbit"));
- self::register("cooked_salmon", new CookedSalmon(new IID(Ids::COOKED_SALMON), "Cooked Salmon"));
- self::register("cookie", new Cookie(new IID(Ids::COOKIE), "Cookie"));
- self::register("copper_ingot", new Item(new IID(Ids::COPPER_INGOT), "Copper Ingot"));
- self::register("coral_fan", new CoralFan(new IID(Ids::CORAL_FAN)));
- self::register("crimson_sign", new ItemBlockWallOrFloor(new IID(Ids::CRIMSON_SIGN), Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN()));
- self::register("dark_oak_sign", new ItemBlockWallOrFloor(new IID(Ids::DARK_OAK_SIGN), Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
- self::register("diamond", new Item(new IID(Ids::DIAMOND), "Diamond"));
- self::register("disc_fragment_5", new Item(new IID(Ids::DISC_FRAGMENT_5), "Disc Fragment (5)"));
- self::register("dragon_breath", new Item(new IID(Ids::DRAGON_BREATH), "Dragon's Breath"));
- self::register("dried_kelp", new DriedKelp(new IID(Ids::DRIED_KELP), "Dried Kelp"));
+ self::register("acacia_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
+ self::register("amethyst_shard", fn(IID $id) => new Item($id, "Amethyst Shard"));
+ self::register("apple", fn(IID $id) => new Apple($id, "Apple"));
+ self::register("arrow", fn(IID $id) => new Arrow($id, "Arrow"));
+ self::register("baked_potato", fn(IID $id) => new BakedPotato($id, "Baked Potato"));
+ self::register("bamboo", fn(IID $id) => new Bamboo($id, "Bamboo"));
+ self::register("banner", fn(IID $id) => new Banner($id, Blocks::BANNER(), Blocks::WALL_BANNER()));
+ self::register("beetroot", fn(IID $id) => new Beetroot($id, "Beetroot"));
+ self::register("beetroot_seeds", fn(IID $id) => new BeetrootSeeds($id, "Beetroot Seeds"));
+ self::register("beetroot_soup", fn(IID $id) => new BeetrootSoup($id, "Beetroot Soup"));
+ self::register("birch_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
+ self::register("blaze_powder", fn(IID $id) => new Item($id, "Blaze Powder"));
+ self::register("blaze_rod", fn(IID $id) => new BlazeRod($id, "Blaze Rod"));
+ self::register("bleach", fn(IID $id) => new Item($id, "Bleach"));
+ self::register("bone", fn(IID $id) => new Item($id, "Bone"));
+ self::register("bone_meal", fn(IID $id) => new Fertilizer($id, "Bone Meal"));
+ self::register("book", fn(IID $id) => new Book($id, "Book", [EnchantmentTags::ALL]));
+ self::register("bow", fn(IID $id) => new Bow($id, "Bow", [EnchantmentTags::BOW]));
+ self::register("bowl", fn(IID $id) => new Bowl($id, "Bowl"));
+ self::register("bread", fn(IID $id) => new Bread($id, "Bread"));
+ self::register("brick", fn(IID $id) => new Item($id, "Brick"));
+ self::register("bucket", fn(IID $id) => new Bucket($id, "Bucket"));
+ self::register("carrot", fn(IID $id) => new Carrot($id, "Carrot"));
+ self::register("charcoal", fn(IID $id) => new Coal($id, "Charcoal"));
+ self::register("cherry_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN()));
+ self::register("chemical_aluminium_oxide", fn(IID $id) => new Item($id, "Aluminium Oxide"));
+ self::register("chemical_ammonia", fn(IID $id) => new Item($id, "Ammonia"));
+ self::register("chemical_barium_sulphate", fn(IID $id) => new Item($id, "Barium Sulphate"));
+ self::register("chemical_benzene", fn(IID $id) => new Item($id, "Benzene"));
+ self::register("chemical_boron_trioxide", fn(IID $id) => new Item($id, "Boron Trioxide"));
+ self::register("chemical_calcium_bromide", fn(IID $id) => new Item($id, "Calcium Bromide"));
+ self::register("chemical_calcium_chloride", fn(IID $id) => new Item($id, "Calcium Chloride"));
+ self::register("chemical_cerium_chloride", fn(IID $id) => new Item($id, "Cerium Chloride"));
+ self::register("chemical_charcoal", fn(IID $id) => new Item($id, "Charcoal"));
+ self::register("chemical_crude_oil", fn(IID $id) => new Item($id, "Crude Oil"));
+ self::register("chemical_glue", fn(IID $id) => new Item($id, "Glue"));
+ self::register("chemical_hydrogen_peroxide", fn(IID $id) => new Item($id, "Hydrogen Peroxide"));
+ self::register("chemical_hypochlorite", fn(IID $id) => new Item($id, "Hypochlorite"));
+ self::register("chemical_ink", fn(IID $id) => new Item($id, "Ink"));
+ self::register("chemical_iron_sulphide", fn(IID $id) => new Item($id, "Iron Sulphide"));
+ self::register("chemical_latex", fn(IID $id) => new Item($id, "Latex"));
+ self::register("chemical_lithium_hydride", fn(IID $id) => new Item($id, "Lithium Hydride"));
+ self::register("chemical_luminol", fn(IID $id) => new Item($id, "Luminol"));
+ self::register("chemical_magnesium_nitrate", fn(IID $id) => new Item($id, "Magnesium Nitrate"));
+ self::register("chemical_magnesium_oxide", fn(IID $id) => new Item($id, "Magnesium Oxide"));
+ self::register("chemical_magnesium_salts", fn(IID $id) => new Item($id, "Magnesium Salts"));
+ self::register("chemical_mercuric_chloride", fn(IID $id) => new Item($id, "Mercuric Chloride"));
+ self::register("chemical_polyethylene", fn(IID $id) => new Item($id, "Polyethylene"));
+ self::register("chemical_potassium_chloride", fn(IID $id) => new Item($id, "Potassium Chloride"));
+ self::register("chemical_potassium_iodide", fn(IID $id) => new Item($id, "Potassium Iodide"));
+ self::register("chemical_rubbish", fn(IID $id) => new Item($id, "Rubbish"));
+ self::register("chemical_salt", fn(IID $id) => new Item($id, "Salt"));
+ self::register("chemical_soap", fn(IID $id) => new Item($id, "Soap"));
+ self::register("chemical_sodium_acetate", fn(IID $id) => new Item($id, "Sodium Acetate"));
+ self::register("chemical_sodium_fluoride", fn(IID $id) => new Item($id, "Sodium Fluoride"));
+ self::register("chemical_sodium_hydride", fn(IID $id) => new Item($id, "Sodium Hydride"));
+ self::register("chemical_sodium_hydroxide", fn(IID $id) => new Item($id, "Sodium Hydroxide"));
+ self::register("chemical_sodium_hypochlorite", fn(IID $id) => new Item($id, "Sodium Hypochlorite"));
+ self::register("chemical_sodium_oxide", fn(IID $id) => new Item($id, "Sodium Oxide"));
+ self::register("chemical_sugar", fn(IID $id) => new Item($id, "Sugar"));
+ self::register("chemical_sulphate", fn(IID $id) => new Item($id, "Sulphate"));
+ self::register("chemical_tungsten_chloride", fn(IID $id) => new Item($id, "Tungsten Chloride"));
+ self::register("chemical_water", fn(IID $id) => new Item($id, "Water"));
+ self::register("chorus_fruit", fn(IID $id) => new ChorusFruit($id, "Chorus Fruit"));
+ self::register("clay", fn(IID $id) => new Item($id, "Clay"));
+ self::register("clock", fn(IID $id) => new Clock($id, "Clock"));
+ self::register("clownfish", fn(IID $id) => new Clownfish($id, "Clownfish"));
+ self::register("coal", fn(IID $id) => new Coal($id, "Coal"));
+ self::register("cocoa_beans", fn(IID $id) => new CocoaBeans($id, "Cocoa Beans"));
+ self::register("compass", fn(IID $id) => new Compass($id, "Compass", [EnchantmentTags::COMPASS]));
+ self::register("cooked_chicken", fn(IID $id) => new CookedChicken($id, "Cooked Chicken"));
+ self::register("cooked_fish", fn(IID $id) => new CookedFish($id, "Cooked Fish"));
+ self::register("cooked_mutton", fn(IID $id) => new CookedMutton($id, "Cooked Mutton"));
+ self::register("cooked_porkchop", fn(IID $id) => new CookedPorkchop($id, "Cooked Porkchop"));
+ self::register("cooked_rabbit", fn(IID $id) => new CookedRabbit($id, "Cooked Rabbit"));
+ self::register("cooked_salmon", fn(IID $id) => new CookedSalmon($id, "Cooked Salmon"));
+ self::register("cookie", fn(IID $id) => new Cookie($id, "Cookie"));
+ self::register("copper_ingot", fn(IID $id) => new Item($id, "Copper Ingot"));
+ self::register("coral_fan", fn(IID $id) => new CoralFan($id));
+ self::register("crimson_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN()));
+ self::register("dark_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
+ self::register("diamond", fn(IID $id) => new Item($id, "Diamond"));
+ self::register("disc_fragment_5", fn(IID $id) => new Item($id, "Disc Fragment (5)"));
+ self::register("dragon_breath", fn(IID $id) => new Item($id, "Dragon's Breath"));
+ self::register("dried_kelp", fn(IID $id) => new DriedKelp($id, "Dried Kelp"));
//TODO: add interface to dye-colour objects
- self::register("dye", new Dye(new IID(Ids::DYE), "Dye"));
- self::register("echo_shard", new Item(new IID(Ids::ECHO_SHARD), "Echo Shard"));
- self::register("egg", new Egg(new IID(Ids::EGG), "Egg"));
- self::register("emerald", new Item(new IID(Ids::EMERALD), "Emerald"));
- self::register("enchanted_golden_apple", new GoldenAppleEnchanted(new IID(Ids::ENCHANTED_GOLDEN_APPLE), "Enchanted Golden Apple"));
- self::register("ender_pearl", new EnderPearl(new IID(Ids::ENDER_PEARL), "Ender Pearl"));
- self::register("experience_bottle", new ExperienceBottle(new IID(Ids::EXPERIENCE_BOTTLE), "Bottle o' Enchanting"));
- self::register("feather", new Item(new IID(Ids::FEATHER), "Feather"));
- self::register("fermented_spider_eye", new Item(new IID(Ids::FERMENTED_SPIDER_EYE), "Fermented Spider Eye"));
- self::register("fire_charge", new FireCharge(new IID(Ids::FIRE_CHARGE), "Fire Charge"));
- self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod"));
- self::register("flint", new Item(new IID(Ids::FLINT), "Flint"));
- self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel"));
- self::register("ghast_tear", new Item(new IID(Ids::GHAST_TEAR), "Ghast Tear"));
- self::register("glass_bottle", new GlassBottle(new IID(Ids::GLASS_BOTTLE), "Glass Bottle"));
- self::register("glistering_melon", new Item(new IID(Ids::GLISTERING_MELON), "Glistering Melon"));
- self::register("glow_berries", new GlowBerries(new IID(Ids::GLOW_BERRIES), "Glow Berries"));
- self::register("glow_ink_sac", new Item(new IID(Ids::GLOW_INK_SAC), "Glow Ink Sac"));
- self::register("glowstone_dust", new Item(new IID(Ids::GLOWSTONE_DUST), "Glowstone Dust"));
- self::register("gold_ingot", new Item(new IID(Ids::GOLD_INGOT), "Gold Ingot"));
- self::register("gold_nugget", new Item(new IID(Ids::GOLD_NUGGET), "Gold Nugget"));
- self::register("golden_apple", new GoldenApple(new IID(Ids::GOLDEN_APPLE), "Golden Apple"));
- self::register("golden_carrot", new GoldenCarrot(new IID(Ids::GOLDEN_CARROT), "Golden Carrot"));
- self::register("gunpowder", new Item(new IID(Ids::GUNPOWDER), "Gunpowder"));
- self::register("heart_of_the_sea", new Item(new IID(Ids::HEART_OF_THE_SEA), "Heart of the Sea"));
- self::register("honey_bottle", new HoneyBottle(new IID(Ids::HONEY_BOTTLE), "Honey Bottle"));
- self::register("honeycomb", new Item(new IID(Ids::HONEYCOMB), "Honeycomb"));
- self::register("ink_sac", new Item(new IID(Ids::INK_SAC), "Ink Sac"));
- self::register("iron_ingot", new Item(new IID(Ids::IRON_INGOT), "Iron Ingot"));
- self::register("iron_nugget", new Item(new IID(Ids::IRON_NUGGET), "Iron Nugget"));
- self::register("jungle_sign", new ItemBlockWallOrFloor(new IID(Ids::JUNGLE_SIGN), Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
- self::register("lapis_lazuli", new Item(new IID(Ids::LAPIS_LAZULI), "Lapis Lazuli"));
- self::register("lava_bucket", new LiquidBucket(new IID(Ids::LAVA_BUCKET), "Lava Bucket", Blocks::LAVA()));
- self::register("leather", new Item(new IID(Ids::LEATHER), "Leather"));
- self::register("magma_cream", new Item(new IID(Ids::MAGMA_CREAM), "Magma Cream"));
- self::register("mangrove_sign", new ItemBlockWallOrFloor(new IID(Ids::MANGROVE_SIGN), Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
- self::register("medicine", new Medicine(new IID(Ids::MEDICINE), "Medicine"));
- self::register("melon", new Melon(new IID(Ids::MELON), "Melon"));
- self::register("melon_seeds", new MelonSeeds(new IID(Ids::MELON_SEEDS), "Melon Seeds"));
- self::register("milk_bucket", new MilkBucket(new IID(Ids::MILK_BUCKET), "Milk Bucket"));
- self::register("minecart", new Minecart(new IID(Ids::MINECART), "Minecart"));
- self::register("mushroom_stew", new MushroomStew(new IID(Ids::MUSHROOM_STEW), "Mushroom Stew"));
- self::register("nautilus_shell", new Item(new IID(Ids::NAUTILUS_SHELL), "Nautilus Shell"));
- self::register("nether_brick", new Item(new IID(Ids::NETHER_BRICK), "Nether Brick"));
- self::register("nether_quartz", new Item(new IID(Ids::NETHER_QUARTZ), "Nether Quartz"));
- self::register("nether_star", new Item(new IID(Ids::NETHER_STAR), "Nether Star"));
- self::register("netherite_ingot", new class(new IID(Ids::NETHERITE_INGOT), "Netherite Ingot") extends Item{
+ self::register("dye", fn(IID $id) => new Dye($id, "Dye"));
+ self::register("echo_shard", fn(IID $id) => new Item($id, "Echo Shard"));
+ self::register("egg", fn(IID $id) => new Egg($id, "Egg"));
+ self::register("emerald", fn(IID $id) => new Item($id, "Emerald"));
+ self::register("enchanted_book", fn(IID $id) => new EnchantedBook($id, "Enchanted Book", [EnchantmentTags::ALL]));
+ self::register("enchanted_golden_apple", fn(IID $id) => new GoldenAppleEnchanted($id, "Enchanted Golden Apple"));
+ self::register("end_crystal", fn(IID $id) => new EndCrystal($id, "End Crystal"));
+ self::register("ender_pearl", fn(IID $id) => new EnderPearl($id, "Ender Pearl"));
+ self::register("experience_bottle", fn(IID $id) => new ExperienceBottle($id, "Bottle o' Enchanting"));
+ self::register("feather", fn(IID $id) => new Item($id, "Feather"));
+ self::register("fermented_spider_eye", fn(IID $id) => new Item($id, "Fermented Spider Eye"));
+ self::register("fire_charge", fn(IID $id) => new FireCharge($id, "Fire Charge"));
+ self::register("fishing_rod", fn(IID $id) => new FishingRod($id, "Fishing Rod", [EnchantmentTags::FISHING_ROD]));
+ self::register("flint", fn(IID $id) => new Item($id, "Flint"));
+ self::register("flint_and_steel", fn(IID $id) => new FlintSteel($id, "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL]));
+ self::register("ghast_tear", fn(IID $id) => new Item($id, "Ghast Tear"));
+ self::register("glass_bottle", fn(IID $id) => new GlassBottle($id, "Glass Bottle"));
+ self::register("glistering_melon", fn(IID $id) => new Item($id, "Glistering Melon"));
+ self::register("glow_berries", fn(IID $id) => new GlowBerries($id, "Glow Berries"));
+ self::register("glow_ink_sac", fn(IID $id) => new Item($id, "Glow Ink Sac"));
+ self::register("glowstone_dust", fn(IID $id) => new Item($id, "Glowstone Dust"));
+ self::register("goat_horn", fn(IID $id) => new GoatHorn($id, "Goat Horn"));
+ self::register("gold_ingot", fn(IID $id) => new Item($id, "Gold Ingot"));
+ self::register("gold_nugget", fn(IID $id) => new Item($id, "Gold Nugget"));
+ self::register("golden_apple", fn(IID $id) => new GoldenApple($id, "Golden Apple"));
+ self::register("golden_carrot", fn(IID $id) => new GoldenCarrot($id, "Golden Carrot"));
+ self::register("gunpowder", fn(IID $id) => new Item($id, "Gunpowder"));
+ self::register("heart_of_the_sea", fn(IID $id) => new Item($id, "Heart of the Sea"));
+ self::register("honey_bottle", fn(IID $id) => new HoneyBottle($id, "Honey Bottle"));
+ self::register("honeycomb", fn(IID $id) => new Item($id, "Honeycomb"));
+ self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
+ self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
+ self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));
+ self::register("jungle_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
+ self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
+ self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
+ self::register("leather", fn(IID $id) => new Item($id, "Leather"));
+ self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
+ self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
+ self::register("medicine", fn(IID $id) => new Medicine($id, "Medicine"));
+ self::register("melon", fn(IID $id) => new Melon($id, "Melon"));
+ self::register("melon_seeds", fn(IID $id) => new MelonSeeds($id, "Melon Seeds"));
+ self::register("milk_bucket", fn(IID $id) => new MilkBucket($id, "Milk Bucket"));
+ self::register("minecart", fn(IID $id) => new Minecart($id, "Minecart"));
+ self::register("mushroom_stew", fn(IID $id) => new MushroomStew($id, "Mushroom Stew"));
+ self::register("name_tag", fn(IID $id) => new NameTag($id, "Name Tag"));
+ self::register("nautilus_shell", fn(IID $id) => new Item($id, "Nautilus Shell"));
+ self::register("nether_brick", fn(IID $id) => new Item($id, "Nether Brick"));
+ self::register("nether_quartz", fn(IID $id) => new Item($id, "Nether Quartz"));
+ self::register("nether_star", fn(IID $id) => new Item($id, "Nether Star"));
+ self::register("netherite_ingot", fn(IID $id) => new class($id, "Netherite Ingot") extends Item{
public function isFireProof() : bool{ return true; }
});
- self::register("netherite_scrap", new class(new IID(Ids::NETHERITE_SCRAP), "Netherite Scrap") extends Item{
+ self::register("netherite_scrap", fn(IID $id) => new class($id, "Netherite Scrap") extends Item{
public function isFireProof() : bool{ return true; }
});
- self::register("oak_sign", new ItemBlockWallOrFloor(new IID(Ids::OAK_SIGN), Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
- self::register("painting", new PaintingItem(new IID(Ids::PAINTING), "Painting"));
- self::register("paper", new Item(new IID(Ids::PAPER), "Paper"));
- self::register("phantom_membrane", new Item(new IID(Ids::PHANTOM_MEMBRANE), "Phantom Membrane"));
- self::register("poisonous_potato", new PoisonousPotato(new IID(Ids::POISONOUS_POTATO), "Poisonous Potato"));
- self::register("popped_chorus_fruit", new Item(new IID(Ids::POPPED_CHORUS_FRUIT), "Popped Chorus Fruit"));
- self::register("potato", new Potato(new IID(Ids::POTATO), "Potato"));
- self::register("potion", new Potion(new IID(Ids::POTION), "Potion"));
- self::register("prismarine_crystals", new Item(new IID(Ids::PRISMARINE_CRYSTALS), "Prismarine Crystals"));
- self::register("prismarine_shard", new Item(new IID(Ids::PRISMARINE_SHARD), "Prismarine Shard"));
- self::register("pufferfish", new Pufferfish(new IID(Ids::PUFFERFISH), "Pufferfish"));
- self::register("pumpkin_pie", new PumpkinPie(new IID(Ids::PUMPKIN_PIE), "Pumpkin Pie"));
- self::register("pumpkin_seeds", new PumpkinSeeds(new IID(Ids::PUMPKIN_SEEDS), "Pumpkin Seeds"));
- self::register("rabbit_foot", new Item(new IID(Ids::RABBIT_FOOT), "Rabbit's Foot"));
- self::register("rabbit_hide", new Item(new IID(Ids::RABBIT_HIDE), "Rabbit Hide"));
- self::register("rabbit_stew", new RabbitStew(new IID(Ids::RABBIT_STEW), "Rabbit Stew"));
- self::register("raw_beef", new RawBeef(new IID(Ids::RAW_BEEF), "Raw Beef"));
- self::register("raw_chicken", new RawChicken(new IID(Ids::RAW_CHICKEN), "Raw Chicken"));
- self::register("raw_copper", new Item(new IID(Ids::RAW_COPPER), "Raw Copper"));
- self::register("raw_fish", new RawFish(new IID(Ids::RAW_FISH), "Raw Fish"));
- self::register("raw_gold", new Item(new IID(Ids::RAW_GOLD), "Raw Gold"));
- self::register("raw_iron", new Item(new IID(Ids::RAW_IRON), "Raw Iron"));
- self::register("raw_mutton", new RawMutton(new IID(Ids::RAW_MUTTON), "Raw Mutton"));
- self::register("raw_porkchop", new RawPorkchop(new IID(Ids::RAW_PORKCHOP), "Raw Porkchop"));
- self::register("raw_rabbit", new RawRabbit(new IID(Ids::RAW_RABBIT), "Raw Rabbit"));
- self::register("raw_salmon", new RawSalmon(new IID(Ids::RAW_SALMON), "Raw Salmon"));
- self::register("record_11", new Record(new IID(Ids::RECORD_11), RecordType::DISK_11(), "Record 11"));
- self::register("record_13", new Record(new IID(Ids::RECORD_13), RecordType::DISK_13(), "Record 13"));
- self::register("record_5", new Record(new IID(Ids::RECORD_5), RecordType::DISK_5(), "Record 5"));
- self::register("record_blocks", new Record(new IID(Ids::RECORD_BLOCKS), RecordType::DISK_BLOCKS(), "Record Blocks"));
- self::register("record_cat", new Record(new IID(Ids::RECORD_CAT), RecordType::DISK_CAT(), "Record Cat"));
- self::register("record_chirp", new Record(new IID(Ids::RECORD_CHIRP), RecordType::DISK_CHIRP(), "Record Chirp"));
- self::register("record_far", new Record(new IID(Ids::RECORD_FAR), RecordType::DISK_FAR(), "Record Far"));
- self::register("record_mall", new Record(new IID(Ids::RECORD_MALL), RecordType::DISK_MALL(), "Record Mall"));
- self::register("record_mellohi", new Record(new IID(Ids::RECORD_MELLOHI), RecordType::DISK_MELLOHI(), "Record Mellohi"));
- self::register("record_otherside", new Record(new IID(Ids::RECORD_OTHERSIDE), RecordType::DISK_OTHERSIDE(), "Record Otherside"));
- self::register("record_pigstep", new Record(new IID(Ids::RECORD_PIGSTEP), RecordType::DISK_PIGSTEP(), "Record Pigstep"));
- self::register("record_stal", new Record(new IID(Ids::RECORD_STAL), RecordType::DISK_STAL(), "Record Stal"));
- self::register("record_strad", new Record(new IID(Ids::RECORD_STRAD), RecordType::DISK_STRAD(), "Record Strad"));
- self::register("record_wait", new Record(new IID(Ids::RECORD_WAIT), RecordType::DISK_WAIT(), "Record Wait"));
- self::register("record_ward", new Record(new IID(Ids::RECORD_WARD), RecordType::DISK_WARD(), "Record Ward"));
- self::register("redstone_dust", new Redstone(new IID(Ids::REDSTONE_DUST), "Redstone"));
- self::register("rotten_flesh", new RottenFlesh(new IID(Ids::ROTTEN_FLESH), "Rotten Flesh"));
- self::register("scute", new Item(new IID(Ids::SCUTE), "Scute"));
- self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears"));
- self::register("shulker_shell", new Item(new IID(Ids::SHULKER_SHELL), "Shulker Shell"));
- self::register("slimeball", new Item(new IID(Ids::SLIMEBALL), "Slimeball"));
- self::register("snowball", new Snowball(new IID(Ids::SNOWBALL), "Snowball"));
- self::register("spider_eye", new SpiderEye(new IID(Ids::SPIDER_EYE), "Spider Eye"));
- self::register("splash_potion", new SplashPotion(new IID(Ids::SPLASH_POTION), "Splash Potion"));
- self::register("spruce_sign", new ItemBlockWallOrFloor(new IID(Ids::SPRUCE_SIGN), Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
- self::register("spyglass", new Spyglass(new IID(Ids::SPYGLASS), "Spyglass"));
- self::register("steak", new Steak(new IID(Ids::STEAK), "Steak"));
- self::register("stick", new Stick(new IID(Ids::STICK), "Stick"));
- self::register("string", new StringItem(new IID(Ids::STRING), "String"));
- self::register("sugar", new Item(new IID(Ids::SUGAR), "Sugar"));
- self::register("suspicious_stew", new SuspiciousStew(new IID(Ids::SUSPICIOUS_STEW), "Suspicious Stew"));
- self::register("sweet_berries", new SweetBerries(new IID(Ids::SWEET_BERRIES), "Sweet Berries"));
- self::register("totem", new Totem(new IID(Ids::TOTEM), "Totem of Undying"));
- self::register("warped_sign", new ItemBlockWallOrFloor(new IID(Ids::WARPED_SIGN), Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
- self::register("water_bucket", new LiquidBucket(new IID(Ids::WATER_BUCKET), "Water Bucket", Blocks::WATER()));
- self::register("wheat", new Item(new IID(Ids::WHEAT), "Wheat"));
- self::register("wheat_seeds", new WheatSeeds(new IID(Ids::WHEAT_SEEDS), "Wheat Seeds"));
- self::register("writable_book", new WritableBook(new IID(Ids::WRITABLE_BOOK), "Book & Quill"));
- self::register("written_book", new WrittenBook(new IID(Ids::WRITTEN_BOOK), "Written Book"));
+ self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
+ self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting"));
+ self::register("paper", fn(IID $id) => new Item($id, "Paper"));
+ self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane"));
+ self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod"));
+ self::register("poisonous_potato", fn(IID $id) => new PoisonousPotato($id, "Poisonous Potato"));
+ self::register("popped_chorus_fruit", fn(IID $id) => new Item($id, "Popped Chorus Fruit"));
+ self::register("potato", fn(IID $id) => new Potato($id, "Potato"));
+ self::register("potion", fn(IID $id) => new Potion($id, "Potion"));
+ self::register("prismarine_crystals", fn(IID $id) => new Item($id, "Prismarine Crystals"));
+ self::register("prismarine_shard", fn(IID $id) => new Item($id, "Prismarine Shard"));
+ self::register("pufferfish", fn(IID $id) => new Pufferfish($id, "Pufferfish"));
+ self::register("pumpkin_pie", fn(IID $id) => new PumpkinPie($id, "Pumpkin Pie"));
+ self::register("pumpkin_seeds", fn(IID $id) => new PumpkinSeeds($id, "Pumpkin Seeds"));
+ self::register("rabbit_foot", fn(IID $id) => new Item($id, "Rabbit's Foot"));
+ self::register("rabbit_hide", fn(IID $id) => new Item($id, "Rabbit Hide"));
+ self::register("rabbit_stew", fn(IID $id) => new RabbitStew($id, "Rabbit Stew"));
+ self::register("raw_beef", fn(IID $id) => new RawBeef($id, "Raw Beef"));
+ self::register("raw_chicken", fn(IID $id) => new RawChicken($id, "Raw Chicken"));
+ self::register("raw_copper", fn(IID $id) => new Item($id, "Raw Copper"));
+ self::register("raw_fish", fn(IID $id) => new RawFish($id, "Raw Fish"));
+ self::register("raw_gold", fn(IID $id) => new Item($id, "Raw Gold"));
+ self::register("raw_iron", fn(IID $id) => new Item($id, "Raw Iron"));
+ self::register("raw_mutton", fn(IID $id) => new RawMutton($id, "Raw Mutton"));
+ self::register("raw_porkchop", fn(IID $id) => new RawPorkchop($id, "Raw Porkchop"));
+ self::register("raw_rabbit", fn(IID $id) => new RawRabbit($id, "Raw Rabbit"));
+ self::register("raw_salmon", fn(IID $id) => new RawSalmon($id, "Raw Salmon"));
+ self::register("record_11", fn(IID $id) => new Record($id, RecordType::DISK_11, "Record 11"));
+ self::register("record_13", fn(IID $id) => new Record($id, RecordType::DISK_13, "Record 13"));
+ self::register("record_5", fn(IID $id) => new Record($id, RecordType::DISK_5, "Record 5"));
+ self::register("record_blocks", fn(IID $id) => new Record($id, RecordType::DISK_BLOCKS, "Record Blocks"));
+ self::register("record_cat", fn(IID $id) => new Record($id, RecordType::DISK_CAT, "Record Cat"));
+ self::register("record_chirp", fn(IID $id) => new Record($id, RecordType::DISK_CHIRP, "Record Chirp"));
+ self::register("record_far", fn(IID $id) => new Record($id, RecordType::DISK_FAR, "Record Far"));
+ self::register("record_mall", fn(IID $id) => new Record($id, RecordType::DISK_MALL, "Record Mall"));
+ self::register("record_mellohi", fn(IID $id) => new Record($id, RecordType::DISK_MELLOHI, "Record Mellohi"));
+ self::register("record_otherside", fn(IID $id) => new Record($id, RecordType::DISK_OTHERSIDE, "Record Otherside"));
+ self::register("record_pigstep", fn(IID $id) => new Record($id, RecordType::DISK_PIGSTEP, "Record Pigstep"));
+ self::register("record_stal", fn(IID $id) => new Record($id, RecordType::DISK_STAL, "Record Stal"));
+ self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad"));
+ self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait"));
+ self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward"));
+ 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"));
+ self::register("shears", fn(IID $id) => new Shears($id, "Shears", [EnchantmentTags::SHEARS]));
+ self::register("shulker_shell", fn(IID $id) => new Item($id, "Shulker Shell"));
+ self::register("slimeball", fn(IID $id) => new Item($id, "Slimeball"));
+ self::register("snowball", fn(IID $id) => new Snowball($id, "Snowball"));
+ self::register("spider_eye", fn(IID $id) => new SpiderEye($id, "Spider Eye"));
+ self::register("splash_potion", fn(IID $id) => new SplashPotion($id, "Splash Potion"));
+ self::register("spruce_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
+ self::register("spyglass", fn(IID $id) => new Spyglass($id, "Spyglass"));
+ self::register("steak", fn(IID $id) => new Steak($id, "Steak"));
+ self::register("stick", fn(IID $id) => new Stick($id, "Stick"));
+ self::register("string", fn(IID $id) => new StringItem($id, "String"));
+ self::register("sugar", fn(IID $id) => new Item($id, "Sugar"));
+ self::register("suspicious_stew", fn(IID $id) => new SuspiciousStew($id, "Suspicious Stew"));
+ self::register("sweet_berries", fn(IID $id) => new SweetBerries($id, "Sweet Berries"));
+ self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds"));
+ self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying"));
+ self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
+ self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER()));
+ self::register("wheat", fn(IID $id) => new Item($id, "Wheat"));
+ self::register("wheat_seeds", fn(IID $id) => new WheatSeeds($id, "Wheat Seeds"));
+ self::register("writable_book", fn(IID $id) => new WritableBook($id, "Book & Quill"));
+ self::register("written_book", fn(IID $id) => new WrittenBook($id, "Written Book"));
- foreach(BoatType::getAll() as $type){
+ foreach(BoatType::cases() as $type){
//boat type is static, because different types of wood may have different properties
- self::register($type->name() . "_boat", new Boat(new IID(match($type){
- BoatType::OAK() => Ids::OAK_BOAT,
- BoatType::SPRUCE() => Ids::SPRUCE_BOAT,
- BoatType::BIRCH() => Ids::BIRCH_BOAT,
- BoatType::JUNGLE() => Ids::JUNGLE_BOAT,
- BoatType::ACACIA() => Ids::ACACIA_BOAT,
- BoatType::DARK_OAK() => Ids::DARK_OAK_BOAT,
- BoatType::MANGROVE() => Ids::MANGROVE_BOAT,
- default => throw new AssumptionFailedError("Unhandled tree type " . $type->name())
- }), $type->getDisplayName() . " Boat", $type));
+ self::register(strtolower($type->name) . "_boat", fn(IID $id) => new Boat($id, $type->getDisplayName() . " Boat", $type));
}
}
private static function registerSpawnEggs() : void{
- self::register("zombie_spawn_egg", new class(new IID(Ids::ZOMBIE_SPAWN_EGG), "Zombie Spawn Egg") extends SpawnEgg{
+ self::register("zombie_spawn_egg", fn(IID $id) => new class($id, "Zombie Spawn Egg") extends SpawnEgg{
protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
return new Zombie(Location::fromObject($pos, $world, $yaw, $pitch));
}
});
- self::register("squid_spawn_egg", new class(new IID(Ids::SQUID_SPAWN_EGG), "Squid Spawn Egg") extends SpawnEgg{
- public function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
+ self::register("squid_spawn_egg", fn(IID $id) => new class($id, "Squid Spawn Egg") extends SpawnEgg{
+ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
return new Squid(Location::fromObject($pos, $world, $yaw, $pitch));
}
});
- self::register("villager_spawn_egg", new class(new IID(Ids::VILLAGER_SPAWN_EGG), "Villager Spawn Egg") extends SpawnEgg{
- public function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
+ self::register("villager_spawn_egg", fn(IID $id) => new class($id, "Villager Spawn Egg") extends SpawnEgg{
+ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
return new Villager(Location::fromObject($pos, $world, $yaw, $pitch));
}
});
}
private static function registerTierToolItems() : void{
- self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND()));
- self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD()));
- self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON()));
- self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE()));
- self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE()));
- self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD()));
- self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND()));
- self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD()));
- self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON()));
- self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE()));
- self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE()));
- self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD()));
- self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND()));
- self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD()));
- self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON()));
- self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE()));
- self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE()));
- self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD()));
- self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND()));
- self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD()));
- self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON()));
- self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE()));
- self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE()));
- self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD()));
- self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND()));
- self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD()));
- self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON()));
- self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE()));
- self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE()));
- self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD()));
+ self::register("diamond_axe", fn(IID $id) => new Axe($id, "Diamond Axe", ToolTier::DIAMOND, [EnchantmentTags::AXE]));
+ self::register("golden_axe", fn(IID $id) => new Axe($id, "Golden Axe", ToolTier::GOLD, [EnchantmentTags::AXE]));
+ self::register("iron_axe", fn(IID $id) => new Axe($id, "Iron Axe", ToolTier::IRON, [EnchantmentTags::AXE]));
+ self::register("netherite_axe", fn(IID $id) => new Axe($id, "Netherite Axe", ToolTier::NETHERITE, [EnchantmentTags::AXE]));
+ self::register("stone_axe", fn(IID $id) => new Axe($id, "Stone Axe", ToolTier::STONE, [EnchantmentTags::AXE]));
+ self::register("wooden_axe", fn(IID $id) => new Axe($id, "Wooden Axe", ToolTier::WOOD, [EnchantmentTags::AXE]));
+ self::register("diamond_hoe", fn(IID $id) => new Hoe($id, "Diamond Hoe", ToolTier::DIAMOND, [EnchantmentTags::HOE]));
+ self::register("golden_hoe", fn(IID $id) => new Hoe($id, "Golden Hoe", ToolTier::GOLD, [EnchantmentTags::HOE]));
+ self::register("iron_hoe", fn(IID $id) => new Hoe($id, "Iron Hoe", ToolTier::IRON, [EnchantmentTags::HOE]));
+ self::register("netherite_hoe", fn(IID $id) => new Hoe($id, "Netherite Hoe", ToolTier::NETHERITE, [EnchantmentTags::HOE]));
+ self::register("stone_hoe", fn(IID $id) => new Hoe($id, "Stone Hoe", ToolTier::STONE, [EnchantmentTags::HOE]));
+ self::register("wooden_hoe", fn(IID $id) => new Hoe($id, "Wooden Hoe", ToolTier::WOOD, [EnchantmentTags::HOE]));
+ self::register("diamond_pickaxe", fn(IID $id) => new Pickaxe($id, "Diamond Pickaxe", ToolTier::DIAMOND, [EnchantmentTags::PICKAXE]));
+ self::register("golden_pickaxe", fn(IID $id) => new Pickaxe($id, "Golden Pickaxe", ToolTier::GOLD, [EnchantmentTags::PICKAXE]));
+ self::register("iron_pickaxe", fn(IID $id) => new Pickaxe($id, "Iron Pickaxe", ToolTier::IRON, [EnchantmentTags::PICKAXE]));
+ self::register("netherite_pickaxe", fn(IID $id) => new Pickaxe($id, "Netherite Pickaxe", ToolTier::NETHERITE, [EnchantmentTags::PICKAXE]));
+ self::register("stone_pickaxe", fn(IID $id) => new Pickaxe($id, "Stone Pickaxe", ToolTier::STONE, [EnchantmentTags::PICKAXE]));
+ self::register("wooden_pickaxe", fn(IID $id) => new Pickaxe($id, "Wooden Pickaxe", ToolTier::WOOD, [EnchantmentTags::PICKAXE]));
+ self::register("diamond_shovel", fn(IID $id) => new Shovel($id, "Diamond Shovel", ToolTier::DIAMOND, [EnchantmentTags::SHOVEL]));
+ self::register("golden_shovel", fn(IID $id) => new Shovel($id, "Golden Shovel", ToolTier::GOLD, [EnchantmentTags::SHOVEL]));
+ self::register("iron_shovel", fn(IID $id) => new Shovel($id, "Iron Shovel", ToolTier::IRON, [EnchantmentTags::SHOVEL]));
+ self::register("netherite_shovel", fn(IID $id) => new Shovel($id, "Netherite Shovel", ToolTier::NETHERITE, [EnchantmentTags::SHOVEL]));
+ self::register("stone_shovel", fn(IID $id) => new Shovel($id, "Stone Shovel", ToolTier::STONE, [EnchantmentTags::SHOVEL]));
+ self::register("wooden_shovel", fn(IID $id) => new Shovel($id, "Wooden Shovel", ToolTier::WOOD, [EnchantmentTags::SHOVEL]));
+ self::register("diamond_sword", fn(IID $id) => new Sword($id, "Diamond Sword", ToolTier::DIAMOND, [EnchantmentTags::SWORD]));
+ self::register("golden_sword", fn(IID $id) => new Sword($id, "Golden Sword", ToolTier::GOLD, [EnchantmentTags::SWORD]));
+ self::register("iron_sword", fn(IID $id) => new Sword($id, "Iron Sword", ToolTier::IRON, [EnchantmentTags::SWORD]));
+ self::register("netherite_sword", fn(IID $id) => new Sword($id, "Netherite Sword", ToolTier::NETHERITE, [EnchantmentTags::SWORD]));
+ self::register("stone_sword", fn(IID $id) => new Sword($id, "Stone Sword", ToolTier::STONE, [EnchantmentTags::SWORD]));
+ self::register("wooden_sword", fn(IID $id) => new Sword($id, "Wooden Sword", ToolTier::WOOD, [EnchantmentTags::SWORD]));
}
private static function registerArmorItems() : void{
- self::register("chainmail_boots", new Armor(new IID(Ids::CHAINMAIL_BOOTS), "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET)));
- self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2)));
- self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET)));
- self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET)));
- self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET)));
- self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true)));
+ self::register("chainmail_boots", fn(IID $id) => new Armor($id, "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::BOOTS]));
+ self::register("diamond_boots", fn(IID $id) => new Armor($id, "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::BOOTS]));
+ self::register("golden_boots", fn(IID $id) => new Armor($id, "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [EnchantmentTags::BOOTS]));
+ self::register("iron_boots", fn(IID $id) => new Armor($id, "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [EnchantmentTags::BOOTS]));
+ self::register("leather_boots", fn(IID $id) => new Armor($id, "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [EnchantmentTags::BOOTS]));
+ self::register("netherite_boots", fn(IID $id) => new Armor($id, "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::BOOTS]));
- self::register("chainmail_chestplate", new Armor(new IID(Ids::CHAINMAIL_CHESTPLATE), "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST)));
- self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2)));
- self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST)));
- self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST)));
- self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST)));
- self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true)));
+ self::register("chainmail_chestplate", fn(IID $id) => new Armor($id, "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::CHESTPLATE]));
+ self::register("diamond_chestplate", fn(IID $id) => new Armor($id, "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::CHESTPLATE]));
+ self::register("golden_chestplate", fn(IID $id) => new Armor($id, "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [EnchantmentTags::CHESTPLATE]));
+ self::register("iron_chestplate", fn(IID $id) => new Armor($id, "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [EnchantmentTags::CHESTPLATE]));
+ self::register("leather_tunic", fn(IID $id) => new Armor($id, "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [EnchantmentTags::CHESTPLATE]));
+ self::register("netherite_chestplate", fn(IID $id) => new Armor($id, "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::CHESTPLATE]));
- self::register("chainmail_helmet", new Armor(new IID(Ids::CHAINMAIL_HELMET), "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD)));
- self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2)));
- self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD)));
- self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD)));
- self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD)));
- self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true)));
- self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD)));
+ self::register("chainmail_helmet", fn(IID $id) => new Armor($id, "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::HELMET]));
+ self::register("diamond_helmet", fn(IID $id) => new Armor($id, "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::HELMET]));
+ self::register("golden_helmet", fn(IID $id) => new Armor($id, "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [EnchantmentTags::HELMET]));
+ self::register("iron_helmet", fn(IID $id) => new Armor($id, "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [EnchantmentTags::HELMET]));
+ self::register("leather_cap", fn(IID $id) => new Armor($id, "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [EnchantmentTags::HELMET]));
+ self::register("netherite_helmet", fn(IID $id) => new Armor($id, "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::HELMET]));
+ self::register("turtle_helmet", fn(IID $id) => new TurtleHelmet($id, "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [EnchantmentTags::HELMET]));
+
+ self::register("chainmail_leggings", fn(IID $id) => new Armor($id, "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::LEGGINGS]));
+ self::register("diamond_leggings", fn(IID $id) => new Armor($id, "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::LEGGINGS]));
+ self::register("golden_leggings", fn(IID $id) => new Armor($id, "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [EnchantmentTags::LEGGINGS]));
+ self::register("iron_leggings", fn(IID $id) => new Armor($id, "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [EnchantmentTags::LEGGINGS]));
+ self::register("leather_pants", fn(IID $id) => new Armor($id, "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [EnchantmentTags::LEGGINGS]));
+ self::register("netherite_leggings", fn(IID $id) => new Armor($id, "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS]));
+ }
- self::register("chainmail_leggings", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS)));
- self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2)));
- self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS)));
- self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS)));
- self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS)));
- self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true)));
+ private static function registerSmithingTemplates() : void{
+ self::register("netherite_upgrade_smithing_template", fn(IID $id) => new Item($id, "Netherite Upgrade Smithing Template"));
+ self::register("coast_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Coast Armor Trim Smithing Template"));
+ self::register("dune_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Dune Armor Trim Smithing Template"));
+ self::register("eye_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Eye Armor Trim Smithing Template"));
+ self::register("host_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Host Armor Trim Smithing Template"));
+ self::register("raiser_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Raiser Armor Trim Smithing Template"));
+ self::register("rib_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Rib Armor Trim Smithing Template"));
+ self::register("sentry_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Sentry Armor Trim Smithing Template"));
+ self::register("shaper_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Shaper Armor Trim Smithing Template"));
+ self::register("silence_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Silence Armor Trim Smithing Template"));
+ self::register("snout_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Snout Armor Trim Smithing Template"));
+ self::register("spire_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Spire Armor Trim Smithing Template"));
+ self::register("tide_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Tide Armor Trim Smithing Template"));
+ self::register("vex_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Vex Armor Trim Smithing Template"));
+ self::register("ward_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Ward Armor Trim Smithing Template"));
+ self::register("wayfinder_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Wayfinder Armor Trim Smithing Template"));
+ self::register("wild_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Wild Armor Trim Smithing Template"));
}
}
diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php
new file mode 100644
index 00000000000..cae94c666f5
--- /dev/null
+++ b/src/item/enchantment/AvailableEnchantmentRegistry.php
@@ -0,0 +1,212 @@
+register(Enchantments::PROTECTION(), [Tags::ARMOR], []);
+ $this->register(Enchantments::FIRE_PROTECTION(), [Tags::ARMOR], []);
+ $this->register(Enchantments::FEATHER_FALLING(), [Tags::BOOTS], []);
+ $this->register(Enchantments::BLAST_PROTECTION(), [Tags::ARMOR], []);
+ $this->register(Enchantments::PROJECTILE_PROTECTION(), [Tags::ARMOR], []);
+ $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::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
+ $this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
+ $this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);
+ $this->register(Enchantments::EFFICIENCY(), [Tags::BLOCK_TOOLS], [Tags::SHEARS]);
+ $this->register(Enchantments::FORTUNE(), [Tags::BLOCK_TOOLS], []);
+ $this->register(Enchantments::SILK_TOUCH(), [Tags::BLOCK_TOOLS], [Tags::SHEARS]);
+ $this->register(
+ Enchantments::UNBREAKING(),
+ [Tags::ARMOR, Tags::WEAPONS, Tags::FISHING_ROD],
+ [Tags::SHEARS, Tags::FLINT_AND_STEEL, Tags::SHIELD, Tags::CARROT_ON_STICK, Tags::ELYTRA, Tags::BRUSH]
+ );
+ $this->register(Enchantments::POWER(), [Tags::BOW], []);
+ $this->register(Enchantments::PUNCH(), [Tags::BOW], []);
+ $this->register(Enchantments::FLAME(), [Tags::BOW], []);
+ $this->register(Enchantments::INFINITY(), [Tags::BOW], []);
+ $this->register(
+ Enchantments::MENDING(),
+ [],
+ [Tags::ARMOR, Tags::WEAPONS, Tags::FISHING_ROD,
+ Tags::SHEARS, Tags::FLINT_AND_STEEL, Tags::SHIELD, Tags::CARROT_ON_STICK, Tags::ELYTRA, Tags::BRUSH]
+ );
+ $this->register(Enchantments::VANISHING(), [], [Tags::ALL]);
+ $this->register(Enchantments::SWIFT_SNEAK(), [], [Tags::LEGGINGS]);
+ }
+
+ /**
+ * @param string[] $primaryItemTags
+ * @param string[] $secondaryItemTags
+ */
+ public function register(Enchantment $enchantment, array $primaryItemTags, array $secondaryItemTags) : void{
+ $this->enchantments[spl_object_id($enchantment)] = $enchantment;
+ $this->setPrimaryItemTags($enchantment, $primaryItemTags);
+ $this->setSecondaryItemTags($enchantment, $secondaryItemTags);
+ }
+
+ public function unregister(Enchantment $enchantment) : void{
+ unset($this->enchantments[spl_object_id($enchantment)]);
+ unset($this->primaryItemTags[spl_object_id($enchantment)]);
+ unset($this->secondaryItemTags[spl_object_id($enchantment)]);
+ }
+
+ public function unregisterAll() : void{
+ $this->enchantments = [];
+ $this->primaryItemTags = [];
+ $this->secondaryItemTags = [];
+ }
+
+ public function isRegistered(Enchantment $enchantment) : bool{
+ return isset($this->enchantments[spl_object_id($enchantment)]);
+ }
+
+ /**
+ * Returns primary compatibility tags for the specified enchantment.
+ *
+ * An item matching at least one of these tags (or its descendents) can be:
+ * - Offered this enchantment in an enchanting table
+ * - Enchanted by any means allowed by secondary tags
+ *
+ * @return string[]
+ */
+ public function getPrimaryItemTags(Enchantment $enchantment) : array{
+ return $this->primaryItemTags[spl_object_id($enchantment)] ?? [];
+ }
+
+ /**
+ * @param string[] $tags
+ */
+ public function setPrimaryItemTags(Enchantment $enchantment, array $tags) : void{
+ if(!$this->isRegistered($enchantment)){
+ throw new \LogicException("Cannot set primary item tags for non-registered enchantment");
+ }
+ $this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags);
+ }
+
+ /**
+ * Returns secondary compatibility tags for the specified enchantment.
+ *
+ * An item matching at least one of these tags (or its descendents) can be:
+ * - Combined with an enchanted book with this enchantment in an anvil
+ * - Obtained as loot with this enchantment, e.g. fishing, treasure chests, mob equipment, etc.
+ *
+ * @return string[]
+ */
+ public function getSecondaryItemTags(Enchantment $enchantment) : array{
+ return $this->secondaryItemTags[spl_object_id($enchantment)] ?? [];
+ }
+
+ /**
+ * @param string[] $tags
+ */
+ public function setSecondaryItemTags(Enchantment $enchantment, array $tags) : void{
+ if(!$this->isRegistered($enchantment)){
+ throw new \LogicException("Cannot set secondary item tags for non-registered enchantment");
+ }
+ $this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags);
+ }
+
+ /**
+ * Returns enchantments that can be applied to the specified item in an enchanting table (primary only).
+ *
+ * @return Enchantment[]
+ */
+ public function getPrimaryEnchantmentsForItem(Item $item) : array{
+ $itemTags = $item->getEnchantmentTags();
+ if(count($itemTags) === 0 || $item->hasEnchantments()){
+ return [];
+ }
+
+ return array_filter(
+ $this->enchantments,
+ fn(Enchantment $e) => TagRegistry::getInstance()->isTagArrayIntersection($this->getPrimaryItemTags($e), $itemTags)
+ );
+ }
+
+ /**
+ * Returns all available enchantments compatible with the item.
+ *
+ * Warning: not suitable for obtaining enchantments for an enchanting table
+ * (use {@link AvailableEnchantmentRegistry::getPrimaryEnchantmentsForItem()} for that).
+ *
+ * @return Enchantment[]
+ */
+ public function getAllEnchantmentsForItem(Item $item) : array{
+ if(count($item->getEnchantmentTags()) === 0){
+ return [];
+ }
+
+ return array_filter(
+ $this->enchantments,
+ fn(Enchantment $enchantment) => $this->isAvailableForItem($enchantment, $item)
+ );
+ }
+
+ /**
+ * Returns whether the specified enchantment can be applied to the particular item.
+ *
+ * Warning: not suitable for checking the availability of enchantment for an enchanting table.
+ */
+ public function isAvailableForItem(Enchantment $enchantment, Item $item) : bool{
+ $itemTags = $item->getEnchantmentTags();
+ $tagRegistry = TagRegistry::getInstance();
+
+ return $tagRegistry->isTagArrayIntersection($this->getPrimaryItemTags($enchantment), $itemTags) ||
+ $tagRegistry->isTagArrayIntersection($this->getSecondaryItemTags($enchantment), $itemTags);
+ }
+
+ /**
+ * @return Enchantment[]
+ */
+ public function getAll() : array{
+ return $this->enchantments;
+ }
+}
diff --git a/src/item/enchantment/EnchantingHelper.php b/src/item/enchantment/EnchantingHelper.php
new file mode 100644
index 00000000000..fb4e8f27ce1
--- /dev/null
+++ b/src/item/enchantment/EnchantingHelper.php
@@ -0,0 +1,233 @@
+getTypeId() === ItemTypeIds::BOOK ? Items::ENCHANTED_BOOK() : clone $item;
+
+ foreach($enchantments as $enchantment){
+ $resultItem->addEnchantment($enchantment);
+ }
+
+ return $resultItem;
+ }
+
+ /**
+ * @return EnchantingOption[]
+ */
+ public static function generateOptions(Position $tablePos, Item $input, int $seed) : array{
+ if($input->isNull() || $input->hasEnchantments()){
+ return [];
+ }
+
+ $random = new Random($seed);
+
+ $bookshelfCount = self::countBookshelves($tablePos);
+ $baseRequiredLevel = $random->nextRange(1, 8) + ($bookshelfCount >> 1) + $random->nextRange(0, $bookshelfCount);
+ $topRequiredLevel = (int) floor(max($baseRequiredLevel / 3, 1));
+ $middleRequiredLevel = (int) floor($baseRequiredLevel * 2 / 3 + 1);
+ $bottomRequiredLevel = max($baseRequiredLevel, $bookshelfCount * 2);
+
+ return [
+ self::createOption($random, $input, $topRequiredLevel),
+ self::createOption($random, $input, $middleRequiredLevel),
+ self::createOption($random, $input, $bottomRequiredLevel),
+ ];
+ }
+
+ private static function countBookshelves(Position $tablePos) : int{
+ $bookshelfCount = 0;
+ $world = $tablePos->getWorld();
+
+ for($x = -2; $x <= 2; $x++){
+ for($z = -2; $z <= 2; $z++){
+ // We only check blocks at a distance of 2 blocks from the enchanting table
+ if(abs($x) !== 2 && abs($z) !== 2){
+ continue;
+ }
+
+ // Ensure the space between the bookshelf stack at this X/Z and the enchanting table is empty
+ for($y = 0; $y <= 1; $y++){
+ // Calculate the coordinates of the space between the bookshelf and the enchanting table
+ $spaceX = max(min($x, 1), -1);
+ $spaceZ = max(min($z, 1), -1);
+ $spaceBlock = $world->getBlock($tablePos->add($spaceX, $y, $spaceZ));
+ if($spaceBlock->getTypeId() !== BlockTypeIds::AIR){
+ continue 2;
+ }
+ }
+
+ // Finally, check the number of bookshelves at the current position
+ for($y = 0; $y <= 1; $y++){
+ $block = $world->getBlock($tablePos->add($x, $y, $z));
+ if($block->getTypeId() === BlockTypeIds::BOOKSHELF){
+ $bookshelfCount++;
+ if($bookshelfCount === self::MAX_BOOKSHELF_COUNT){
+ return $bookshelfCount;
+ }
+ }
+ }
+ }
+ }
+
+ return $bookshelfCount;
+ }
+
+ private static function createOption(Random $random, Item $inputItem, int $requiredXpLevel) : EnchantingOption{
+ $enchantingPower = $requiredXpLevel;
+
+ $enchantability = $inputItem->getEnchantability();
+ $enchantingPower = $enchantingPower + $random->nextRange(0, $enchantability >> 2) + $random->nextRange(0, $enchantability >> 2) + 1;
+ // Random bonus for enchanting power between 0.85 and 1.15
+ $bonus = 1 + ($random->nextFloat() + $random->nextFloat() - 1) * 0.15;
+ $enchantingPower = (int) round($enchantingPower * $bonus);
+
+ $resultEnchantments = [];
+ $availableEnchantments = self::getAvailableEnchantments($enchantingPower, $inputItem);
+
+ $lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
+ if($lastEnchantment !== null){
+ $resultEnchantments[] = $lastEnchantment;
+
+ // With probability (power + 1) / 50, continue adding enchantments
+ while($random->nextFloat() <= ($enchantingPower + 1) / 50){
+ // Remove from the list of available enchantments anything that conflicts
+ // with previously-chosen enchantments
+ $availableEnchantments = array_filter(
+ $availableEnchantments,
+ function(EnchantmentInstance $e) use ($lastEnchantment){
+ return $e->getType() !== $lastEnchantment->getType() &&
+ $e->getType()->isCompatibleWith($lastEnchantment->getType());
+ }
+ );
+
+ $lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
+ if($lastEnchantment === null){
+ break;
+ }
+
+ $resultEnchantments[] = $lastEnchantment;
+ $enchantingPower >>= 1;
+ }
+ }
+
+ return new EnchantingOption($requiredXpLevel, self::getRandomOptionName($random), $resultEnchantments);
+ }
+
+ /**
+ * @return EnchantmentInstance[]
+ */
+ private static function getAvailableEnchantments(int $enchantingPower, Item $item) : array{
+ $list = [];
+
+ foreach(EnchantmentRegistry::getInstance()->getPrimaryEnchantmentsForItem($item) as $enchantment){
+ for($lvl = $enchantment->getMaxLevel(); $lvl > 0; $lvl--){
+ if($enchantingPower >= $enchantment->getMinEnchantingPower($lvl) &&
+ $enchantingPower <= $enchantment->getMaxEnchantingPower($lvl)
+ ){
+ $list[] = new EnchantmentInstance($enchantment, $lvl);
+ break;
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param EnchantmentInstance[] $enchantments
+ */
+ private static function getRandomWeightedEnchantment(Random $random, array $enchantments) : ?EnchantmentInstance{
+ if(count($enchantments) === 0){
+ return null;
+ }
+
+ $totalWeight = 0;
+ foreach($enchantments as $enchantment){
+ $totalWeight += $enchantment->getType()->getRarity();
+ }
+
+ $result = null;
+ $randomWeight = $random->nextRange(1, $totalWeight);
+
+ foreach($enchantments as $enchantment){
+ $randomWeight -= $enchantment->getType()->getRarity();
+
+ if($randomWeight <= 0){
+ $result = $enchantment;
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ private static function getRandomOptionName(Random $random) : string{
+ $name = "";
+ for($i = $random->nextRange(5, 15); $i > 0; $i--){
+ $name .= chr($random->nextRange(ord("a"), ord("z")));
+ }
+
+ return $name;
+ }
+}
diff --git a/src/item/enchantment/EnchantingOption.php b/src/item/enchantment/EnchantingOption.php
new file mode 100644
index 00000000000..2bedb0cc419
--- /dev/null
+++ b/src/item/enchantment/EnchantingOption.php
@@ -0,0 +1,66 @@
+requiredXpLevel;
+ }
+
+ /**
+ * Returns the name that will be translated to the 'Standard Galactic Alphabet' client-side.
+ * This can be any arbitrary text string, since the vanilla client cannot read the text anyway.
+ * Example: 'bless creature range free'.
+ */
+ public function getDisplayName() : string{
+ return $this->displayName;
+ }
+
+ /**
+ * Returns the enchantments that will be applied to the item when this option is clicked.
+ *
+ * @return EnchantmentInstance[]
+ */
+ public function getEnchantments() : array{
+ return $this->enchantments;
+ }
+}
diff --git a/src/item/enchantment/Enchantment.php b/src/item/enchantment/Enchantment.php
index c53dfab7c00..948f4648bc2 100644
--- a/src/item/enchantment/Enchantment.php
+++ b/src/item/enchantment/Enchantment.php
@@ -23,9 +23,13 @@
namespace pocketmine\item\enchantment;
+use DaveRandom\CallbackValidator\CallbackType;
+use DaveRandom\CallbackValidator\ParameterType;
+use DaveRandom\CallbackValidator\ReturnType;
use pocketmine\lang\Translatable;
use pocketmine\utils\NotCloneable;
use pocketmine\utils\NotSerializable;
+use pocketmine\utils\Utils;
/**
* Manages enchantment type data.
@@ -34,13 +38,32 @@ class Enchantment{
use NotCloneable;
use NotSerializable;
+ /** @var \Closure(int $level) : int $minEnchantingPower */
+ private \Closure $minEnchantingPower;
+
+ /**
+ * @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower
+ *
+ * @param int $primaryItemFlags @deprecated
+ * @param int $secondaryItemFlags @deprecated
+ * @param int $enchantingPowerRange Value used to calculate the maximum enchanting power (minEnchantingPower + enchantingPowerRange)
+ */
public function __construct(
private Translatable|string $name,
private int $rarity,
private int $primaryItemFlags,
private int $secondaryItemFlags,
- private int $maxLevel
- ){}
+ private int $maxLevel,
+ ?\Closure $minEnchantingPower = null,
+ private int $enchantingPowerRange = 50
+ ){
+ $this->minEnchantingPower = $minEnchantingPower ?? fn(int $level) : int => 1;
+
+ Utils::validateCallableSignature(new CallbackType(
+ new ReturnType("int"),
+ new ParameterType("level", "int")
+ ), $this->minEnchantingPower);
+ }
/**
* Returns a translation key for this enchantment's name.
@@ -58,6 +81,9 @@ public function getRarity() : int{
/**
* Returns a bitset indicating what item types can have this item applied from an enchanting table.
+ *
+ * @deprecated
+ * @see AvailableEnchantmentRegistry::getPrimaryItemTags()
*/
public function getPrimaryItemFlags() : int{
return $this->primaryItemFlags;
@@ -66,6 +92,9 @@ public function getPrimaryItemFlags() : int{
/**
* Returns a bitset indicating what item types cannot have this item applied from an enchanting table, but can from
* an anvil.
+ *
+ * @deprecated
+ * @see AvailableEnchantmentRegistry::getSecondaryItemTags()
*/
public function getSecondaryItemFlags() : int{
return $this->secondaryItemFlags;
@@ -73,6 +102,9 @@ public function getSecondaryItemFlags() : int{
/**
* Returns whether this enchantment can apply to the item type from an enchanting table.
+ *
+ * @deprecated
+ * @see AvailableEnchantmentRegistry
*/
public function hasPrimaryItemType(int $flag) : bool{
return ($this->primaryItemFlags & $flag) !== 0;
@@ -80,6 +112,9 @@ public function hasPrimaryItemType(int $flag) : bool{
/**
* Returns whether this enchantment can apply to the item type from an anvil, if it is not a primary item.
+ *
+ * @deprecated
+ * @see AvailableEnchantmentRegistry
*/
public function hasSecondaryItemType(int $flag) : bool{
return ($this->secondaryItemFlags & $flag) !== 0;
@@ -92,5 +127,34 @@ public function getMaxLevel() : int{
return $this->maxLevel;
}
- //TODO: methods for min/max XP cost bounds based on enchantment level (not needed yet - enchanting is client-side)
+ /**
+ * Returns whether this enchantment can be applied to the item along with the given enchantment.
+ */
+ public function isCompatibleWith(Enchantment $other) : bool{
+ return IncompatibleEnchantmentRegistry::getInstance()->areCompatible($this, $other);
+ }
+
+ /**
+ * Returns the minimum enchanting power value required for the particular level of the enchantment
+ * to be available in an enchanting table.
+ *
+ * Enchanting power is a random value based on the number of bookshelves around an enchanting table
+ * and the enchantability of the item being enchanted. It is only used when determining the available
+ * enchantments for the enchantment options.
+ */
+ public function getMinEnchantingPower(int $level) : int{
+ return ($this->minEnchantingPower)($level);
+ }
+
+ /**
+ * Returns the maximum enchanting power value allowed for the particular level of the enchantment
+ * to be available in an enchanting table.
+ *
+ * Enchanting power is a random value based on the number of bookshelves around an enchanting table
+ * and the enchantability of the item being enchanted. It is only used when determining the available
+ * enchantments for the enchantment options.
+ */
+ public function getMaxEnchantingPower(int $level) : int{
+ return $this->getMinEnchantingPower($level) + $this->enchantingPowerRange;
+ }
}
diff --git a/src/item/enchantment/IncompatibleEnchantmentGroups.php b/src/item/enchantment/IncompatibleEnchantmentGroups.php
new file mode 100644
index 00000000000..74574562c5d
--- /dev/null
+++ b/src/item/enchantment/IncompatibleEnchantmentGroups.php
@@ -0,0 +1,34 @@
+>
+ * @var true[][]
+ */
+ private array $incompatibilityMap = [];
+
+ private function __construct(){
+ $this->register(Groups::PROTECTION, [Enchantments::PROTECTION(), Enchantments::FIRE_PROTECTION(), Enchantments::BLAST_PROTECTION(), Enchantments::PROJECTILE_PROTECTION()]);
+ $this->register(Groups::BOW_INFINITE, [Enchantments::INFINITY(), Enchantments::MENDING()]);
+ $this->register(Groups::BLOCK_DROPS, [Enchantments::FORTUNE(), Enchantments::SILK_TOUCH()]);
+ }
+
+ /**
+ * Register incompatibility for an enchantment group.
+ *
+ * All enchantments belonging to the same group are incompatible with each other,
+ * i.e. they cannot be added together on the same item.
+ *
+ * @param Enchantment[] $enchantments
+ */
+ public function register(string $tag, array $enchantments) : void{
+ foreach($enchantments as $enchantment){
+ $this->incompatibilityMap[spl_object_id($enchantment)][$tag] = true;
+ }
+ }
+
+ /**
+ * Unregister incompatibility for some enchantments of a particular group.
+ *
+ * @param Enchantment[] $enchantments
+ */
+ public function unregister(string $tag, array $enchantments) : void{
+ foreach($enchantments as $enchantment){
+ unset($this->incompatibilityMap[spl_object_id($enchantment)][$tag]);
+ }
+ }
+
+ /**
+ * Unregister incompatibility for all enchantments of a particular group.
+ */
+ public function unregisterAll(string $tag) : void{
+ foreach($this->incompatibilityMap as $id => $tags){
+ unset($this->incompatibilityMap[$id][$tag]);
+ }
+ }
+
+ /**
+ * Returns whether two enchantments can be applied to the same item.
+ */
+ public function areCompatible(Enchantment $first, Enchantment $second) : bool{
+ $firstIncompatibilities = $this->incompatibilityMap[spl_object_id($first)] ?? [];
+ $secondIncompatibilities = $this->incompatibilityMap[spl_object_id($second)] ?? [];
+ return count(array_intersect_key($firstIncompatibilities, $secondIncompatibilities)) === 0;
+ }
+}
diff --git a/src/item/enchantment/ItemEnchantmentTagRegistry.php b/src/item/enchantment/ItemEnchantmentTagRegistry.php
new file mode 100644
index 00000000000..210cd8e8644
--- /dev/null
+++ b/src/item/enchantment/ItemEnchantmentTagRegistry.php
@@ -0,0 +1,190 @@
+>
+ * @var string[][]
+ */
+ private array $tagMap = [];
+
+ private function __construct(){
+ $this->register(Tags::ARMOR, [Tags::HELMET, Tags::CHESTPLATE, Tags::LEGGINGS, Tags::BOOTS]);
+ $this->register(Tags::SHIELD);
+ $this->register(Tags::SWORD);
+ $this->register(Tags::TRIDENT);
+ $this->register(Tags::BOW);
+ $this->register(Tags::CROSSBOW);
+ $this->register(Tags::SHEARS);
+ $this->register(Tags::FLINT_AND_STEEL);
+ $this->register(Tags::BLOCK_TOOLS, [Tags::AXE, Tags::PICKAXE, Tags::SHOVEL, Tags::HOE]);
+ $this->register(Tags::FISHING_ROD);
+ $this->register(Tags::CARROT_ON_STICK);
+ $this->register(Tags::COMPASS);
+ $this->register(Tags::MASK);
+ $this->register(Tags::ELYTRA);
+ $this->register(Tags::BRUSH);
+ $this->register(Tags::WEAPONS, [
+ Tags::SWORD,
+ Tags::TRIDENT,
+ Tags::BOW,
+ Tags::CROSSBOW,
+ Tags::BLOCK_TOOLS,
+ ]);
+ }
+
+ /**
+ * Register tag and its nested tags.
+ *
+ * @param string[] $nestedTags
+ */
+ public function register(string $tag, array $nestedTags = []) : void{
+ $this->assertNotInternalTag($tag);
+
+ foreach($nestedTags as $nestedTag){
+ if(!isset($this->tagMap[$nestedTag])){
+ $this->register($nestedTag);
+ }
+ $this->tagMap[$tag][] = $nestedTag;
+ }
+
+ if(!isset($this->tagMap[$tag])){
+ $this->tagMap[$tag] = [];
+ $this->tagMap[Tags::ALL][] = $tag;
+ }
+ }
+
+ public function unregister(string $tag) : void{
+ if(!isset($this->tagMap[$tag])){
+ return;
+ }
+ $this->assertNotInternalTag($tag);
+
+ unset($this->tagMap[$tag]);
+
+ foreach(Utils::stringifyKeys($this->tagMap) as $key => $nestedTags){
+ if(($nestedKey = array_search($tag, $nestedTags, true)) !== false){
+ unset($this->tagMap[$key][$nestedKey]);
+ }
+ }
+ }
+
+ /**
+ * Remove specified nested tags.
+ *
+ * @param string[] $nestedTags
+ */
+ public function removeNested(string $tag, array $nestedTags) : void{
+ $this->assertNotInternalTag($tag);
+ $this->tagMap[$tag] = array_diff($this->tagMap[$tag], $nestedTags);
+ }
+
+ /**
+ * Returns nested tags of a particular tag.
+ *
+ * @return string[]
+ */
+ public function getNested(string $tag) : array{
+ return $this->tagMap[$tag] ?? [];
+ }
+
+ /**
+ * @param string[] $firstTags
+ * @param string[] $secondTags
+ */
+ public function isTagArrayIntersection(array $firstTags, array $secondTags) : bool{
+ if(count($firstTags) === 0 || count($secondTags) === 0){
+ return false;
+ }
+
+ $firstLeafTags = $this->getLeafTagsForArray($firstTags);
+ $secondLeafTags = $this->getLeafTagsForArray($secondTags);
+
+ return count(array_intersect($firstLeafTags, $secondLeafTags)) !== 0;
+ }
+
+ /**
+ * Returns all tags that are recursively nested within each tag in the array and do not have any nested tags.
+ *
+ * @param string[] $tags
+ *
+ * @return string[]
+ */
+ private function getLeafTagsForArray(array $tags) : array{
+ $leafTagArrays = [];
+ foreach($tags as $tag){
+ $leafTagArrays[] = $this->getLeafTags($tag);
+ }
+ return array_unique(array_merge(...$leafTagArrays));
+ }
+
+ /**
+ * Returns all tags that are recursively nested within the given tag and do not have any nested tags.
+ *
+ * @return string[]
+ */
+ private function getLeafTags(string $tag) : array{
+ $result = [];
+ $tagsToHandle = [$tag];
+
+ while(count($tagsToHandle) !== 0){
+ $currentTag = array_shift($tagsToHandle);
+ $nestedTags = $this->getNested($currentTag);
+
+ if(count($nestedTags) === 0){
+ $result[] = $currentTag;
+ }else{
+ $tagsToHandle = array_merge($tagsToHandle, $nestedTags);
+ }
+ }
+
+ return $result;
+ }
+
+ private function assertNotInternalTag(string $tag) : void{
+ if($tag === Tags::ALL){
+ throw new \InvalidArgumentException(
+ "Cannot perform any operations on the internal item enchantment tag '$tag'"
+ );
+ }
+ }
+}
diff --git a/src/item/enchantment/ItemEnchantmentTags.php b/src/item/enchantment/ItemEnchantmentTags.php
new file mode 100644
index 00000000000..50abf8db3b7
--- /dev/null
+++ b/src/item/enchantment/ItemEnchantmentTags.php
@@ -0,0 +1,57 @@
+typeModifier = $typeModifier;
if($applicableDamageTypes !== null){
diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php
index e76e7164241..47a750ff27d 100644
--- a/src/item/enchantment/StringToEnchantmentParser.php
+++ b/src/item/enchantment/StringToEnchantmentParser.php
@@ -43,6 +43,7 @@ private static function make() : self{
$result->register("fire_aspect", fn() => VanillaEnchantments::FIRE_ASPECT());
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
$result->register("flame", fn() => VanillaEnchantments::FLAME());
+ $result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
$result->register("mending", fn() => VanillaEnchantments::MENDING());
@@ -51,6 +52,7 @@ private static function make() : self{
$result->register("protection", fn() => VanillaEnchantments::PROTECTION());
$result->register("punch", fn() => VanillaEnchantments::PUNCH());
$result->register("respiration", fn() => VanillaEnchantments::RESPIRATION());
+ $result->register("aqua_affinity", fn() => VanillaEnchantments::AQUA_AFFINITY());
$result->register("sharpness", fn() => VanillaEnchantments::SHARPNESS());
$result->register("silk_touch", fn() => VanillaEnchantments::SILK_TOUCH());
$result->register("swift_sneak", fn() => VanillaEnchantments::SWIFT_SNEAK());
diff --git a/src/item/enchantment/VanillaEnchantments.php b/src/item/enchantment/VanillaEnchantments.php
index 2be5eed71e9..19ce3971633 100644
--- a/src/item/enchantment/VanillaEnchantments.php
+++ b/src/item/enchantment/VanillaEnchantments.php
@@ -33,12 +33,14 @@
* @see build/generate-registry-annotations.php
* @generate-registry-docblock
*
+ * @method static Enchantment AQUA_AFFINITY()
* @method static ProtectionEnchantment BLAST_PROTECTION()
* @method static Enchantment EFFICIENCY()
* @method static ProtectionEnchantment FEATHER_FALLING()
* @method static FireAspectEnchantment FIRE_ASPECT()
* @method static ProtectionEnchantment FIRE_PROTECTION()
* @method static Enchantment FLAME()
+ * @method static Enchantment FORTUNE()
* @method static Enchantment INFINITY()
* @method static KnockbackEnchantment KNOCKBACK()
* @method static Enchantment MENDING()
@@ -58,46 +60,233 @@ final class VanillaEnchantments{
use RegistryTrait;
protected static function setup() : void{
- self::register("PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_all(), Rarity::COMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 0.75, null));
- self::register("FIRE_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_fire(), Rarity::UNCOMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.25, [
- EntityDamageEvent::CAUSE_FIRE,
- EntityDamageEvent::CAUSE_FIRE_TICK,
- EntityDamageEvent::CAUSE_LAVA
- //TODO: check fireballs
- ]));
- self::register("FEATHER_FALLING", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_fall(), Rarity::UNCOMMON, ItemFlags::FEET, ItemFlags::NONE, 4, 2.5, [
- EntityDamageEvent::CAUSE_FALL
- ]));
- self::register("BLAST_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_explosion(), Rarity::RARE, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.5, [
- EntityDamageEvent::CAUSE_BLOCK_EXPLOSION,
- EntityDamageEvent::CAUSE_ENTITY_EXPLOSION
- ]));
- self::register("PROJECTILE_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_projectile(), Rarity::UNCOMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.5, [
- EntityDamageEvent::CAUSE_PROJECTILE
- ]));
- self::register("THORNS", new Enchantment(KnownTranslationFactory::enchantment_thorns(), Rarity::MYTHIC, ItemFlags::TORSO, ItemFlags::HEAD | ItemFlags::LEGS | ItemFlags::FEET, 3));
- self::register("RESPIRATION", new Enchantment(KnownTranslationFactory::enchantment_oxygen(), Rarity::RARE, ItemFlags::HEAD, ItemFlags::NONE, 3));
+ self::register("PROTECTION", new ProtectionEnchantment(
+ KnownTranslationFactory::enchantment_protect_all(),
+ Rarity::COMMON,
+ 0,
+ 0,
+ 4,
+ 0.75,
+ null,
+ fn(int $level) : int => 11 * ($level - 1) + 1,
+ 20
+ ));
+ self::register("FIRE_PROTECTION", new ProtectionEnchantment(
+ KnownTranslationFactory::enchantment_protect_fire(),
+ Rarity::UNCOMMON,
+ 0,
+ 0,
+ 4,
+ 1.25,
+ [
+ EntityDamageEvent::CAUSE_FIRE,
+ EntityDamageEvent::CAUSE_FIRE_TICK,
+ EntityDamageEvent::CAUSE_LAVA
+ //TODO: check fireballs
+ ],
+ fn(int $level) : int => 8 * ($level - 1) + 10,
+ 12
+ ));
+ self::register("FEATHER_FALLING", new ProtectionEnchantment(
+ KnownTranslationFactory::enchantment_protect_fall(),
+ Rarity::UNCOMMON,
+ 0,
+ 0,
+ 4,
+ 2.5,
+ [
+ EntityDamageEvent::CAUSE_FALL
+ ],
+ fn(int $level) : int => 6 * ($level - 1) + 5,
+ 10
+ ));
+ self::register("BLAST_PROTECTION", new ProtectionEnchantment(
+ KnownTranslationFactory::enchantment_protect_explosion(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 4,
+ 1.5,
+ [
+ EntityDamageEvent::CAUSE_BLOCK_EXPLOSION,
+ EntityDamageEvent::CAUSE_ENTITY_EXPLOSION
+ ],
+ fn(int $level) : int => 8 * ($level - 1) + 5,
+ 12
+ ));
+ self::register("PROJECTILE_PROTECTION", new ProtectionEnchantment(
+ KnownTranslationFactory::enchantment_protect_projectile(),
+ Rarity::UNCOMMON,
+ 0,
+ 0,
+ 4,
+ 1.5,
+ [
+ EntityDamageEvent::CAUSE_PROJECTILE
+ ],
+ fn(int $level) : int => 6 * ($level - 1) + 3,
+ 15
+ ));
+ self::register("THORNS", new Enchantment(
+ KnownTranslationFactory::enchantment_thorns(),
+ Rarity::MYTHIC,
+ 0,
+ 0,
+ 3,
+ fn(int $level) : int => 20 * ($level - 1) + 10,
+ 50
+ ));
+ self::register("RESPIRATION", new Enchantment(
+ KnownTranslationFactory::enchantment_oxygen(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 3,
+ fn(int $level) : int => 10 * $level,
+ 30
+ ));
+ self::register("AQUA_AFFINITY", new Enchantment(
+ KnownTranslationFactory::enchantment_waterWorker(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 1,
+ null,
+ 40
+ ));
- self::register("SHARPNESS", new SharpnessEnchantment(KnownTranslationFactory::enchantment_damage_all(), Rarity::COMMON, ItemFlags::SWORD, ItemFlags::AXE, 5));
- //TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
+ self::register("SHARPNESS", new SharpnessEnchantment(
+ KnownTranslationFactory::enchantment_damage_all(),
+ Rarity::COMMON,
+ 0,
+ 0,
+ 5,
+ fn(int $level) : int => 11 * ($level - 1) + 1,
+ 20
+ ));
+ self::register("KNOCKBACK", new KnockbackEnchantment(
+ KnownTranslationFactory::enchantment_knockback(),
+ Rarity::UNCOMMON,
+ 0,
+ 0,
+ 2,
+ fn(int $level) : int => 20 * ($level - 1) + 5,
+ 50
+ ));
+ self::register("FIRE_ASPECT", new FireAspectEnchantment(
+ KnownTranslationFactory::enchantment_fire(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 2,
+ fn(int $level) : int => 20 * ($level - 1) + 10,
+ 50
+ ));
+ //TODO: smite, bane of arthropods, looting (these don't make sense now because their applicable mobs don't exist yet)
- self::register("KNOCKBACK", new KnockbackEnchantment(KnownTranslationFactory::enchantment_knockback(), Rarity::UNCOMMON, ItemFlags::SWORD, ItemFlags::NONE, 2));
- self::register("FIRE_ASPECT", new FireAspectEnchantment(KnownTranslationFactory::enchantment_fire(), Rarity::RARE, ItemFlags::SWORD, ItemFlags::NONE, 2));
+ self::register("EFFICIENCY", new Enchantment(
+ KnownTranslationFactory::enchantment_digging(),
+ Rarity::COMMON,
+ 0,
+ 0,
+ 5,
+ fn(int $level) : int => 10 * ($level - 1) + 1,
+ 50
+ ));
+ self::register("FORTUNE", new Enchantment(
+ KnownTranslationFactory::enchantment_lootBonusDigger(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 3,
+ fn(int $level) : int => 9 * ($level - 1) + 15,
+ 50
+ ));
+ self::register("SILK_TOUCH", new Enchantment(
+ KnownTranslationFactory::enchantment_untouching(),
+ Rarity::MYTHIC,
+ 0,
+ 0,
+ 1,
+ fn(int $level) : int => 15,
+ 50
+ ));
+ self::register("UNBREAKING", new Enchantment(
+ KnownTranslationFactory::enchantment_durability(),
+ Rarity::UNCOMMON,
+ 0,
+ 0,
+ 3,
+ fn(int $level) : int => 8 * ($level - 1) + 5,
+ 50
+ ));
- self::register("EFFICIENCY", new Enchantment(KnownTranslationFactory::enchantment_digging(), Rarity::COMMON, ItemFlags::DIG, ItemFlags::SHEARS, 5));
- self::register("SILK_TOUCH", new Enchantment(KnownTranslationFactory::enchantment_untouching(), Rarity::MYTHIC, ItemFlags::DIG, ItemFlags::SHEARS, 1));
- self::register("UNBREAKING", new Enchantment(KnownTranslationFactory::enchantment_durability(), Rarity::UNCOMMON, ItemFlags::DIG | ItemFlags::ARMOR | ItemFlags::FISHING_ROD | ItemFlags::BOW, ItemFlags::TOOL | ItemFlags::CARROT_STICK | ItemFlags::ELYTRA, 3));
+ self::register("POWER", new Enchantment(
+ KnownTranslationFactory::enchantment_arrowDamage(),
+ Rarity::COMMON,
+ 0,
+ 0,
+ 5,
+ fn(int $level) : int => 10 * ($level - 1) + 1,
+ 15
+ ));
+ self::register("PUNCH", new Enchantment(
+ KnownTranslationFactory::enchantment_arrowKnockback(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 2,
+ fn(int $level) : int => 20 * ($level - 1) + 12,
+ 25
+ ));
+ self::register("FLAME", new Enchantment(
+ KnownTranslationFactory::enchantment_arrowFire(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 1,
+ fn(int $level) : int => 20,
+ 30
+ ));
+ self::register("INFINITY", new Enchantment(
+ KnownTranslationFactory::enchantment_arrowInfinite(),
+ Rarity::MYTHIC,
+ 0,
+ 0,
+ 1,
+ fn(int $level) : int => 20,
+ 30
+ ));
- self::register("POWER", new Enchantment(KnownTranslationFactory::enchantment_arrowDamage(), Rarity::COMMON, ItemFlags::BOW, ItemFlags::NONE, 5));
- self::register("PUNCH", new Enchantment(KnownTranslationFactory::enchantment_arrowKnockback(), Rarity::RARE, ItemFlags::BOW, ItemFlags::NONE, 2));
- self::register("FLAME", new Enchantment(KnownTranslationFactory::enchantment_arrowFire(), Rarity::RARE, ItemFlags::BOW, ItemFlags::NONE, 1));
- self::register("INFINITY", new Enchantment(KnownTranslationFactory::enchantment_arrowInfinite(), Rarity::MYTHIC, ItemFlags::BOW, ItemFlags::NONE, 1));
+ self::register("MENDING", new Enchantment(
+ KnownTranslationFactory::enchantment_mending(),
+ Rarity::RARE,
+ 0,
+ 0,
+ 1,
+ fn(int $level) : int => 25,
+ 50
+ ));
- self::register("MENDING", new Enchantment(KnownTranslationFactory::enchantment_mending(), Rarity::RARE, ItemFlags::NONE, ItemFlags::ALL, 1));
+ self::register("VANISHING", new Enchantment(
+ KnownTranslationFactory::enchantment_curse_vanishing(),
+ Rarity::MYTHIC,
+ 0,
+ 0,
+ 1,
+ fn(int $level) : int => 25,
+ 25
+ ));
- self::register("VANISHING", new Enchantment(KnownTranslationFactory::enchantment_curse_vanishing(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::ALL, 1));
-
- self::register("SWIFT_SNEAK", new Enchantment(KnownTranslationFactory::enchantment_swift_sneak(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::LEGS, 3));
+ self::register("SWIFT_SNEAK", new Enchantment(
+ KnownTranslationFactory::enchantment_swift_sneak(),
+ Rarity::MYTHIC,
+ 0,
+ 0,
+ 3,
+ fn(int $level) : int => 10 * $level,
+ 5
+ ));
}
protected static function register(string $name, Enchantment $member) : void{
diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php
index ea8c2952e5e..8153a80d6d1 100644
--- a/src/lang/KnownTranslationFactory.php
+++ b/src/lang/KnownTranslationFactory.php
@@ -603,6 +603,31 @@ public static function commands_whitelist_usage() : Translatable{
return new Translatable(KnownTranslationKeys::COMMANDS_WHITELIST_USAGE, []);
}
+ public static function commands_xp_failure_widthdrawXp() : Translatable{
+ return new Translatable(KnownTranslationKeys::COMMANDS_XP_FAILURE_WIDTHDRAWXP, []);
+ }
+
+ public static function commands_xp_success(Translatable|string $param0, Translatable|string $param1) : Translatable{
+ return new Translatable(KnownTranslationKeys::COMMANDS_XP_SUCCESS, [
+ 0 => $param0,
+ 1 => $param1,
+ ]);
+ }
+
+ public static function commands_xp_success_levels(Translatable|string $param0, Translatable|string $param1) : Translatable{
+ return new Translatable(KnownTranslationKeys::COMMANDS_XP_SUCCESS_LEVELS, [
+ 0 => $param0,
+ 1 => $param1,
+ ]);
+ }
+
+ public static function commands_xp_success_negative_levels(Translatable|string $param0, Translatable|string $param1) : Translatable{
+ return new Translatable(KnownTranslationKeys::COMMANDS_XP_SUCCESS_NEGATIVE_LEVELS, [
+ 0 => $param0,
+ 1 => $param1,
+ ]);
+ }
+
public static function death_attack_anvil(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_ANVIL, [
0 => $param0,
@@ -667,6 +692,12 @@ public static function death_attack_fireworks(Translatable|string $param0) : Tra
]);
}
+ public static function death_attack_flyIntoWall(Translatable|string $param0) : Translatable{
+ return new Translatable(KnownTranslationKeys::DEATH_ATTACK_FLYINTOWALL, [
+ 0 => $param0,
+ ]);
+ }
+
public static function death_attack_generic(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [
0 => $param0,
@@ -1025,6 +1056,14 @@ public static function item_record_chirp_desc() : Translatable{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_CHIRP_DESC, []);
}
+ public static function item_record_creator_desc() : Translatable{
+ return new Translatable(KnownTranslationKeys::ITEM_RECORD_CREATOR_DESC, []);
+ }
+
+ public static function item_record_creator_music_box_desc() : Translatable{
+ return new Translatable(KnownTranslationKeys::ITEM_RECORD_CREATOR_MUSIC_BOX_DESC, []);
+ }
+
public static function item_record_far_desc() : Translatable{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_FAR_DESC, []);
}
@@ -1045,6 +1084,14 @@ public static function item_record_pigstep_desc() : Translatable{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_PIGSTEP_DESC, []);
}
+ public static function item_record_precipice_desc() : Translatable{
+ return new Translatable(KnownTranslationKeys::ITEM_RECORD_PRECIPICE_DESC, []);
+ }
+
+ public static function item_record_relic_desc() : Translatable{
+ return new Translatable(KnownTranslationKeys::ITEM_RECORD_RELIC_DESC, []);
+ }
+
public static function item_record_stal_desc() : Translatable{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_STAL_DESC, []);
}
@@ -1536,6 +1583,14 @@ public static function pocketmine_command_whitelist_description() : Translatable
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WHITELIST_DESCRIPTION, []);
}
+ public static function pocketmine_command_xp_description() : Translatable{
+ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_XP_DESCRIPTION, []);
+ }
+
+ public static function pocketmine_command_xp_usage() : Translatable{
+ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_XP_USAGE, []);
+ }
+
public static function pocketmine_crash_archive(Translatable|string $param0, Translatable|string $param1) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_CRASH_ARCHIVE, [
0 => $param0,
@@ -2056,6 +2111,14 @@ public static function pocketmine_permission_command_whitelist_remove() : Transl
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE, []);
}
+ public static function pocketmine_permission_command_xp_other() : Translatable{
+ return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_XP_OTHER, []);
+ }
+
+ public static function pocketmine_permission_command_xp_self() : Translatable{
+ return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_XP_SELF, []);
+ }
+
public static function pocketmine_permission_group_console() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_CONSOLE, []);
}
diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php
index c8345273060..4805d0c568a 100644
--- a/src/lang/KnownTranslationKeys.php
+++ b/src/lang/KnownTranslationKeys.php
@@ -137,6 +137,10 @@ final class KnownTranslationKeys{
public const COMMANDS_WHITELIST_REMOVE_SUCCESS = "commands.whitelist.remove.success";
public const COMMANDS_WHITELIST_REMOVE_USAGE = "commands.whitelist.remove.usage";
public const COMMANDS_WHITELIST_USAGE = "commands.whitelist.usage";
+ public const COMMANDS_XP_FAILURE_WIDTHDRAWXP = "commands.xp.failure.widthdrawXp";
+ public const COMMANDS_XP_SUCCESS = "commands.xp.success";
+ public const COMMANDS_XP_SUCCESS_LEVELS = "commands.xp.success.levels";
+ public const COMMANDS_XP_SUCCESS_NEGATIVE_LEVELS = "commands.xp.success.negative.levels";
public const DEATH_ATTACK_ANVIL = "death.attack.anvil";
public const DEATH_ATTACK_ARROW = "death.attack.arrow";
public const DEATH_ATTACK_ARROW_ITEM = "death.attack.arrow.item";
@@ -147,6 +151,7 @@ final class KnownTranslationKeys{
public const DEATH_ATTACK_FALL = "death.attack.fall";
public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock";
public const DEATH_ATTACK_FIREWORKS = "death.attack.fireworks";
+ public const DEATH_ATTACK_FLYINTOWALL = "death.attack.flyIntoWall";
public const DEATH_ATTACK_GENERIC = "death.attack.generic";
public const DEATH_ATTACK_INFIRE = "death.attack.inFire";
public const DEATH_ATTACK_INWALL = "death.attack.inWall";
@@ -227,11 +232,15 @@ final class KnownTranslationKeys{
public const ITEM_RECORD_BLOCKS_DESC = "item.record_blocks.desc";
public const ITEM_RECORD_CAT_DESC = "item.record_cat.desc";
public const ITEM_RECORD_CHIRP_DESC = "item.record_chirp.desc";
+ public const ITEM_RECORD_CREATOR_DESC = "item.record_creator.desc";
+ public const ITEM_RECORD_CREATOR_MUSIC_BOX_DESC = "item.record_creator_music_box.desc";
public const ITEM_RECORD_FAR_DESC = "item.record_far.desc";
public const ITEM_RECORD_MALL_DESC = "item.record_mall.desc";
public const ITEM_RECORD_MELLOHI_DESC = "item.record_mellohi.desc";
public const ITEM_RECORD_OTHERSIDE_DESC = "item.record_otherside.desc";
public const ITEM_RECORD_PIGSTEP_DESC = "item.record_pigstep.desc";
+ public const ITEM_RECORD_PRECIPICE_DESC = "item.record_precipice.desc";
+ public const ITEM_RECORD_RELIC_DESC = "item.record_relic.desc";
public const ITEM_RECORD_STAL_DESC = "item.record_stal.desc";
public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
public const ITEM_RECORD_WAIT_DESC = "item.record_wait.desc";
@@ -336,6 +345,8 @@ final class KnownTranslationKeys{
public const POCKETMINE_COMMAND_VERSION_SERVERSOFTWAREVERSION = "pocketmine.command.version.serverSoftwareVersion";
public const POCKETMINE_COMMAND_VERSION_USAGE = "pocketmine.command.version.usage";
public const POCKETMINE_COMMAND_WHITELIST_DESCRIPTION = "pocketmine.command.whitelist.description";
+ public const POCKETMINE_COMMAND_XP_DESCRIPTION = "pocketmine.command.xp.description";
+ public const POCKETMINE_COMMAND_XP_USAGE = "pocketmine.command.xp.usage";
public const POCKETMINE_CRASH_ARCHIVE = "pocketmine.crash.archive";
public const POCKETMINE_CRASH_CREATE = "pocketmine.crash.create";
public const POCKETMINE_CRASH_ERROR = "pocketmine.crash.error";
@@ -449,6 +460,8 @@ final class KnownTranslationKeys{
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_LIST = "pocketmine.permission.command.whitelist.list";
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_RELOAD = "pocketmine.permission.command.whitelist.reload";
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE = "pocketmine.permission.command.whitelist.remove";
+ public const POCKETMINE_PERMISSION_COMMAND_XP_OTHER = "pocketmine.permission.command.xp.other";
+ public const POCKETMINE_PERMISSION_COMMAND_XP_SELF = "pocketmine.permission.command.xp.self";
public const POCKETMINE_PERMISSION_GROUP_CONSOLE = "pocketmine.permission.group.console";
public const POCKETMINE_PERMISSION_GROUP_OPERATOR = "pocketmine.permission.group.operator";
public const POCKETMINE_PERMISSION_GROUP_USER = "pocketmine.permission.group.user";
diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php
index 0f8f60fe678..13b5db5b795 100644
--- a/src/network/mcpe/ChunkRequestTask.php
+++ b/src/network/mcpe/ChunkRequestTask.php
@@ -28,62 +28,56 @@
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\LevelChunkPacket;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
+use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\serializer\ChunkSerializer;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\FastChunkSerializer;
+use function chr;
class ChunkRequestTask extends AsyncTask{
private const TLS_KEY_PROMISE = "promise";
- private const TLS_KEY_ERROR_HOOK = "errorHook";
protected string $chunk;
protected int $chunkX;
protected int $chunkZ;
+ /** @phpstan-var DimensionIds::* */
+ private int $dimensionId;
/** @phpstan-var NonThreadSafeValue */
protected NonThreadSafeValue $compressor;
private string $tiles;
/**
- * @phpstan-param (\Closure() : void)|null $onError
+ * @phpstan-param DimensionIds::* $dimensionId
*/
- public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor, ?\Closure $onError = null){
+ public function __construct(int $chunkX, int $chunkZ, int $dimensionId, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor){
$this->compressor = new NonThreadSafeValue($compressor);
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
$this->chunkX = $chunkX;
$this->chunkZ = $chunkZ;
+ $this->dimensionId = $dimensionId;
$this->tiles = ChunkSerializer::serializeTiles($chunk);
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
- $this->storeLocal(self::TLS_KEY_ERROR_HOOK, $onError);
}
public function onRun() : void{
$chunk = FastChunkSerializer::deserializeTerrain($this->chunk);
- $subCount = ChunkSerializer::getSubChunkCount($chunk);
+ $dimensionId = $this->dimensionId;
+
+ $subCount = ChunkSerializer::getSubChunkCount($chunk, $dimensionId);
$converter = TypeConverter::getInstance();
- $encoderContext = new PacketSerializerContext($converter->getItemTypeDictionary());
- $payload = ChunkSerializer::serializeFullChunk($chunk, $converter->getBlockTranslator(), $encoderContext, $this->tiles);
+ $payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $this->tiles);
$stream = new BinaryStream();
- PacketBatch::encodePackets($stream, $encoderContext, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload)]);
- $this->setResult($this->compressor->deserialize()->compress($stream->getBuffer()));
- }
+ PacketBatch::encodePackets($stream, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]);
- public function onError() : void{
- /**
- * @var \Closure|null $hook
- * @phpstan-var (\Closure() : void)|null $hook
- */
- $hook = $this->fetchLocal(self::TLS_KEY_ERROR_HOOK);
- if($hook !== null){
- $hook();
- }
+ $compressor = $this->compressor->deserialize();
+ $this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getBuffer()));
}
public function onCompletion() : void{
diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php
index bdd3a892df8..e4c303121f7 100644
--- a/src/network/mcpe/InventoryManager.php
+++ b/src/network/mcpe/InventoryManager.php
@@ -35,10 +35,12 @@
use pocketmine\block\inventory\SmithingTableInventory;
use pocketmine\block\inventory\StonecutterInventory;
use pocketmine\crafting\FurnaceType;
-use pocketmine\inventory\CreativeInventory;
+use pocketmine\data\bedrock\EnchantmentIdMap;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\inventory\transaction\InventoryTransaction;
+use pocketmine\item\enchantment\EnchantingOption;
+use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\network\mcpe\cache\CreativeInventoryCache;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
@@ -47,8 +49,12 @@
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
+use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
+use pocketmine\network\mcpe\protocol\types\Enchant;
+use pocketmine\network\mcpe\protocol\types\EnchantOption as ProtocolEnchantOption;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
+use pocketmine\network\mcpe\protocol\types\inventory\FullContainerName;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
@@ -58,7 +64,9 @@
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet;
+use function array_fill_keys;
use function array_keys;
+use function array_map;
use function array_search;
use function count;
use function get_class;
@@ -89,6 +97,7 @@ class InventoryManager{
private array $complexSlotToInventoryMap = [];
private int $lastInventoryNetworkId = ContainerIds::FIRST;
+ private int $currentWindowType = WindowTypes::CONTAINER;
private int $clientSelectedHotbarSlot = -1;
@@ -104,12 +113,18 @@ class InventoryManager{
private bool $fullSyncRequested = false;
+ /** @var int[] network recipe ID => enchanting table option index */
+ private array $enchantingTableOptions = [];
+ //TODO: this should be based on the total number of crafting recipes - if there are ever 100k recipes, this will
+ //conflict with regular recipes
+ private int $nextEnchantingTableOptionId = 100000;
+
public function __construct(
private Player $player,
private NetworkSession $session
){
$this->containerOpenCallbacks = new ObjectSet();
- $this->containerOpenCallbacks->add(\Closure::fromCallable([self::class, 'createContainerOpen']));
+ $this->containerOpenCallbacks->add(self::createContainerOpen(...));
$this->add(ContainerIds::INVENTORY, $this->player->getInventory());
$this->add(ContainerIds::OFFHAND, $this->player->getOffHandInventory());
@@ -117,9 +132,7 @@ public function __construct(
$this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory());
$this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid());
- $this->player->getInventory()->getHeldItemIndexChangeListeners()->add(function() : void{
- $this->syncSelectedHotbarSlot();
- });
+ $this->player->getInventory()->getHeldItemIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...));
}
private function associateIdWithInventory(int $id, Inventory $inventory) : void{
@@ -316,9 +329,15 @@ public function onCurrentWindowChange(Inventory $inventory) : void{
foreach($this->containerOpenCallbacks as $callback){
$pks = $callback($windowId, $inventory);
if($pks !== null){
+ $windowType = null;
foreach($pks as $pk){
+ if($pk instanceof ContainerOpenPacket){
+ //workaround useless bullshit in 1.21 - ContainerClose requires a type now for some reason
+ $windowType = $pk->windowType;
+ }
$this->session->sendDataPacket($pk);
}
+ $this->currentWindowType = $windowType ?? WindowTypes::CONTAINER;
$this->syncContents($inventory);
return;
}
@@ -341,11 +360,11 @@ protected static function createContainerOpen(int $id, Inventory $inv) : ?array{
$blockPosition = BlockPosition::fromVector3($inv->getHolder());
$windowType = match(true){
$inv instanceof LoomInventory => WindowTypes::LOOM,
- $inv instanceof FurnaceInventory => match($inv->getFurnaceType()->id()){
- FurnaceType::FURNACE()->id() => WindowTypes::FURNACE,
- FurnaceType::BLAST_FURNACE()->id() => WindowTypes::BLAST_FURNACE,
- FurnaceType::SMOKER()->id() => WindowTypes::SMOKER,
- default => throw new AssumptionFailedError("Unreachable")
+ $inv instanceof FurnaceInventory => match($inv->getFurnaceType()){
+ FurnaceType::FURNACE => WindowTypes::FURNACE,
+ FurnaceType::BLAST_FURNACE => WindowTypes::BLAST_FURNACE,
+ FurnaceType::SMOKER => WindowTypes::SMOKER,
+ FurnaceType::CAMPFIRE, FurnaceType::SOUL_CAMPFIRE => throw new \LogicException("Campfire inventory cannot be displayed to a player")
},
$inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT,
$inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND,
@@ -368,10 +387,11 @@ public function onClientOpenMainInventory() : void{
$this->openWindowDeferred(function() : void{
$windowId = $this->getNewWindowId();
$this->associateIdWithInventory($windowId, $this->player->getInventory());
+ $this->currentWindowType = WindowTypes::INVENTORY;
$this->session->sendDataPacket(ContainerOpenPacket::entityInv(
$windowId,
- WindowTypes::INVENTORY,
+ $this->currentWindowType,
$this->player->getId()
));
});
@@ -380,11 +400,12 @@ public function onClientOpenMainInventory() : void{
public function onCurrentWindowRemove() : void{
if(isset($this->networkIdToInventoryMap[$this->lastInventoryNetworkId])){
$this->remove($this->lastInventoryNetworkId);
- $this->session->sendDataPacket(ContainerClosePacket::create($this->lastInventoryNetworkId, true));
+ $this->session->sendDataPacket(ContainerClosePacket::create($this->lastInventoryNetworkId, $this->currentWindowType, true));
if($this->pendingCloseWindowId !== null){
throw new AssumptionFailedError("We should not have opened a new window while a window was waiting to be closed");
}
$this->pendingCloseWindowId = $this->lastInventoryNetworkId;
+ $this->enchantingTableOptions = [];
}
}
@@ -400,7 +421,7 @@ public function onClientRemoveWindow(int $id) : void{
//Always send this, even if no window matches. If we told the client to close a window, it will behave as if it
//initiated the close and expect an ack.
- $this->session->sendDataPacket(ContainerClosePacket::create($id, false));
+ $this->session->sendDataPacket(ContainerClosePacket::create($id, $this->currentWindowType, false));
if($this->pendingCloseWindowId === $id){
$this->pendingCloseWindowId = null;
@@ -412,6 +433,41 @@ public function onClientRemoveWindow(int $id) : void{
}
}
+ /**
+ * Compares itemstack extra data for equality. This is used to verify legacy InventoryTransaction slot predictions.
+ *
+ * TODO: It would be preferable if we didn't have to deserialize this, to improve performance and reduce attack
+ * surface. However, the raw data may not match due to differences in ordering. Investigate whether the
+ * client-provided NBT is consistently sorted.
+ */
+ private function itemStackExtraDataEqual(ItemStack $left, ItemStack $right) : bool{
+ if($left->getRawExtraData() === $right->getRawExtraData()){
+ return true;
+ }
+
+ $typeConverter = $this->session->getTypeConverter();
+ $leftExtraData = $typeConverter->deserializeItemStackExtraData($left->getRawExtraData(), $left->getId());
+ $rightExtraData = $typeConverter->deserializeItemStackExtraData($right->getRawExtraData(), $right->getId());
+
+ $leftNbt = $leftExtraData->getNbt();
+ $rightNbt = $rightExtraData->getNbt();
+ return
+ $leftExtraData->getCanPlaceOn() === $rightExtraData->getCanPlaceOn() &&
+ $leftExtraData->getCanDestroy() === $rightExtraData->getCanDestroy() && (
+ $leftNbt === $rightNbt || //this covers null === null and fast object identity
+ ($leftNbt !== null && $rightNbt !== null && $leftNbt->equals($rightNbt))
+ );
+ }
+
+ private function itemStacksEqual(ItemStack $left, ItemStack $right) : bool{
+ return
+ $left->getId() === $right->getId() &&
+ $left->getMeta() === $right->getMeta() &&
+ $left->getBlockRuntimeId() === $right->getBlockRuntimeId() &&
+ $left->getCount() === $right->getCount() &&
+ $this->itemStackExtraDataEqual($left, $right);
+ }
+
public function onSlotChange(Inventory $inventory, int $slot) : void{
$inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null;
if($inventoryEntry === null){
@@ -421,7 +477,7 @@ public function onSlotChange(Inventory $inventory, int $slot) : void{
}
$currentItem = $this->session->getTypeConverter()->coreItemStackToNet($inventory->getItem($slot));
$clientSideItem = $inventoryEntry->predictions[$slot] ?? null;
- if($clientSideItem === null || !$clientSideItem->equals($currentItem)){
+ if($clientSideItem === null || !$this->itemStacksEqual($currentItem, $clientSideItem)){
//no prediction or incorrect - do not associate this with the currently active itemstack request
$this->trackItemStack($inventoryEntry, $slot, $currentItem, null);
$inventoryEntry->pendingSyncs[$slot] = $currentItem;
@@ -433,6 +489,56 @@ public function onSlotChange(Inventory $inventory, int $slot) : void{
unset($inventoryEntry->predictions[$slot]);
}
+ private function sendInventorySlotPackets(int $windowId, int $netSlot, ItemStackWrapper $itemStackWrapper) : void{
+ /*
+ * TODO: HACK!
+ * As of 1.20.12, the client ignores change of itemstackID in some cases when the old item == the new item.
+ * Notably, this happens with armor, offhand and enchanting tables, but not with main inventory.
+ * While we could track the items previously sent to the client, that's a waste of memory and would
+ * cost performance. Instead, clear the slot(s) first, then send the new item(s).
+ * The network cost of doing this is fortunately minimal, as an air itemstack is only 1 byte.
+ */
+ if($itemStackWrapper->getStackId() !== 0){
+ $this->session->sendDataPacket(InventorySlotPacket::create(
+ $windowId,
+ $netSlot,
+ new FullContainerName($this->lastInventoryNetworkId),
+ new ItemStackWrapper(0, ItemStack::null()),
+ new ItemStackWrapper(0, ItemStack::null())
+ ));
+ }
+ //now send the real contents
+ $this->session->sendDataPacket(InventorySlotPacket::create(
+ $windowId,
+ $netSlot,
+ new FullContainerName($this->lastInventoryNetworkId),
+ new ItemStackWrapper(0, ItemStack::null()),
+ $itemStackWrapper
+ ));
+ }
+
+ /**
+ * @param ItemStackWrapper[] $itemStackWrappers
+ */
+ private function sendInventoryContentPackets(int $windowId, array $itemStackWrappers) : void{
+ /*
+ * TODO: HACK!
+ * As of 1.20.12, the client ignores change of itemstackID in some cases when the old item == the new item.
+ * Notably, this happens with armor, offhand and enchanting tables, but not with main inventory.
+ * While we could track the items previously sent to the client, that's a waste of memory and would
+ * cost performance. Instead, clear the slot(s) first, then send the new item(s).
+ * The network cost of doing this is fortunately minimal, as an air itemstack is only 1 byte.
+ */
+ $this->session->sendDataPacket(InventoryContentPacket::create(
+ $windowId,
+ array_fill_keys(array_keys($itemStackWrappers), new ItemStackWrapper(0, ItemStack::null())),
+ new FullContainerName($this->lastInventoryNetworkId),
+ new ItemStackWrapper(0, ItemStack::null())
+ ));
+ //now send the real contents
+ $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $itemStackWrappers, new FullContainerName($this->lastInventoryNetworkId), new ItemStackWrapper(0, ItemStack::null())));
+ }
+
public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) : void{
$entry = $this->inventories[spl_object_id($inventory)] ?? null;
if($entry === null){
@@ -457,24 +563,9 @@ public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack)
//This can cause a lot of problems (totems, arrows, and more...).
//The workaround is to send an InventoryContentPacket instead
//BDS (Bedrock Dedicated Server) also seems to work this way.
- $this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper]));
+ $this->sendInventoryContentPackets($windowId, [$itemStackWrapper]);
}else{
- if($windowId === ContainerIds::ARMOR){
- //TODO: HACK!
- //When right-clicking to equip armour, the client predicts the content of the armour slot, but
- //doesn't report it in the transaction packet. The server then sends an InventorySlotPacket to
- //the client, assuming the slot changed for some other reason, since there is no prediction for
- //the slot.
- //However, later requests involving that itemstack will refer to the request ID in which the
- //armour was equipped, instead of the stack ID provided by the server in the outgoing
- //InventorySlotPacket. (Perhaps because the item is already the same as the client actually
- //predicted, but didn't tell us?)
- //We work around this bug by setting the slot to air and then back to the correct item. In
- //theory, setting a different count and then back again (or changing any other property) would
- //also work, but this is simpler.
- $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, new ItemStackWrapper(0, ItemStack::null())));
- }
- $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
+ $this->sendInventorySlotPackets($windowId, $netSlot, $itemStackWrapper);
}
unset($entry->predictions[$slot], $entry->pendingSyncs[$slot]);
}
@@ -501,20 +592,17 @@ public function syncContents(Inventory $inventory) : void{
$info = $this->trackItemStack($entry, $slot, $itemStack, null);
$contents[] = new ItemStackWrapper($info->getStackId(), $itemStack);
}
+ $clearSlotWrapper = new ItemStackWrapper(0, ItemStack::null());
if($entry->complexSlotMap !== null){
foreach($contents as $slotId => $info){
$packetSlot = $entry->complexSlotMap->mapCoreToNet($slotId) ?? null;
if($packetSlot === null){
continue;
}
- $this->session->sendDataPacket(InventorySlotPacket::create(
- $windowId,
- $packetSlot,
- $info
- ));
+ $this->sendInventorySlotPackets($windowId, $packetSlot, $info);
}
}else{
- $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $contents));
+ $this->sendInventoryContentPackets($windowId, $contents);
}
}
}
@@ -603,7 +691,40 @@ public function syncSelectedHotbarSlot() : void{
}
public function syncCreative() : void{
- $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache(CreativeInventory::getInstance()));
+ $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory()));
+ }
+
+ /**
+ * @param EnchantingOption[] $options
+ */
+ public function syncEnchantingTableOptions(array $options) : void{
+ $protocolOptions = [];
+
+ foreach($options as $index => $option){
+ $optionId = $this->nextEnchantingTableOptionId++;
+ $this->enchantingTableOptions[$optionId] = $index;
+
+ $protocolEnchantments = array_map(
+ fn(EnchantmentInstance $e) => new Enchant(EnchantmentIdMap::getInstance()->toId($e->getType()), $e->getLevel()),
+ $option->getEnchantments()
+ );
+ // We don't pay attention to the $slotFlags, $heldActivatedEnchantments and $selfActivatedEnchantments
+ // as everything works fine without them (perhaps these values are used somehow in the BDS).
+ $protocolOptions[] = new ProtocolEnchantOption(
+ $option->getRequiredXpLevel(),
+ 0, $protocolEnchantments,
+ [],
+ [],
+ $option->getDisplayName(),
+ $optionId
+ );
+ }
+
+ $this->session->sendDataPacket(PlayerEnchantOptionsPacket::create($protocolOptions));
+ }
+
+ public function getEnchantingTableOptionIndex(int $recipeId) : ?int{
+ return $this->enchantingTableOptions[$recipeId] ?? null;
}
private function newItemStackId() : int{
diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php
index 12d21974dd8..259a602d488 100644
--- a/src/network/mcpe/JwtUtils.php
+++ b/src/network/mcpe/JwtUtils.php
@@ -23,28 +23,26 @@
namespace pocketmine\network\mcpe;
-use FG\ASN1\Exception\ParserException;
-use FG\ASN1\Universal\Integer;
-use FG\ASN1\Universal\Sequence;
use pocketmine\utils\AssumptionFailedError;
+use pocketmine\utils\BinaryStream;
use pocketmine\utils\Utils;
use function base64_decode;
use function base64_encode;
+use function bin2hex;
+use function chr;
use function count;
use function explode;
-use function gmp_export;
-use function gmp_import;
-use function gmp_init;
-use function gmp_strval;
use function is_array;
use function json_decode;
use function json_encode;
use function json_last_error_msg;
+use function ltrim;
use function openssl_error_string;
use function openssl_pkey_get_details;
use function openssl_pkey_get_public;
use function openssl_sign;
use function openssl_verify;
+use function ord;
use function preg_match;
use function rtrim;
use function sprintf;
@@ -54,13 +52,19 @@
use function str_split;
use function strlen;
use function strtr;
-use const GMP_BIG_ENDIAN;
-use const GMP_MSW_FIRST;
+use function substr;
use const JSON_THROW_ON_ERROR;
use const OPENSSL_ALGO_SHA384;
use const STR_PAD_LEFT;
final class JwtUtils{
+ public const BEDROCK_SIGNING_KEY_CURVE_NAME = "secp384r1";
+
+ private const ASN1_INTEGER_TAG = "\x02";
+ private const ASN1_SEQUENCE_TAG = "\x30";
+
+ private const SIGNATURE_PART_LENGTH = 48;
+ private const SIGNATURE_ALGORITHM = OPENSSL_ALGO_SHA384;
/**
* @return string[]
@@ -97,30 +101,84 @@ public static function parse(string $token) : array{
return [$header, $body, $signature];
}
+ private static function signaturePartToAsn1(string $part) : string{
+ if(strlen($part) !== self::SIGNATURE_PART_LENGTH){
+ throw new JwtException("R and S for a SHA384 signature must each be exactly 48 bytes, but have " . strlen($part) . " bytes");
+ }
+ $part = ltrim($part, "\x00");
+ if(ord($part[0]) >= 128){
+ //ASN.1 integers with a leading 1 bit are considered negative - add a leading 0 byte to prevent this
+ //ECDSA signature R and S values are always positive
+ $part = "\x00" . $part;
+ }
+
+ //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed
+ return self::ASN1_INTEGER_TAG . chr(strlen($part)) . $part;
+ }
+
+ private static function rawSignatureToDer(string $rawSignature) : string{
+ if(strlen($rawSignature) !== self::SIGNATURE_PART_LENGTH * 2){
+ throw new JwtException("JWT signature has unexpected length, expected 96, got " . strlen($rawSignature));
+ }
+
+ [$rString, $sString] = str_split($rawSignature, self::SIGNATURE_PART_LENGTH);
+ $sequence = self::signaturePartToAsn1($rString) . self::signaturePartToAsn1($sString);
+
+ //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed
+ return self::ASN1_SEQUENCE_TAG . chr(strlen($sequence)) . $sequence;
+ }
+
+ private static function signaturePartFromAsn1(BinaryStream $stream) : string{
+ $prefix = $stream->get(1);
+ if($prefix !== self::ASN1_INTEGER_TAG){
+ throw new \InvalidArgumentException("Expected an ASN.1 INTEGER tag, got " . bin2hex($prefix));
+ }
+ //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed
+ $length = $stream->getByte();
+ if($length > self::SIGNATURE_PART_LENGTH + 1){ //each part may have an extra leading 0 byte to prevent it being interpreted as a negative number
+ throw new \InvalidArgumentException("Expected at most 49 bytes for signature R or S, got $length");
+ }
+ $part = $stream->get($length);
+ return str_pad(ltrim($part, "\x00"), self::SIGNATURE_PART_LENGTH, "\x00", STR_PAD_LEFT);
+ }
+
+ private static function rawSignatureFromDer(string $derSignature) : string{
+ if($derSignature[0] !== self::ASN1_SEQUENCE_TAG){
+ throw new \InvalidArgumentException("Invalid DER signature, expected ASN.1 SEQUENCE tag, got " . bin2hex($derSignature[0]));
+ }
+
+ //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed
+ $length = ord($derSignature[1]);
+ $parts = substr($derSignature, 2, $length);
+ if(strlen($parts) !== $length){
+ throw new \InvalidArgumentException("Invalid DER signature, expected $length sequence bytes, got " . strlen($parts));
+ }
+
+ $stream = new BinaryStream($parts);
+ $rRaw = self::signaturePartFromAsn1($stream);
+ $sRaw = self::signaturePartFromAsn1($stream);
+
+ if(!$stream->feof()){
+ throw new \InvalidArgumentException("Invalid DER signature, unexpected trailing sequence data");
+ }
+
+ return $rRaw . $sRaw;
+ }
+
/**
* @throws JwtException
*/
public static function verify(string $jwt, \OpenSSLAsymmetricKey $signingKey) : bool{
[$header, $body, $signature] = self::split($jwt);
- $plainSignature = self::b64UrlDecode($signature);
- if(strlen($plainSignature) !== 96){
- throw new JwtException("JWT signature has unexpected length, expected 96, got " . strlen($plainSignature));
- }
-
- [$rString, $sString] = str_split($plainSignature, 48);
- $convert = fn(string $str) => gmp_strval(gmp_import($str, 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), 10);
-
- $sequence = new Sequence(
- new Integer($convert($rString)),
- new Integer($convert($sString))
- );
+ $rawSignature = self::b64UrlDecode($signature);
+ $derSignature = self::rawSignatureToDer($rawSignature);
$v = openssl_verify(
$header . '.' . $body,
- $sequence->getBinary(),
+ $derSignature,
$signingKey,
- OPENSSL_ALGO_SHA384
+ self::SIGNATURE_ALGORITHM
);
switch($v){
case 0: return false;
@@ -139,33 +197,13 @@ public static function create(array $header, array $claims, \OpenSSLAsymmetricKe
openssl_sign(
$jwtBody,
- $rawDerSig,
+ $derSignature,
$signingKey,
- OPENSSL_ALGO_SHA384
+ self::SIGNATURE_ALGORITHM
);
- try{
- $asnObject = Sequence::fromBinary($rawDerSig);
- }catch(ParserException $e){
- throw new AssumptionFailedError("Failed to parse OpenSSL signature: " . $e->getMessage(), 0, $e);
- }
- if(count($asnObject) !== 2){
- throw new AssumptionFailedError("OpenSSL produced invalid signature, expected exactly 2 parts");
- }
- [$r, $s] = [$asnObject[0], $asnObject[1]];
- if(!($r instanceof Integer) || !($s instanceof Integer)){
- throw new AssumptionFailedError("OpenSSL produced invalid signature, expected 2 INTEGER parts");
- }
- $rString = $r->getContent();
- $sString = $s->getContent();
-
- $toBinary = fn($str) => str_pad(
- gmp_export(gmp_init($str, 10), 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST),
- 48,
- "\x00",
- STR_PAD_LEFT
- );
- $jwtSig = JwtUtils::b64UrlEncode($toBinary($rString) . $toBinary($sString));
+ $rawSignature = self::rawSignatureFromDer($derSignature);
+ $jwtSig = self::b64UrlEncode($rawSignature);
return "$jwtBody.$jwtSig";
}
@@ -203,6 +241,17 @@ public static function parseDerPublicKey(string $derKey) : \OpenSSLAsymmetricKey
if($signingKeyOpenSSL === false){
throw new JwtException("OpenSSL failed to parse key: " . openssl_error_string());
}
+ $details = openssl_pkey_get_details($signingKeyOpenSSL);
+ if($details === false){
+ throw new JwtException("OpenSSL failed to get details from key: " . openssl_error_string());
+ }
+ if(!isset($details['ec']['curve_name'])){
+ throw new JwtException("Expected an EC key");
+ }
+ $curve = $details['ec']['curve_name'];
+ if($curve !== self::BEDROCK_SIGNING_KEY_CURVE_NAME){
+ throw new JwtException("Key must belong to curve " . self::BEDROCK_SIGNING_KEY_CURVE_NAME . ", got $curve");
+ }
return $signingKeyOpenSSL;
}
}
diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php
index 9053dd6b632..0eee71e0eec 100644
--- a/src/network/mcpe/NetworkSession.php
+++ b/src/network/mcpe/NetworkSession.php
@@ -25,10 +25,12 @@
use pocketmine\entity\effect\EffectInstance;
use pocketmine\event\player\PlayerDuplicateLoginEvent;
+use pocketmine\event\player\PlayerResourcePackOfferEvent;
use pocketmine\event\server\DataPacketDecodeEvent;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\form\Form;
+use pocketmine\item\Item;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\math\Vector3;
@@ -53,6 +55,7 @@
use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
+use pocketmine\network\mcpe\protocol\ClientboundCloseFormPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
@@ -63,11 +66,11 @@
use pocketmine\network\mcpe\protocol\PacketDecodeException;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
+use pocketmine\network\mcpe\protocol\PlayerStartItemCooldownPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
@@ -83,8 +86,10 @@
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\command\CommandData;
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
+use pocketmine\network\mcpe\protocol\types\command\CommandOverload;
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
+use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
@@ -99,6 +104,8 @@
use pocketmine\player\PlayerInfo;
use pocketmine\player\UsedChunkStatus;
use pocketmine\player\XboxLivePlayerInfo;
+use pocketmine\promise\Promise;
+use pocketmine\promise\PromiseResolver;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
@@ -106,7 +113,9 @@
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
+use pocketmine\world\format\io\GlobalItemDataHandlers;
use pocketmine\world\Position;
+use pocketmine\YmlServerProperties;
use function array_map;
use function array_values;
use function base64_encode;
@@ -115,7 +124,9 @@
use function get_class;
use function implode;
use function in_array;
+use function is_string;
use function json_encode;
+use function ord;
use function random_bytes;
use function str_split;
use function strcasecmp;
@@ -154,15 +165,24 @@ class NetworkSession{
/** @var string[] */
private array $sendBuffer = [];
-
/**
- * @var \SplQueue|CompressBatchPromise[]
- * @phpstan-var \SplQueue
+ * @var PromiseResolver[]
+ * @phpstan-var list>
*/
+ private array $sendBufferAckPromises = [];
+
+ /** @phpstan-var \SplQueue>}> */
private \SplQueue $compressedQueue;
private bool $forceAsyncCompression = true;
private bool $enableCompression = false; //disabled until handshake completed
+ private int $nextAckReceiptId = 0;
+ /**
+ * @var PromiseResolver[][]
+ * @phpstan-var array>>
+ */
+ private array $ackPromisesByReceiptId = [];
+
private ?InventoryManager $invManager = null;
/**
@@ -175,7 +195,6 @@ public function __construct(
private Server $server,
private NetworkSessionManager $manager,
private PacketPool $packetPool,
- private PacketSerializerContext $packetSerializerContext,
private PacketSender $sender,
private PacketBroadcaster $broadcaster,
private EntityEventBroadcaster $entityEventBroadcaster,
@@ -196,7 +215,7 @@ public function __construct(
$this->setHandler(new SessionStartPacketHandler(
$this,
- fn() => $this->onSessionStartSuccess()
+ $this->onSessionStartSuccess(...)
));
$this->manager->add($this);
@@ -224,17 +243,19 @@ function(PlayerInfo $info) : void{
$this->logger->setPrefix($this->getLogPrefix());
$this->manager->markLoginReceived($this);
},
- \Closure::fromCallable([$this, "setAuthenticationStatus"])
+ $this->setAuthenticationStatus(...)
));
}
protected function createPlayer() : void{
$this->server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion(
- \Closure::fromCallable([$this, 'onPlayerCreated']),
+ $this->onPlayerCreated(...),
function() : void{
//TODO: this should never actually occur... right?
- $this->logger->error("Failed to create player");
- $this->disconnectWithError(KnownTranslationFactory::pocketmine_disconnect_error_internal());
+ $this->disconnectWithError(
+ reason: "Failed to create player",
+ disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error_internal()
+ );
}
);
}
@@ -350,15 +371,27 @@ public function handleEncoded(string $payload) : void{
}
}
+ if(strlen($payload) < 1){
+ throw new PacketHandlingException("No bytes in payload");
+ }
+
if($this->enableCompression){
- Timings::$playerNetworkReceiveDecompress->startTiming();
- try{
- $decompressed = $this->compressor->decompress($payload);
- }catch(DecompressionException $e){
- $this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
- throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
- }finally{
- Timings::$playerNetworkReceiveDecompress->stopTiming();
+ $compressionType = ord($payload[0]);
+ $compressed = substr($payload, 1);
+ if($compressionType === CompressionAlgorithm::NONE){
+ $decompressed = $compressed;
+ }elseif($compressionType === $this->compressor->getNetworkId()){
+ Timings::$playerNetworkReceiveDecompress->startTiming();
+ try{
+ $decompressed = $this->compressor->decompress($compressed);
+ }catch(DecompressionException $e){
+ $this->logger->debug("Failed to decompress packet: " . base64_encode($compressed));
+ throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
+ }finally{
+ Timings::$playerNetworkReceiveDecompress->stopTiming();
+ }
+ }else{
+ throw new PacketHandlingException("Packet compressed with unexpected compression type $compressionType");
}
}else{
$decompressed = $payload;
@@ -366,12 +399,8 @@ public function handleEncoded(string $payload) : void{
try{
$stream = new BinaryStream($decompressed);
- $count = 0;
foreach(PacketBatch::decodeRaw($stream) as $buffer){
$this->gamePacketLimiter->decrement();
- if(++$count > 100){
- throw new PacketHandlingException("Too many packets in batch");
- }
$packet = $this->packetPool->getPacket($buffer);
if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
@@ -405,16 +434,18 @@ public function handleDataPacket(Packet $packet, string $buffer) : void{
$timings->startTiming();
try{
- $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer);
- $ev->call();
- if($ev->isCancelled()){
- return;
+ if(DataPacketDecodeEvent::hasHandlers()){
+ $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer);
+ $ev->call();
+ if($ev->isCancelled()){
+ return;
+ }
}
$decodeTimings = Timings::getDecodeDataPacketTimings($packet);
$decodeTimings->startTiming();
try{
- $stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext);
+ $stream = PacketSerializer::decoder($buffer, 0);
try{
$packet->decode($stream);
}catch(PacketDecodeException $e){
@@ -428,25 +459,44 @@ public function handleDataPacket(Packet $packet, string $buffer) : void{
$decodeTimings->stopTiming();
}
- $ev = new DataPacketReceiveEvent($this, $packet);
- $ev->call();
- if(!$ev->isCancelled()){
- $handlerTimings = Timings::getHandleDataPacketTimings($packet);
- $handlerTimings->startTiming();
- try{
- if($this->handler === null || !$packet->handle($this->handler)){
- $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
- }
- }finally{
- $handlerTimings->stopTiming();
+ if(DataPacketReceiveEvent::hasHandlers()){
+ $ev = new DataPacketReceiveEvent($this, $packet);
+ $ev->call();
+ if($ev->isCancelled()){
+ return;
}
}
+ $handlerTimings = Timings::getHandleDataPacketTimings($packet);
+ $handlerTimings->startTiming();
+ try{
+ if($this->handler === null || !$packet->handle($this->handler)){
+ $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
+ }
+ }finally{
+ $handlerTimings->stopTiming();
+ }
}finally{
$timings->stopTiming();
}
}
- public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
+ public function handleAckReceipt(int $receiptId) : void{
+ if(!$this->connected){
+ return;
+ }
+ if(isset($this->ackPromisesByReceiptId[$receiptId])){
+ $promises = $this->ackPromisesByReceiptId[$receiptId];
+ unset($this->ackPromisesByReceiptId[$receiptId]);
+ foreach($promises as $promise){
+ $promise->resolve(true);
+ }
+ }
+ }
+
+ /**
+ * @phpstan-param PromiseResolver|null $ackReceiptResolver
+ */
+ private function sendDataPacketInternal(ClientboundPacket $packet, bool $immediate, ?PromiseResolver $ackReceiptResolver) : bool{
if(!$this->connected){
return false;
}
@@ -458,15 +508,22 @@ public function sendDataPacket(ClientboundPacket $packet, bool $immediate = fals
$timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming();
try{
- $ev = new DataPacketSendEvent([$this], [$packet]);
- $ev->call();
- if($ev->isCancelled()){
- return false;
+ if(DataPacketSendEvent::hasHandlers()){
+ $ev = new DataPacketSendEvent([$this], [$packet]);
+ $ev->call();
+ if($ev->isCancelled()){
+ return false;
+ }
+ $packets = $ev->getPackets();
+ }else{
+ $packets = [$packet];
}
- $packets = $ev->getPackets();
+ if($ackReceiptResolver !== null){
+ $this->sendBufferAckPromises[] = $ackReceiptResolver;
+ }
foreach($packets as $evPacket){
- $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket));
+ $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket));
}
if($immediate){
$this->flushSendBuffer(true);
@@ -478,6 +535,23 @@ public function sendDataPacket(ClientboundPacket $packet, bool $immediate = fals
}
}
+ public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
+ return $this->sendDataPacketInternal($packet, $immediate, null);
+ }
+
+ /**
+ * @phpstan-return Promise
+ */
+ public function sendDataPacketWithReceipt(ClientboundPacket $packet, bool $immediate = false) : Promise{
+ $resolver = new PromiseResolver();
+
+ if(!$this->sendDataPacketInternal($packet, $immediate, $resolver)){
+ $resolver->reject();
+ }
+
+ return $resolver->getPromise();
+ }
+
/**
* @internal
*/
@@ -514,21 +588,20 @@ private function flushSendBuffer(bool $immediate = false) : void{
PacketBatch::encodeRaw($stream, $this->sendBuffer);
if($this->enableCompression){
- $promise = $this->server->prepareBatch($stream->getBuffer(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
+ $batch = $this->server->prepareBatch($stream->getBuffer(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
}else{
- $promise = new CompressBatchPromise();
- $promise->resolve($stream->getBuffer());
+ $batch = $stream->getBuffer();
}
$this->sendBuffer = [];
- $this->queueCompressedNoBufferFlush($promise, $immediate);
+ $ackPromises = $this->sendBufferAckPromises;
+ $this->sendBufferAckPromises = [];
+ $this->queueCompressedNoBufferFlush($batch, $immediate, $ackPromises);
}finally{
Timings::$playerNetworkSend->stopTiming();
}
}
}
- public function getPacketSerializerContext() : PacketSerializerContext{ return $this->packetSerializerContext; }
-
public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; }
public function getEntityEventBroadcaster() : EntityEventBroadcaster{ return $this->entityEventBroadcaster; }
@@ -539,7 +612,7 @@ public function getCompressor() : Compressor{
public function getTypeConverter() : TypeConverter{ return $this->typeConverter; }
- public function queueCompressed(CompressBatchPromise $payload, bool $immediate = false) : void{
+ public function queueCompressed(CompressBatchPromise|string $payload, bool $immediate = false) : void{
Timings::$playerNetworkSend->startTiming();
try{
$this->flushSendBuffer($immediate); //Maintain ordering if possible
@@ -549,36 +622,30 @@ public function queueCompressed(CompressBatchPromise $payload, bool $immediate =
}
}
- private function queueCompressedNoBufferFlush(CompressBatchPromise $payload, bool $immediate = false) : void{
+ /**
+ * @param PromiseResolver[] $ackPromises
+ *
+ * @phpstan-param list> $ackPromises
+ */
+ private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false, array $ackPromises = []) : void{
Timings::$playerNetworkSend->startTiming();
try{
- if($immediate){
+ if(is_string($batch)){
+ if($immediate){
+ //Skips all queues
+ $this->sendEncoded($batch, true, $ackPromises);
+ }else{
+ $this->compressedQueue->enqueue([$batch, $ackPromises]);
+ $this->flushCompressedQueue();
+ }
+ }elseif($immediate){
//Skips all queues
- $this->sendEncoded($payload->getResult(), true);
+ $this->sendEncoded($batch->getResult(), true, $ackPromises);
}else{
- $this->compressedQueue->enqueue($payload);
- $payload->onResolve(function(CompressBatchPromise $payload) : void{
- if($this->connected && $this->compressedQueue->bottom() === $payload){
- Timings::$playerNetworkSend->startTiming();
- try{
- $this->compressedQueue->dequeue(); //result unused
- $this->sendEncoded($payload->getResult());
-
- while(!$this->compressedQueue->isEmpty()){
- /** @var CompressBatchPromise $current */
- $current = $this->compressedQueue->bottom();
- if($current->hasResult()){
- $this->compressedQueue->dequeue();
-
- $this->sendEncoded($current->getResult());
- }else{
- //can't send any more queued until this one is ready
- break;
- }
- }
- }finally{
- Timings::$playerNetworkSend->stopTiming();
- }
+ $this->compressedQueue->enqueue([$batch, $ackPromises]);
+ $batch->onResolve(function() : void{
+ if($this->connected){
+ $this->flushCompressedQueue();
}
});
}
@@ -587,13 +654,48 @@ private function queueCompressedNoBufferFlush(CompressBatchPromise $payload, boo
}
}
- private function sendEncoded(string $payload, bool $immediate = false) : void{
+ private function flushCompressedQueue() : void{
+ Timings::$playerNetworkSend->startTiming();
+ try{
+ while(!$this->compressedQueue->isEmpty()){
+ /** @var CompressBatchPromise|string $current */
+ [$current, $ackPromises] = $this->compressedQueue->bottom();
+ if(is_string($current)){
+ $this->compressedQueue->dequeue();
+ $this->sendEncoded($current, false, $ackPromises);
+
+ }elseif($current->hasResult()){
+ $this->compressedQueue->dequeue();
+ $this->sendEncoded($current->getResult(), false, $ackPromises);
+
+ }else{
+ //can't send any more queued until this one is ready
+ break;
+ }
+ }
+ }finally{
+ Timings::$playerNetworkSend->stopTiming();
+ }
+ }
+
+ /**
+ * @param PromiseResolver[] $ackPromises
+ * @phpstan-param list> $ackPromises
+ */
+ private function sendEncoded(string $payload, bool $immediate, array $ackPromises) : void{
if($this->cipher !== null){
Timings::$playerNetworkSendEncrypt->startTiming();
$payload = $this->cipher->encrypt($payload);
Timings::$playerNetworkSendEncrypt->stopTiming();
}
- $this->sender->send($payload, $immediate);
+
+ if(count($ackPromises) > 0){
+ $ackReceiptId = $this->nextAckReceiptId++;
+ $this->ackPromisesByReceiptId[$ackReceiptId] = $ackPromises;
+ }else{
+ $ackReceiptId = null;
+ }
+ $this->sender->send($payload, $immediate, $ackReceiptId);
}
/**
@@ -613,6 +715,19 @@ private function tryDisconnect(\Closure $func, Translatable|string $reason) : vo
$this->setHandler(null);
$this->connected = false;
+ $ackPromisesByReceiptId = $this->ackPromisesByReceiptId;
+ $this->ackPromisesByReceiptId = [];
+ foreach($ackPromisesByReceiptId as $resolvers){
+ foreach($resolvers as $resolver){
+ $resolver->reject();
+ }
+ }
+ $sendBufferAckPromises = $this->sendBufferAckPromises;
+ $this->sendBufferAckPromises = [];
+ foreach($sendBufferAckPromises as $resolver){
+ $resolver->reject();
+ }
+
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_close($reason)));
}
}
@@ -631,7 +746,7 @@ private function sendDisconnectPacket(Translatable|string $message) : void{
}else{
$translated = $message;
}
- $this->sendDataPacket(DisconnectPacket::create($translated));
+ $this->sendDataPacket(DisconnectPacket::create(0, $translated, ""));
}
/**
@@ -651,8 +766,13 @@ public function disconnect(Translatable|string $reason, Translatable|string|null
}, $reason);
}
- public function disconnectWithError(Translatable|string $reason) : void{
- $this->disconnect(KnownTranslationFactory::pocketmine_disconnect_error($reason, implode("-", str_split(bin2hex(random_bytes(6)), 4))));
+ public function disconnectWithError(Translatable|string $reason, Translatable|string|null $disconnectScreenMessage = null) : void{
+ $errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4));
+
+ $this->disconnect(
+ reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->prefix(TextFormat::RED),
+ disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error($disconnectScreenMessage ?? $reason, $errorId),
+ );
}
public function disconnectIncompatibleProtocol(int $protocolVersion) : void{
@@ -670,7 +790,7 @@ function() use ($protocolVersion) : void{
public function transfer(string $ip, int $port, Translatable|string|null $reason = null) : void{
$reason ??= KnownTranslationFactory::pocketmine_disconnect_transfer();
$this->tryDisconnect(function() use ($ip, $port, $reason) : void{
- $this->sendDataPacket(TransferPacket::create($ip, $port), true);
+ $this->sendDataPacket(TransferPacket::create($ip, $port, false), true);
if($this->player !== null){
$this->player->onPostDisconnect($reason, null);
}
@@ -711,7 +831,10 @@ private function setAuthenticationStatus(bool $authenticated, bool $authRequired
}
if($error !== null){
- $this->disconnectWithError(KnownTranslationFactory::pocketmine_disconnect_invalidSession($error));
+ $this->disconnectWithError(
+ reason: KnownTranslationFactory::pocketmine_disconnect_invalidSession($error),
+ disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error_authentication()
+ );
return;
}
@@ -730,7 +853,7 @@ private function setAuthenticationStatus(bool $authenticated, bool $authRequired
}
$this->logger->debug("Xbox Live authenticated: " . ($this->authenticated ? "YES" : "NO"));
- $checkXUID = $this->server->getConfigGroup()->getPropertyBool("player.verify-xuid", true);
+ $checkXUID = $this->server->getConfigGroup()->getPropertyBool(YmlServerProperties::PLAYER_VERIFY_XUID, true);
$myXUID = $this->info instanceof XboxLivePlayerInfo ? $this->info->getXuid() : "";
$kickForXUIDMismatch = function(string $xuid) use ($checkXUID, $myXUID) : bool{
if($checkXUID && $myXUID !== $xuid){
@@ -786,9 +909,7 @@ private function setAuthenticationStatus(bool $authenticated, bool $authRequired
$this->cipher = EncryptionContext::fakeGCM($encryptionKey);
- $this->setHandler(new HandshakePacketHandler(function() : void{
- $this->onServerLoginSuccess();
- }));
+ $this->setHandler(new HandshakePacketHandler($this->onServerLoginSuccess(...)));
$this->logger->debug("Enabled encryption");
}));
}else{
@@ -802,7 +923,19 @@ private function onServerLoginSuccess() : void{
$this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::LOGIN_SUCCESS));
$this->logger->debug("Initiating resource packs phase");
- $this->setHandler(new ResourcePacksPacketHandler($this, $this->server->getResourcePackManager(), function() : void{
+
+ $packManager = $this->server->getResourcePackManager();
+ $resourcePacks = $packManager->getResourceStack();
+ $keys = [];
+ foreach($resourcePacks as $resourcePack){
+ $key = $packManager->getPackEncryptionKey($resourcePack->getPackId());
+ if($key !== null){
+ $keys[$resourcePack->getPackId()] = $key;
+ }
+ }
+ $event = new PlayerResourcePackOfferEvent($this->info, $resourcePacks, $keys, $packManager->resourcePacksRequired());
+ $event->call();
+ $this->setHandler(new ResourcePacksPacketHandler($this, $event->getResourcePacks(), $event->getEncryptionKeys(), $event->mustAccept(), function() : void{
$this->createPlayer();
}));
}
@@ -817,9 +950,7 @@ private function beginSpawnSequence() : void{
public function notifyTerrainReady() : void{
$this->logger->debug("Sending spawn notification, waiting for spawn response");
$this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::PLAYER_SPAWN));
- $this->setHandler(new SpawnResponsePacketHandler(function() : void{
- $this->onClientSpawnResponse();
- }));
+ $this->setHandler(new SpawnResponsePacketHandler($this->onClientSpawnResponse(...)));
}
private function onClientSpawnResponse() : void{
@@ -985,8 +1116,9 @@ public function syncAvailableCommands() : void{
0,
$aliasObj,
[
- [CommandParameter::standard("args", AvailableCommandsPacket::ARG_TYPE_RAWTEXT, 0, true)]
- ]
+ new CommandOverload(chaining: false, parameters: [CommandParameter::standard("args", AvailableCommandsPacket::ARG_TYPE_RAWTEXT, 0, true)])
+ ],
+ chainedSubCommandData: []
);
$commandData[$command->getLabel()] = $data;
@@ -1042,6 +1174,10 @@ public function onFormSent(int $id, Form $form) : bool{
return $this->sendDataPacket(ModalFormRequestPacket::create($id, json_encode($form, JSON_THROW_ON_ERROR)));
}
+ public function onCloseAllForms() : void{
+ $this->sendDataPacket(ClientboundCloseFormPacket::create());
+ }
+
/**
* Instructs the networksession to start using the chunk at the given coordinates. This may occur asynchronously.
* @param \Closure $onCompletion To be called when chunk sending has completed.
@@ -1061,7 +1197,7 @@ function(CompressBatchPromise $promise) use ($world, $onCompletion, $chunkX, $ch
$this->logger->debug("Tried to send no-longer-active chunk $chunkX $chunkZ in world " . $world->getFolderName());
return;
}
- if(!$status->equals(UsedChunkStatus::REQUESTED_SENDING())){
+ if($status !== UsedChunkStatus::REQUESTED_SENDING){
//TODO: make this an error
//this could be triggered due to the shitty way that chunk resends are handled
//right now - not because of the spammy re-requesting, but because the chunk status reverts
@@ -1110,12 +1246,12 @@ public function getInvManager() : ?InventoryManager{
*/
public function syncPlayerList(array $players) : void{
$this->sendDataPacket(PlayerListPacket::add(array_map(function(Player $player) : PlayerListEntry{
- return PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), TypeConverter::getInstance()->getSkinAdapter()->toSkinData($player->getSkin()), $player->getXuid());
+ return PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $this->typeConverter->getSkinAdapter()->toSkinData($player->getSkin()), $player->getXuid());
}, $players)));
}
public function onPlayerAdded(Player $p) : void{
- $this->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($p->getUniqueId(), $p->getId(), $p->getDisplayName(), TypeConverter::getInstance()->getSkinAdapter()->toSkinData($p->getSkin()), $p->getXuid())]));
+ $this->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($p->getUniqueId(), $p->getId(), $p->getDisplayName(), $this->typeConverter->getSkinAdapter()->toSkinData($p->getSkin()), $p->getXuid())]));
}
public function onPlayerRemoved(Player $p) : void{
@@ -1156,6 +1292,13 @@ public function onOpenSignEditor(Vector3 $signPosition, bool $frontSide) : void{
$this->sendDataPacket(OpenSignPacket::create(BlockPosition::fromVector3($signPosition), $frontSide));
}
+ public function onItemCooldownChanged(Item $item, int $ticks) : void{
+ $this->sendDataPacket(PlayerStartItemCooldownPacket::create(
+ GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName(),
+ $ticks
+ ));
+ }
+
public function tick() : void{
if(!$this->isConnected()){
$this->dispose();
diff --git a/src/network/mcpe/PacketSender.php b/src/network/mcpe/PacketSender.php
index 4842ea93e96..36e556fe4a6 100644
--- a/src/network/mcpe/PacketSender.php
+++ b/src/network/mcpe/PacketSender.php
@@ -28,7 +28,7 @@ interface PacketSender{
/**
* Pushes a packet into the channel to be processed.
*/
- public function send(string $payload, bool $immediate) : void;
+ public function send(string $payload, bool $immediate, ?int $receiptId) : void;
/**
* Closes the channel, terminating the connection.
diff --git a/src/network/mcpe/StandardEntityEventBroadcaster.php b/src/network/mcpe/StandardEntityEventBroadcaster.php
index 30ac628f6ed..3e2df399483 100644
--- a/src/network/mcpe/StandardEntityEventBroadcaster.php
+++ b/src/network/mcpe/StandardEntityEventBroadcaster.php
@@ -38,9 +38,10 @@
use pocketmine\network\mcpe\protocol\RemoveActorPacket;
use pocketmine\network\mcpe\protocol\SetActorDataPacket;
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
-use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
+use pocketmine\network\mcpe\protocol\types\entity\UpdateAttribute;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
+use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use function array_map;
@@ -51,7 +52,7 @@
final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
public function __construct(
- private StandardPacketBroadcaster $broadcaster,
+ private PacketBroadcaster $broadcaster,
private TypeConverter $typeConverter
){}
@@ -66,7 +67,7 @@ public function syncAttributes(array $recipients, Living $entity, array $attribu
if(count($attributes) > 0){
$this->sendDataPacket($recipients, UpdateAttributesPacket::create(
$entity->getId(),
- array_map(fn(Attribute $attr) => new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []), $attributes),
+ array_map(fn(Attribute $attr) => new UpdateAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getDefaultValue(), []), $attributes),
0
));
}
@@ -87,12 +88,13 @@ public function onEntityEffectAdded(array $recipients, Living $entity, EffectIns
EffectIdMap::getInstance()->toId($effect->getType()),
$effect->getAmplifier(),
$effect->isVisible(),
- $effect->getDuration()
+ $effect->getDuration(),
+ tick: 0
));
}
public function onEntityEffectRemoved(array $recipients, Living $entity, EffectInstance $effect) : void{
- $this->sendDataPacket($recipients, MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType())));
+ $this->sendDataPacket($recipients, MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType()), tick: 0));
}
public function onEntityRemoved(array $recipients, Entity $entity) : void{
@@ -130,7 +132,8 @@ public function onMobArmorChange(array $recipients, Living $mob) : void{
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getHelmet())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getChestplate())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getLeggings())),
- ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots()))
+ ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots())),
+ new ItemStackWrapper(0, ItemStack::null())
));
}
@@ -139,6 +142,13 @@ public function onPickUpItem(array $recipients, Entity $collector, Entity $picke
}
public function onEmote(array $recipients, Human $from, string $emoteId) : void{
- $this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
+ $this->sendDataPacket($recipients, EmotePacket::create(
+ $from->getId(),
+ $emoteId,
+ 0, //seems to be irrelevant for the client, we cannot risk rebroadcasting random values received
+ "",
+ "",
+ EmotePacket::FLAG_SERVER | EmotePacket::FLAG_MUTE_ANNOUNCEMENT
+ ));
}
}
diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php
index 9de3d214af5..32afdeeb7c2 100644
--- a/src/network/mcpe/StandardPacketBroadcaster.php
+++ b/src/network/mcpe/StandardPacketBroadcaster.php
@@ -26,7 +26,6 @@
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\BinaryStream;
@@ -37,29 +36,26 @@
final class StandardPacketBroadcaster implements PacketBroadcaster{
public function __construct(
- private Server $server,
- private PacketSerializerContext $protocolContext
+ private Server $server
){}
public function broadcastPackets(array $recipients, array $packets) : void{
//TODO: this shouldn't really be called here, since the broadcaster might be replaced by an alternative
//implementation that doesn't fire events
- $ev = new DataPacketSendEvent($recipients, $packets);
- $ev->call();
- if($ev->isCancelled()){
- return;
+ if(DataPacketSendEvent::hasHandlers()){
+ $ev = new DataPacketSendEvent($recipients, $packets);
+ $ev->call();
+ if($ev->isCancelled()){
+ return;
+ }
+ $packets = $ev->getPackets();
}
- $packets = $ev->getPackets();
$compressors = [];
/** @var NetworkSession[][] $targetsByCompressor */
$targetsByCompressor = [];
foreach($recipients as $recipient){
- if($recipient->getPacketSerializerContext() !== $this->protocolContext){
- throw new \InvalidArgumentException("Only recipients with the same protocol context as the broadcaster can be broadcast to by this broadcaster");
- }
-
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
$compressor = $recipient->getCompressor();
$compressors[spl_object_id($compressor)] = $compressor;
@@ -70,7 +66,7 @@ public function broadcastPackets(array $recipients, array $packets) : void{
$totalLength = 0;
$packetBuffers = [];
foreach($packets as $packet){
- $buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder($this->protocolContext), $packet);
+ $buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder(), $packet);
//varint length prefix + packet buffer
$totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer);
$packetBuffers[] = $buffer;
@@ -86,9 +82,9 @@ public function broadcastPackets(array $recipients, array $packets) : void{
PacketBatch::encodeRaw($stream, $packetBuffers);
$batchBuffer = $stream->getBuffer();
- $promise = $this->server->prepareBatch($batchBuffer, $compressor, timings: Timings::$playerNetworkSendCompressBroadcast);
+ $batch = $this->server->prepareBatch($batchBuffer, $compressor, timings: Timings::$playerNetworkSendCompressBroadcast);
foreach($compressorTargets as $target){
- $target->queueCompressed($promise);
+ $target->queueCompressed($batch);
}
}else{
foreach($compressorTargets as $target){
diff --git a/src/network/mcpe/auth/ProcessLoginTask.php b/src/network/mcpe/auth/ProcessLoginTask.php
index 1ea36fa3756..b4c9e6d9caa 100644
--- a/src/network/mcpe/auth/ProcessLoginTask.php
+++ b/src/network/mcpe/auth/ProcessLoginTask.php
@@ -34,13 +34,17 @@
use function base64_decode;
use function igbinary_serialize;
use function igbinary_unserialize;
-use function openssl_error_string;
use function time;
class ProcessLoginTask extends AsyncTask{
private const TLS_KEY_ON_COMPLETION = "completion";
- public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
+ /**
+ * New Mojang root auth key. Mojang notified third-party developers of this change prior to the release of 1.20.0.
+ * Expectations were that this would be used starting a "couple of weeks" after the release, but as of 2023-07-01,
+ * it has not yet been deployed.
+ */
+ public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
private const CLOCK_DRIFT_MAX = 60;
@@ -114,26 +118,24 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi
try{
[$headersArray, $claimsArray, ] = JwtUtils::parse($jwt);
}catch(JwtException $e){
- //TODO: we shouldn't be showing internal information like this to the client
throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e);
}
$mapper = new \JsonMapper();
$mapper->bExceptionOnMissingData = true;
$mapper->bExceptionOnUndefinedProperty = true;
+ $mapper->bStrictObjectTypeChecking = true;
$mapper->bEnforceMapType = false;
try{
/** @var JwtHeader $headers */
$headers = $mapper->map($headersArray, new JwtHeader());
}catch(\JsonMapper_Exception $e){
- //TODO: we shouldn't be showing internal information like this to the client
throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e);
}
$headerDerKey = base64_decode($headers->x5u, true);
if($headerDerKey === false){
- //TODO: we shouldn't be showing internal information like this to the client
throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u");
}
@@ -149,7 +151,7 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi
try{
$signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey);
}catch(JwtException $e){
- throw new VerifyLoginException("Invalid JWT public key: " . openssl_error_string());
+ throw new VerifyLoginException("Invalid JWT public key: " . $e->getMessage(), null, 0, $e);
}
try{
if(!JwtUtils::verify($jwt, $signingKeyOpenSSL)){
@@ -166,6 +168,7 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi
$mapper = new \JsonMapper();
$mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case
$mapper->bExceptionOnMissingData = true;
+ $mapper->bStrictObjectTypeChecking = true;
$mapper->bEnforceMapType = false;
$mapper->bRemoveUndefinedAttributes = true;
try{
@@ -189,6 +192,12 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi
if($identityPublicKey === false){
throw new VerifyLoginException("Invalid identityPublicKey: base64 error decoding");
}
+ try{
+ //verify key format and parameters
+ JwtUtils::parseDerPublicKey($identityPublicKey);
+ }catch(JwtException $e){
+ throw new VerifyLoginException("Invalid identityPublicKey: " . $e->getMessage(), null, 0, $e);
+ }
$currentPublicKey = $identityPublicKey; //if there are further links, the next link should be signed with this
}
}
diff --git a/src/network/mcpe/cache/ChunkCache.php b/src/network/mcpe/cache/ChunkCache.php
index 15b69694562..6d802008578 100644
--- a/src/network/mcpe/cache/ChunkCache.php
+++ b/src/network/mcpe/cache/ChunkCache.php
@@ -27,6 +27,7 @@
use pocketmine\network\mcpe\ChunkRequestTask;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Compressor;
+use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\world\ChunkListener;
use pocketmine\world\ChunkListenerNoOpTrait;
use pocketmine\world\format\Chunk;
@@ -116,16 +117,10 @@ public function request(int $chunkX, int $chunkZ) : CompressBatchPromise{
new ChunkRequestTask(
$chunkX,
$chunkZ,
+ DimensionIds::OVERWORLD, //TODO: not hardcode this
$chunk,
$this->caches[$chunkHash],
- $this->compressor,
- function() use ($chunkHash, $chunkX, $chunkZ) : void{
- $this->world->getLogger()->error("Failed preparing chunk $chunkX $chunkZ, retrying");
-
- if(isset($this->caches[$chunkHash])){
- $this->restartPendingRequest($chunkX, $chunkZ);
- }
- }
+ $this->compressor
)
);
@@ -143,32 +138,19 @@ private function destroy(int $chunkX, int $chunkZ) : bool{
return $existing !== null;
}
- /**
- * Restarts an async request for an unresolved chunk.
- *
- * @throws \InvalidArgumentException
- */
- private function restartPendingRequest(int $chunkX, int $chunkZ) : void{
- $chunkHash = World::chunkHash($chunkX, $chunkZ);
- $existing = $this->caches[$chunkHash] ?? null;
- if($existing === null || $existing->hasResult()){
- throw new \InvalidArgumentException("Restart can only be applied to unresolved promises");
- }
- $existing->cancel();
- unset($this->caches[$chunkHash]);
-
- $this->request($chunkX, $chunkZ)->onResolve(...$existing->getResolveCallbacks());
- }
-
/**
* @throws \InvalidArgumentException
*/
private function destroyOrRestart(int $chunkX, int $chunkZ) : void{
- $cache = $this->caches[World::chunkHash($chunkX, $chunkZ)] ?? null;
+ $chunkPosHash = World::chunkHash($chunkX, $chunkZ);
+ $cache = $this->caches[$chunkPosHash] ?? null;
if($cache !== null){
if(!$cache->hasResult()){
//some requesters are waiting for this chunk, so their request needs to be fulfilled
- $this->restartPendingRequest($chunkX, $chunkZ);
+ $cache->cancel();
+ unset($this->caches[$chunkPosHash]);
+
+ $this->request($chunkX, $chunkZ)->onResolve(...$cache->getResolveCallbacks());
}else{
//dump the cache, it'll be regenerated the next time it's requested
$this->destroy($chunkX, $chunkZ);
diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php
index 35b20c74aa0..565a6c18e0c 100644
--- a/src/network/mcpe/cache/CraftingDataCache.php
+++ b/src/network/mcpe/cache/CraftingDataCache.php
@@ -25,21 +25,18 @@
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\FurnaceType;
-use pocketmine\crafting\RecipeIngredient;
use pocketmine\crafting\ShapedRecipe;
use pocketmine\crafting\ShapelessRecipe;
use pocketmine\crafting\ShapelessRecipeType;
-use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
-use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\recipe\CraftingRecipeBlockName;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipeBlockName;
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
use pocketmine\network\mcpe\protocol\types\recipe\PotionContainerChangeRecipe as ProtocolPotionContainerChangeRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\PotionTypeRecipe as ProtocolPotionTypeRecipe;
-use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient;
+use pocketmine\network\mcpe\protocol\types\recipe\RecipeUnlockingRequirement;
use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
use pocketmine\timings\Timings;
@@ -86,27 +83,24 @@ private function buildCraftingDataCache(CraftingManager $manager) : CraftingData
$converter = TypeConverter::getInstance();
$recipesWithTypeIds = [];
+ $noUnlockingRequirement = new RecipeUnlockingRequirement(null);
foreach($manager->getCraftingRecipeIndex() as $index => $recipe){
if($recipe instanceof ShapelessRecipe){
- $typeTag = match($recipe->getType()->id()){
- ShapelessRecipeType::CRAFTING()->id() => CraftingRecipeBlockName::CRAFTING_TABLE,
- ShapelessRecipeType::STONECUTTER()->id() => CraftingRecipeBlockName::STONECUTTER,
- ShapelessRecipeType::CARTOGRAPHY()->id() => CraftingRecipeBlockName::CARTOGRAPHY_TABLE,
- ShapelessRecipeType::SMITHING()->id() => CraftingRecipeBlockName::SMITHING_TABLE,
- default => throw new AssumptionFailedError("Unreachable"),
+ $typeTag = match($recipe->getType()){
+ ShapelessRecipeType::CRAFTING => CraftingRecipeBlockName::CRAFTING_TABLE,
+ ShapelessRecipeType::STONECUTTER => CraftingRecipeBlockName::STONECUTTER,
+ ShapelessRecipeType::CARTOGRAPHY => CraftingRecipeBlockName::CARTOGRAPHY_TABLE,
+ ShapelessRecipeType::SMITHING => CraftingRecipeBlockName::SMITHING_TABLE,
};
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
CraftingDataPacket::ENTRY_SHAPELESS,
Binary::writeInt($index),
- array_map(function(RecipeIngredient $item) use ($converter) : ProtocolRecipeIngredient{
- return $converter->coreRecipeIngredientToNet($item);
- }, $recipe->getIngredientList()),
- array_map(function(Item $item) use ($converter) : ItemStack{
- return $converter->coreItemStackToNet($item);
- }, $recipe->getResults()),
+ array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()),
+ array_map($converter->coreItemStackToNet(...), $recipe->getResults()),
$nullUUID,
$typeTag,
50,
+ $noUnlockingRequirement,
$index
);
}elseif($recipe instanceof ShapedRecipe){
@@ -121,25 +115,26 @@ private function buildCraftingDataCache(CraftingManager $manager) : CraftingData
CraftingDataPacket::ENTRY_SHAPED,
Binary::writeInt($index),
$inputs,
- array_map(function(Item $item) use ($converter) : ItemStack{
- return $converter->coreItemStackToNet($item);
- }, $recipe->getResults()),
+ array_map($converter->coreItemStackToNet(...), $recipe->getResults()),
$nullUUID,
CraftingRecipeBlockName::CRAFTING_TABLE,
50,
- $index
+ true,
+ $noUnlockingRequirement,
+ $index,
);
}else{
//TODO: probably special recipe types
}
}
- foreach(FurnaceType::getAll() as $furnaceType){
- $typeTag = match($furnaceType->id()){
- FurnaceType::FURNACE()->id() => FurnaceRecipeBlockName::FURNACE,
- FurnaceType::BLAST_FURNACE()->id() => FurnaceRecipeBlockName::BLAST_FURNACE,
- FurnaceType::SMOKER()->id() => FurnaceRecipeBlockName::SMOKER,
- default => throw new AssumptionFailedError("Unreachable"),
+ foreach(FurnaceType::cases() as $furnaceType){
+ $typeTag = match($furnaceType){
+ FurnaceType::FURNACE => FurnaceRecipeBlockName::FURNACE,
+ FurnaceType::BLAST_FURNACE => FurnaceRecipeBlockName::BLAST_FURNACE,
+ FurnaceType::SMOKER => FurnaceRecipeBlockName::SMOKER,
+ FurnaceType::CAMPFIRE => FurnaceRecipeBlockName::CAMPFIRE,
+ FurnaceType::SOUL_CAMPFIRE => FurnaceRecipeBlockName::SOUL_CAMPFIRE
};
foreach($manager->getFurnaceRecipeManager($furnaceType)->getAll() as $recipe){
$input = $converter->coreRecipeIngredientToNet($recipe->getInput())->getDescriptor();
@@ -175,7 +170,7 @@ private function buildCraftingDataCache(CraftingManager $manager) : CraftingData
}
$potionContainerChangeRecipes = [];
- $itemTypeDictionary = TypeConverter::getInstance()->getItemTypeDictionary();
+ $itemTypeDictionary = $converter->getItemTypeDictionary();
foreach($manager->getPotionContainerChangeRecipes() as $recipe){
$input = $itemTypeDictionary->fromStringId($recipe->getInputItemId());
$ingredient = $converter->coreRecipeIngredientToNet($recipe->getIngredient())->getDescriptor();
diff --git a/src/network/mcpe/compression/CompressBatchTask.php b/src/network/mcpe/compression/CompressBatchTask.php
index 96e9051b683..a513cfa86cd 100644
--- a/src/network/mcpe/compression/CompressBatchTask.php
+++ b/src/network/mcpe/compression/CompressBatchTask.php
@@ -25,6 +25,7 @@
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
+use function chr;
class CompressBatchTask extends AsyncTask{
@@ -43,7 +44,8 @@ public function __construct(
}
public function onRun() : void{
- $this->setResult($this->compressor->deserialize()->compress($this->data));
+ $compressor = $this->compressor->deserialize();
+ $this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($this->data));
}
public function onCompletion() : void{
diff --git a/src/network/mcpe/compression/Compressor.php b/src/network/mcpe/compression/Compressor.php
index a299f4eb08a..fbb98e78905 100644
--- a/src/network/mcpe/compression/Compressor.php
+++ b/src/network/mcpe/compression/Compressor.php
@@ -23,6 +23,8 @@
namespace pocketmine\network\mcpe\compression;
+use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
+
interface Compressor{
/**
* @throws DecompressionException
@@ -31,6 +33,14 @@ public function decompress(string $payload) : string;
public function compress(string $payload) : string;
+ /**
+ * Returns the canonical ID of this compressor, used to tell the remote end how to decompress a packet compressed
+ * with this compressor.
+ *
+ * @return CompressionAlgorithm::*
+ */
+ public function getNetworkId() : int;
+
/**
* Returns the minimum size of packet batch that the compressor will attempt to compress.
*
diff --git a/src/network/mcpe/compression/ZlibCompressor.php b/src/network/mcpe/compression/ZlibCompressor.php
index 317a64451f7..e908721c7a2 100644
--- a/src/network/mcpe/compression/ZlibCompressor.php
+++ b/src/network/mcpe/compression/ZlibCompressor.php
@@ -23,6 +23,7 @@
namespace pocketmine\network\mcpe\compression;
+use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use function function_exists;
@@ -37,7 +38,7 @@ final class ZlibCompressor implements Compressor{
public const DEFAULT_LEVEL = 7;
public const DEFAULT_THRESHOLD = 256;
- public const DEFAULT_MAX_DECOMPRESSION_SIZE = 2 * 1024 * 1024;
+ public const DEFAULT_MAX_DECOMPRESSION_SIZE = 8 * 1024 * 1024;
/**
* @see SingletonTrait::make()
@@ -67,17 +68,16 @@ public function decompress(string $payload) : string{
return $result;
}
- private static function zlib_encode(string $data, int $level) : string{
- return Utils::assumeNotFalse(zlib_encode($data, ZLIB_ENCODING_RAW, $level), "ZLIB compression failed");
- }
-
public function compress(string $payload) : string{
$compressible = $this->minCompressionSize !== null && strlen($payload) >= $this->minCompressionSize;
- if(function_exists('libdeflate_deflate_compress')){
- return $compressible ?
- libdeflate_deflate_compress($payload, $this->level) :
- self::zlib_encode($payload, 0);
- }
- return self::zlib_encode($payload, $compressible ? $this->level : 0);
+ $level = $compressible ? $this->level : 0;
+
+ return function_exists('libdeflate_deflate_compress') ?
+ libdeflate_deflate_compress($payload, $level) :
+ Utils::assumeNotFalse(zlib_encode($payload, ZLIB_ENCODING_RAW, $level), "ZLIB compression failed");
+ }
+
+ public function getNetworkId() : int{
+ return CompressionAlgorithm::ZLIB;
}
}
diff --git a/src/network/mcpe/convert/ItemTranslator.php b/src/network/mcpe/convert/ItemTranslator.php
index ea7a64d4033..2c5ad0607f9 100644
--- a/src/network/mcpe/convert/ItemTranslator.php
+++ b/src/network/mcpe/convert/ItemTranslator.php
@@ -23,6 +23,7 @@
namespace pocketmine\network\mcpe\convert;
+use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\ItemDeserializer;
use pocketmine\data\bedrock\item\ItemSerializer;
use pocketmine\data\bedrock\item\ItemTypeDeserializeException;
@@ -37,18 +38,19 @@
* This class handles translation between network item ID+metadata to PocketMine-MP internal ID+metadata and vice versa.
*/
final class ItemTranslator{
- public const NO_BLOCK_RUNTIME_ID = 0;
+ public const NO_BLOCK_RUNTIME_ID = 0; //this is technically a valid block runtime ID, but is used to represent "no block" (derp mojang)
public function __construct(
private ItemTypeDictionary $itemTypeDictionary,
private BlockStateDictionary $blockStateDictionary,
private ItemSerializer $itemSerializer,
- private ItemDeserializer $itemDeserializer
+ private ItemDeserializer $itemDeserializer,
+ private BlockItemIdMap $blockItemIdMap
){}
/**
* @return int[]|null
- * @phpstan-return array{int, int, int}|null
+ * @phpstan-return array{int, int, ?int}|null
*/
public function toNetworkIdQuiet(Item $item) : ?array{
try{
@@ -60,7 +62,7 @@ public function toNetworkIdQuiet(Item $item) : ?array{
/**
* @return int[]
- * @phpstan-return array{int, int, int}
+ * @phpstan-return array{int, int, ?int}
*
* @throws ItemTypeSerializeException
*/
@@ -78,7 +80,7 @@ public function toNetworkId(Item $item) : array{
throw new AssumptionFailedError("Unmapped blockstate returned by blockstate serializer: " . $blockStateData->toNbt());
}
}else{
- $blockRuntimeId = self::NO_BLOCK_RUNTIME_ID; //this is technically a valid block runtime ID, but is used to represent "no block" (derp mojang)
+ $blockRuntimeId = null;
}
return [$numericId, $itemData->getMeta(), $blockRuntimeId];
@@ -106,11 +108,13 @@ public function fromNetworkId(int $networkId, int $networkMeta, int $networkBloc
}
$blockStateData = null;
- if($networkBlockRuntimeId !== self::NO_BLOCK_RUNTIME_ID){
+ if($this->blockItemIdMap->lookupBlockId($stringId) !== null){
$blockStateData = $this->blockStateDictionary->generateDataFromStateId($networkBlockRuntimeId);
if($blockStateData === null){
throw new TypeConversionException("Blockstate runtimeID $networkBlockRuntimeId does not correspond to any known blockstate");
}
+ }elseif($networkBlockRuntimeId !== self::NO_BLOCK_RUNTIME_ID){
+ throw new TypeConversionException("Item $stringId is not a blockitem, but runtime ID $networkBlockRuntimeId was provided");
}
try{
diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php
index e01a5a10f55..e886b2b8be0 100644
--- a/src/network/mcpe/convert/TypeConverter.php
+++ b/src/network/mcpe/convert/TypeConverter.php
@@ -30,13 +30,17 @@
use pocketmine\crafting\TagWildcardRecipeIngredient;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\item\BlockItemIdMap;
+use pocketmine\data\bedrock\item\ItemTypeNames;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
+use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
+use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
+use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield;
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient;
use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor;
@@ -76,13 +80,14 @@ public function __construct(){
);
$this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
- $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId("minecraft:shield");
+ $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
$this->itemTranslator = new ItemTranslator(
$this->itemTypeDictionary,
$this->blockTranslator->getBlockStateDictionary(),
GlobalItemDataHandlers::getSerializer(),
- GlobalItemDataHandlers::getDeserializer()
+ GlobalItemDataHandlers::getDeserializer(),
+ $this->blockItemIdMap
);
$this->skinAdapter = new LegacySkinAdapter();
@@ -107,33 +112,23 @@ public function setSkinAdapter(SkinAdapter $skinAdapter) : void{
* @internal
*/
public function coreGameModeToProtocol(GameMode $gamemode) : int{
- switch($gamemode->id()){
- case GameMode::SURVIVAL()->id():
- return ProtocolGameMode::SURVIVAL;
- case GameMode::CREATIVE()->id():
- case GameMode::SPECTATOR()->id():
- return ProtocolGameMode::CREATIVE;
- case GameMode::ADVENTURE()->id():
- return ProtocolGameMode::ADVENTURE;
- default:
- throw new AssumptionFailedError("Unknown game mode");
- }
+ return match($gamemode){
+ GameMode::SURVIVAL => ProtocolGameMode::SURVIVAL,
+ //TODO: native spectator support
+ GameMode::CREATIVE, GameMode::SPECTATOR => ProtocolGameMode::CREATIVE,
+ GameMode::ADVENTURE => ProtocolGameMode::ADVENTURE,
+ };
}
public function protocolGameModeToCore(int $gameMode) : ?GameMode{
- switch($gameMode){
- case ProtocolGameMode::SURVIVAL:
- return GameMode::SURVIVAL();
- case ProtocolGameMode::CREATIVE:
- return GameMode::CREATIVE();
- case ProtocolGameMode::ADVENTURE:
- return GameMode::ADVENTURE();
- case ProtocolGameMode::CREATIVE_VIEWER:
- case ProtocolGameMode::SURVIVAL_VIEWER:
- return GameMode::SPECTATOR();
- default:
- return null;
- }
+ return match($gameMode){
+ ProtocolGameMode::SURVIVAL => GameMode::SURVIVAL,
+ ProtocolGameMode::CREATIVE => GameMode::CREATIVE,
+ ProtocolGameMode::ADVENTURE => GameMode::ADVENTURE,
+ ProtocolGameMode::SURVIVAL_VIEWER, ProtocolGameMode::CREATIVE_VIEWER => GameMode::SPECTATOR,
+ //TODO: native spectator support
+ default => null,
+ };
}
public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
@@ -147,7 +142,7 @@ public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : Proto
}elseif($ingredient instanceof ExactRecipeIngredient){
$item = $ingredient->getItem();
[$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId($item);
- if($blockRuntimeId !== ItemTranslator::NO_BLOCK_RUNTIME_ID){
+ if($blockRuntimeId !== null){
$meta = $this->blockTranslator->getBlockStateDictionary()->getMetaFromStateId($blockRuntimeId);
if($meta === null){
throw new AssumptionFailedError("Every block state should have an associated meta value");
@@ -226,26 +221,36 @@ public function coreItemStackToNet(Item $itemStack) : ItemStack{
[$id, $meta, $blockRuntimeId] = $idMeta;
}
+ $extraData = $id === $this->shieldRuntimeId ?
+ new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
+ new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
+ $extraDataSerializer = PacketSerializer::encoder();
+ $extraData->write($extraDataSerializer);
+
return new ItemStack(
$id,
$meta,
$itemStack->getCount(),
- $blockRuntimeId,
- $nbt,
- [],
- [],
- $id === $this->shieldRuntimeId ? 0 : null
+ $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
+ $extraDataSerializer->getBuffer(),
);
}
/**
+ * WARNING: Avoid this in server-side code. If you need to compare ItemStacks provided by the client to the
+ * server, prefer encoding the server's itemstack and comparing the serialized ItemStack, instead of converting
+ * the client's ItemStack to a core Item.
+ * This method will fully decode the item's extra data, which can be very costly if the item has a lot of NBT data.
+ *
* @throws TypeConversionException
*/
public function netItemStackToCore(ItemStack $itemStack) : Item{
if($itemStack->getId() === 0){
return VanillaItems::AIR();
}
- $compound = $itemStack->getNbt();
+ $extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
+
+ $compound = $extraData->getNbt();
$itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
@@ -264,4 +269,11 @@ public function netItemStackToCore(ItemStack $itemStack) : Item{
return $itemResult;
}
+
+ public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
+ $extraDataDeserializer = PacketSerializer::decoder($extraData, 0);
+ return $id === $this->shieldRuntimeId ?
+ ItemStackExtraDataShield::read($extraDataDeserializer) :
+ ItemStackExtraData::read($extraDataDeserializer);
+ }
}
diff --git a/src/network/mcpe/encryption/EncryptionUtils.php b/src/network/mcpe/encryption/EncryptionUtils.php
index 920c54a7ef9..4ad9ceb31c2 100644
--- a/src/network/mcpe/encryption/EncryptionUtils.php
+++ b/src/network/mcpe/encryption/EncryptionUtils.php
@@ -33,6 +33,7 @@
use function openssl_digest;
use function openssl_error_string;
use function openssl_pkey_derive;
+use function openssl_pkey_get_details;
use function str_pad;
use const STR_PAD_LEFT;
@@ -42,7 +43,20 @@ private function __construct(){
//NOOP
}
+ private static function validateKey(\OpenSSLAsymmetricKey $key) : void{
+ $keyDetails = Utils::assumeNotFalse(openssl_pkey_get_details($key));
+ if(!isset($keyDetails["ec"]["curve_name"])){
+ throw new \InvalidArgumentException("Key must be an EC key");
+ }
+ $curveName = $keyDetails["ec"]["curve_name"];
+ if($curveName !== JwtUtils::BEDROCK_SIGNING_KEY_CURVE_NAME){
+ throw new \InvalidArgumentException("Key must belong to the " . JwtUtils::BEDROCK_SIGNING_KEY_CURVE_NAME . " elliptic curve, got $curveName");
+ }
+ }
+
public static function generateSharedSecret(\OpenSSLAsymmetricKey $localPriv, \OpenSSLAsymmetricKey $remotePub) : \GMP{
+ self::validateKey($localPriv);
+ self::validateKey($remotePub);
$hexSecret = openssl_pkey_derive($remotePub, $localPriv, 48);
if($hexSecret === false){
throw new \InvalidArgumentException("Failed to derive shared secret: " . openssl_error_string());
diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php
index f49a9b81765..e74eb87c618 100644
--- a/src/network/mcpe/handler/InGamePacketHandler.php
+++ b/src/network/mcpe/handler/InGamePacketHandler.php
@@ -24,7 +24,6 @@
namespace pocketmine\network\mcpe\handler;
use pocketmine\block\BaseSign;
-use pocketmine\block\ItemFrame;
use pocketmine\block\Lectern;
use pocketmine\block\tile\Sign;
use pocketmine\block\utils\SignText;
@@ -57,11 +56,9 @@
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
-use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
-use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
@@ -79,7 +76,6 @@
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
-use pocketmine\network\mcpe\protocol\RequestAbilityPacket;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
@@ -111,12 +107,11 @@
use pocketmine\utils\Utils;
use pocketmine\world\format\Chunk;
use function array_push;
-use function base64_encode;
use function count;
use function fmod;
+use function get_debug_type;
use function implode;
use function in_array;
-use function is_bool;
use function is_infinite;
use function is_nan;
use function json_decode;
@@ -201,7 +196,7 @@ public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
}
$hasMoved = $this->lastPlayerAuthInputPosition === null || !$this->lastPlayerAuthInputPosition->equals($rawPos);
- $newPos = $rawPos->round(4)->subtract(0, 1.62, 0);
+ $newPos = $rawPos->subtract(0, 1.62, 0)->round(4);
if($this->forceMoveSync && $hasMoved){
$curPos = $this->player->getLocation();
@@ -224,11 +219,13 @@ public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
$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);
+ $flying = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_FLYING, PlayerAuthInputFlags::STOP_FLYING);
$mismatch =
($sneaking !== null && !$this->player->toggleSneak($sneaking)) |
($sprinting !== null && !$this->player->toggleSprint($sprinting)) |
($swimming !== null && !$this->player->toggleSwim($swimming)) |
- ($gliding !== null && !$this->player->toggleGlide($gliding));
+ ($gliding !== null && !$this->player->toggleGlide($gliding)) |
+ ($flying !== null && !$this->player->toggleFlight($flying));
if((bool) $mismatch){
$this->player->sendData([$this->player]);
}
@@ -236,6 +233,9 @@ public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){
$this->player->jump();
}
+ if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){
+ $this->player->missSwing();
+ }
}
if(!$this->forceMoveSync && $hasMoved){
@@ -442,9 +442,18 @@ private function handleNormalTransaction(NormalTransactionData $data, int $itemS
return false;
}
$serverItemStack = $this->session->getTypeConverter()->coreItemStackToNet($sourceSlotItem);
- //because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
- //itemstack info with the one the client sent. This is costly, but we don't have any other option :(
- if(!$serverItemStack->equals($clientItemStack)){
+ //Sadly we don't have itemstack IDs here, so we have to compare the basic item properties to ensure that we're
+ //dropping the item the client expects (inventory might be out of sync with the client).
+ if(
+ $serverItemStack->getId() !== $clientItemStack->getId() ||
+ $serverItemStack->getMeta() !== $clientItemStack->getMeta() ||
+ $serverItemStack->getCount() !== $clientItemStack->getCount() ||
+ $serverItemStack->getBlockRuntimeId() !== $clientItemStack->getBlockRuntimeId()
+ //Raw extraData may not match because of TAG_Compound key ordering differences, and decoding it to compare
+ //is costly. Assume that we're in sync if id+meta+count+runtimeId match.
+ //NB: Make sure $clientItemStack isn't used to create the dropped item, as that would allow the client
+ //to change the item NBT since we're not validating it.
+ ){
return false;
}
@@ -484,15 +493,18 @@ private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
- if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
- $this->onFailedBlockAction($vBlockPos, $data->getFace());
- }
+ $this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos);
+ //always sync this in case plugins caused a different result than the client expected
+ //we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating
+ //more information up the stack. For now I think this is good enough.
+ //if only the client would tell us what blocks it thinks changed...
+ $this->syncBlocksNearby($vBlockPos, $data->getFace());
return true;
case UseItemTransactionData::ACTION_BREAK_BLOCK:
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->breakBlock($vBlockPos)){
- $this->onFailedBlockAction($vBlockPos, null);
+ $this->syncBlocksNearby($vBlockPos, null);
}
return true;
case UseItemTransactionData::ACTION_CLICK_AIR:
@@ -520,9 +532,9 @@ private static function validateFacing(int $facing) : void{
}
/**
- * Internal function used to execute rollbacks when an action fails on a block.
+ * Syncs blocks nearby to ensure that the client and server agree on the world's blocks after a block interaction.
*/
- private function onFailedBlockAction(Vector3 $blockPos, ?int $face) : void{
+ private function syncBlocksNearby(Vector3 $blockPos, ?int $face) : void{
if($blockPos->distanceSquared($this->player->getLocation()) < 10000){
$blocks = $blockPos->sidesArray();
if($face !== null){
@@ -659,7 +671,7 @@ public function handleBlockPickRequest(BlockPickRequestPacket $packet) : bool{
}
public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{
- return false; //TODO
+ return $this->player->pickEntity($packet->actorUniqueId);
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
@@ -673,7 +685,7 @@ private function handlePlayerActionFromData(int $action, BlockPosition $blockPos
case PlayerAction::START_BREAK:
self::validateFacing($face);
if(!$this->player->attackBlock($pos, $face)){
- $this->onFailedBlockAction($pos, $face);
+ $this->syncBlocksNearby($pos, $face);
}
break;
@@ -728,10 +740,6 @@ public function handlePlayerHotbar(PlayerHotbarPacket $packet) : bool{
return true; //this packet is useless
}
- public function handleCraftingEvent(CraftingEventPacket $packet) : bool{
- return true; //this is a broken useless packet, so we don't use it
- }
-
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
if($pos->distanceSquared($this->player->getLocation()) > 10000){
@@ -743,27 +751,32 @@ public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
if($block instanceof BaseSign){
- if(($textBlobTag = $nbt->getCompoundTag(Sign::TAG_FRONT_TEXT)?->getTag(Sign::TAG_TEXT_BLOB)) instanceof StringTag){
- try{
- $text = SignText::fromBlob($textBlobTag->getValue());
- }catch(\InvalidArgumentException $e){
- throw PacketHandlingException::wrap($e, "Invalid sign text update");
- }
+ $frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT);
+ if(!$frontTextTag instanceof CompoundTag){
+ throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data");
+ }
+ $textBlobTag = $frontTextTag->getTag(Sign::TAG_TEXT_BLOB);
+ if(!$textBlobTag instanceof StringTag){
+ throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data");
+ }
+
+ try{
+ $text = SignText::fromBlob($textBlobTag->getValue());
+ }catch(\InvalidArgumentException $e){
+ throw PacketHandlingException::wrap($e, "Invalid sign text update");
+ }
- try{
- if(!$block->updateText($this->player, $text)){
- foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){
- $this->session->sendDataPacket($updatePacket);
- }
+ try{
+ if(!$block->updateText($this->player, $text)){
+ foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){
+ $this->session->sendDataPacket($updatePacket);
}
- }catch(\UnexpectedValueException $e){
- throw PacketHandlingException::wrap($e);
}
-
- return true;
+ }catch(\UnexpectedValueException $e){
+ throw PacketHandlingException::wrap($e);
}
- $this->session->getLogger()->debug("Invalid sign update data: " . base64_encode($packet->nbt->getEncodedNbt()));
+ return true;
}
return false;
@@ -775,7 +788,7 @@ public function handlePlayerInput(PlayerInputPacket $packet) : bool{
public function handleSetPlayerGameType(SetPlayerGameTypePacket $packet) : bool{
$gameMode = $this->session->getTypeConverter()->protocolGameModeToCore($packet->gamemode);
- if($gameMode === null || !$gameMode->equals($this->player->getGamemode())){
+ if($gameMode !== $this->player->getGamemode()){
//Set this back to default. TODO: handle this properly
$this->session->syncGameMode($this->player->getGamemode(), true);
}
@@ -796,15 +809,6 @@ public function handleRequestChunkRadius(RequestChunkRadiusPacket $packet) : boo
return true;
}
- public function handleItemFrameDropItem(ItemFrameDropItemPacket $packet) : bool{
- $blockPosition = $packet->blockPosition;
- $block = $this->player->getWorld()->getBlockAt($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ());
- if($block instanceof ItemFrame && $block->getFramedItem() !== null){
- return $this->player->attackBlock(new Vector3($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ()), $block->getFacing());
- }
- return false;
- }
-
public function handleBossEvent(BossEventPacket $packet) : bool{
return false; //TODO
}
@@ -866,8 +870,12 @@ private function checkBookText(string $string, string $fieldName, int $softLimit
}
public function handleBookEdit(BookEditPacket $packet) : bool{
+ $inventory = $this->player->getInventory();
+ if(!$inventory->slotExists($packet->inventorySlot)){
+ return false;
+ }
//TODO: break this up into book API things
- $oldBook = $this->player->getInventory()->getItem($packet->inventorySlot);
+ $oldBook = $inventory->getItem($packet->inventorySlot);
if(!($oldBook instanceof WritableBook)){
return false;
}
@@ -982,11 +990,6 @@ public function handleLabTable(LabTablePacket $packet) : bool{
}
public function handleLecternUpdate(LecternUpdatePacket $packet) : bool{
- if($packet->dropBook){
- //Drop book is handled with an interact event on use item transaction
- return true;
- }
-
$pos = $packet->blockPosition;
$chunkX = $pos->getX() >> Chunk::COORD_BIT_SIZE;
$chunkZ = $pos->getZ() >> Chunk::COORD_BIT_SIZE;
@@ -998,7 +1001,7 @@ public function handleLecternUpdate(LecternUpdatePacket $packet) : bool{
$lectern = $world->getBlockAt($pos->getX(), $pos->getY(), $pos->getZ());
if($lectern instanceof Lectern && $this->player->canInteract($lectern->getPosition(), 15)){
if(!$lectern->onPageTurn($packet->page)){
- $this->onFailedBlockAction($lectern->getPosition(), null);
+ $this->syncBlocksNearby($lectern->getPosition(), null);
}
return true;
}
@@ -1024,22 +1027,4 @@ public function handleEmote(EmotePacket $packet) : bool{
$this->player->emote($packet->getEmoteId());
return true;
}
-
- public function handleRequestAbility(RequestAbilityPacket $packet) : bool{
- if($packet->getAbilityId() === RequestAbilityPacket::ABILITY_FLYING){
- $isFlying = $packet->getAbilityValue();
- if(!is_bool($isFlying)){
- throw new PacketHandlingException("Flying ability should always have a bool value");
- }
- if($isFlying !== $this->player->isFlying()){
- if(!$this->player->toggleFlight($isFlying)){
- $this->session->syncAbilities($this->player);
- }
- }
-
- return true;
- }
-
- return false;
- }
}
diff --git a/src/network/mcpe/handler/ItemStackContainerIdTranslator.php b/src/network/mcpe/handler/ItemStackContainerIdTranslator.php
index f9ea9ef5f68..e9356dd73b2 100644
--- a/src/network/mcpe/handler/ItemStackContainerIdTranslator.php
+++ b/src/network/mcpe/handler/ItemStackContainerIdTranslator.php
@@ -70,6 +70,7 @@ public static function translate(int $containerInterfaceId, int $currentWindowId
ContainerUIIds::MATERIAL_REDUCER_OUTPUT,
ContainerUIIds::SMITHING_TABLE_INPUT,
ContainerUIIds::SMITHING_TABLE_MATERIAL,
+ ContainerUIIds::SMITHING_TABLE_TEMPLATE,
ContainerUIIds::STONECUTTER_INPUT,
ContainerUIIds::TRADE2_INGREDIENT1,
ContainerUIIds::TRADE2_INGREDIENT2,
@@ -84,6 +85,7 @@ public static function translate(int $containerInterfaceId, int $currentWindowId
ContainerUIIds::FURNACE_FUEL,
ContainerUIIds::FURNACE_INGREDIENT,
ContainerUIIds::FURNACE_RESULT,
+ ContainerUIIds::HORSE_EQUIP,
ContainerUIIds::LEVEL_ENTITY, //chest
ContainerUIIds::SHULKER_BOX,
ContainerUIIds::SMOKER_INGREDIENT => [$currentWindowId, $slotId],
diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php
index f9532291c36..54a19259010 100644
--- a/src/network/mcpe/handler/ItemStackRequestExecutor.php
+++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php
@@ -23,12 +23,13 @@
namespace pocketmine\network\mcpe\handler;
-use pocketmine\inventory\CreativeInventory;
+use pocketmine\block\inventory\EnchantInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\CraftingTransaction;
+use pocketmine\inventory\transaction\EnchantingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilder;
use pocketmine\inventory\transaction\TransactionBuilderInventory;
@@ -111,10 +112,10 @@ private function matchItemStack(Inventory $inventory, int $slotId, int $clientIt
* @throws ItemStackRequestProcessException
*/
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
- [$windowId, $slotId] = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $info->getSlotId());
+ [$windowId, $slotId] = ItemStackContainerIdTranslator::translate($info->getContainerName()->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $info->getSlotId());
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slotId);
if($windowAndSlot === null){
- throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerId() . ", slot ID: " . $info->getSlotId());
+ throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerName()->getContainerId() . ", slot ID: " . $info->getSlotId());
}
[$inventory, $slot] = $windowAndSlot;
if(!$inventory->slotExists($slot)){
@@ -141,7 +142,7 @@ protected function transferItems(ItemStackRequestSlotInfo $source, ItemStackRequ
* @throws ItemStackRequestProcessException
*/
protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
- if($slotInfo->getContainerId() === ContainerUIIds::CREATED_OUTPUT && $slotInfo->getSlotId() === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
+ if($slotInfo->getContainerName()->getContainerId() === ContainerUIIds::CREATED_OUTPUT && $slotInfo->getSlotId() === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
//special case for the "created item" output slot
//TODO: do we need to send a response for this slot info?
return $this->takeCreatedItem($count);
@@ -288,7 +289,7 @@ protected function takeCreatedItem(int $count) : Item{
* @throws ItemStackRequestProcessException
*/
private function assertDoingCrafting() : void{
- if(!$this->specialTransaction instanceof CraftingTransaction){
+ if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction){
if($this->specialTransaction === null){
throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action");
}else{
@@ -327,14 +328,23 @@ protected function processItemStackRequestAction(ItemStackRequestAction $action)
$this->builder->addAction(new DestroyItemAction($destroyed));
}elseif($action instanceof CreativeCreateStackRequestAction){
- $item = CreativeInventory::getInstance()->getItem($action->getCreativeItemId());
+ $item = $this->player->getCreativeInventory()->getItem($action->getCreativeItemId());
if($item === null){
throw new ItemStackRequestProcessException("No such creative item index: " . $action->getCreativeItemId());
}
$this->setNextCreatedItem($item, true);
}elseif($action instanceof CraftRecipeStackRequestAction){
- $this->beginCrafting($action->getRecipeId(), 1);
+ $window = $this->player->getCurrentWindow();
+ if($window instanceof EnchantInventory){
+ $optionId = $this->inventoryManager->getEnchantingTableOptionIndex($action->getRecipeId());
+ if($optionId !== null && ($option = $window->getOption($optionId)) !== null){
+ $this->specialTransaction = new EnchantingTransaction($this->player, $option, $optionId + 1);
+ $this->setNextCreatedItem($window->getOutput($optionId));
+ }
+ }else{
+ $this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
+ }
}elseif($action instanceof CraftRecipeAutoStackRequestAction){
$this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
}elseif($action instanceof CraftingConsumeInputStackRequestAction){
@@ -381,7 +391,7 @@ public function generateInventoryTransaction() : InventoryTransaction{
public function buildItemStackResponse() : ItemStackResponse{
$builder = new ItemStackResponseBuilder($this->request->getRequestId(), $this->inventoryManager);
foreach($this->requestSlotInfos as $requestInfo){
- $builder->addSlot($requestInfo->getContainerId(), $requestInfo->getSlotId());
+ $builder->addSlot($requestInfo->getContainerName()->getContainerId(), $requestInfo->getSlotId());
}
return $builder->build();
diff --git a/src/network/mcpe/handler/ItemStackResponseBuilder.php b/src/network/mcpe/handler/ItemStackResponseBuilder.php
index 09af69f2a3b..1369e3ba722 100644
--- a/src/network/mcpe/handler/ItemStackResponseBuilder.php
+++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php
@@ -27,6 +27,7 @@
use pocketmine\item\Durable;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
+use pocketmine\network\mcpe\protocol\types\inventory\FullContainerName;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponseContainerInfo;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponseSlotInfo;
@@ -99,7 +100,7 @@ public function build() : ItemStackResponse{
$responseContainerInfos = [];
foreach($responseInfosByContainer as $containerInterfaceId => $responseInfos){
- $responseContainerInfos[] = new ItemStackResponseContainerInfo($containerInterfaceId, $responseInfos);
+ $responseContainerInfos[] = new ItemStackResponseContainerInfo(new FullContainerName($containerInterfaceId), $responseInfos);
}
return new ItemStackResponse(ItemStackResponse::RESULT_OK, $this->requestId, $responseContainerInfos);
diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php
index a8c3d4d628b..2e3a5151989 100644
--- a/src/network/mcpe/handler/LoginPacketHandler.php
+++ b/src/network/mcpe/handler/LoginPacketHandler.php
@@ -73,8 +73,10 @@ public function handleLogin(LoginPacket $packet) : bool{
try{
$skin = $this->session->getTypeConverter()->getSkinAdapter()->fromSkinData(ClientDataToSkinDataHelper::fromClientData($clientData));
}catch(\InvalidArgumentException | InvalidSkinException $e){
- $this->session->getLogger()->debug("Invalid skin: " . $e->getMessage());
- $this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_invalidSkin());
+ $this->session->disconnectWithError(
+ reason: "Invalid skin: " . $e->getMessage(),
+ disconnectScreenMessage: KnownTranslationFactory::disconnectionScreen_invalidSkin()
+ );
return true;
}
@@ -83,6 +85,9 @@ public function handleLogin(LoginPacket $packet) : bool{
throw new PacketHandlingException("Invalid login UUID");
}
$uuid = Uuid::fromString($extraData->identity);
+ $arrClientData = (array) $clientData;
+ $arrClientData["TitleID"] = $extraData->titleId;
+
if($extraData->XUID !== ""){
$playerInfo = new XboxLivePlayerInfo(
$extraData->XUID,
@@ -90,7 +95,7 @@ public function handleLogin(LoginPacket $packet) : bool{
$uuid,
$skin,
$clientData->LanguageCode,
- (array) $clientData
+ $arrClientData
);
}else{
$playerInfo = new PlayerInfo(
@@ -98,7 +103,7 @@ public function handleLogin(LoginPacket $packet) : bool{
$uuid,
$skin,
$clientData->LanguageCode,
- (array) $clientData
+ $arrClientData
);
}
($this->playerInfoConsumer)($playerInfo);
@@ -164,6 +169,7 @@ protected function fetchAuthData(JwtChain $chain) : AuthenticationData{
$mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models
$mapper->bExceptionOnMissingData = true;
$mapper->bExceptionOnUndefinedProperty = true;
+ $mapper->bStrictObjectTypeChecking = true;
try{
/** @var AuthenticationData $extraData */
$extraData = $mapper->map($claims["extraData"], new AuthenticationData());
@@ -192,6 +198,7 @@ protected function parseClientData(string $clientDataJwt) : ClientData{
$mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models
$mapper->bExceptionOnMissingData = true;
$mapper->bExceptionOnUndefinedProperty = true;
+ $mapper->bStrictObjectTypeChecking = true;
try{
$clientData = $mapper->map($clientDataClaims, new ClientData());
}catch(\JsonMapper_Exception $e){
diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php
index 036f9aed18d..b80874938c4 100644
--- a/src/network/mcpe/handler/PreSpawnPacketHandler.php
+++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php
@@ -37,8 +37,9 @@
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\LevelSettings;
+use pocketmine\network\mcpe\protocol\types\NetworkPermissions;
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
-use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
+use pocketmine\network\mcpe\protocol\types\ServerAuthMovementMode;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
use pocketmine\player\Player;
use pocketmine\Server;
@@ -97,7 +98,7 @@ public function setUp() : void{
$this->server->getMotd(),
"",
false,
- new PlayerMovementSettings(PlayerMovementType::SERVER_AUTHORITATIVE_V1, 0, false),
+ new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V2, 0, false),
0,
0,
"",
@@ -106,6 +107,7 @@ public function setUp() : void{
Uuid::fromString(Uuid::NIL),
false,
false,
+ new NetworkPermissions(disableClientSounds: true),
[],
0,
$typeConverter->getItemTypeDictionary()->getEntries(),
diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php
index 7438fe47c1a..5e26713946f 100644
--- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php
+++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php
@@ -37,12 +37,13 @@
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType;
use pocketmine\resourcepacks\ResourcePack;
-use pocketmine\resourcepacks\ResourcePackManager;
+use function array_keys;
use function array_map;
use function ceil;
use function count;
use function implode;
use function strpos;
+use function strtolower;
use function substr;
/**
@@ -50,43 +51,82 @@
* packs to the client.
*/
class ResourcePacksPacketHandler extends PacketHandler{
- private const PACK_CHUNK_SIZE = 128 * 1024; //128KB
+ private const PACK_CHUNK_SIZE = 256 * 1024; //256KB
+
+ /**
+ * Larger values allow downloading more chunks at the same time, increasing download speed, but the client may choke
+ * and cause the download speed to drop (due to ACKs taking too long to arrive).
+ */
+ private const MAX_CONCURRENT_CHUNK_REQUESTS = 1;
+
+ /**
+ * @var ResourcePack[]
+ * @phpstan-var array
+ */
+ private array $resourcePacksById = [];
/** @var bool[][] uuid => [chunk index => hasSent] */
private array $downloadedChunks = [];
+ /** @phpstan-var \SplQueue */
+ private \SplQueue $requestQueue;
+
+ private int $activeRequests = 0;
+
/**
- * @phpstan-param \Closure() : void $completionCallback
+ * @param ResourcePack[] $resourcePackStack
+ * @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
+ *
+ * @phpstan-param list $resourcePackStack
+ * @phpstan-param array $encryptionKeys
+ * @phpstan-param \Closure() : void $completionCallback
*/
public function __construct(
private NetworkSession $session,
- private ResourcePackManager $resourcePackManager,
+ private array $resourcePackStack,
+ private array $encryptionKeys,
+ private bool $mustAccept,
private \Closure $completionCallback
- ){}
+ ){
+ $this->requestQueue = new \SplQueue();
+ foreach($resourcePackStack as $pack){
+ $this->resourcePacksById[$pack->getPackId()] = $pack;
+ }
+ }
+
+ private function getPackById(string $id) : ?ResourcePack{
+ return $this->resourcePacksById[strtolower($id)] ?? null;
+ }
public function setUp() : void{
$resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{
//TODO: more stuff
- $encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId());
return new ResourcePackInfoEntry(
$pack->getPackId(),
$pack->getPackVersion(),
$pack->getPackSize(),
- $encryptionKey ?? "",
+ $this->encryptionKeys[$pack->getPackId()] ?? "",
"",
$pack->getPackId(),
false
);
- }, $this->resourcePackManager->getResourceStack());
+ }, $this->resourcePackStack);
//TODO: support forcing server packs
- $this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false));
+ $this->session->sendDataPacket(ResourcePacksInfoPacket::create(
+ resourcePackEntries: $resourcePackEntries,
+ mustAccept: $this->mustAccept,
+ hasAddons: false,
+ hasScripts: false
+ ));
$this->session->getLogger()->debug("Waiting for client to accept resource packs");
}
private function disconnectWithError(string $error) : void{
- $this->session->getLogger()->error("Error downloading resource packs: " . $error);
- $this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_resourcePack());
+ $this->session->disconnectWithError(
+ reason: "Error downloading resource packs: " . $error,
+ disconnectScreenMessage: KnownTranslationFactory::disconnectionScreen_resourcePack()
+ );
}
public function handleResourcePackClientResponse(ResourcePackClientResponsePacket $packet) : bool{
@@ -102,11 +142,11 @@ public function handleResourcePackClientResponse(ResourcePackClientResponsePacke
if($splitPos !== false){
$uuid = substr($uuid, 0, $splitPos);
}
- $pack = $this->resourcePackManager->getPackById($uuid);
+ $pack = $this->getPackById($uuid);
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
- $this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList()));
+ $this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", array_keys($this->resourcePacksById)));
return false;
}
@@ -126,7 +166,7 @@ public function handleResourcePackClientResponse(ResourcePackClientResponsePacke
case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS:
$stack = array_map(static function(ResourcePack $pack) : ResourcePackStackEntry{
return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks
- }, $this->resourcePackManager->getResourceStack());
+ }, $this->resourcePackStack);
//we support chemistry blocks by default, the client should already have this installed
$stack[] = new ResourcePackStackEntry("0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0", "");
@@ -134,7 +174,7 @@ public function handleResourcePackClientResponse(ResourcePackClientResponsePacke
//we don't force here, because it doesn't have user-facing effects
//but it does have an annoying side-effect when true: it makes
//the client remove its own non-server-supplied resource packs.
- $this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, ProtocolInfo::MINECRAFT_VERSION_NETWORK, new Experiments([], false)));
+ $this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, ProtocolInfo::MINECRAFT_VERSION_NETWORK, new Experiments([], false), false));
$this->session->getLogger()->debug("Applying resource pack stack");
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
@@ -149,9 +189,9 @@ public function handleResourcePackClientResponse(ResourcePackClientResponsePacke
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
- $pack = $this->resourcePackManager->getPackById($packet->packId);
+ $pack = $this->getPackById($packet->packId);
if(!($pack instanceof ResourcePack)){
- $this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList()));
+ $this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", array_keys($this->resourcePacksById)));
return false;
}
@@ -174,8 +214,37 @@ public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $p
$this->downloadedChunks[$packId][$packet->chunkIndex] = true;
}
- $this->session->sendDataPacket(ResourcePackChunkDataPacket::create($packId, $packet->chunkIndex, $offset, $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE)));
+ $this->requestQueue->enqueue([$pack, $packet->chunkIndex]);
+ $this->processChunkRequestQueue();
return true;
}
+
+ private function processChunkRequestQueue() : void{
+ if($this->activeRequests >= self::MAX_CONCURRENT_CHUNK_REQUESTS || $this->requestQueue->isEmpty()){
+ return;
+ }
+ /**
+ * @var ResourcePack $pack
+ * @var int $chunkIndex
+ */
+ [$pack, $chunkIndex] = $this->requestQueue->dequeue();
+
+ $packId = $pack->getPackId();
+ $offset = $chunkIndex * self::PACK_CHUNK_SIZE;
+ $chunkData = $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE);
+ $this->activeRequests++;
+ $this->session
+ ->sendDataPacketWithReceipt(ResourcePackChunkDataPacket::create($packId, $chunkIndex, $offset, $chunkData))
+ ->onCompletion(
+ function() : void{
+ $this->activeRequests--;
+ $this->processChunkRequestQueue();
+ },
+ function() : void{
+ //this may have been rejected because of a disconnection - this will do nothing in that case
+ $this->disconnectWithError("Plugin interrupted sending of resource packs");
+ }
+ );
+ }
}
diff --git a/src/network/mcpe/handler/SessionStartPacketHandler.php b/src/network/mcpe/handler/SessionStartPacketHandler.php
index dd7ae47a854..8163574dbd0 100644
--- a/src/network/mcpe/handler/SessionStartPacketHandler.php
+++ b/src/network/mcpe/handler/SessionStartPacketHandler.php
@@ -27,7 +27,6 @@
use pocketmine\network\mcpe\protocol\NetworkSettingsPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\RequestNetworkSettingsPacket;
-use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
final class SessionStartPacketHandler extends PacketHandler{
@@ -50,7 +49,7 @@ public function handleRequestNetworkSettings(RequestNetworkSettingsPacket $packe
//TODO: we're filling in the defaults to get pre-1.19.30 behaviour back for now, but we should explore the new options in the future
$this->session->sendDataPacket(NetworkSettingsPacket::create(
NetworkSettingsPacket::COMPRESS_EVERYTHING,
- CompressionAlgorithm::ZLIB,
+ $this->session->getCompressor()->getNetworkId(),
false,
0,
0
diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php
index 93dff68a2dc..44328f8f3f0 100644
--- a/src/network/mcpe/raklib/RakLibInterface.php
+++ b/src/network/mcpe/raklib/RakLibInterface.php
@@ -33,14 +33,15 @@
use pocketmine\network\mcpe\PacketBroadcaster;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\PacketHandlingException;
use pocketmine\player\GameMode;
use pocketmine\Server;
+use pocketmine\thread\ThreadCrashException;
use pocketmine\timings\Timings;
use pocketmine\utils\Utils;
+use pocketmine\YmlServerProperties;
use raklib\generic\DisconnectReason;
use raklib\generic\SocketException;
use raklib\protocol\EncapsulatedPacket;
@@ -82,7 +83,6 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
private PacketBroadcaster $packetBroadcaster;
private EntityEventBroadcaster $entityEventBroadcaster;
- private PacketSerializerContext $packetSerializerContext;
private TypeConverter $typeConverter;
public function __construct(
@@ -92,12 +92,10 @@ public function __construct(
bool $ipV6,
PacketBroadcaster $packetBroadcaster,
EntityEventBroadcaster $entityEventBroadcaster,
- PacketSerializerContext $packetSerializerContext,
TypeConverter $typeConverter
){
$this->server = $server;
$this->packetBroadcaster = $packetBroadcaster;
- $this->packetSerializerContext = $packetSerializerContext;
$this->entityEventBroadcaster = $entityEventBroadcaster;
$this->typeConverter = $typeConverter;
@@ -124,7 +122,7 @@ public function __construct(
$threadToMainBuffer,
new InternetAddress($ip, $port, $ipV6 ? 6 : 4),
$this->rakServerId,
- $this->server->getConfigGroup()->getPropertyInt("network.max-mtu-size", 1492),
+ $this->server->getConfigGroup()->getPropertyInt(YmlServerProperties::NETWORK_MAX_MTU_SIZE, 1492),
self::MCPE_RAKNET_PROTOCOL_VERSION,
$sleeperEntry
);
@@ -154,7 +152,7 @@ public function tick() : void{
if(!$this->rakLib->isRunning()){
$e = $this->rakLib->getCrashInfo();
if($e !== null){
- throw new \RuntimeException("RakLib crashed: " . $e->makePrettyMessage());
+ throw new ThreadCrashException("RakLib crashed", $e);
}
throw new \Exception("RakLib Thread crashed without crash information");
}
@@ -190,7 +188,6 @@ public function onClientConnect(int $sessionId, string $address, int $port, int
$this->server,
$this->network->getSessionManager(),
PacketPool::getInstance(),
- $this->packetSerializerContext,
new RakLibPacketSender($sessionId, $this),
$this->packetBroadcaster,
$this->entityEventBroadcaster,
@@ -217,11 +214,14 @@ public function onPacketReceive(int $sessionId, string $packet) : void{
$session->handleEncoded($buf);
}catch(PacketHandlingException $e){
$logger = $session->getLogger();
- $logger->error("Bad packet: " . $e->getMessage());
+ $session->disconnectWithError(
+ reason: "Bad packet: " . $e->getMessage(),
+ disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error_badPacket()
+ );
//intentionally doesn't use logException, we don't want spammy packet error traces to appear in release mode
$logger->debug(implode("\n", Utils::printableExceptionInfo($e)));
- $session->disconnectWithError(KnownTranslationFactory::pocketmine_disconnect_error_badPacket());
+
$this->interface->blockAddress($address, 5);
}catch(\Throwable $e){
//record the name of the player who caused the crash, to make it easier to find the reproducing steps
@@ -252,7 +252,9 @@ public function addRawPacketFilter(string $regex) : void{
}
public function onPacketAck(int $sessionId, int $identifierACK) : void{
-
+ if(isset($this->sessions[$sessionId])){
+ $this->sessions[$sessionId]->handleAckReceipt($identifierACK);
+ }
}
public function setName(string $name) : void{
@@ -269,8 +271,8 @@ public function setName(string $name) : void{
$this->rakServerId,
$this->server->getName(),
match($this->server->getGamemode()){
- GameMode::SURVIVAL() => "Survival",
- GameMode::ADVENTURE() => "Adventure",
+ GameMode::SURVIVAL => "Survival",
+ GameMode::ADVENTURE => "Adventure",
default => "Creative"
}
]) . ";"
@@ -289,12 +291,13 @@ public function onBandwidthStatsUpdate(int $bytesSentDiff, int $bytesReceivedDif
$this->network->getBandwidthTracker()->add($bytesSentDiff, $bytesReceivedDiff);
}
- public function putPacket(int $sessionId, string $payload, bool $immediate = true) : void{
+ public function putPacket(int $sessionId, string $payload, bool $immediate = true, ?int $receiptId = null) : void{
if(isset($this->sessions[$sessionId])){
$pk = new EncapsulatedPacket();
$pk->buffer = self::MCPE_RAKNET_PACKET_ID . $payload;
$pk->reliability = PacketReliability::RELIABLE_ORDERED;
$pk->orderChannel = 0;
+ $pk->identifierACK = $receiptId;
$this->interface->sendEncapsulated($sessionId, $pk, $immediate);
}
diff --git a/src/network/mcpe/raklib/RakLibPacketSender.php b/src/network/mcpe/raklib/RakLibPacketSender.php
index d940c282b44..df8cf9a00e9 100644
--- a/src/network/mcpe/raklib/RakLibPacketSender.php
+++ b/src/network/mcpe/raklib/RakLibPacketSender.php
@@ -33,9 +33,9 @@ public function __construct(
private RakLibInterface $handler
){}
- public function send(string $payload, bool $immediate) : void{
+ public function send(string $payload, bool $immediate, ?int $receiptId) : void{
if(!$this->closed){
- $this->handler->putPacket($this->sessionId, $payload, $immediate);
+ $this->handler->putPacket($this->sessionId, $payload, $immediate, $receiptId);
}
}
diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php
index a3f7c16090b..5137b94ba29 100644
--- a/src/network/mcpe/raklib/RakLibServer.php
+++ b/src/network/mcpe/raklib/RakLibServer.php
@@ -29,6 +29,7 @@
use pocketmine\thread\log\ThreadSafeLogger;
use pocketmine\thread\NonThreadSafeValue;
use pocketmine\thread\Thread;
+use pocketmine\thread\ThreadCrashException;
use raklib\generic\SocketException;
use raklib\server\ipc\RakLibToUserThreadMessageSender;
use raklib\server\ipc\UserToRakLibThreadMessageReceiver;
@@ -37,17 +38,12 @@
use raklib\server\SimpleProtocolAcceptor;
use raklib\utils\ExceptionTraceCleaner;
use raklib\utils\InternetAddress;
-use function error_get_last;
use function gc_enable;
use function ini_set;
-use function register_shutdown_function;
class RakLibServer extends Thread{
- protected bool $cleanShutdown = false;
protected bool $ready = false;
protected string $mainPath;
- /** @phpstan-var NonThreadSafeValue|null */
- public ?NonThreadSafeValue $crashInfo = null;
/** @phpstan-var NonThreadSafeValue */
protected NonThreadSafeValue $address;
@@ -69,86 +65,48 @@ public function __construct(
$this->address = new NonThreadSafeValue($address);
}
- /**
- * @return void
- */
- public function shutdownHandler(){
- if($this->cleanShutdown !== true && $this->crashInfo === null){
- $error = error_get_last();
-
- if($error !== null){
- $this->logger->emergency("Fatal error: " . $error["message"] . " in " . $error["file"] . " on line " . $error["line"]);
- $this->setCrashInfo(RakLibThreadCrashInfo::fromLastErrorInfo($error));
- }else{
- $this->logger->emergency("RakLib shutdown unexpectedly");
- }
- }
- }
-
- public function getCrashInfo() : ?RakLibThreadCrashInfo{
- return $this->crashInfo?->deserialize();
- }
-
- private function setCrashInfo(RakLibThreadCrashInfo $info) : void{
- $this->synchronized(function() use ($info) : void{
- $this->crashInfo = new NonThreadSafeValue($info);
- $this->notify();
- });
- }
-
public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{
$this->start($options);
$this->synchronized(function() : void{
- while(!$this->ready && $this->crashInfo === null){
+ while(!$this->ready && $this->getCrashInfo() === null){
$this->wait();
}
- $crashInfo = $this->crashInfo?->deserialize();
+ $crashInfo = $this->getCrashInfo();
if($crashInfo !== null){
- if($crashInfo->getClass() === SocketException::class){
+ if($crashInfo->getType() === SocketException::class){
throw new SocketException($crashInfo->getMessage());
}
- throw new \RuntimeException("RakLib failed to start: " . $crashInfo->makePrettyMessage());
+ throw new ThreadCrashException("RakLib failed to start", $crashInfo);
}
});
}
protected function onRun() : void{
- try{
- gc_enable();
- ini_set("display_errors", '1');
- ini_set("display_startup_errors", '1');
+ gc_enable();
+ ini_set("display_errors", '1');
+ ini_set("display_startup_errors", '1');
+ \GlobalLogger::set($this->logger);
- register_shutdown_function([$this, "shutdownHandler"]);
-
- try{
- $socket = new ServerSocket($this->address->deserialize());
- }catch(SocketException $e){
- $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e));
- return;
- }
- $manager = new Server(
- $this->serverId,
- $this->logger,
- $socket,
- $this->maxMtuSize,
- new SimpleProtocolAcceptor($this->protocolVersion),
- new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)),
- new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())),
- new ExceptionTraceCleaner($this->mainPath)
- );
- $this->synchronized(function() : void{
- $this->ready = true;
- $this->notify();
- });
- while(!$this->isKilled){
- $manager->tickProcessor();
- }
- $manager->waitShutdown();
- $this->cleanShutdown = true;
- }catch(\Throwable $e){
- $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e));
- $this->logger->logException($e);
+ $socket = new ServerSocket($this->address->deserialize());
+ $manager = new Server(
+ $this->serverId,
+ $this->logger,
+ $socket,
+ $this->maxMtuSize,
+ new SimpleProtocolAcceptor($this->protocolVersion),
+ new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)),
+ new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())),
+ new ExceptionTraceCleaner($this->mainPath),
+ recvMaxSplitParts: 512
+ );
+ $this->synchronized(function() : void{
+ $this->ready = true;
+ $this->notify();
+ });
+ while(!$this->isKilled){
+ $manager->tickProcessor();
}
+ $manager->waitShutdown();
}
public function getThreadName() : string{
diff --git a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php
deleted file mode 100644
index 60e04b4b4e4..00000000000
--- a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php
+++ /dev/null
@@ -1,61 +0,0 @@
-getMessage(), $e->getFile(), $e->getLine());
- }
-
- /**
- * @phpstan-param array{message: string, file: string, line: int} $info
- */
- public static function fromLastErrorInfo(array $info) : self{
- return new self(null, $info["message"], $info["file"], $info["line"]);
- }
-
- public function getClass() : ?string{ return $this->class; }
-
- public function getMessage() : string{ return $this->message; }
-
- public function getFile() : string{ return $this->file; }
-
- public function getLine() : int{ return $this->line; }
-
- public function makePrettyMessage() : string{
- return sprintf("%s: \"%s\" in %s on line %d", $this->class ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line);
- }
-}
diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php
index afda6b7bbc7..9120f34a7e0 100644
--- a/src/network/mcpe/serializer/ChunkSerializer.php
+++ b/src/network/mcpe/serializer/ChunkSerializer.php
@@ -30,7 +30,7 @@
use pocketmine\network\mcpe\convert\BlockTranslator;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
+use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\Chunk;
@@ -43,12 +43,34 @@ private function __construct(){
//NOOP
}
+ /**
+ * Returns the min/max subchunk index expected in the protocol.
+ * This has no relation to the world height supported by PM.
+ *
+ * @phpstan-param DimensionIds::* $dimensionId
+ * @return int[]
+ * @phpstan-return array{int, int}
+ */
+ public static function getDimensionChunkBounds(int $dimensionId) : array{
+ return match($dimensionId){
+ DimensionIds::OVERWORLD => [-4, 19],
+ DimensionIds::NETHER => [0, 7],
+ DimensionIds::THE_END => [0, 15],
+ default => throw new \InvalidArgumentException("Unknown dimension ID $dimensionId"),
+ };
+ }
+
/**
* Returns the number of subchunks that will be sent from the given chunk.
* Chunks are sent in a stack, so every chunk below the top non-empty one must be sent.
+ *
+ * @phpstan-param DimensionIds::* $dimensionId
*/
- public static function getSubChunkCount(Chunk $chunk) : int{
- for($y = Chunk::MAX_SUBCHUNK_INDEX, $count = count($chunk->getSubChunks()); $y >= Chunk::MIN_SUBCHUNK_INDEX; --$y, --$count){
+ public static function getSubChunkCount(Chunk $chunk, int $dimensionId) : int{
+ //if the protocol world bounds ever exceed the PM supported bounds again in the future, we might need to
+ //polyfill some stuff here
+ [$minSubChunkIndex, $maxSubChunkIndex] = self::getDimensionChunkBounds($dimensionId);
+ for($y = $maxSubChunkIndex, $count = $maxSubChunkIndex - $minSubChunkIndex + 1; $y >= $minSubChunkIndex; --$y, --$count){
if($chunk->getSubChunk($y)->isEmptyFast()){
continue;
}
@@ -58,18 +80,23 @@ public static function getSubChunkCount(Chunk $chunk) : int{
return 0;
}
- public static function serializeFullChunk(Chunk $chunk, BlockTranslator $blockTranslator, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{
- $stream = PacketSerializer::encoder($encoderContext);
+ /**
+ * @phpstan-param DimensionIds::* $dimensionId
+ */
+ public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, ?string $tiles = null) : string{
+ $stream = PacketSerializer::encoder();
- $subChunkCount = self::getSubChunkCount($chunk);
+ $subChunkCount = self::getSubChunkCount($chunk, $dimensionId);
$writtenCount = 0;
- for($y = Chunk::MIN_SUBCHUNK_INDEX; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){
+
+ [$minSubChunkIndex, $maxSubChunkIndex] = self::getDimensionChunkBounds($dimensionId);
+ for($y = $minSubChunkIndex; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){
self::serializeSubChunk($chunk->getSubChunk($y), $blockTranslator, $stream, false);
}
$biomeIdMap = LegacyBiomeIdToStringIdMap::getInstance();
//all biomes must always be written :(
- for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
+ for($y = $minSubChunkIndex; $y <= $maxSubChunkIndex; ++$y){
self::serializeBiomePalette($chunk->getSubChunk($y)->getBiomeArray(), $biomeIdMap, $stream);
}
diff --git a/src/network/query/QueryInfo.php b/src/network/query/QueryInfo.php
index 2d36ac7e6e6..0bf5b4f65ee 100644
--- a/src/network/query/QueryInfo.php
+++ b/src/network/query/QueryInfo.php
@@ -29,6 +29,7 @@
use pocketmine\Server;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
+use pocketmine\YmlServerProperties;
use function array_map;
use function chr;
use function count;
@@ -66,11 +67,11 @@ final class QueryInfo{
public function __construct(Server $server){
$this->serverName = $server->getMotd();
- $this->listPlugins = $server->getConfigGroup()->getPropertyBool("settings.query-plugins", true);
+ $this->listPlugins = $server->getConfigGroup()->getPropertyBool(YmlServerProperties::SETTINGS_QUERY_PLUGINS, true);
$this->plugins = $server->getPluginManager()->getPlugins();
$this->players = array_map(fn(Player $p) => $p->getName(), $server->getOnlinePlayers());
- $this->gametype = ($server->getGamemode()->equals(GameMode::SURVIVAL()) || $server->getGamemode()->equals(GameMode::ADVENTURE())) ? "SMP" : "CMP";
+ $this->gametype = ($server->getGamemode() === GameMode::SURVIVAL || $server->getGamemode() === GameMode::ADVENTURE) ? "SMP" : "CMP";
$this->version = $server->getVersion();
$this->server_engine = $server->getName() . " " . $server->getPocketMineVersion();
$world = $server->getWorldManager()->getDefaultWorld();
diff --git a/src/permission/DefaultPermissionNames.php b/src/permission/DefaultPermissionNames.php
index fab532e2869..a916e813c05 100644
--- a/src/permission/DefaultPermissionNames.php
+++ b/src/permission/DefaultPermissionNames.php
@@ -84,6 +84,8 @@ final class DefaultPermissionNames{
public const COMMAND_WHITELIST_LIST = "pocketmine.command.whitelist.list";
public const COMMAND_WHITELIST_RELOAD = "pocketmine.command.whitelist.reload";
public const COMMAND_WHITELIST_REMOVE = "pocketmine.command.whitelist.remove";
+ public const COMMAND_XP_OTHER = "pocketmine.command.xp.other";
+ public const COMMAND_XP_SELF = "pocketmine.command.xp.self";
public const GROUP_CONSOLE = "pocketmine.group.console";
public const GROUP_OPERATOR = "pocketmine.group.operator";
public const GROUP_USER = "pocketmine.group.user";
diff --git a/src/permission/DefaultPermissions.php b/src/permission/DefaultPermissions.php
index 4af3d1b099c..1030e9ff8cc 100644
--- a/src/permission/DefaultPermissions.php
+++ b/src/permission/DefaultPermissions.php
@@ -83,7 +83,7 @@ public static function registerCorePermissions() : void{
self::registerPermission(new Permission(Names::COMMAND_PLUGINS, l10n::pocketmine_permission_command_plugins()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_SAVE_DISABLE, l10n::pocketmine_permission_command_save_disable()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_SAVE_ENABLE, l10n::pocketmine_permission_command_save_enable()), [$operatorRoot]);
- self::registerPermission(new Permission(Names::COMMAND_SAVE_PERFORM, l10n::pocketmine_permission_command_save_enable()), [$operatorRoot]);
+ self::registerPermission(new Permission(Names::COMMAND_SAVE_PERFORM, l10n::pocketmine_permission_command_save_perform()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_SAY, l10n::pocketmine_permission_command_say()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_SEED, l10n::pocketmine_permission_command_seed()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_SETWORLDSPAWN, l10n::pocketmine_permission_command_setworldspawn()), [$operatorRoot]);
@@ -112,5 +112,7 @@ public static function registerCorePermissions() : void{
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_LIST, l10n::pocketmine_permission_command_whitelist_list()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_RELOAD, l10n::pocketmine_permission_command_whitelist_reload()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_REMOVE, l10n::pocketmine_permission_command_whitelist_remove()), [$operatorRoot]);
+ self::registerPermission(new Permission(Names::COMMAND_XP_OTHER, l10n::pocketmine_permission_command_xp_other()), [$operatorRoot]);
+ self::registerPermission(new Permission(Names::COMMAND_XP_SELF, l10n::pocketmine_permission_command_xp_self()), [$operatorRoot]);
}
}
diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php
index b7e622934fe..1291ba86bf4 100644
--- a/src/permission/PermissionManager.php
+++ b/src/permission/PermissionManager.php
@@ -72,19 +72,21 @@ public function subscribeToPermission(string $permission, PermissibleInternal $p
}
public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{
- if(isset($this->permSubs[$permission])){
- unset($this->permSubs[$permission][spl_object_id($permissible)]);
- if(count($this->permSubs[$permission]) === 0){
+ if(isset($this->permSubs[$permission][spl_object_id($permissible)])){
+ if(count($this->permSubs[$permission]) === 1){
unset($this->permSubs[$permission]);
+ }else{
+ unset($this->permSubs[$permission][spl_object_id($permissible)]);
}
}
}
public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
- foreach($this->permSubs as $permission => &$subs){
- unset($subs[spl_object_id($permissible)]);
- if(count($subs) === 0){
+ foreach($this->permSubs as $permission => $subs){
+ if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
unset($this->permSubs[$permission]);
+ }else{
+ unset($this->permSubs[$permission][spl_object_id($permissible)]);
}
}
}
diff --git a/src/player/ChunkSelector.php b/src/player/ChunkSelector.php
index feb1b25e21a..14d6b2888bd 100644
--- a/src/player/ChunkSelector.php
+++ b/src/player/ChunkSelector.php
@@ -30,7 +30,7 @@
final class ChunkSelector{
/**
- * @preturn \Generator|int[]
+ * @return \Generator|int[]
* @phpstan-return \Generator
*/
public function selectChunks(int $radius, int $centerX, int $centerZ) : \Generator{
diff --git a/src/player/GameMode.php b/src/player/GameMode.php
index 0fc60ece462..519cbbe8ae4 100644
--- a/src/player/GameMode.php
+++ b/src/player/GameMode.php
@@ -25,73 +25,76 @@
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
use function mb_strtolower;
+use function spl_object_id;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static GameMode ADVENTURE()
* @method static GameMode CREATIVE()
* @method static GameMode SPECTATOR()
* @method static GameMode SURVIVAL()
+ *
+ * @phpstan-type TMetadata array{0: string, 1: Translatable, 2: list}
*/
-final class GameMode{
- use EnumTrait {
- __construct as Enum___construct;
- register as Enum_register;
- }
+enum GameMode{
+ use LegacyEnumShimTrait;
- /** @var self[] */
- protected static array $aliasMap = [];
+ case SURVIVAL;
+ case CREATIVE;
+ case ADVENTURE;
+ case SPECTATOR;
- protected static function setup() : void{
- self::registerAll(
- new self("survival", "Survival", KnownTranslationFactory::gameMode_survival(), ["survival", "s", "0"]),
- new self("creative", "Creative", KnownTranslationFactory::gameMode_creative(), ["creative", "c", "1"]),
- new self("adventure", "Adventure", KnownTranslationFactory::gameMode_adventure(), ["adventure", "a", "2"]),
- new self("spectator", "Spectator", KnownTranslationFactory::gameMode_spectator(), ["spectator", "v", "view", "3"])
- );
- }
+ public static function fromString(string $str) : ?self{
+ /**
+ * @var self[]|null $aliasMap
+ * @phpstan-var array|null $aliasMap
+ */
+ static $aliasMap = null;
- protected static function register(self $member) : void{
- self::Enum_register($member);
- foreach($member->getAliases() as $alias){
- self::$aliasMap[mb_strtolower($alias)] = $member;
+ if($aliasMap === null){
+ $aliasMap = [];
+ foreach(self::cases() as $case){
+ foreach($case->getAliases() as $alias){
+ $aliasMap[$alias] = $case;
+ }
+ }
}
- }
- public static function fromString(string $str) : ?self{
- self::checkInit();
- return self::$aliasMap[mb_strtolower($str)] ?? null;
+ return $aliasMap[mb_strtolower($str)] ?? null;
}
/**
- * @param string[] $aliases
+ * @phpstan-return TMetadata
*/
- private function __construct(
- string $enumName,
- private string $englishName,
- private Translatable $translatableName,
- private array $aliases = []
- ){
- $this->Enum___construct($enumName);
+ private function getMetadata() : array{
+ /** @phpstan-var array $cache */
+ static $cache = [];
+
+ return $cache[spl_object_id($this)] ??= match($this){
+ self::SURVIVAL => ["Survival", KnownTranslationFactory::gameMode_survival(), ["survival", "s", "0"]],
+ self::CREATIVE => ["Creative", KnownTranslationFactory::gameMode_creative(), ["creative", "c", "1"]],
+ self::ADVENTURE => ["Adventure", KnownTranslationFactory::gameMode_adventure(), ["adventure", "a", "2"]],
+ self::SPECTATOR => ["Spectator", KnownTranslationFactory::gameMode_spectator(), ["spectator", "v", "view", "3"]]
+ };
}
public function getEnglishName() : string{
- return $this->englishName;
+ return $this->getMetadata()[0];
}
- public function getTranslatableName() : Translatable{ return $this->translatableName; }
+ public function getTranslatableName() : Translatable{
+ return $this->getMetadata()[1];
+ }
/**
* @return string[]
*/
public function getAliases() : array{
- return $this->aliases;
+ return $this->getMetadata()[2];
}
//TODO: ability sets per gamemode
diff --git a/src/player/Player.php b/src/player/Player.php
index 3083538be66..e4c74c4ef0c 100644
--- a/src/player/Player.php
+++ b/src/player/Player.php
@@ -57,6 +57,7 @@
use pocketmine\event\player\PlayerDropItemEvent;
use pocketmine\event\player\PlayerEmoteEvent;
use pocketmine\event\player\PlayerEntityInteractEvent;
+use pocketmine\event\player\PlayerEntityPickEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent;
use pocketmine\event\player\PlayerInteractEvent;
@@ -66,6 +67,7 @@
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\event\player\PlayerJumpEvent;
use pocketmine\event\player\PlayerKickEvent;
+use pocketmine\event\player\PlayerMissSwingEvent;
use pocketmine\event\player\PlayerMoveEvent;
use pocketmine\event\player\PlayerPostChunkSendEvent;
use pocketmine\event\player\PlayerQuitEvent;
@@ -80,6 +82,7 @@
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\inventory\CallbackInventoryListener;
+use pocketmine\inventory\CreativeInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCraftingInventory;
use pocketmine\inventory\PlayerCursorInventory;
@@ -117,6 +120,7 @@
use pocketmine\permission\PermissibleDelegateTrait;
use pocketmine\player\chat\StandardChatFormatter;
use pocketmine\Server;
+use pocketmine\ServerProperties;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat;
@@ -132,6 +136,7 @@
use pocketmine\world\sound\ItemBreakSound;
use pocketmine\world\sound\Sound;
use pocketmine\world\World;
+use pocketmine\YmlServerProperties;
use Ramsey\Uuid\UuidInterface;
use function abs;
use function array_filter;
@@ -217,6 +222,7 @@ public static function isValidUserName(?string $name) : bool{
protected array $permanentWindows = [];
protected PlayerCursorInventory $cursorInventory;
protected PlayerCraftingInventory $craftingGrid;
+ protected CreativeInventory $creativeInventory;
protected int $messageCounter = 2;
@@ -278,7 +284,11 @@ public static function isValidUserName(?string $name) : bool{
protected string $locale = "en_US";
protected int $startAction = -1;
- /** @var int[] ID => ticks map */
+
+ /**
+ * @phpstan-var array
+ * @var int[] stateId|cooldownTag => ticks map
+ */
protected array $usedItemsCooldown = [];
private int $lastEmoteTick = 0;
@@ -307,13 +317,15 @@ public function __construct(Server $server, NetworkSession $session, PlayerInfo
$this->uuid = $this->playerInfo->getUuid();
$this->xuid = $this->playerInfo instanceof XboxLivePlayerInfo ? $this->playerInfo->getXuid() : "";
+ $this->creativeInventory = CreativeInventory::getInstance();
+
$rootPermissions = [DefaultPermissions::ROOT_USER => true];
if($this->server->isOp($this->username)){
$rootPermissions[DefaultPermissions::ROOT_OPERATOR] = true;
}
$this->perm = new PermissibleBase($rootPermissions);
- $this->chunksPerTick = $this->server->getConfigGroup()->getPropertyInt("chunk-sending.per-tick", 4);
- $this->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt("chunk-sending.spawn-radius", 4) ** 2) * M_PI);
+ $this->chunksPerTick = $this->server->getConfigGroup()->getPropertyInt(YmlServerProperties::CHUNK_SENDING_PER_TICK, 4);
+ $this->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt(YmlServerProperties::CHUNK_SENDING_SPAWN_RADIUS, 4) ** 2) * M_PI);
$this->chunkSelector = new ChunkSelector();
$this->chunkLoader = new class implements ChunkLoader{};
@@ -324,7 +336,7 @@ public function __construct(Server $server, NetworkSession $session, PlayerInfo
$zSpawnChunk = $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE;
$world->registerChunkLoader($this->chunkLoader, $xSpawnChunk, $zSpawnChunk, true);
$world->registerChunkListener($this, $xSpawnChunk, $zSpawnChunk);
- $this->usedChunks[World::chunkHash($xSpawnChunk, $zSpawnChunk)] = UsedChunkStatus::NEEDED();
+ $this->usedChunks[World::chunkHash($xSpawnChunk, $zSpawnChunk)] = UsedChunkStatus::NEEDED;
parent::__construct($spawnLocation, $this->playerInfo->getSkin(), $namedtag);
}
@@ -365,7 +377,7 @@ function() : void{
$this->lastPlayed = $nbt->getLong(self::TAG_LAST_PLAYED, $now);
if(!$this->server->getForceGamemode() && ($gameModeTag = $nbt->getTag(self::TAG_GAME_MODE)) instanceof IntTag){
- $this->internalSetGameMode(GameModeIdMap::getInstance()->fromId($gameModeTag->getValue()) ?? GameMode::SURVIVAL()); //TODO: bad hack here to avoid crashes on corrupted data
+ $this->internalSetGameMode(GameModeIdMap::getInstance()->fromId($gameModeTag->getValue()) ?? GameMode::SURVIVAL); //TODO: bad hack here to avoid crashes on corrupted data
}else{
$this->internalSetGameMode($this->server->getGamemode());
}
@@ -578,7 +590,7 @@ public function setViewDistance(int $distance) : void{
$this->viewDistance = $newViewDistance;
- $this->spawnThreshold = (int) (min($this->viewDistance, $this->server->getConfigGroup()->getPropertyInt("chunk-sending.spawn-radius", 4)) ** 2 * M_PI);
+ $this->spawnThreshold = (int) (min($this->viewDistance, $this->server->getConfigGroup()->getPropertyInt(YmlServerProperties::CHUNK_SENDING_SPAWN_RADIUS, 4)) ** 2 * M_PI);
$this->nextChunkOrderRun = 0;
@@ -623,6 +635,10 @@ public function setDisplayName(string $name) : void{
$this->displayName = $ev->getNewName();
}
+ public function canBeRenamed() : bool{
+ return false;
+ }
+
/**
* Returns the player's locale, e.g. en_US.
*/
@@ -686,7 +702,7 @@ public function getItemUseDuration() : int{
*/
public function getItemCooldownExpiry(Item $item) : int{
$this->checkItemCooldowns();
- return $this->usedItemsCooldown[$item->getStateId()] ?? 0;
+ return $this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()] ?? 0;
}
/**
@@ -694,7 +710,7 @@ public function getItemCooldownExpiry(Item $item) : int{
*/
public function hasItemCooldown(Item $item) : bool{
$this->checkItemCooldowns();
- return isset($this->usedItemsCooldown[$item->getStateId()]);
+ return isset($this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()]);
}
/**
@@ -703,7 +719,8 @@ public function hasItemCooldown(Item $item) : bool{
public function resetItemCooldown(Item $item, ?int $ticks = null) : void{
$ticks = $ticks ?? $item->getCooldownTicks();
if($ticks > 0){
- $this->usedItemsCooldown[$item->getStateId()] = $this->server->getTick() + $ticks;
+ $this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()] = $this->server->getTick() + $ticks;
+ $this->getNetworkSession()->onItemCooldownChanged($item, $ticks);
}
}
@@ -761,7 +778,7 @@ protected function unloadChunk(int $x, int $z, ?World $world = null) : void{
protected function spawnEntitiesOnAllChunks() : void{
foreach($this->usedChunks as $chunkHash => $status){
- if($status->equals(UsedChunkStatus::SENT())){
+ if($status === UsedChunkStatus::SENT){
World::getXZ($chunkHash, $chunkX, $chunkZ);
$this->spawnEntitiesOnChunk($chunkX, $chunkZ);
}
@@ -803,31 +820,31 @@ protected function requestChunks() : void{
++$count;
- $this->usedChunks[$index] = UsedChunkStatus::REQUESTED_GENERATION();
+ $this->usedChunks[$index] = UsedChunkStatus::REQUESTED_GENERATION;
$this->activeChunkGenerationRequests[$index] = true;
unset($this->loadQueue[$index]);
- $this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
- $this->getWorld()->registerChunkListener($this, $X, $Z);
+ $world->registerChunkLoader($this->chunkLoader, $X, $Z, true);
+ $world->registerChunkListener($this, $X, $Z);
if(isset($this->tickingChunks[$index])){
- $this->getWorld()->registerTickingChunk($this->chunkTicker, $X, $Z);
+ $world->registerTickingChunk($this->chunkTicker, $X, $Z);
}
- $this->getWorld()->requestChunkPopulation($X, $Z, $this->chunkLoader)->onCompletion(
+ $world->requestChunkPopulation($X, $Z, $this->chunkLoader)->onCompletion(
function() use ($X, $Z, $index, $world) : void{
if(!$this->isConnected() || !isset($this->usedChunks[$index]) || $world !== $this->getWorld()){
return;
}
- if(!$this->usedChunks[$index]->equals(UsedChunkStatus::REQUESTED_GENERATION())){
+ if($this->usedChunks[$index] !== UsedChunkStatus::REQUESTED_GENERATION){
//We may have previously requested this, decided we didn't want it, and then decided we did want
//it again, all before the generation request got executed. In that case, the promise would have
//multiple callbacks for this player. In that case, only the first one matters.
return;
}
unset($this->activeChunkGenerationRequests[$index]);
- $this->usedChunks[$index] = UsedChunkStatus::REQUESTED_SENDING();
+ $this->usedChunks[$index] = UsedChunkStatus::REQUESTED_SENDING;
$this->getNetworkSession()->startUsingChunk($X, $Z, function() use ($X, $Z, $index) : void{
- $this->usedChunks[$index] = UsedChunkStatus::SENT();
+ $this->usedChunks[$index] = UsedChunkStatus::SENT;
if($this->spawnChunkLoadCount === -1){
$this->spawnEntitiesOnChunk($X, $Z);
}elseif($this->spawnChunkLoadCount++ === $this->spawnThreshold){
@@ -944,7 +961,7 @@ protected function orderChunks() : void{
$this->location->getFloorX() >> Chunk::COORD_BIT_SIZE,
$this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE
) as $radius => $hash){
- if(!isset($this->usedChunks[$hash]) || $this->usedChunks[$hash]->equals(UsedChunkStatus::NEEDED())){
+ if(!isset($this->usedChunks[$hash]) || $this->usedChunks[$hash] === UsedChunkStatus::NEEDED){
$newOrder[$hash] = true;
}
if($radius < $tickingChunkRadius){
@@ -998,7 +1015,7 @@ public function getUsedChunkStatus(int $chunkX, int $chunkZ) : ?UsedChunkStatus{
*/
public function hasReceivedChunk(int $chunkX, int $chunkZ) : bool{
$status = $this->usedChunks[World::chunkHash($chunkX, $chunkZ)] ?? null;
- return $status !== null && $status->equals(UsedChunkStatus::SENT());
+ return $status === UsedChunkStatus::SENT;
}
/**
@@ -1106,7 +1123,7 @@ public function getGamemode() : GameMode{
protected function internalSetGameMode(GameMode $gameMode) : void{
$this->gamemode = $gameMode;
- $this->allowFlight = $this->gamemode->equals(GameMode::CREATIVE());
+ $this->allowFlight = $this->gamemode === GameMode::CREATIVE;
$this->hungerManager->setEnabled($this->isSurvival());
if($this->isSpectator()){
@@ -1129,10 +1146,10 @@ protected function internalSetGameMode(GameMode $gameMode) : void{
}
/**
- * Sets the gamemode, and if needed, kicks the Player.
+ * Sets the provided gamemode.
*/
public function setGamemode(GameMode $gm) : bool{
- if($this->gamemode->equals($gm)){
+ if($this->gamemode === $gm){
return false;
}
@@ -1161,7 +1178,7 @@ public function setGamemode(GameMode $gm) : bool{
* @param bool $literal whether a literal check should be performed
*/
public function isSurvival(bool $literal = false) : bool{
- return $this->gamemode->equals(GameMode::SURVIVAL()) || (!$literal && $this->gamemode->equals(GameMode::ADVENTURE()));
+ return $this->gamemode === GameMode::SURVIVAL || (!$literal && $this->gamemode === GameMode::ADVENTURE);
}
/**
@@ -1171,7 +1188,7 @@ public function isSurvival(bool $literal = false) : bool{
* @param bool $literal whether a literal check should be performed
*/
public function isCreative(bool $literal = false) : bool{
- return $this->gamemode->equals(GameMode::CREATIVE()) || (!$literal && $this->gamemode->equals(GameMode::SPECTATOR()));
+ return $this->gamemode === GameMode::CREATIVE || (!$literal && $this->gamemode === GameMode::SPECTATOR);
}
/**
@@ -1181,22 +1198,18 @@ public function isCreative(bool $literal = false) : bool{
* @param bool $literal whether a literal check should be performed
*/
public function isAdventure(bool $literal = false) : bool{
- return $this->gamemode->equals(GameMode::ADVENTURE()) || (!$literal && $this->gamemode->equals(GameMode::SPECTATOR()));
+ return $this->gamemode === GameMode::ADVENTURE || (!$literal && $this->gamemode === GameMode::SPECTATOR);
}
public function isSpectator() : bool{
- return $this->gamemode->equals(GameMode::SPECTATOR());
+ return $this->gamemode === GameMode::SPECTATOR;
}
/**
* TODO: make this a dynamic ability instead of being hardcoded
*/
public function hasFiniteResources() : bool{
- return !$this->gamemode->equals(GameMode::CREATIVE());
- }
-
- public function isFireProof() : bool{
- return $this->isCreative();
+ return $this->gamemode !== GameMode::CREATIVE;
}
public function getDrops() : array{
@@ -1216,7 +1229,7 @@ public function getXpDropAmount() : int{
}
protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{
- if($this->isSpectator()){
+ if($this->gamemode === GameMode::SPECTATOR){
$this->onGround = false;
}else{
$bb = clone $this->boundingBox;
@@ -1329,18 +1342,20 @@ protected function processMostRecentMovements() : void{
$deltaAngle = abs($this->lastLocation->yaw - $to->yaw) + abs($this->lastLocation->pitch - $to->pitch);
if($delta > 0.0001 || $deltaAngle > 1.0){
- $ev = new PlayerMoveEvent($this, $from, $to);
+ if(PlayerMoveEvent::hasHandlers()){
+ $ev = new PlayerMoveEvent($this, $from, $to);
- $ev->call();
+ $ev->call();
- if($ev->isCancelled()){
- $this->revertMovement($from);
- return;
- }
+ if($ev->isCancelled()){
+ $this->revertMovement($from);
+ return;
+ }
- if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
- $this->teleport($ev->getTo());
- return;
+ if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
+ $this->teleport($ev->getTo());
+ return;
+ }
}
$this->lastLocation = $to;
@@ -1384,7 +1399,7 @@ public function jump() : void{
public function setMotion(Vector3 $motion) : bool{
if(parent::setMotion($motion)){
$this->broadcastMotion();
- $this->getNetworkSession()->sendDataPacket(SetActorMotionPacket::create($this->id, $motion));
+ $this->getNetworkSession()->sendDataPacket(SetActorMotionPacket::create($this->id, $motion, tick: 0));
return true;
}
@@ -1436,6 +1451,10 @@ public function onUpdate(int $currentTick) : bool{
$this->entityBaseTick($tickDiff);
Timings::$entityBaseTick->stopTiming();
+ if($this->isCreative() && $this->fireTicks > 1){
+ $this->fireTicks = 1;
+ }
+
if(!$this->isSpectator() && $this->isAlive()){
Timings::$playerCheckNearEntities->startTiming();
$this->checkNearEntities();
@@ -1603,11 +1622,11 @@ public function useHeldItem() : bool{
$returnedItems = [];
$result = $item->onClickAir($this, $directionVector, $returnedItems);
- if($result->equals(ItemUseResult::FAIL())){
+ if($result === ItemUseResult::FAIL){
return false;
}
- $this->resetItemCooldown($item);
+ $this->resetItemCooldown($oldItem);
$this->returnItemsFromAction($oldItem, $item, $returnedItems);
$this->setUsingItem($item instanceof Releasable && $item->canStartUsingItem($this));
@@ -1636,7 +1655,7 @@ public function consumeHeldItem() : bool{
}
$this->setUsingItem(false);
- $this->resetItemCooldown($slot);
+ $this->resetItemCooldown($oldItem);
$slot->pop();
$this->returnItemsFromAction($oldItem, $slot, [$slot->getResidue()]);
@@ -1663,8 +1682,8 @@ public function releaseHeldItem() : bool{
$returnedItems = [];
$result = $item->onReleaseUsing($this, $returnedItems);
- if($result->equals(ItemUseResult::SUCCESS())){
- $this->resetItemCooldown($item);
+ if($result === ItemUseResult::SUCCESS){
+ $this->resetItemCooldown($oldItem);
$this->returnItemsFromAction($oldItem, $item, $returnedItems);
return true;
}
@@ -1691,29 +1710,58 @@ public function pickBlock(Vector3 $pos, bool $addTileNBT) : bool{
$ev->call();
if(!$ev->isCancelled()){
- if($existingSlot !== -1){
- if($existingSlot < $this->inventory->getHotbarSize()){
- $this->inventory->setHeldItemIndex($existingSlot);
- }else{
- $this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
- }
- }else{
- $firstEmpty = $this->inventory->firstEmpty();
- if($firstEmpty === -1){ //full inventory
- $this->inventory->setItemInHand($item);
- }elseif($firstEmpty < $this->inventory->getHotbarSize()){
- $this->inventory->setItem($firstEmpty, $item);
- $this->inventory->setHeldItemIndex($firstEmpty);
- }else{
- $this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
- $this->inventory->setItemInHand($item);
- }
- }
+ $this->equipOrAddPickedItem($existingSlot, $item);
}
return true;
}
+ public function pickEntity(int $entityId) : bool{
+ $entity = $this->getWorld()->getEntity($entityId);
+ if($entity === null){
+ return true;
+ }
+
+ $item = $entity->getPickedItem();
+ if($item === null){
+ return true;
+ }
+
+ $ev = new PlayerEntityPickEvent($this, $entity, $item);
+ $existingSlot = $this->inventory->first($item);
+ if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
+ $ev->cancel();
+ }
+ $ev->call();
+
+ if(!$ev->isCancelled()){
+ $this->equipOrAddPickedItem($existingSlot, $item);
+ }
+
+ return true;
+ }
+
+ private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{
+ if($existingSlot !== -1){
+ if($existingSlot < $this->inventory->getHotbarSize()){
+ $this->inventory->setHeldItemIndex($existingSlot);
+ }else{
+ $this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
+ }
+ }else{
+ $firstEmpty = $this->inventory->firstEmpty();
+ if($firstEmpty === -1){ //full inventory
+ $this->inventory->setItemInHand($item);
+ }elseif($firstEmpty < $this->inventory->getHotbarSize()){
+ $this->inventory->setItem($firstEmpty, $item);
+ $this->inventory->setHeldItemIndex($firstEmpty);
+ }else{
+ $this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
+ $this->inventory->setItemInHand($item);
+ }
+ }
+ }
+
/**
* Performs a left-click (attack) action on the block.
*
@@ -1746,7 +1794,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{
return true;
}
- if(!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()){
+ if(!$this->isCreative() && !$target->getBreakInfo()->breaksInstantly()){
$this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16);
}
@@ -1837,7 +1885,7 @@ public function attackEntity(Entity $entity) : bool{
if(!$this->canInteract($entity->getLocation(), self::MAX_REACH_DISTANCE_ENTITY_INTERACTION)){
$this->logger->debug("Cancelled attack of entity " . $entity->getId() . " due to not currently being interactable");
$ev->cancel();
- }elseif($this->isSpectator() || ($entity instanceof Player && !$this->server->getConfigGroup()->getConfigBool("pvp"))){
+ }elseif($this->isSpectator() || ($entity instanceof Player && !$this->server->getConfigGroup()->getConfigBool(ServerProperties::PVP))){
$ev->cancel();
}
@@ -1890,6 +1938,19 @@ public function attackEntity(Entity $entity) : bool{
return true;
}
+ /**
+ * Performs actions associated with the attack action (left-click) without a target entity.
+ * Under normal circumstances, this will just play the no-damage attack sound and the arm-swing animation.
+ */
+ public function missSwing() : void{
+ $ev = new PlayerMissSwingEvent($this);
+ $ev->call();
+ if(!$ev->isCancelled()){
+ $this->broadcastSound(new EntityAttackNoDamageSound());
+ $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
+ }
+ }
+
/**
* Interacts with the given entity using the currently-held item.
*/
@@ -2125,6 +2186,13 @@ public function onFormSubmit(int $formId, mixed $responseData) : bool{
return true;
}
+ /**
+ * Closes the current viewing form and forms in queue.
+ */
+ public function closeAllForms() : void{
+ $this->getNetworkSession()->onCloseAllForms();
+ }
+
/**
* Transfers a player to another server.
*
@@ -2532,6 +2600,24 @@ public function getCraftingGrid() : CraftingGrid{
return $this->craftingGrid;
}
+ /**
+ * Returns the creative inventory shown to the player.
+ * Unless changed by a plugin, this is usually the same for all players.
+ */
+ public function getCreativeInventory() : CreativeInventory{
+ return $this->creativeInventory;
+ }
+
+ /**
+ * To set a custom creative inventory, you need to make a clone of a CreativeInventory instance.
+ */
+ public function setCreativeInventory(CreativeInventory $inventory) : void{
+ $this->creativeInventory = $inventory;
+ if($this->spawned && $this->isConnected()){
+ $this->getNetworkSession()->getInvManager()?->syncCreative();
+ }
+ }
+
/**
* @internal Called to clean up crafting grid and cursor inventory when it is detected that the player closed their
* inventory.
@@ -2654,8 +2740,8 @@ public function openSignEditor(Vector3 $position) : void{
public function onChunkChanged(int $chunkX, int $chunkZ, Chunk $chunk) : void{
$status = $this->usedChunks[$hash = World::chunkHash($chunkX, $chunkZ)] ?? null;
- if($status !== null && $status->equals(UsedChunkStatus::SENT())){
- $this->usedChunks[$hash] = UsedChunkStatus::NEEDED();
+ if($status === UsedChunkStatus::SENT){
+ $this->usedChunks[$hash] = UsedChunkStatus::NEEDED;
$this->nextChunkOrderRun = 0;
}
}
diff --git a/src/player/UsedChunkStatus.php b/src/player/UsedChunkStatus.php
index 69e249a24dc..dda41fe7e70 100644
--- a/src/player/UsedChunkStatus.php
+++ b/src/player/UsedChunkStatus.php
@@ -23,28 +23,22 @@
namespace pocketmine\player;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static UsedChunkStatus NEEDED()
* @method static UsedChunkStatus REQUESTED_GENERATION()
* @method static UsedChunkStatus REQUESTED_SENDING()
* @method static UsedChunkStatus SENT()
*/
-final class UsedChunkStatus{
- use EnumTrait;
+enum UsedChunkStatus{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("NEEDED"),
- new self("REQUESTED_GENERATION"),
- new self("REQUESTED_SENDING"),
- new self("SENT")
- );
- }
+ case NEEDED;
+ case REQUESTED_GENERATION;
+ case REQUESTED_SENDING;
+ case SENT;
}
diff --git a/src/plugin/PluginBase.php b/src/plugin/PluginBase.php
index c8f3e1c3993..da2eae824d0 100644
--- a/src/plugin/PluginBase.php
+++ b/src/plugin/PluginBase.php
@@ -30,19 +30,16 @@
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\scheduler\TaskScheduler;
use pocketmine\Server;
-use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Config;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
+use function copy;
use function count;
use function dirname;
-use function fclose;
use function file_exists;
-use function fopen;
use function mkdir;
use function rtrim;
use function str_contains;
-use function stream_copy_to_stream;
use function strtolower;
use function trim;
use const DIRECTORY_SEPARATOR;
@@ -50,6 +47,8 @@
abstract class PluginBase implements Plugin, CommandExecutor{
private bool $isEnabled = false;
+ private string $resourceFolder;
+
private ?Config $config = null;
private string $configFile;
@@ -67,9 +66,11 @@ public function __construct(
$this->dataFolder = rtrim($dataFolder, "/" . DIRECTORY_SEPARATOR) . "/";
//TODO: this is accessed externally via reflection, not unused
$this->file = rtrim($file, "/" . DIRECTORY_SEPARATOR) . "/";
+ $this->resourceFolder = Path::join($this->file, "resources") . "/";
+
$this->configFile = Path::join($this->dataFolder, "config.yml");
- $prefix = $this->getDescription()->getPrefix();
+ $prefix = $this->description->getPrefix();
$this->logger = new PluginLogger($server->getLogger(), $prefix !== "" ? $prefix : $this->getName());
$this->scheduler = new TaskScheduler($this->getFullName());
@@ -144,9 +145,9 @@ public function getLogger() : \AttachableLogger{
private function registerYamlCommands() : void{
$pluginCmds = [];
- foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){
+ foreach(Utils::stringifyKeys($this->description->getCommands()) as $key => $data){
if(str_contains($key, ":")){
- $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName(), ":")));
+ $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->description->getFullName(), ":")));
continue;
}
@@ -162,7 +163,7 @@ private function registerYamlCommands() : void{
$aliasList = [];
foreach($data->getAliases() as $alias){
if(str_contains($alias, ":")){
- $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName(), ":")));
+ $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->description->getFullName(), ":")));
continue;
}
$aliasList[] = $alias;
@@ -180,7 +181,7 @@ private function registerYamlCommands() : void{
}
if(count($pluginCmds) > 0){
- $this->server->getCommandMap()->registerAll($this->getDescription()->getName(), $pluginCmds);
+ $this->server->getCommandMap()->registerAll($this->description->getName(), $pluginCmds);
}
}
@@ -189,9 +190,9 @@ private function registerYamlCommands() : void{
* @phpstan-return (Command&PluginOwned)|null
*/
public function getCommand(string $name){
- $command = $this->getServer()->getPluginCommand($name);
+ $command = $this->server->getPluginCommand($name);
if($command === null || $command->getOwningPlugin() !== $this){
- $command = $this->getServer()->getPluginCommand(strtolower($this->description->getName()) . ":" . $name);
+ $command = $this->server->getPluginCommand(strtolower($this->description->getName()) . ":" . $name);
}
if($command instanceof PluginOwned && $command->getOwningPlugin() === $this){
@@ -209,6 +210,27 @@ public function onCommand(CommandSender $sender, Command $command, string $label
}
/**
+ * Returns the path to the folder where the plugin's embedded resource files are usually located.
+ * Note: This is NOT the same as the data folder. The files in this folder should be considered read-only.
+ */
+ public function getResourceFolder() : string{
+ return $this->resourceFolder;
+ }
+
+ /**
+ * Returns the full path to a data file in the plugin's resources folder.
+ * This path can be used with standard PHP functions like fopen() or file_get_contents().
+ *
+ * Note: Any path returned by this function should be considered READ-ONLY.
+ */
+ public function getResourcePath(string $filename) : string{
+ return Path::join($this->getResourceFolder(), $filename);
+ }
+
+ /**
+ * @deprecated Prefer using standard PHP functions with {@link PluginBase::getResourcePath()}, like
+ * file_get_contents() or fopen().
+ *
* Gets an embedded resource on the plugin file.
* WARNING: You must close the resource given using fclose()
*
@@ -226,26 +248,21 @@ public function saveResource(string $filename, bool $replace = false) : bool{
return false;
}
- if(($resource = $this->getResource($filename)) === null){
+ $source = Path::join($this->resourceFolder, $filename);
+ if(!file_exists($source)){
return false;
}
- $out = Path::join($this->dataFolder, $filename);
- if(!file_exists(dirname($out))){
- mkdir(dirname($out), 0755, true);
- }
-
- if(file_exists($out) && !$replace){
+ $destination = Path::join($this->dataFolder, $filename);
+ if(file_exists($destination) && !$replace){
return false;
}
- $fp = fopen($out, "wb");
- if($fp === false) throw new AssumptionFailedError("fopen() should not fail with wb flags");
+ if(!file_exists(dirname($destination))){
+ mkdir(dirname($destination), 0755, true);
+ }
- $ret = stream_copy_to_stream($resource, $fp) > 0;
- fclose($fp);
- fclose($resource);
- return $ret;
+ return copy($source, $destination);
}
/**
diff --git a/src/plugin/PluginDescription.php b/src/plugin/PluginDescription.php
index 9fc562af91f..72f0add7fd5 100644
--- a/src/plugin/PluginDescription.php
+++ b/src/plugin/PluginDescription.php
@@ -203,7 +203,7 @@ private function loadMap(array $plugin) : void{
}
$this->order = $order;
}else{
- $this->order = PluginEnableOrder::POSTWORLD();
+ $this->order = PluginEnableOrder::POSTWORLD;
}
$this->authors = [];
diff --git a/src/plugin/PluginEnableOrder.php b/src/plugin/PluginEnableOrder.php
index 5e6255a16ee..c942ea33f99 100644
--- a/src/plugin/PluginEnableOrder.php
+++ b/src/plugin/PluginEnableOrder.php
@@ -23,63 +23,48 @@
namespace pocketmine\plugin;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
use function mb_strtolower;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static PluginEnableOrder POSTWORLD()
* @method static PluginEnableOrder STARTUP()
*/
-final class PluginEnableOrder{
- use EnumTrait {
- __construct as Enum___construct;
- register as Enum_register;
- }
+enum PluginEnableOrder{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("startup", ["startup"]),
- new self("postworld", ["postworld"])
- );
- }
-
- /**
- * @var self[]
- * @phpstan-var array
- */
- private static array $aliasMap = [];
-
- protected static function register(self $member) : void{
- self::Enum_register($member);
- foreach($member->getAliases() as $alias){
- self::$aliasMap[mb_strtolower($alias)] = $member;
- }
- }
+ case STARTUP;
+ case POSTWORLD;
public static function fromString(string $name) : ?self{
- self::checkInit();
- return self::$aliasMap[mb_strtolower($name)] ?? null;
- }
+ /**
+ * @var self[]|null $aliasMap
+ * @phpstan-var array|null $aliasMap
+ */
+ static $aliasMap = null;
- /**
- * @param string[] $aliases
- * @phpstan-param list $aliases
- */
- private function __construct(
- string $enumName,
- private array $aliases
- ){
- $this->Enum___construct($enumName);
+ if($aliasMap === null){
+ $aliasMap = [];
+ foreach(self::cases() as $case){
+ foreach($case->getAliases() as $alias){
+ $aliasMap[$alias] = $case;
+ }
+ }
+ }
+ return $aliasMap[mb_strtolower($name)] ?? null;
}
/**
* @return string[]
* @phpstan-return list
*/
- public function getAliases() : array{ return $this->aliases; }
+ public function getAliases() : array{
+ return match($this){
+ self::STARTUP => ["startup"],
+ self::POSTWORLD => ["postworld"]
+ };
+ }
}
diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php
index 67ca8cc374e..198e4e893bf 100644
--- a/src/plugin/PluginManager.php
+++ b/src/plugin/PluginManager.php
@@ -510,9 +510,12 @@ public function disablePlugin(Plugin $plugin) : void{
unset($this->enabledPlugins[$plugin->getDescription()->getName()]);
foreach(Utils::stringifyKeys($this->pluginDependents) as $dependency => $dependentList){
- unset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()]);
- if(count($this->pluginDependents[$dependency]) === 0){
- unset($this->pluginDependents[$dependency]);
+ if(isset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()])){
+ if(count($this->pluginDependents[$dependency]) === 1){
+ unset($this->pluginDependents[$dependency]);
+ }else{
+ unset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()]);
+ }
}
}
@@ -647,6 +650,11 @@ public function registerEvent(string $event, \Closure $handler, int $priority, P
$handlerName = Utils::getNiceClosureName($handler);
+ $reflect = new \ReflectionFunction($handler);
+ if($reflect->isGenerator()){
+ throw new PluginException("Generator function $handlerName cannot be used as an event handler");
+ }
+
if(!$plugin->isEnabled()){
throw new PluginException("Plugin attempted to register event handler " . $handlerName . "() to event " . $event . " while not enabled");
}
diff --git a/src/promise/Promise.php b/src/promise/Promise.php
index e7188197c7d..27a5e50aab8 100644
--- a/src/promise/Promise.php
+++ b/src/promise/Promise.php
@@ -23,6 +23,7 @@
namespace pocketmine\promise;
+use function count;
use function spl_object_id;
/**
@@ -41,8 +42,11 @@ public function __construct(private PromiseSharedData $shared){}
* @phpstan-param \Closure() : void $onFailure
*/
public function onCompletion(\Closure $onSuccess, \Closure $onFailure) : void{
- if($this->shared->resolved){
- $this->shared->result === null ? $onFailure() : $onSuccess($this->shared->result);
+ $state = $this->shared->state;
+ if($state === true){
+ $onSuccess($this->shared->result);
+ }elseif($state === false){
+ $onFailure();
}else{
$this->shared->onSuccess[spl_object_id($onSuccess)] = $onSuccess;
$this->shared->onFailure[spl_object_id($onFailure)] = $onFailure;
@@ -50,6 +54,58 @@ public function onCompletion(\Closure $onSuccess, \Closure $onFailure) : void{
}
public function isResolved() : bool{
- return $this->shared->resolved;
+ //TODO: perhaps this should return true when rejected? currently there's no way to tell if a promise was
+ //rejected or just hasn't been resolved yet
+ return $this->shared->state === true;
+ }
+
+ /**
+ * Returns a promise that will resolve only once all the Promises in
+ * `$promises` have resolved. The resolution value of the returned promise
+ * will be an array containing the resolution values of each Promises in
+ * `$promises` indexed by the respective Promises' array keys.
+ *
+ * @param Promise[] $promises
+ *
+ * @phpstan-template TPromiseValue
+ * @phpstan-template TKey of array-key
+ * @phpstan-param array> $promises
+ *
+ * @phpstan-return Promise>
+ */
+ public static function all(array $promises) : Promise{
+ /** @phpstan-var PromiseResolver> $resolver */
+ $resolver = new PromiseResolver();
+ if(count($promises) === 0){
+ $resolver->resolve([]);
+ return $resolver->getPromise();
+ }
+ $values = [];
+ $toResolve = count($promises);
+ $continue = true;
+
+ foreach($promises as $key => $promise){
+ $promise->onCompletion(
+ function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{
+ $values[$key] = $value;
+
+ if(count($values) === $toResolve){
+ $resolver->resolve($values);
+ }
+ },
+ function() use ($resolver, &$continue) : void{
+ if($continue){
+ $continue = false;
+ $resolver->reject();
+ }
+ }
+ );
+
+ if(!$continue){
+ break;
+ }
+ }
+
+ return $resolver->getPromise();
}
}
diff --git a/src/promise/PromiseResolver.php b/src/promise/PromiseResolver.php
index 97b181d0b25..2019f261d03 100644
--- a/src/promise/PromiseResolver.php
+++ b/src/promise/PromiseResolver.php
@@ -41,10 +41,10 @@ public function __construct(){
* @phpstan-param TValue $value
*/
public function resolve(mixed $value) : void{
- if($this->shared->resolved){
+ if($this->shared->state !== null){
throw new \LogicException("Promise has already been resolved/rejected");
}
- $this->shared->resolved = true;
+ $this->shared->state = true;
$this->shared->result = $value;
foreach($this->shared->onSuccess as $c){
$c($value);
@@ -54,10 +54,10 @@ public function resolve(mixed $value) : void{
}
public function reject() : void{
- if($this->shared->resolved){
+ if($this->shared->state !== null){
throw new \LogicException("Promise has already been resolved/rejected");
}
- $this->shared->resolved = true;
+ $this->shared->state = false;
foreach($this->shared->onFailure as $c){
$c();
}
diff --git a/src/promise/PromiseSharedData.php b/src/promise/PromiseSharedData.php
index be667eadb46..8bc2691f46c 100644
--- a/src/promise/PromiseSharedData.php
+++ b/src/promise/PromiseSharedData.php
@@ -41,8 +41,8 @@ final class PromiseSharedData{
*/
public array $onFailure = [];
- public bool $resolved = false;
+ public ?bool $state = null;
- /** @phpstan-var TValue|null */
- public mixed $result = null;
+ /** @phpstan-var TValue */
+ public mixed $result;
}
diff --git a/src/resourcepacks/ResourcePack.php b/src/resourcepacks/ResourcePack.php
index 04feeeb3d70..cdab3447b8d 100644
--- a/src/resourcepacks/ResourcePack.php
+++ b/src/resourcepacks/ResourcePack.php
@@ -60,6 +60,8 @@ public function getSha256() : string;
* @param int $start Offset to start reading the chunk from
* @param int $length Maximum length of data to return.
*
+ * @phpstan-param positive-int $length
+ *
* @return string byte-array
* @throws \InvalidArgumentException if the chunk does not exist
*/
diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php
index 6429ac06bbc..2df6750def6 100644
--- a/src/resourcepacks/ResourcePackManager.php
+++ b/src/resourcepacks/ResourcePackManager.php
@@ -154,6 +154,13 @@ public function resourcePacksRequired() : bool{
return $this->serverForceResources;
}
+ /**
+ * Sets whether players must accept resource packs in order to join.
+ */
+ public function setResourcePacksRequired(bool $value) : void{
+ $this->serverForceResources = $value;
+ }
+
/**
* Returns an array of resource packs in use, sorted in order of priority.
* @return ResourcePack[]
diff --git a/src/resourcepacks/ZippedResourcePack.php b/src/resourcepacks/ZippedResourcePack.php
index 7ba5c467d5f..c4daeedf7b2 100644
--- a/src/resourcepacks/ZippedResourcePack.php
+++ b/src/resourcepacks/ZippedResourcePack.php
@@ -108,6 +108,7 @@ public function __construct(string $zipPath){
$mapper = new \JsonMapper();
$mapper->bExceptionOnMissingData = true;
+ $mapper->bStrictObjectTypeChecking = true;
try{
/** @var Manifest $manifest */
@@ -153,6 +154,9 @@ public function getSha256(bool $cached = true) : string{
}
public function getPackChunk(int $start, int $length) : string{
+ if($length < 1){
+ throw new \InvalidArgumentException("Pack length must be positive");
+ }
fseek($this->fileResource, $start);
if(feof($this->fileResource)){
throw new \InvalidArgumentException("Requested a resource pack chunk with invalid start offset");
diff --git a/src/scheduler/AsyncPool.php b/src/scheduler/AsyncPool.php
index 85a27390a27..bb79df507f1 100644
--- a/src/scheduler/AsyncPool.php
+++ b/src/scheduler/AsyncPool.php
@@ -24,15 +24,18 @@
namespace pocketmine\scheduler;
use pmmp\thread\Thread as NativeThread;
-use pmmp\thread\ThreadSafeArray;
use pocketmine\snooze\SleeperHandler;
use pocketmine\thread\log\ThreadSafeLogger;
+use pocketmine\thread\ThreadCrashException;
use pocketmine\thread\ThreadSafeClassLoader;
+use pocketmine\timings\Timings;
+use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function array_keys;
use function array_map;
use function assert;
use function count;
+use function get_class;
use function spl_object_id;
use function time;
use const PHP_INT_MAX;
@@ -130,6 +133,8 @@ private function getWorker(int $workerId) : AsyncPoolWorkerEntry{
foreach($this->workerStartHooks as $hook){
$hook($workerId);
}
+ }else{
+ $this->checkCrashedWorker($workerId, null);
}
return $this->workers[$workerId];
@@ -146,7 +151,6 @@ public function submitTaskToWorker(AsyncTask $task, int $worker) : void{
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
}
- $task->progressUpdates = new ThreadSafeArray();
$task->setSubmitted();
$this->getWorker($worker)->submit($task);
@@ -199,6 +203,33 @@ public function submitTask(AsyncTask $task) : int{
return $worker;
}
+ private function checkCrashedWorker(int $workerId, ?AsyncTask $crashedTask) : void{
+ $entry = $this->workers[$workerId];
+ if($entry->worker->isTerminated()){
+ if($crashedTask === null){
+ foreach($entry->tasks as $task){
+ if($task->isTerminated()){
+ $crashedTask = $task;
+ break;
+ }elseif(!$task->isFinished()){
+ break;
+ }
+ }
+ }
+ $info = $entry->worker->getCrashInfo();
+ if($info !== null){
+ if($crashedTask !== null){
+ $message = "Worker $workerId crashed while running task " . get_class($crashedTask) . "#" . spl_object_id($crashedTask);
+ }else{
+ $message = "Worker $workerId crashed while doing unknown work";
+ }
+ throw new ThreadCrashException($message, $info);
+ }else{
+ throw new \RuntimeException("Worker $workerId crashed for unknown reason");
+ }
+ }
+ }
+
/**
* Collects finished and/or crashed tasks from the workers, firing their on-completion hooks where appropriate.
*
@@ -231,10 +262,10 @@ public function collectTasksFromWorker(int $worker) : bool{
if($task->isFinished()){ //make sure the task actually executed before trying to collect
$queue->dequeue();
- if($task->isCrashed()){
- $this->logger->critical("Could not execute asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": Task crashed");
- $task->onError();
- }elseif(!$task->hasCancelledRun()){
+ if($task->isTerminated()){
+ $this->checkCrashedWorker($worker, $task);
+ throw new AssumptionFailedError("checkCrashedWorker() should have thrown an exception, making this unreachable");
+ }else{
/*
* It's possible for a task to submit a progress update and then finish before the progress
* update is detected by the parent thread, so here we consume any missed updates.
@@ -244,11 +275,13 @@ public function collectTasksFromWorker(int $worker) : bool{
* lost. Thus, it's necessary to do one last check here to make sure all progress updates have
* been consumed before completing.
*/
- $task->checkProgressUpdates();
- $task->onCompletion();
+ $this->checkTaskProgressUpdates($task);
+ Timings::getAsyncTaskCompletionTimings($task)->time(function() use ($task) : void{
+ $task->onCompletion();
+ });
}
}else{
- $task->checkProgressUpdates();
+ $this->checkTaskProgressUpdates($task);
$more = true;
break; //current task is still running, skip to next worker
}
@@ -296,4 +329,10 @@ public function shutdown() : void{
}
$this->workers = [];
}
+
+ private function checkTaskProgressUpdates(AsyncTask $task) : void{
+ Timings::getAsyncTaskProgressUpdateTimings($task)->time(function() use ($task) : void{
+ $task->checkProgressUpdates();
+ });
+ }
}
diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php
index 367957b4e32..b0b64347adc 100644
--- a/src/scheduler/AsyncTask.php
+++ b/src/scheduler/AsyncTask.php
@@ -24,11 +24,10 @@
namespace pocketmine\scheduler;
use pmmp\thread\Runnable;
-use pmmp\thread\Thread as NativeThread;
use pmmp\thread\ThreadSafe;
use pmmp\thread\ThreadSafeArray;
use pocketmine\thread\NonThreadSafeValue;
-use function assert;
+use function array_key_exists;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_null;
@@ -61,44 +60,35 @@
*/
abstract class AsyncTask extends Runnable{
/**
- * @var \ArrayObject|mixed[]|null object hash => mixed data
- * @phpstan-var \ArrayObject>|null
+ * @var mixed[][] object hash => mixed data
+ * @phpstan-var array>
*
- * Used to store objects which are only needed on one thread and should not be serialized.
+ * Used to store thread-local data to be used by onCompletion().
*/
- private static ?\ArrayObject $threadLocalStorage = null;
+ private static array $threadLocalStorage = [];
- /** @phpstan-var ThreadSafeArray */
- public ThreadSafeArray $progressUpdates;
+ /** @phpstan-var ThreadSafeArray|null */
+ private ?ThreadSafeArray $progressUpdates = null;
private ThreadSafe|string|int|bool|null|float $result = null;
- private bool $cancelRun = false;
- private bool $submitted = false;
- private bool $crashed = false;
+ private bool $submitted = false;
private bool $finished = false;
public function run() : void{
$this->result = null;
- if(!$this->cancelRun){
- try{
- $this->onRun();
- }catch(\Throwable $e){
- $this->crashed = true;
-
- \GlobalLogger::get()->logException($e);
- }
- }
+ $this->onRun();
$this->finished = true;
- $worker = NativeThread::getCurrentThread();
- assert($worker instanceof AsyncWorker);
- $worker->getNotifier()->wakeupSleeper();
+ AsyncWorker::getNotifier()->wakeupSleeper();
}
+ /**
+ * @deprecated
+ */
public function isCrashed() : bool{
- return $this->crashed || $this->isTerminated();
+ return $this->isTerminated();
}
/**
@@ -106,7 +96,7 @@ public function isCrashed() : bool{
* because it is not true prior to task execution.
*/
public function isFinished() : bool{
- return $this->finished || $this->isCrashed();
+ return $this->finished || $this->isTerminated();
}
public function hasResult() : bool{
@@ -127,12 +117,18 @@ public function setResult(mixed $result) : void{
$this->result = is_scalar($result) || is_null($result) || $result instanceof ThreadSafe ? $result : new NonThreadSafeValue($result);
}
+ /**
+ * @deprecated
+ */
public function cancelRun() : void{
- $this->cancelRun = true;
+ //NOOP
}
+ /**
+ * @deprecated
+ */
public function hasCancelledRun() : bool{
- return $this->cancelRun;
+ return false;
}
public function setSubmitted() : void{
@@ -163,15 +159,22 @@ public function onCompletion() : void{
* @param mixed $progress A value that can be safely serialize()'ed.
*/
public function publishProgress(mixed $progress) : void{
- $this->progressUpdates[] = igbinary_serialize($progress) ?? throw new \InvalidArgumentException("Progress must be serializable");
+ $progressUpdates = $this->progressUpdates;
+ if($progressUpdates === null){
+ $progressUpdates = $this->progressUpdates = new ThreadSafeArray();
+ }
+ $progressUpdates[] = igbinary_serialize($progress) ?? throw new \InvalidArgumentException("Progress must be serializable");
}
/**
* @internal Only call from AsyncPool.php on the main thread
*/
public function checkProgressUpdates() : void{
- while(($progress = $this->progressUpdates->shift()) !== null){
- $this->onProgressUpdate(igbinary_unserialize($progress));
+ $progressUpdates = $this->progressUpdates;
+ if($progressUpdates !== null){
+ while(($progress = $progressUpdates->shift()) !== null){
+ $this->onProgressUpdate(igbinary_unserialize($progress));
+ }
}
}
@@ -188,8 +191,7 @@ public function onProgressUpdate($progress) : void{
}
/**
- * Called from the main thread when the async task experiences an error during onRun(). Use this for things like
- * promise rejection.
+ * @deprecated No longer used
*/
public function onError() : void{
@@ -210,15 +212,6 @@ public function onError() : void{
* from.
*/
protected function storeLocal(string $key, mixed $complexData) : void{
- if(self::$threadLocalStorage === null){
- /*
- * It's necessary to use an object (not array) here because pthreads is stupid. Non-default array statics
- * will be inherited when task classes are copied to the worker thread, which would cause unwanted
- * inheritance of primitive thread-locals, which we really don't want for various reasons.
- * It won't try to inherit objects though, so this is the easiest solution.
- */
- self::$threadLocalStorage = new \ArrayObject();
- }
self::$threadLocalStorage[spl_object_id($this)][$key] = $complexData;
}
@@ -234,7 +227,7 @@ protected function storeLocal(string $key, mixed $complexData) : void{
*/
protected function fetchLocal(string $key){
$id = spl_object_id($this);
- if(self::$threadLocalStorage === null || !isset(self::$threadLocalStorage[$id][$key])){
+ if(!isset(self::$threadLocalStorage[$id]) || !array_key_exists($key, self::$threadLocalStorage[$id])){
throw new \InvalidArgumentException("No matching thread-local data found on this thread");
}
@@ -243,12 +236,7 @@ protected function fetchLocal(string $key){
final public function __destruct(){
$this->reallyDestruct();
- if(self::$threadLocalStorage !== null && isset(self::$threadLocalStorage[$h = spl_object_id($this)])){
- unset(self::$threadLocalStorage[$h]);
- if(self::$threadLocalStorage->count() === 0){
- self::$threadLocalStorage = null;
- }
- }
+ unset(self::$threadLocalStorage[spl_object_id($this)]);
}
/**
diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php
index 517542495d6..5fdfb1ebb73 100644
--- a/src/scheduler/AsyncWorker.php
+++ b/src/scheduler/AsyncWorker.php
@@ -36,7 +36,7 @@ class AsyncWorker extends Worker{
/** @var mixed[] */
private static array $store = [];
- private const TLS_KEY_NOTIFIER = self::class . "::notifier";
+ private static ?SleeperNotifier $notifier = null;
public function __construct(
private ThreadSafeLogger $logger,
@@ -45,12 +45,11 @@ public function __construct(
private SleeperHandlerEntry $sleeperEntry
){}
- public function getNotifier() : SleeperNotifier{
- $notifier = $this->getFromThreadStore(self::TLS_KEY_NOTIFIER);
- if(!$notifier instanceof SleeperNotifier){
- throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage");
+ public static function getNotifier() : SleeperNotifier{
+ if(self::$notifier !== null){
+ return self::$notifier;
}
- return $notifier;
+ throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage");
}
protected function onRun() : void{
@@ -66,17 +65,13 @@ protected function onRun() : void{
$this->logger->debug("No memory limit set");
}
- $this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier());
+ self::$notifier = $this->sleeperEntry->createNotifier();
}
public function getLogger() : ThreadSafeLogger{
return $this->logger;
}
- public function handleException(\Throwable $e) : void{
- $this->logger->logException($e);
- }
-
public function getThreadName() : string{
return "AsyncWorker#" . $this->id;
}
@@ -88,6 +83,8 @@ public function getAsyncWorkerId() : int{
/**
* Saves mixed data into the worker's thread-local object store. This can be used to store objects which you
* want to use on this worker thread from multiple AsyncTasks.
+ *
+ * @deprecated Use static class properties instead.
*/
public function saveToThreadStore(string $identifier, mixed $value) : void{
if(NativeThread::getCurrentThread() !== $this){
@@ -103,6 +100,8 @@ public function saveToThreadStore(string $identifier, mixed $value) : void{
* account for the possibility that what you're trying to retrieve might not exist.
*
* Objects stored in this storage may ONLY be retrieved while the task is running.
+ *
+ * @deprecated Use static class properties instead.
*/
public function getFromThreadStore(string $identifier) : mixed{
if(NativeThread::getCurrentThread() !== $this){
@@ -113,6 +112,8 @@ public function getFromThreadStore(string $identifier) : mixed{
/**
* Removes previously-stored mixed data from the worker's thread-local object store.
+ *
+ * @deprecated Use static class properties instead.
*/
public function removeFromThreadStore(string $identifier) : void{
if(NativeThread::getCurrentThread() !== $this){
diff --git a/src/stats/SendUsageTask.php b/src/stats/SendUsageTask.php
index 08c17c73a82..d218774b5fe 100644
--- a/src/stats/SendUsageTask.php
+++ b/src/stats/SendUsageTask.php
@@ -31,6 +31,7 @@
use pocketmine\utils\Process;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
+use pocketmine\YmlServerProperties;
use Ramsey\Uuid\Uuid;
use function array_map;
use function array_values;
@@ -57,7 +58,7 @@ class SendUsageTask extends AsyncTask{
* @phpstan-param array $playerList
*/
public function __construct(Server $server, int $type, array $playerList = []){
- $endpoint = "http://" . $server->getConfigGroup()->getPropertyString("anonymous-statistics.host", "stats.pocketmine.net") . "/";
+ $endpoint = "http://" . $server->getConfigGroup()->getPropertyString(YmlServerProperties::ANONYMOUS_STATISTICS_HOST, "stats.pocketmine.net") . "/";
$data = [];
$data["uniqueServerId"] = $server->getServerUniqueId()->toString();
diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php
index c35dd779144..e1c9d7c6bb1 100644
--- a/src/thread/CommonThreadPartsTrait.php
+++ b/src/thread/CommonThreadPartsTrait.php
@@ -23,10 +23,16 @@
namespace pocketmine\thread;
+use pmmp\thread\Thread as NativeThread;
use pmmp\thread\ThreadSafeArray;
+use pocketmine\crash\CrashDump;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\Server;
+use function error_get_last;
use function error_reporting;
+use function implode;
+use function register_shutdown_function;
+use function set_exception_handler;
trait CommonThreadPartsTrait{
/**
@@ -38,6 +44,8 @@ trait CommonThreadPartsTrait{
protected bool $isKilled = false;
+ private ?ThreadCrashInfo $crashInfo = null;
+
/**
* @return ThreadSafeClassLoader[]
*/
@@ -71,9 +79,7 @@ public function setClassLoaders(?array $autoloaders = null) : void{
/**
* Registers the class loaders for this thread.
*
- * WARNING: This method MUST be called from any descendent threads' run() method to make autoloading usable.
- * If you do not do this, you will not be able to use new classes that were not loaded when the thread was started
- * (unless you are using a custom autoloader).
+ * @internal
*/
public function registerClassLoaders() : void{
if($this->composerAutoloaderPath !== null){
@@ -88,12 +94,83 @@ public function registerClassLoaders() : void{
}
}
+ public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; }
+
+ public function start(int $options = NativeThread::INHERIT_NONE) : bool{
+ ThreadManager::getInstance()->add($this);
+
+ if($this->getClassLoaders() === null){
+ $this->setClassLoaders();
+ }
+ return parent::start($options);
+ }
+
final public function run() : void{
error_reporting(-1);
$this->registerClassLoaders();
//set this after the autoloader is registered
ErrorToExceptionHandler::set();
+
+ //this permits adding extra functionality to the exception and shutdown handlers via overriding
+ set_exception_handler($this->onUncaughtException(...));
+ register_shutdown_function($this->onShutdown(...));
+
$this->onRun();
+ $this->isKilled = true;
+ }
+
+ /**
+ * Stops the thread using the best way possible. Try to stop it yourself before calling this.
+ */
+ public function quit() : void{
+ $this->isKilled = true;
+
+ if(!$this->isJoined()){
+ $this->notify();
+ $this->join();
+ }
+
+ ThreadManager::getInstance()->remove($this);
+ }
+
+ /**
+ * Called by set_exception_handler() when an uncaught exception is thrown.
+ */
+ protected function onUncaughtException(\Throwable $e) : void{
+ $this->synchronized(function() use ($e) : void{
+ $this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName());
+ \GlobalLogger::get()->logException($e);
+ });
+ }
+
+ /**
+ * Called by register_shutdown_function() when the thread shuts down. This may be because of a benign shutdown, or
+ * because of a fatal error. Use isKilled to determine which.
+ */
+ protected function onShutdown() : void{
+ $this->synchronized(function() : void{
+ if($this->isTerminated() && $this->crashInfo === null){
+ $last = error_get_last();
+ if($last !== null && ($last["type"] & CrashDump::FATAL_ERROR_MASK) !== 0){
+ //fatal error
+ $crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName());
+ }else{
+ //probably misused exit()
+ $crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName());
+ }
+ $this->crashInfo = $crashInfo;
+
+ $lines = [];
+ //mimic exception printed format
+ $lines[] = "Fatal error: " . $crashInfo->makePrettyMessage();
+ $lines[] = "--- Stack trace ---";
+ foreach($crashInfo->getTrace() as $frame){
+ $lines[] = " " . $frame->getPrintableFrame();
+ }
+ $lines[] = "--- End of fatal error information ---";
+ \GlobalLogger::get()->critical(implode("\n", $lines));
+ }
+ });
}
/**
diff --git a/src/thread/Thread.php b/src/thread/Thread.php
index 706f964298f..a6c36e14c76 100644
--- a/src/thread/Thread.php
+++ b/src/thread/Thread.php
@@ -37,28 +37,4 @@
*/
abstract class Thread extends NativeThread{
use CommonThreadPartsTrait;
-
- public function start(int $options = NativeThread::INHERIT_NONE) : bool{
- //this is intentionally not traitified
- ThreadManager::getInstance()->add($this);
-
- if($this->getClassLoaders() === null){
- $this->setClassLoaders();
- }
- return parent::start($options);
- }
-
- /**
- * Stops the thread using the best way possible. Try to stop it yourself before calling this.
- */
- public function quit() : void{
- $this->isKilled = true;
-
- if(!$this->isJoined()){
- $this->notify();
- $this->join();
- }
-
- ThreadManager::getInstance()->remove($this);
- }
}
diff --git a/src/thread/ThreadCrashException.php b/src/thread/ThreadCrashException.php
new file mode 100644
index 00000000000..0eba3793441
--- /dev/null
+++ b/src/thread/ThreadCrashException.php
@@ -0,0 +1,38 @@
+crashInfo = $crashInfo;
+ }
+
+ public function getCrashInfo() : ThreadCrashInfo{
+ return $this->crashInfo;
+ }
+}
diff --git a/src/thread/ThreadCrashInfo.php b/src/thread/ThreadCrashInfo.php
new file mode 100644
index 00000000000..66aae927a88
--- /dev/null
+++ b/src/thread/ThreadCrashInfo.php
@@ -0,0 +1,89 @@
+ */
+ private ThreadSafeArray $trace;
+
+ /**
+ * @param ThreadCrashInfoFrame[] $trace
+ */
+ public function __construct(
+ private string $type,
+ private string $message,
+ private string $file,
+ private int $line,
+ array $trace,
+ private string $threadName
+ ){
+ $this->trace = ThreadSafeArray::fromArray($trace);
+ }
+
+ public static function fromThrowable(\Throwable $e, string $threadName) : self{
+ return new self(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine(), Utils::printableTraceWithMetadata($e->getTrace()), $threadName);
+ }
+
+ /**
+ * @phpstan-param array{type: int, message: string, file: string, line: int} $info
+ */
+ public static function fromLastErrorInfo(array $info, string $threadName) : self{
+ try{
+ $class = ErrorTypeToStringMap::get($info["type"]);
+ }catch(\InvalidArgumentException){
+ $class = "Unknown error type (" . $info["type"] . ")";
+ }
+ return new self($class, $info["message"], $info["file"], $info["line"], Utils::printableTraceWithMetadata(Utils::currentTrace()), $threadName);
+ }
+
+ public function getType() : string{ return $this->type; }
+
+ public function getMessage() : string{ return $this->message; }
+
+ public function getFile() : string{ return $this->file; }
+
+ public function getLine() : int{ return $this->line; }
+
+ /**
+ * @return ThreadCrashInfoFrame[]
+ */
+ public function getTrace() : array{
+ return (array) $this->trace;
+ }
+
+ public function getThreadName() : string{ return $this->threadName; }
+
+ public function makePrettyMessage() : string{
+ return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line);
+ }
+}
diff --git a/src/thread/ThreadCrashInfoFrame.php b/src/thread/ThreadCrashInfoFrame.php
new file mode 100644
index 00000000000..27f470387cd
--- /dev/null
+++ b/src/thread/ThreadCrashInfoFrame.php
@@ -0,0 +1,41 @@
+printableFrame; }
+
+ public function getFile() : ?string{ return $this->file; }
+
+ public function getLine() : int{ return $this->line; }
+}
diff --git a/src/thread/ThreadSafeClassLoader.php b/src/thread/ThreadSafeClassLoader.php
index 95b983dc1b8..fd9e6afed6e 100644
--- a/src/thread/ThreadSafeClassLoader.php
+++ b/src/thread/ThreadSafeClassLoader.php
@@ -51,12 +51,12 @@ class ThreadSafeClassLoader extends ThreadSafe{
* @var ThreadSafeArray|string[]
* @phpstan-var ThreadSafeArray
*/
- private $fallbackLookup;
+ private ThreadSafeArray $fallbackLookup;
/**
* @var ThreadSafeArray|string[][]
* @phpstan-var ThreadSafeArray>
*/
- private $psr4Lookup;
+ private ThreadSafeArray $psr4Lookup;
public function __construct(){
$this->fallbackLookup = new ThreadSafeArray();
diff --git a/src/thread/Worker.php b/src/thread/Worker.php
index 3bc5cda97aa..cc0b56046a4 100644
--- a/src/thread/Worker.php
+++ b/src/thread/Worker.php
@@ -23,7 +23,6 @@
namespace pocketmine\thread;
-use pmmp\thread\Thread as NativeThread;
use pmmp\thread\Worker as NativeWorker;
use pocketmine\scheduler\AsyncTask;
@@ -39,31 +38,4 @@
*/
abstract class Worker extends NativeWorker{
use CommonThreadPartsTrait;
-
- public function start(int $options = NativeThread::INHERIT_NONE) : bool{
- //this is intentionally not traitified
- ThreadManager::getInstance()->add($this);
-
- if($this->getClassLoaders() === null){
- $this->setClassLoaders();
- }
- return parent::start($options);
- }
-
- /**
- * Stops the thread using the best way possible. Try to stop it yourself before calling this.
- */
- public function quit() : void{
- $this->isKilled = true;
-
- if(!$this->isShutdown()){
- $this->synchronized(function() : void{
- while($this->unstack() !== null);
- });
- $this->notify();
- $this->shutdown();
- }
-
- ThreadManager::getInstance()->remove($this);
- }
}
diff --git a/src/timings/Timings.php b/src/timings/Timings.php
index aa05ffb2e1b..563af69bff6 100644
--- a/src/timings/Timings.php
+++ b/src/timings/Timings.php
@@ -29,11 +29,13 @@
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\player\Player;
+use pocketmine\scheduler\AsyncTask;
use pocketmine\scheduler\TaskHandler;
use function get_class;
use function str_starts_with;
abstract class Timings{
+ public const GROUP_MINECRAFT = "Minecraft";
public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
private static bool $initialized = false;
@@ -115,6 +117,17 @@ abstract class Timings{
/** @var TimingsHandler[][] */
private static array $eventHandlers = [];
+ private static TimingsHandler $asyncTaskProgressUpdateParent;
+ private static TimingsHandler $asyncTaskCompletionParent;
+ private static TimingsHandler $asyncTaskErrorParent;
+
+ /** @var TimingsHandler[] */
+ private static array $asyncTaskProgressUpdate = [];
+ /** @var TimingsHandler[] */
+ private static array $asyncTaskCompletion = [];
+ /** @var TimingsHandler[] */
+ private static array $asyncTaskError = [];
+
public static function init() : void{
if(self::$initialized){
return;
@@ -122,8 +135,8 @@ public static function init() : void{
self::$initialized = true;
self::$fullTick = new TimingsHandler("Full Server Tick");
- self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick, group: self::GROUP_BREAKDOWN);
- self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick, group: self::GROUP_BREAKDOWN);
+ self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick);
+ self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick);
self::$memoryManager = new TimingsHandler("Memory Manager");
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
self::$titleTick = new TimingsHandler("Console Title Tick");
@@ -131,47 +144,51 @@ public static function init() : void{
self::$connection = new TimingsHandler("Connection Handler");
self::$playerNetworkSend = new TimingsHandler("Player Network Send", self::$connection);
- self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
- self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
- self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
- self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
- self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
- self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
+ self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend);
+ self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress);
+ self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress);
+ self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend);
+ self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend);
+ self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend);
self::$playerNetworkReceive = new TimingsHandler("Player Network Receive", self::$connection);
- self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
- self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
+ self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive);
+ self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive);
- self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
+ self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend);
self::$playerMove = new TimingsHandler("Player Movement");
self::$playerChunkOrder = new TimingsHandler("Player Order Chunks");
- self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
+ self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend);
self::$scheduler = new TimingsHandler("Scheduler");
self::$serverCommand = new TimingsHandler("Server Command");
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
- self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
- self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
+ self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation);
+ self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation);
self::$syncPlayerDataLoad = new TimingsHandler("Player Data Load");
self::$syncPlayerDataSave = new TimingsHandler("Player Data Save");
- self::$entityMove = new TimingsHandler("Entity Movement", group: self::GROUP_BREAKDOWN);
- self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove, group: self::GROUP_BREAKDOWN);
+ self::$entityMove = new TimingsHandler("Entity Movement");
+ self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove);
+
+ self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove);
+ self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove);
- self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove, group: self::GROUP_BREAKDOWN);
- self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
+ self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities");
+ self::$entityBaseTick = new TimingsHandler("Entity Base Tick");
+ self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living");
+ self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity");
- self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
- self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
- self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
- self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
+ self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks");
- self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks", group: self::GROUP_BREAKDOWN);
- self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks", group: self::GROUP_BREAKDOWN);
+ self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks");
+ self::$asyncTaskProgressUpdateParent = new TimingsHandler("Async Tasks - Progress Updates", self::$schedulerAsync);
+ self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync);
+ self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync);
- self::$playerCommand = new TimingsHandler("Player Command", group: self::GROUP_BREAKDOWN);
- self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache", group: self::GROUP_BREAKDOWN);
+ self::$playerCommand = new TimingsHandler("Player Command");
+ self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache");
}
@@ -193,8 +210,7 @@ public static function getScheduledTaskTimings(TaskHandler $task, int $period) :
}
/**
- * @phpstan-template T of object
- * @phpstan-param class-string $class
+ * @phpstan-param class-string $class
*/
private static function shortenCoreClassName(string $class, string $prefix) : string{
if(str_starts_with($class, $prefix)){
@@ -213,7 +229,7 @@ public static function getEntityTimings(Entity $entity) : TimingsHandler{
}else{
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
}
- self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN);
+ self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName);
}
return self::$entityTypeTimingMap[$entity::class];
@@ -223,8 +239,7 @@ public static function getTileEntityTimings(Tile $tile) : TimingsHandler{
self::init();
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
- "Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
- group: self::GROUP_BREAKDOWN
+ "Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\")
);
}
@@ -234,7 +249,7 @@ public static function getTileEntityTimings(Tile $tile) : TimingsHandler{
public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
self::init();
if(!isset(self::$packetReceiveTimingMap[$pk::class])){
- self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
+ self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive);
}
return self::$packetReceiveTimingMap[$pk::class];
@@ -243,31 +258,28 @@ public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : Timi
public static function getDecodeDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler(
"Decode - " . $pk->getName(),
- self::getReceiveDataPacketTimings($pk),
- group: self::GROUP_BREAKDOWN
+ self::getReceiveDataPacketTimings($pk)
);
}
public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler(
"Handler - " . $pk->getName(),
- self::getReceiveDataPacketTimings($pk),
- group: self::GROUP_BREAKDOWN
+ self::getReceiveDataPacketTimings($pk)
);
}
public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler(
"Encode - " . $pk->getName(),
- self::getSendDataPacketTimings($pk),
- group: self::GROUP_BREAKDOWN
+ self::getSendDataPacketTimings($pk)
);
}
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
self::init();
if(!isset(self::$packetSendTimingMap[$pk::class])){
- self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
+ self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend);
}
return self::$packetSendTimingMap[$pk::class];
@@ -276,7 +288,7 @@ public static function getSendDataPacketTimings(ClientboundPacket $pk) : Timings
public static function getCommandDispatchTimings(string $commandName) : TimingsHandler{
self::init();
- return self::$commandTimingMap[$commandName] ??= new TimingsHandler("Command - " . $commandName, group: self::GROUP_BREAKDOWN);
+ return self::$commandTimingMap[$commandName] ??= new TimingsHandler("Command - " . $commandName);
}
public static function getEventTimings(Event $event) : TimingsHandler{
@@ -289,8 +301,7 @@ public static function getEventTimings(Event $event) : TimingsHandler{
}
/**
- * @phpstan-template TEvent of Event
- * @phpstan-param class-string $event
+ * @phpstan-param class-string $event
*/
public static function getEventHandlerTimings(string $event, string $handlerName, string $group) : TimingsHandler{
if(!isset(self::$eventHandlers[$event][$handlerName])){
@@ -299,4 +310,46 @@ public static function getEventHandlerTimings(string $event, string $handlerName
return self::$eventHandlers[$event][$handlerName];
}
+
+ public static function getAsyncTaskProgressUpdateTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
+ $taskClass = $task::class;
+ if(!isset(self::$asyncTaskProgressUpdate[$taskClass])){
+ self::init();
+ self::$asyncTaskProgressUpdate[$taskClass] = new TimingsHandler(
+ "AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Progress Updates",
+ self::$asyncTaskProgressUpdateParent,
+ $group
+ );
+ }
+
+ return self::$asyncTaskProgressUpdate[$taskClass];
+ }
+
+ public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
+ $taskClass = $task::class;
+ if(!isset(self::$asyncTaskCompletion[$taskClass])){
+ self::init();
+ self::$asyncTaskCompletion[$taskClass] = new TimingsHandler(
+ "AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Completion Handler",
+ self::$asyncTaskCompletionParent,
+ $group
+ );
+ }
+
+ return self::$asyncTaskCompletion[$taskClass];
+ }
+
+ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
+ $taskClass = $task::class;
+ if(!isset(self::$asyncTaskError[$taskClass])){
+ self::init();
+ self::$asyncTaskError[$taskClass] = new TimingsHandler(
+ "AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Error Handler",
+ self::$asyncTaskErrorParent,
+ $group
+ );
+ }
+
+ return self::$asyncTaskError[$taskClass];
+ }
}
diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php
index ba6c3cfea2c..574dd6d2b88 100644
--- a/src/timings/TimingsHandler.php
+++ b/src/timings/TimingsHandler.php
@@ -120,7 +120,7 @@ public static function tick(bool $measure = true) : void{
public function __construct(
private string $name,
private ?TimingsHandler $parent = null,
- private string $group = "Minecraft"
+ private string $group = Timings::GROUP_MINECRAFT
){}
public function getName() : string{ return $this->name; }
diff --git a/src/updater/UpdateCheckTask.php b/src/updater/UpdateCheckTask.php
index ae56682f3e8..af73f05af49 100644
--- a/src/updater/UpdateCheckTask.php
+++ b/src/updater/UpdateCheckTask.php
@@ -55,6 +55,7 @@ public function onRun() : void{
}else{
$mapper = new \JsonMapper();
$mapper->bExceptionOnMissingData = true;
+ $mapper->bStrictObjectTypeChecking = true;
$mapper->bEnforceMapType = false;
try{
/** @var UpdateInfo $responseObj */
diff --git a/src/updater/UpdateChecker.php b/src/updater/UpdateChecker.php
index ddb0f4d6693..4a3ee344465 100644
--- a/src/updater/UpdateChecker.php
+++ b/src/updater/UpdateChecker.php
@@ -27,6 +27,7 @@
use pocketmine\Server;
use pocketmine\utils\VersionString;
use pocketmine\VersionInfo;
+use pocketmine\YmlServerProperties;
use function date;
use function strtolower;
use function ucfirst;
@@ -43,7 +44,7 @@ public function __construct(Server $server, string $endpoint){
$this->logger = new \PrefixedLogger($server->getLogger(), "Update Checker");
$this->endpoint = "http://$endpoint/api/";
- if($server->getConfigGroup()->getPropertyBool("auto-updater.enabled", true)){
+ if($server->getConfigGroup()->getPropertyBool(YmlServerProperties::AUTO_UPDATER_ENABLED, true)){
$this->doCheck();
}
}
@@ -59,7 +60,7 @@ public function checkUpdateCallback(UpdateInfo $updateInfo) : void{
$this->checkUpdate($updateInfo);
if($this->hasUpdate()){
(new UpdateNotifyEvent($this))->call();
- if($this->server->getConfigGroup()->getPropertyBool("auto-updater.on-update.warn-console", true)){
+ if($this->server->getConfigGroup()->getPropertyBool(YmlServerProperties::AUTO_UPDATER_ON_UPDATE_WARN_CONSOLE, true)){
$this->showConsoleUpdate();
}
}else{
@@ -157,7 +158,7 @@ protected function checkUpdate(UpdateInfo $updateInfo) : void{
* Returns the channel used for update checking (stable, beta, dev)
*/
public function getChannel() : string{
- return strtolower($this->server->getConfigGroup()->getPropertyString("auto-updater.preferred-channel", "stable"));
+ return strtolower($this->server->getConfigGroup()->getPropertyString(YmlServerProperties::AUTO_UPDATER_PREFERRED_CHANNEL, "stable"));
}
/**
diff --git a/src/utils/EnumTrait.php b/src/utils/EnumTrait.php
index ba0a4fcf24d..7427e69f492 100644
--- a/src/utils/EnumTrait.php
+++ b/src/utils/EnumTrait.php
@@ -28,7 +28,10 @@
* __callStatic().
*
* Classes using this trait need to include \@method tags in their class docblock for every enum member.
- * Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
+ * Alternatively, just put \@generate-registry-docblock in the docblock and run build/generate-registry-annotations.php
+ *
+ * @deprecated Use native PHP 8.1 enums instead. Use {@link LegacyEnumShimTrait} if you need to provide backwards
+ * compatible EnumTrait-like API for migrated enums.
*/
trait EnumTrait{
use RegistryTrait;
diff --git a/src/utils/LegacyEnumShimTrait.php b/src/utils/LegacyEnumShimTrait.php
new file mode 100644
index 00000000000..4bb3587ab93
--- /dev/null
+++ b/src/utils/LegacyEnumShimTrait.php
@@ -0,0 +1,92 @@
+ 0){
+ throw new \ArgumentCountError("Expected exactly 0 arguments, " . count($arguments) . " passed");
+ }
+ return self::getAll()[mb_strtoupper($name)];
+ }
+
+ /**
+ * Returns a list of all cases, indexed by name.
+ *
+ * @return self[]
+ * @phpstan-return array
+ */
+ public static function getAll() : array{
+ /** @var array|null $result */
+ static $result = null;
+ if($result === null){
+ $result = [];
+ foreach(self::cases() as $case){
+ $result[mb_strtoupper($case->name)] = $case;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Shim for {@link \UnitEnum::name}.
+ *
+ * @deprecated Use the native enum's name property instead.
+ */
+ public function name() : string{
+ return $this->name;
+ }
+
+ /**
+ * Alias of spl_object_id($this).
+ *
+ * @deprecated
+ */
+ public function id() : int{
+ return spl_object_id($this);
+ }
+
+ /**
+ * Returns whether the two objects are equivalent.
+ *
+ * @deprecated Native enums can be safely compared with ===.
+ */
+ public function equals(self $other) : bool{
+ return $this === $other;
+ }
+}
diff --git a/src/utils/MainLogger.php b/src/utils/MainLogger.php
index 402a68edbbd..2eaee78831d 100644
--- a/src/utils/MainLogger.php
+++ b/src/utils/MainLogger.php
@@ -23,7 +23,6 @@
namespace pocketmine\utils;
-use LogLevel;
use pmmp\thread\Thread as NativeThread;
use pocketmine\thread\log\AttachableThreadSafeLogger;
use pocketmine\thread\log\ThreadSafeLoggerAttachment;
@@ -40,12 +39,12 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
private bool $useFormattingCodes = false;
private string $mainThreadName;
private string $timezone;
- private MainLoggerThread $logWriterThread;
+ private ?MainLoggerThread $logWriterThread = null;
/**
* @throws \RuntimeException
*/
- public function __construct(string $logFile, bool $useFormattingCodes, string $mainThreadName, \DateTimeZone $timezone, bool $logDebug = false){
+ public function __construct(?string $logFile, bool $useFormattingCodes, string $mainThreadName, \DateTimeZone $timezone, bool $logDebug = false, ?string $logArchiveDir = null){
parent::__construct();
$this->logDebug = $logDebug;
@@ -53,8 +52,10 @@ public function __construct(string $logFile, bool $useFormattingCodes, string $m
$this->mainThreadName = $mainThreadName;
$this->timezone = $timezone->getName();
- $this->logWriterThread = new MainLoggerThread($logFile);
- $this->logWriterThread->start(NativeThread::INHERIT_NONE);
+ if($logFile !== null){
+ $this->logWriterThread = new MainLoggerThread($logFile, $logArchiveDir);
+ $this->logWriterThread->start(NativeThread::INHERIT_NONE);
+ }
}
/**
@@ -132,28 +133,28 @@ public function logException(\Throwable $e, $trace = null){
public function log($level, $message){
switch($level){
- case LogLevel::EMERGENCY:
+ case \LogLevel::EMERGENCY:
$this->emergency($message);
break;
- case LogLevel::ALERT:
+ case \LogLevel::ALERT:
$this->alert($message);
break;
- case LogLevel::CRITICAL:
+ case \LogLevel::CRITICAL:
$this->critical($message);
break;
- case LogLevel::ERROR:
+ case \LogLevel::ERROR:
$this->error($message);
break;
- case LogLevel::WARNING:
+ case \LogLevel::WARNING:
$this->warning($message);
break;
- case LogLevel::NOTICE:
+ case \LogLevel::NOTICE:
$this->notice($message);
break;
- case LogLevel::INFO:
+ case \LogLevel::INFO:
$this->info($message);
break;
- case LogLevel::DEBUG:
+ case \LogLevel::DEBUG:
$this->debug($message);
break;
}
@@ -167,10 +168,12 @@ public function buffer(\Closure $c) : void{
}
public function shutdownLogWriterThread() : void{
- if(NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
- $this->logWriterThread->shutdown();
- }else{
- throw new \LogicException("Only the creator thread can shutdown the logger thread");
+ if($this->logWriterThread !== null){
+ if(NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
+ $this->logWriterThread->shutdown();
+ }else{
+ throw new \LogicException("Only the creator thread can shutdown the logger thread");
+ }
}
}
@@ -194,7 +197,9 @@ protected function send(string $message, string $level, string $prefix, string $
$this->synchronized(function() use ($message, $level, $time) : void{
Terminal::writeLine($message);
- $this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
+ if($this->logWriterThread !== null){
+ $this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
+ }
/**
* @var ThreadSafeLoggerAttachment $attachment
@@ -206,11 +211,11 @@ protected function send(string $message, string $level, string $prefix, string $
}
public function syncFlushBuffer() : void{
- $this->logWriterThread->syncFlushBuffer();
+ $this->logWriterThread?->syncFlushBuffer();
}
public function __destruct(){
- if(!$this->logWriterThread->isJoined() && NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
+ if($this->logWriterThread !== null && !$this->logWriterThread->isJoined() && NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
$this->shutdownLogWriterThread();
}
}
diff --git a/src/utils/MainLoggerThread.php b/src/utils/MainLoggerThread.php
index 548e23a4f12..990644f65cd 100644
--- a/src/utils/MainLoggerThread.php
+++ b/src/utils/MainLoggerThread.php
@@ -25,23 +25,42 @@
use pmmp\thread\Thread;
use pmmp\thread\ThreadSafeArray;
+use function clearstatcache;
+use function date;
use function fclose;
+use function file_exists;
use function fopen;
+use function fstat;
use function fwrite;
+use function is_dir;
+use function is_file;
use function is_resource;
+use function mkdir;
+use function pathinfo;
+use function rename;
+use function strlen;
use function touch;
+use const PATHINFO_EXTENSION;
+use const PATHINFO_FILENAME;
final class MainLoggerThread extends Thread{
+
/** @phpstan-var ThreadSafeArray */
private ThreadSafeArray $buffer;
private bool $syncFlush = false;
private bool $shutdown = false;
public function __construct(
- private string $logFile
+ private string $logFile,
+ private ?string $archiveDir,
+ private readonly int $maxFileSize = 32 * 1024 * 1024 //32 MB
){
$this->buffer = new ThreadSafeArray();
touch($this->logFile);
+ if($this->archiveDir !== null && !@mkdir($this->archiveDir) && !is_dir($this->archiveDir)){
+ throw new \RuntimeException("Unable to create archive directory: " . (
+ is_file($this->archiveDir) ? "it already exists and is not a directory" : "permission denied"));
+ }
}
public function write(string $line) : void{
@@ -71,12 +90,64 @@ public function shutdown() : void{
$this->join();
}
+ /** @return resource */
+ private function openLogFile(string $file, int &$size){
+ $logResource = fopen($file, "ab");
+ if(!is_resource($logResource)){
+ throw new \RuntimeException("Couldn't open log file");
+ }
+ $stat = fstat($logResource);
+ if($stat === false){
+ throw new AssumptionFailedError("fstat() should not fail here");
+ }
+ $size = $stat['size'];
+ return $logResource;
+ }
+
+ /**
+ * @param resource $logResource
+ * @return resource
+ */
+ private function archiveLogFile($logResource, int &$size, string $archiveDir){
+ fclose($logResource);
+
+ clearstatcache();
+
+ $i = 0;
+ $date = date("Y-m-d\TH.i.s");
+ $baseName = pathinfo($this->logFile, PATHINFO_FILENAME);
+ $extension = pathinfo($this->logFile, PATHINFO_EXTENSION);
+ do{
+ //this shouldn't be necessary, but in case the user messes with the system time for some reason ...
+ $fileName = "$baseName.$date.$i.$extension";
+ $out = $this->archiveDir . "/" . $fileName;
+ $i++;
+ }while(file_exists($out));
+
+ //the user may have externally deleted the whole directory - make sure it exists before we do anything
+ @mkdir($archiveDir);
+ rename($this->logFile, $out);
+
+ $logResource = $this->openLogFile($this->logFile, $size);
+ fwrite($logResource, "--- Starting new log file - old log file archived as $fileName ---\n");
+
+ return $logResource;
+ }
+
+ private function logFileReadyToArchive(int $size) : bool{
+ return $size >= $this->maxFileSize;
+ }
+
/**
* @param resource $logResource
*/
- private function writeLogStream($logResource) : void{
+ private function writeLogStream(&$logResource, int &$size, ?string $archiveDir) : void{
while(($chunk = $this->buffer->shift()) !== null){
fwrite($logResource, $chunk);
+ $size += strlen($chunk);
+ if($archiveDir !== null && $this->logFileReadyToArchive($size)){
+ $logResource = $this->archiveLogFile($logResource, $size, $archiveDir);
+ }
}
$this->synchronized(function() : void{
@@ -88,13 +159,15 @@ private function writeLogStream($logResource) : void{
}
public function run() : void{
- $logResource = fopen($this->logFile, "ab");
- if(!is_resource($logResource)){
- throw new \RuntimeException("Couldn't open log file");
+ $size = 0;
+ $logResource = $this->openLogFile($this->logFile, $size);
+ $archiveDir = $this->archiveDir;
+ if($archiveDir !== null && $this->logFileReadyToArchive($size)){
+ $logResource = $this->archiveLogFile($logResource, $size, $archiveDir);
}
while(!$this->shutdown){
- $this->writeLogStream($logResource);
+ $this->writeLogStream($logResource, $size, $archiveDir);
$this->synchronized(function() : void{
if(!$this->shutdown && !$this->syncFlush){
$this->wait();
@@ -102,7 +175,7 @@ public function run() : void{
});
}
- $this->writeLogStream($logResource);
+ $this->writeLogStream($logResource, $size, $archiveDir);
fclose($logResource);
}
diff --git a/src/utils/Process.php b/src/utils/Process.php
index b5c1e85979a..1370ab27c79 100644
--- a/src/utils/Process.php
+++ b/src/utils/Process.php
@@ -23,7 +23,7 @@
namespace pocketmine\utils;
-use pocketmine\thread\ThreadManager;
+use pocketmine\thread\Thread;
use function count;
use function exec;
use function fclose;
@@ -122,10 +122,13 @@ public static function getThreadCount() : int{
//TODO: more OS
- return count(ThreadManager::getInstance()->getAll()) + 2; //MainLogger + Main Thread
+ return Thread::getRunningCount() + 1; //pmmpthread doesn't count the main thread
}
- public static function kill(int $pid, bool $subprocesses) : void{
+ /**
+ * @param bool $subprocesses @deprecated
+ */
+ public static function kill(int $pid, bool $subprocesses = false) : void{
$logger = \GlobalLogger::get();
if($logger instanceof MainLogger){
$logger->syncFlushBuffer();
diff --git a/src/utils/RegistryTrait.php b/src/utils/RegistryTrait.php
index f8ffc114381..f1972b5187a 100644
--- a/src/utils/RegistryTrait.php
+++ b/src/utils/RegistryTrait.php
@@ -33,12 +33,12 @@
* These faux constants are exposed in static class methods, which are handled using __callStatic().
*
* Classes using this trait need to include \@method tags in their class docblock for every faux constant.
- * Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
+ * Alternatively, just put \@generate-registry-docblock in the docblock and run build/generate-registry-annotations.php
*/
trait RegistryTrait{
/**
- * @var object[]
- * @phpstan-var array
+ * @var object[]|null
+ * @phpstan-var array|null
*/
private static $members = null;
@@ -54,6 +54,9 @@ private static function verifyName(string $name) : void{
* @throws \InvalidArgumentException
*/
private static function _registryRegister(string $name, object $member) : void{
+ if(self::$members === null){
+ throw new AssumptionFailedError("Cannot register members outside of " . self::class . "::setup()");
+ }
self::verifyName($name);
$upperName = mb_strtoupper($name);
if(isset(self::$members[$upperName])){
@@ -86,6 +89,9 @@ protected static function checkInit() : void{
*/
private static function _registryFromString(string $name) : object{
self::checkInit();
+ if(self::$members === null){
+ throw new AssumptionFailedError(self::class . "::checkInit() did not initialize self::\$members correctly");
+ }
$upperName = mb_strtoupper($name);
if(!isset(self::$members[$upperName])){
throw new \InvalidArgumentException("No such registry member: " . self::class . "::" . $upperName);
@@ -108,6 +114,13 @@ public static function __callStatic($name, $arguments){
if(count($arguments) > 0){
throw new \ArgumentCountError("Expected exactly 0 arguments, " . count($arguments) . " passed");
}
+
+ //fast path
+ if(self::$members !== null && isset(self::$members[$name])){
+ return self::preprocessMember(self::$members[$name]);
+ }
+
+ //fallback
try{
return self::_registryFromString($name);
}catch(\InvalidArgumentException $e){
@@ -121,8 +134,6 @@ public static function __callStatic($name, $arguments){
*/
private static function _registryGetAll() : array{
self::checkInit();
- return array_map(function(object $o) : object{
- return self::preprocessMember($o);
- }, self::$members);
+ return array_map(self::preprocessMember(...), self::$members ?? throw new AssumptionFailedError(self::class . "::checkInit() did not initialize self::\$members correctly"));
}
}
diff --git a/src/utils/ServerKiller.php b/src/utils/ServerKiller.php
index 04d52c950b2..514a265ba6b 100644
--- a/src/utils/ServerKiller.php
+++ b/src/utils/ServerKiller.php
@@ -24,7 +24,8 @@
namespace pocketmine\utils;
use pocketmine\thread\Thread;
-use function time;
+use function hrtime;
+use function intdiv;
class ServerKiller extends Thread{
private bool $stopped = false;
@@ -34,15 +35,17 @@ public function __construct(
){}
protected function onRun() : void{
- $start = time();
- $this->synchronized(function() : void{
- if(!$this->stopped){
- $this->wait($this->time * 1000000);
+ $start = hrtime(true);
+ $remaining = $this->time * 1_000_000;
+ $this->synchronized(function() use (&$remaining, $start) : void{
+ while(!$this->stopped && $remaining > 0){
+ $this->wait($remaining);
+ $remaining -= intdiv(hrtime(true) - $start, 1000);
}
});
- if(time() - $start >= $this->time){
+ if($remaining <= 0){
echo "\nTook too long to stop, server was killed forcefully!\n";
- @Process::kill(Process::pid(), true);
+ @Process::kill(Process::pid());
}
}
diff --git a/src/utils/StringToTParser.php b/src/utils/StringToTParser.php
index f87db717373..d98153ac15a 100644
--- a/src/utils/StringToTParser.php
+++ b/src/utils/StringToTParser.php
@@ -59,7 +59,22 @@ public function override(string $alias, \Closure $callback) : void{
}
/**
- * Tries to parse the specified string into an enchantment.
+ * Registers a new alias for an existing known alias.
+ */
+ public function registerAlias(string $existing, string $alias) : void{
+ $existingKey = $this->reprocess($existing);
+ if(!isset($this->callbackMap[$existingKey])){
+ throw new \InvalidArgumentException("Cannot register new alias for unknown existing alias \"$existing\"");
+ }
+ $newKey = $this->reprocess($alias);
+ if(isset($this->callbackMap[$newKey])){
+ throw new \InvalidArgumentException("Alias \"$newKey\" is already registered");
+ }
+ $this->callbackMap[$newKey] = $this->callbackMap[$existingKey];
+ }
+
+ /**
+ * Tries to parse the specified string into a corresponding instance of T.
* @phpstan-return T|null
*/
public function parse(string $input){
diff --git a/src/utils/Utils.php b/src/utils/Utils.php
index 11548e193be..ef3f2d2499e 100644
--- a/src/utils/Utils.php
+++ b/src/utils/Utils.php
@@ -31,6 +31,7 @@
use pocketmine\entity\Location;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\math\Vector3;
+use pocketmine\thread\ThreadCrashInfoFrame;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use function array_combine;
@@ -79,7 +80,7 @@
use function preg_replace;
use function shell_exec;
use function spl_object_id;
-use function str_ends_with;
+use function str_contains;
use function str_pad;
use function str_split;
use function str_starts_with;
@@ -120,7 +121,7 @@ final class Utils{
*/
public static function getNiceClosureName(\Closure $closure) : string{
$func = new \ReflectionFunction($closure);
- if(!str_ends_with($func->getName(), '{closure}')){
+ if(!str_contains($func->getName(), '{closure')){
//closure wraps a named function, can be done with reflection or fromCallable()
//isClosure() is useless here because it just tells us if $func is reflecting a Closure object
@@ -172,16 +173,17 @@ public static function cloneCallback() : \Closure{
}
/**
- * @phpstan-template T of object
+ * @phpstan-template TKey of array-key
+ * @phpstan-template TValue of object
*
* @param object[] $array
- * @phpstan-param T[] $array
+ * @phpstan-param array $array
*
* @return object[]
- * @phpstan-return T[]
+ * @phpstan-return array
*/
public static function cloneObjectArray(array $array) : array{
- /** @phpstan-var \Closure(T) : T $callback */
+ /** @phpstan-var \Closure(TValue) : TValue $callback */
$callback = self::cloneCallback();
return array_map($callback, $array);
}
@@ -469,6 +471,30 @@ public static function printableTrace(array $trace, int $maxStringLength = 80) :
return $messages;
}
+ /**
+ * Similar to {@link Utils::printableTrace()}, but associates metadata such as file and line number with each frame.
+ * This is used to transmit thread-safe information about crash traces to the main thread when a thread crashes.
+ *
+ * @param mixed[][] $rawTrace
+ * @phpstan-param list> $rawTrace
+ *
+ * @return ThreadCrashInfoFrame[]
+ */
+ public static function printableTraceWithMetadata(array $rawTrace, int $maxStringLength = 80) : array{
+ $printableTrace = self::printableTrace($rawTrace, $maxStringLength);
+ $safeTrace = [];
+ foreach($printableTrace as $frameId => $printableFrame){
+ $rawFrame = $rawTrace[$frameId];
+ $safeTrace[$frameId] = new ThreadCrashInfoFrame(
+ $printableFrame,
+ $rawFrame["file"] ?? "unknown",
+ $rawFrame["line"] ?? 0
+ );
+ }
+
+ return $safeTrace;
+ }
+
/**
* @return mixed[][]
* @phpstan-return list>
diff --git a/src/wizard/SetupWizard.php b/src/wizard/SetupWizard.php
index c9170bd8dda..0f1a99f4b3d 100644
--- a/src/wizard/SetupWizard.php
+++ b/src/wizard/SetupWizard.php
@@ -27,13 +27,13 @@
*/
namespace pocketmine\wizard;
-use pocketmine\data\java\GameModeIdMap;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Language;
use pocketmine\lang\LanguageNotFoundException;
use pocketmine\lang\Translatable;
use pocketmine\player\GameMode;
use pocketmine\Server;
+use pocketmine\ServerProperties;
use pocketmine\utils\Config;
use pocketmine\utils\Internet;
use pocketmine\utils\InternetException;
@@ -48,8 +48,11 @@
use const STDIN;
class SetupWizard{
+ /** @deprecated */
public const DEFAULT_NAME = Server::DEFAULT_SERVER_NAME;
+ /** @deprecated */
public const DEFAULT_PORT = Server::DEFAULT_PORT_IPV4;
+ /** @deprecated */
public const DEFAULT_PLAYERS = Server::DEFAULT_MAX_PLAYERS;
private Language $lang;
@@ -91,7 +94,7 @@ public function run() : bool{
//this has to happen here to prevent user avoiding agreeing to license
$config = new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES);
- $config->set("language", $lang);
+ $config->set(ServerProperties::LANGUAGE, $lang);
$config->save();
if(strtolower($this->getInput($this->lang->translate(KnownTranslationFactory::skip_installer()), "n", "y/N")) === "y"){
@@ -101,10 +104,12 @@ public function run() : bool{
$this->writeLine();
$this->welcome();
- $this->generateBaseConfig();
- $this->generateUserFiles();
- $this->networkFunctions();
+ $this->generateBaseConfig($config);
+ $this->generateUserFiles($config);
+ $this->networkFunctions($config);
+ $config->save();
+
$this->printIpDetails();
$this->endWizard();
@@ -151,32 +156,33 @@ private function askPort(Translatable $prompt, int $default) : int{
}
}
- private function generateBaseConfig() : void{
- $config = new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES);
-
- $config->set("motd", ($name = $this->getInput($this->lang->translate(KnownTranslationFactory::name_your_server()), self::DEFAULT_NAME)));
- $config->set("server-name", $name);
+ private function generateBaseConfig(Config $config) : void{
+ $config->set(ServerProperties::MOTD, ($name = $this->getInput($this->lang->translate(KnownTranslationFactory::name_your_server()), Server::DEFAULT_SERVER_NAME)));
$this->message($this->lang->translate(KnownTranslationFactory::port_warning()));
- $config->set("server-port", $this->askPort(KnownTranslationFactory::server_port_v4(), Server::DEFAULT_PORT_IPV4));
- $config->set("server-portv6", $this->askPort(KnownTranslationFactory::server_port_v6(), Server::DEFAULT_PORT_IPV6));
+ $config->set(ServerProperties::SERVER_PORT_IPV4, $this->askPort(KnownTranslationFactory::server_port_v4(), Server::DEFAULT_PORT_IPV4));
+ $config->set(ServerProperties::SERVER_PORT_IPV6, $this->askPort(KnownTranslationFactory::server_port_v6(), Server::DEFAULT_PORT_IPV6));
$this->message($this->lang->translate(KnownTranslationFactory::gamemode_info()));
do{
- $gamemode = GameModeIdMap::getInstance()->fromId((int) $this->getInput($this->lang->translate(KnownTranslationFactory::default_gamemode()), (string) GameModeIdMap::getInstance()->toId(GameMode::SURVIVAL())));
+ $input = (int) $this->getInput($this->lang->translate(KnownTranslationFactory::default_gamemode()), "0");
+ $gamemode = match($input){
+ 0 => GameMode::SURVIVAL,
+ 1 => GameMode::CREATIVE,
+ default => null
+ };
}while($gamemode === null);
- $config->set("gamemode", $gamemode->name());
-
- $config->set("max-players", (int) $this->getInput($this->lang->translate(KnownTranslationFactory::max_players()), (string) self::DEFAULT_PLAYERS));
+ //TODO: this probably shouldn't use the enum name directly
+ $config->set(ServerProperties::GAME_MODE, $gamemode->name);
- $config->set("view-distance", (int) $this->getInput($this->lang->translate(KnownTranslationFactory::view_distance()), (string) Server::DEFAULT_MAX_VIEW_DISTANCE));
+ $config->set(ServerProperties::MAX_PLAYERS, (int) $this->getInput($this->lang->translate(KnownTranslationFactory::max_players()), (string) Server::DEFAULT_MAX_PLAYERS));
- $config->save();
+ $config->set(ServerProperties::VIEW_DISTANCE, (int) $this->getInput($this->lang->translate(KnownTranslationFactory::view_distance()), (string) Server::DEFAULT_MAX_VIEW_DISTANCE));
}
- private function generateUserFiles() : void{
+ private function generateUserFiles(Config $config) : void{
$this->message($this->lang->translate(KnownTranslationFactory::op_info()));
$op = strtolower($this->getInput($this->lang->translate(KnownTranslationFactory::op_who()), ""));
@@ -190,27 +196,22 @@ private function generateUserFiles() : void{
$this->message($this->lang->translate(KnownTranslationFactory::whitelist_info()));
- $config = new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES);
if(strtolower($this->getInput($this->lang->translate(KnownTranslationFactory::whitelist_enable()), "n", "y/N")) === "y"){
$this->error($this->lang->translate(KnownTranslationFactory::whitelist_warning()));
- $config->set("white-list", true);
+ $config->set(ServerProperties::WHITELIST, true);
}else{
- $config->set("white-list", false);
+ $config->set(ServerProperties::WHITELIST, false);
}
- $config->save();
}
- private function networkFunctions() : void{
- $config = new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES);
+ private function networkFunctions(Config $config) : void{
$this->error($this->lang->translate(KnownTranslationFactory::query_warning1()));
$this->error($this->lang->translate(KnownTranslationFactory::query_warning2()));
if(strtolower($this->getInput($this->lang->translate(KnownTranslationFactory::query_disable()), "n", "y/N")) === "y"){
- $config->set("enable-query", false);
+ $config->set(ServerProperties::ENABLE_QUERY, false);
}else{
- $config->set("enable-query", true);
+ $config->set(ServerProperties::ENABLE_QUERY, true);
}
-
- $config->save();
}
private function printIpDetails() : void{
diff --git a/src/world/World.php b/src/world/World.php
index b2b6dfac2a1..a8e624dd5c5 100644
--- a/src/world/World.php
+++ b/src/world/World.php
@@ -52,6 +52,7 @@
use pocketmine\event\world\ChunkPopulateEvent;
use pocketmine\event\world\ChunkUnloadEvent;
use pocketmine\event\world\SpawnChangeEvent;
+use pocketmine\event\world\WorldDifficultyChangeEvent;
use pocketmine\event\world\WorldDisplayNameChangeEvent;
use pocketmine\event\world\WorldParticleEvent;
use pocketmine\event\world\WorldSaveEvent;
@@ -103,6 +104,7 @@
use pocketmine\world\sound\BlockPlaceSound;
use pocketmine\world\sound\Sound;
use pocketmine\world\utils\SubChunkExplorer;
+use pocketmine\YmlServerProperties;
use function abs;
use function array_filter;
use function array_key_exists;
@@ -200,6 +202,11 @@ class World implements ChunkManager{
* @phpstan-var array>
*/
private array $blockCache = [];
+ /**
+ * @var AxisAlignedBB[][][] chunkHash => [relativeBlockHash => AxisAlignedBB[]]
+ * @phpstan-var array>>
+ */
+ private array $blockCollisionBoxCache = [];
private int $sendTimeTicker = 0;
@@ -507,14 +514,14 @@ public function __construct(
$this->time = $this->provider->getWorldData()->getTime();
$cfg = $this->server->getConfigGroup();
- $this->chunkTickRadius = min($this->server->getViewDistance(), max(0, $cfg->getPropertyInt("chunk-ticking.tick-radius", 4)));
+ $this->chunkTickRadius = min($this->server->getViewDistance(), max(0, $cfg->getPropertyInt(YmlServerProperties::CHUNK_TICKING_TICK_RADIUS, 4)));
if($cfg->getPropertyInt("chunk-ticking.per-tick", 40) <= 0){
//TODO: this needs l10n
$this->logger->warning("\"chunk-ticking.per-tick\" setting is deprecated, but you've used it to disable chunk ticking. Set \"chunk-ticking.tick-radius\" to 0 in \"pocketmine.yml\" instead.");
$this->chunkTickRadius = 0;
}
- $this->tickedBlocksPerSubchunkPerTick = $cfg->getPropertyInt("chunk-ticking.blocks-per-subchunk-per-tick", self::DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK);
- $this->maxConcurrentChunkPopulationTasks = $cfg->getPropertyInt("chunk-generation.population-queue-size", 2);
+ $this->tickedBlocksPerSubchunkPerTick = $cfg->getPropertyInt(YmlServerProperties::CHUNK_TICKING_BLOCKS_PER_SUBCHUNK_PER_TICK, self::DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK);
+ $this->maxConcurrentChunkPopulationTasks = $cfg->getPropertyInt(YmlServerProperties::CHUNK_GENERATION_POPULATION_QUEUE_SIZE, 2);
$this->initRandomTickBlocksFromConfig($cfg);
@@ -535,7 +542,7 @@ public function __construct(
private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{
$dontTickBlocks = [];
$parser = StringToItemParser::getInstance();
- foreach($cfg->getProperty("chunk-ticking.disable-block-ticking", []) as $name){
+ foreach($cfg->getProperty(YmlServerProperties::CHUNK_TICKING_DISABLE_BLOCK_TICKING, []) as $name){
$name = (string) $name;
$item = $parser->parse($name);
if($item !== null){
@@ -646,6 +653,7 @@ public function onUnload() : void{
$this->provider->close();
$this->blockCache = [];
+ $this->blockCollisionBoxCache = [];
$this->unloaded = true;
}
@@ -687,16 +695,19 @@ private function filterViewersForPosition(Vector3 $pos, array $allowed) : array{
*/
public function addSound(Vector3 $pos, Sound $sound, ?array $players = null) : void{
$players ??= $this->getViewersForPosition($pos);
- $ev = new WorldSoundEvent($this, $sound, $pos, $players);
- $ev->call();
+ if(WorldSoundEvent::hasHandlers()){
+ $ev = new WorldSoundEvent($this, $sound, $pos, $players);
+ $ev->call();
+ if($ev->isCancelled()){
+ return;
+ }
- if($ev->isCancelled()){
- return;
+ $sound = $ev->getSound();
+ $players = $ev->getRecipients();
}
- $pk = $ev->getSound()->encode($pos);
- $players = $ev->getRecipients();
+ $pk = $sound->encode($pos);
if(count($pk) > 0){
if($players === $this->getViewersForPosition($pos)){
foreach($pk as $e){
@@ -713,23 +724,26 @@ public function addSound(Vector3 $pos, Sound $sound, ?array $players = null) : v
*/
public function addParticle(Vector3 $pos, Particle $particle, ?array $players = null) : void{
$players ??= $this->getViewersForPosition($pos);
- $ev = new WorldParticleEvent($this, $particle, $pos, $players);
- $ev->call();
+ if(WorldParticleEvent::hasHandlers()){
+ $ev = new WorldParticleEvent($this, $particle, $pos, $players);
+ $ev->call();
+ if($ev->isCancelled()){
+ return;
+ }
- if($ev->isCancelled()){
- return;
+ $particle = $ev->getParticle();
+ $players = $ev->getRecipients();
}
- $pk = $ev->getParticle()->encode($pos);
- $players = $ev->getRecipients();
+ $pk = $particle->encode($pos);
if(count($pk) > 0){
if($players === $this->getViewersForPosition($pos)){
foreach($pk as $e){
$this->broadcastPacketToViewers($pos, $e);
}
}else{
- NetworkBroadcastUtils::broadcastPackets($this->filterViewersForPosition($pos, $ev->getRecipients()), $pk);
+ NetworkBroadcastUtils::broadcastPackets($this->filterViewersForPosition($pos, $players), $pk);
}
}
}
@@ -812,14 +826,15 @@ public function unregisterChunkLoader(ChunkLoader $loader, int $chunkX, int $chu
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$loaderId = spl_object_id($loader);
if(isset($this->chunkLoaders[$chunkHash][$loaderId])){
- unset($this->chunkLoaders[$chunkHash][$loaderId]);
- if(count($this->chunkLoaders[$chunkHash]) === 0){
+ if(count($this->chunkLoaders[$chunkHash]) === 1){
unset($this->chunkLoaders[$chunkHash]);
$this->unloadChunkRequest($chunkX, $chunkZ, true);
if(isset($this->chunkPopulationRequestMap[$chunkHash]) && !isset($this->activeChunkPopulationTasks[$chunkHash])){
$this->chunkPopulationRequestMap[$chunkHash]->reject();
unset($this->chunkPopulationRequestMap[$chunkHash]);
}
+ }else{
+ unset($this->chunkLoaders[$chunkHash][$loaderId]);
}
}
}
@@ -847,11 +862,12 @@ public function registerChunkListener(ChunkListener $listener, int $chunkX, int
public function unregisterChunkListener(ChunkListener $listener, int $chunkX, int $chunkZ) : void{
$hash = World::chunkHash($chunkX, $chunkZ);
if(isset($this->chunkListeners[$hash])){
- unset($this->chunkListeners[$hash][spl_object_id($listener)]);
- unset($this->playerChunkListeners[$hash][spl_object_id($listener)]);
- if(count($this->chunkListeners[$hash]) === 0){
+ if(count($this->chunkListeners[$hash]) === 1){
unset($this->chunkListeners[$hash]);
unset($this->playerChunkListeners[$hash]);
+ }else{
+ unset($this->chunkListeners[$hash][spl_object_id($listener)]);
+ unset($this->playerChunkListeners[$hash][spl_object_id($listener)]);
}
}
}
@@ -934,9 +950,7 @@ protected function actuallyDoTick(int $currentTick) : void{
$this->providerGarbageCollectionTicker = 0;
}
- //Do block updates
$this->timings->scheduledBlockUpdates->startTiming();
-
//Delayed updates
while($this->scheduledBlockUpdateQueue->count() > 0 && $this->scheduledBlockUpdateQueue->current()["priority"] <= $currentTick){
/** @var Vector3 $vec */
@@ -948,7 +962,9 @@ protected function actuallyDoTick(int $currentTick) : void{
$block = $this->getBlock($vec);
$block->onScheduledUpdate();
}
+ $this->timings->scheduledBlockUpdates->stopTiming();
+ $this->timings->neighbourBlockUpdates->startTiming();
//Normal updates
while($this->neighbourBlockUpdateQueue->count() > 0){
$index = $this->neighbourBlockUpdateQueue->dequeue();
@@ -959,23 +975,21 @@ protected function actuallyDoTick(int $currentTick) : void{
}
$block = $this->getBlockAt($x, $y, $z);
- $replacement = $block->readStateFromWorld(); //for blocks like fences, force recalculation of connected AABBs
- if($replacement !== $block){
- $replacement->position($this, $x, $y, $z);
- $block = $replacement;
- }
- $ev = new BlockUpdateEvent($block);
- $ev->call();
- if(!$ev->isCancelled()){
- foreach($this->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z)) as $entity){
- $entity->onNearbyBlockChange();
+ if(BlockUpdateEvent::hasHandlers()){
+ $ev = new BlockUpdateEvent($block);
+ $ev->call();
+ if($ev->isCancelled()){
+ continue;
}
- $block->onNearbyBlockChange();
}
+ foreach($this->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z)) as $entity){
+ $entity->onNearbyBlockChange();
+ }
+ $block->onNearbyBlockChange();
}
- $this->timings->scheduledBlockUpdates->stopTiming();
+ $this->timings->neighbourBlockUpdates->stopTiming();
$this->timings->entityTick->startTiming();
//Update entities that need update
@@ -1002,8 +1016,12 @@ protected function actuallyDoTick(int $currentTick) : void{
continue;
}
World::getXZ($index, $chunkX, $chunkZ);
+ if(!$this->isChunkLoaded($chunkX, $chunkZ)){
+ //a previous chunk may have caused this one to be unloaded by a ChunkListener
+ continue;
+ }
if(count($blocks) > 512){
- $chunk = $this->getChunk($chunkX, $chunkZ);
+ $chunk = $this->getChunk($chunkX, $chunkZ) ?? throw new AssumptionFailedError("We already checked that the chunk is loaded");
foreach($this->getChunkPlayers($chunkX, $chunkZ) as $p){
$p->onChunkChanged($chunkX, $chunkZ, $chunk);
}
@@ -1085,19 +1103,22 @@ public function createBlockUpdatePackets(array $blocks) : array{
$blockPosition = BlockPosition::fromVector3($b);
$tile = $this->getTileAt($b->x, $b->y, $b->z);
- if($tile instanceof Spawnable && count($fakeStateProperties = $tile->getRenderUpdateBugWorkaroundStateProperties($fullBlock)) > 0){
- $originalStateData = $blockTranslator->internalIdToNetworkStateData($fullBlock->getStateId());
- $fakeStateData = new BlockStateData(
- $originalStateData->getName(),
- array_merge($originalStateData->getStates(), $fakeStateProperties),
- $originalStateData->getVersion()
- );
- $packets[] = UpdateBlockPacket::create(
- $blockPosition,
- $blockTranslator->getBlockStateDictionary()->lookupStateIdFromData($fakeStateData) ?? throw new AssumptionFailedError("Unmapped fake blockstate data: " . $fakeStateData->toNbt()),
- UpdateBlockPacket::FLAG_NETWORK,
- UpdateBlockPacket::DATA_LAYER_NORMAL
- );
+ if($tile instanceof Spawnable){
+ $expectedClass = $fullBlock->getIdInfo()->getTileClass();
+ if($expectedClass !== null && $tile instanceof $expectedClass && count($fakeStateProperties = $tile->getRenderUpdateBugWorkaroundStateProperties($fullBlock)) > 0){
+ $originalStateData = $blockTranslator->internalIdToNetworkStateData($fullBlock->getStateId());
+ $fakeStateData = new BlockStateData(
+ $originalStateData->getName(),
+ array_merge($originalStateData->getStates(), $fakeStateProperties),
+ $originalStateData->getVersion()
+ );
+ $packets[] = UpdateBlockPacket::create(
+ $blockPosition,
+ $blockTranslator->getBlockStateDictionary()->lookupStateIdFromData($fakeStateData) ?? throw new AssumptionFailedError("Unmapped fake blockstate data: " . $fakeStateData->toNbt()),
+ UpdateBlockPacket::FLAG_NETWORK,
+ UpdateBlockPacket::DATA_LAYER_NORMAL
+ );
+ }
}
$packets[] = UpdateBlockPacket::create(
$blockPosition,
@@ -1117,6 +1138,7 @@ public function createBlockUpdatePackets(array $blocks) : array{
public function clearCache(bool $force = false) : void{
if($force){
$this->blockCache = [];
+ $this->blockCollisionBoxCache = [];
}else{
$count = 0;
foreach($this->blockCache as $list){
@@ -1126,6 +1148,16 @@ public function clearCache(bool $force = false) : void{
break;
}
}
+
+ $count = 0;
+ foreach($this->blockCollisionBoxCache as $list){
+ $count += count($list);
+ if($count > 2048){
+ //TODO: Is this really the best logic?
+ $this->blockCollisionBoxCache = [];
+ break;
+ }
+ }
}
}
@@ -1193,13 +1225,14 @@ public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $ch
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$tickerId = spl_object_id($ticker);
if(isset($this->registeredTickingChunks[$chunkHash][$tickerId])){
- unset($this->registeredTickingChunks[$chunkHash][$tickerId]);
- if(count($this->registeredTickingChunks[$chunkHash]) === 0){
+ if(count($this->registeredTickingChunks[$chunkHash]) === 1){
unset(
$this->registeredTickingChunks[$chunkHash],
$this->recheckTickingChunks[$chunkHash],
$this->validTickingChunks[$chunkHash]
);
+ }else{
+ unset($this->registeredTickingChunks[$chunkHash][$tickerId]);
}
}
}
@@ -1427,9 +1460,9 @@ public function scheduleDelayedBlockUpdate(Vector3 $pos, int $delay) : void{
$this->scheduledBlockUpdateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), $delay + $this->server->getTick());
}
- private function tryAddToNeighbourUpdateQueue(Vector3 $pos) : void{
- if($this->isInWorld($pos->x, $pos->y, $pos->z)){
- $hash = World::blockHash($pos->x, $pos->y, $pos->z);
+ private function tryAddToNeighbourUpdateQueue(int $x, int $y, int $z) : void{
+ if($this->isInWorld($x, $y, $z)){
+ $hash = World::blockHash($x, $y, $z);
if(!isset($this->neighbourBlockUpdateQueueIndex[$hash])){
$this->neighbourBlockUpdateQueue->enqueue($hash);
$this->neighbourBlockUpdateQueueIndex[$hash] = true;
@@ -1437,17 +1470,28 @@ private function tryAddToNeighbourUpdateQueue(Vector3 $pos) : void{
}
}
+ /**
+ * Identical to {@link World::notifyNeighbourBlockUpdate()}, but without the Vector3 requirement. We don't want or
+ * need Vector3 in the places where this is called.
+ *
+ * TODO: make this the primary method in PM6
+ */
+ private function internalNotifyNeighbourBlockUpdate(int $x, int $y, int $z) : void{
+ $this->tryAddToNeighbourUpdateQueue($x, $y, $z);
+ foreach(Facing::OFFSET as [$dx, $dy, $dz]){
+ $this->tryAddToNeighbourUpdateQueue($x + $dx, $y + $dy, $z + $dz);
+ }
+ }
+
/**
* Notify the blocks at and around the position that the block at the position may have changed.
* This will cause onNearbyBlockChange() to be called for these blocks.
+ * TODO: Accept plain integers in PM6 - the Vector3 requirement is an unnecessary inconvenience
*
* @see Block::onNearbyBlockChange()
*/
public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{
- $this->tryAddToNeighbourUpdateQueue($pos);
- foreach($pos->sides() as $side){
- $this->tryAddToNeighbourUpdateQueue($side);
- }
+ $this->internalNotifyNeighbourBlockUpdate($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ());
}
/**
@@ -1491,25 +1535,53 @@ public function getCollisionBlocks(AxisAlignedBB $bb, bool $targetFirst = false)
return $collides;
}
+ /**
+ * Returns a list of all block AABBs which overlap the full block area at the given coordinates.
+ * This checks a padding of 1 block around the coordinates to account for oversized AABBs of blocks like fences.
+ * Larger AABBs (>= 2 blocks on any axis) are not accounted for.
+ *
+ * @return AxisAlignedBB[]
+ */
+ private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{
+ $block = $this->getBlockAt($x, $y, $z);
+ $boxes = $block->getCollisionBoxes();
+
+ $cellBB = AxisAlignedBB::one()->offset($x, $y, $z);
+ foreach(Facing::OFFSET as [$dx, $dy, $dz]){
+ $extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes();
+ foreach($extraBoxes as $extraBox){
+ if($extraBox->intersectsWith($cellBB)){
+ $boxes[] = $extraBox;
+ }
+ }
+ }
+
+ return $boxes;
+ }
+
/**
* @return AxisAlignedBB[]
* @phpstan-return list
*/
- public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entities = true) : array{
- $minX = (int) floor($bb->minX - 1);
- $minY = (int) floor($bb->minY - 1);
- $minZ = (int) floor($bb->minZ - 1);
- $maxX = (int) floor($bb->maxX + 1);
- $maxY = (int) floor($bb->maxY + 1);
- $maxZ = (int) floor($bb->maxZ + 1);
+ public function getBlockCollisionBoxes(AxisAlignedBB $bb) : array{
+ $minX = (int) floor($bb->minX);
+ $minY = (int) floor($bb->minY);
+ $minZ = (int) floor($bb->minZ);
+ $maxX = (int) floor($bb->maxX);
+ $maxY = (int) floor($bb->maxY);
+ $maxZ = (int) floor($bb->maxZ);
$collides = [];
for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){
+ $chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
for($y = $minY; $y <= $maxY; ++$y){
- $block = $this->getBlockAt($x, $y, $z);
- foreach($block->getCollisionBoxes() as $blockBB){
+ $relativeBlockHash = World::chunkBlockHash($x, $y, $z);
+
+ $boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z);
+
+ foreach($boxes as $blockBB){
if($blockBB->intersectsWith($bb)){
$collides[] = $blockBB;
}
@@ -1518,6 +1590,19 @@ public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entit
}
}
+ return $collides;
+ }
+
+ /**
+ * @deprecated Use {@link World::getBlockCollisionBoxes()} instead (alongside {@link World::getCollidingEntities()}
+ * if entity collision boxes are also required).
+ *
+ * @return AxisAlignedBB[]
+ * @phpstan-return list
+ */
+ public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entities = true) : array{
+ $collides = $this->getBlockCollisionBoxes($bb);
+
if($entities){
foreach($this->getCollidingEntities($bb->expandedCopy(0.25, 0.25, 0.25), $entity) as $ent){
$collides[] = clone $ent->boundingBox;
@@ -1585,16 +1670,19 @@ public function getSkyLightReduction() : int{
}
/**
- * Returns the highest available level of any type of light at the given coordinates, adjusted for the current
- * weather and time of day.
+ * Returns the highest level of any type of light at the given coordinates, adjusted for the current weather and
+ * time of day.
*/
public function getFullLight(Vector3 $pos) : int{
- return $this->getFullLightAt($pos->x, $pos->y, $pos->z);
+ $floorX = $pos->getFloorX();
+ $floorY = $pos->getFloorY();
+ $floorZ = $pos->getFloorZ();
+ return $this->getFullLightAt($floorX, $floorY, $floorZ);
}
/**
- * Returns the highest available level of any type of light at the given coordinates, adjusted for the current
- * weather and time of day.
+ * Returns the highest level of any type of light at the given coordinates, adjusted for the current weather and
+ * time of day.
*/
public function getFullLightAt(int $x, int $y, int $z) : int{
$skyLight = $this->getRealBlockSkyLightAt($x, $y, $z);
@@ -1606,18 +1694,43 @@ public function getFullLightAt(int $x, int $y, int $z) : int{
}
/**
- * Returns the highest available level of any type of light at, or adjacent to, the given coordinates, adjusted for
- * the current weather and time of day.
+ * Returns the highest level of any type of light at, or adjacent to, the given coordinates, adjusted for the
+ * current weather and time of day.
*/
public function getHighestAdjacentFullLightAt(int $x, int $y, int $z) : int{
- return $this->getHighestAdjacentLight($x, $y, $z, \Closure::fromCallable([$this, 'getFullLightAt']));
+ return $this->getHighestAdjacentLight($x, $y, $z, $this->getFullLightAt(...));
+ }
+
+ /**
+ * Returns the highest potential level of any type of light at the target coordinates.
+ * This is not affected by weather or time of day.
+ */
+ public function getPotentialLight(Vector3 $pos) : int{
+ $floorX = $pos->getFloorX();
+ $floorY = $pos->getFloorY();
+ $floorZ = $pos->getFloorZ();
+ return $this->getPotentialLightAt($floorX, $floorY, $floorZ);
+ }
+
+ /**
+ * Returns the highest potential level of any type of light at the target coordinates.
+ * This is not affected by weather or time of day.
+ */
+ public function getPotentialLightAt(int $x, int $y, int $z) : int{
+ return max($this->getPotentialBlockSkyLightAt($x, $y, $z), $this->getBlockLightAt($x, $y, $z));
+ }
+
+ /**
+ * Returns the highest potential level of any type of light at, or adjacent to, the given coordinates.
+ * This is not affected by weather or time of day.
+ */
+ public function getHighestAdjacentPotentialLightAt(int $x, int $y, int $z) : int{
+ return $this->getHighestAdjacentLight($x, $y, $z, $this->getPotentialLightAt(...));
}
/**
* Returns the highest potential level of sky light at the target coordinates, regardless of the time of day or
* weather conditions.
- * You usually don't want to use this for vanilla gameplay logic; prefer the real sky light instead.
- * @see World::getRealBlockSkyLightAt()
*
* @return int 0-15
*/
@@ -1682,14 +1795,10 @@ public function updateAllLight(int $x, int $y, int $z) : void{
*/
private function getHighestAdjacentLight(int $x, int $y, int $z, \Closure $lightGetter) : int{
$max = 0;
- foreach([
- [$x + 1, $y, $z],
- [$x - 1, $y, $z],
- [$x, $y + 1, $z],
- [$x, $y - 1, $z],
- [$x, $y, $z + 1],
- [$x, $y, $z - 1]
- ] as [$x1, $y1, $z1]){
+ foreach(Facing::OFFSET as [$offsetX, $offsetY, $offsetZ]){
+ $x1 = $x + $offsetX;
+ $y1 = $y + $offsetY;
+ $z1 = $z + $offsetZ;
if(
!$this->isInWorld($x1, $y1, $z1) ||
($chunk = $this->getChunk($x1 >> Chunk::COORD_BIT_SIZE, $z1 >> Chunk::COORD_BIT_SIZE)) === null ||
@@ -1706,7 +1815,7 @@ private function getHighestAdjacentLight(int $x, int $y, int $z, \Closure $light
* Returns the highest potential level of sky light in the positions adjacent to the specified block coordinates.
*/
public function getHighestAdjacentPotentialBlockSkyLight(int $x, int $y, int $z) : int{
- return $this->getHighestAdjacentLight($x, $y, $z, \Closure::fromCallable([$this, 'getPotentialBlockSkyLightAt']));
+ return $this->getHighestAdjacentLight($x, $y, $z, $this->getPotentialBlockSkyLightAt(...));
}
/**
@@ -1721,7 +1830,7 @@ public function getHighestAdjacentRealBlockSkyLight(int $x, int $y, int $z) : in
* Returns the highest block light level available in the positions adjacent to the specified block coordinates.
*/
public function getHighestAdjacentBlockLight(int $x, int $y, int $z) : int{
- return $this->getHighestAdjacentLight($x, $y, $z, \Closure::fromCallable([$this, 'getBlockLightAt']));
+ return $this->getHighestAdjacentLight($x, $y, $z, $this->getBlockLightAt(...));
}
private function executeQueuedLightUpdates() : void{
@@ -1858,6 +1967,14 @@ public function setBlockAt(int $x, int $y, int $z, Block $block, bool $update =
$relativeBlockHash = World::chunkBlockHash($x, $y, $z);
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
+ unset($this->blockCollisionBoxCache[$chunkHash][$relativeBlockHash]);
+ //blocks like fences have collision boxes that reach into neighbouring blocks, so we need to invalidate the
+ //caches for those blocks as well
+ foreach(Facing::OFFSET as [$offsetX, $offsetY, $offsetZ]){
+ $sideChunkPosHash = World::chunkHash(($x + $offsetX) >> Chunk::COORD_BIT_SIZE, ($z + $offsetZ) >> Chunk::COORD_BIT_SIZE);
+ $sideChunkBlockHash = World::chunkBlockHash($x + $offsetX, $y + $offsetY, $z + $offsetZ);
+ unset($this->blockCollisionBoxCache[$sideChunkPosHash][$sideChunkBlockHash]);
+ }
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
@@ -1870,7 +1987,7 @@ public function setBlockAt(int $x, int $y, int $z, Block $block, bool $update =
if($update){
$this->updateAllLight($x, $y, $z);
- $this->notifyNeighbourBlockUpdate($pos);
+ $this->internalNotifyNeighbourBlockUpdate($x, $y, $z);
}
$this->timings->setBlock->stopTiming();
@@ -1920,7 +2037,7 @@ public function dropExperience(Vector3 $pos, int $amount) : array{
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
* @phpstan-param-out Item $item
*/
- public function useBreakOn(Vector3 $vector, Item &$item = null, ?Player $player = null, bool $createParticles = false, array &$returnedItems = []) : bool{
+ public function useBreakOn(Vector3 $vector, ?Item &$item = null, ?Player $player = null, bool $createParticles = false, array &$returnedItems = []) : bool{
$vector = $vector->floor();
$chunkX = $vector->getFloorX() >> Chunk::COORD_BIT_SIZE;
@@ -2032,6 +2149,12 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli
if($clickVector === null){
$clickVector = new Vector3(0.0, 0.0, 0.0);
+ }else{
+ $clickVector = new Vector3(
+ min(1.0, max(0.0, $clickVector->x)),
+ min(1.0, max(0.0, $clickVector->y)),
+ min(1.0, max(0.0, $clickVector->z))
+ );
}
if(!$this->isInWorld($blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z)){
@@ -2050,19 +2173,25 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli
if($player !== null){
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK);
+ if($player->isSneaking()){
+ $ev->setUseItem(false);
+ $ev->setUseBlock($item->isNull()); //opening doors is still possible when sneaking if using an empty hand
+ }
if($player->isSpectator()){
$ev->cancel(); //set it to cancelled so plugins can bypass this
}
$ev->call();
if(!$ev->isCancelled()){
- if((!$player->isSneaking() || $item->isNull()) && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){
+ if($ev->useBlock() && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){
return true;
}
- $result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
- if(!$result->equals(ItemUseResult::NONE())){
- return $result->equals(ItemUseResult::SUCCESS());
+ if($ev->useItem()){
+ $result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
+ if($result !== ItemUseResult::NONE){
+ return $result === ItemUseResult::SUCCESS;
+ }
}
}else{
return false;
@@ -2204,9 +2333,6 @@ public function getNearbyEntities(AxisAlignedBB $bb, ?Entity $entity = null) : a
for($x = $minX; $x <= $maxX; ++$x){
for($z = $minZ; $z <= $maxZ; ++$z){
- if(!$this->isChunkLoaded($x, $z)){
- continue;
- }
foreach($this->getChunkEntities($x, $z) as $ent){
if($ent !== $entity && $ent->boundingBox->intersectsWith($bb)){
$nearby[] = $ent;
@@ -2247,9 +2373,6 @@ public function getNearestEntity(Vector3 $pos, float $maxDistance, string $entit
for($x = $minX; $x <= $maxX; ++$x){
for($z = $minZ; $z <= $maxZ; ++$z){
- if(!$this->isChunkLoaded($x, $z)){
- continue;
- }
foreach($this->getChunkEntities($x, $z) as $entity){
if(!($entity instanceof $entityType) || $entity->isFlaggedForDespawn() || (!$includeDead && !$entity->isAlive())){
continue;
@@ -2448,6 +2571,7 @@ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void{
$this->chunks[$chunkHash] = $chunk;
unset($this->blockCache[$chunkHash]);
+ unset($this->blockCollisionBoxCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
$chunk->setTerrainDirty();
$this->markTickingChunkForRecheck($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
@@ -2457,7 +2581,9 @@ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void{
}
if($oldChunk === null){
- (new ChunkLoadEvent($this, $chunkX, $chunkZ, $chunk, true))->call();
+ if(ChunkLoadEvent::hasHandlers()){
+ (new ChunkLoadEvent($this, $chunkX, $chunkZ, $chunk, true))->call();
+ }
foreach($this->getChunkListeners($chunkX, $chunkZ) as $listener){
$listener->onChunkLoaded($chunkX, $chunkZ, $chunk);
@@ -2507,7 +2633,7 @@ public function isChunkGenerated(int $x, int $z) : bool{
public function isChunkPopulated(int $x, int $z) : bool{
$chunk = $this->loadChunk($x, $z);
- return $chunk !== null ? $chunk->isPopulated() : false;
+ return $chunk !== null && $chunk->isPopulated();
}
/**
@@ -2573,9 +2699,10 @@ public function removeEntity(Entity $entity) : void{
$pos = $this->entityLastKnownPositions[$entity->getId()];
$chunkHash = World::chunkHash($pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE);
if(isset($this->entitiesByChunk[$chunkHash][$entity->getId()])){
- unset($this->entitiesByChunk[$chunkHash][$entity->getId()]);
- if(count($this->entitiesByChunk[$chunkHash]) === 0){
+ if(count($this->entitiesByChunk[$chunkHash]) === 1){
unset($this->entitiesByChunk[$chunkHash]);
+ }else{
+ unset($this->entitiesByChunk[$chunkHash][$entity->getId()]);
}
}
unset($this->entityLastKnownPositions[$entity->getId()]);
@@ -2608,9 +2735,10 @@ public function onEntityMoved(Entity $entity) : void{
if($oldChunkX !== $newChunkX || $oldChunkZ !== $newChunkZ){
$oldChunkHash = World::chunkHash($oldChunkX, $oldChunkZ);
if(isset($this->entitiesByChunk[$oldChunkHash][$entity->getId()])){
- unset($this->entitiesByChunk[$oldChunkHash][$entity->getId()]);
- if(count($this->entitiesByChunk[$oldChunkHash]) === 0){
+ if(count($this->entitiesByChunk[$oldChunkHash]) === 1){
unset($this->entitiesByChunk[$oldChunkHash]);
+ }else{
+ unset($this->entitiesByChunk[$oldChunkHash][$entity->getId()]);
}
}
@@ -2727,10 +2855,13 @@ public function loadChunk(int $x, int $z) : ?Chunk{
}
$this->chunks[$chunkHash] = $chunk;
unset($this->blockCache[$chunkHash]);
+ unset($this->blockCollisionBoxCache[$chunkHash]);
$this->initChunk($x, $z, $chunkData);
- (new ChunkLoadEvent($this, $x, $z, $this->chunks[$chunkHash], false))->call();
+ if(ChunkLoadEvent::hasHandlers()){
+ (new ChunkLoadEvent($this, $x, $z, $this->chunks[$chunkHash], false))->call();
+ }
if(!$this->isChunkInUse($x, $z)){
$this->logger->debug("Newly loaded chunk $x $z has no loaders registered, will be unloaded at next available opportunity");
@@ -2843,12 +2974,14 @@ public function unloadChunk(int $x, int $z, bool $safe = true, bool $trySave = t
$chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== null){
- $ev = new ChunkUnloadEvent($this, $x, $z, $chunk);
- $ev->call();
- if($ev->isCancelled()){
- $this->timings->doChunkUnload->stopTiming();
+ if(ChunkUnloadEvent::hasHandlers()){
+ $ev = new ChunkUnloadEvent($this, $x, $z, $chunk);
+ $ev->call();
+ if($ev->isCancelled()){
+ $this->timings->doChunkUnload->stopTiming();
- return false;
+ return false;
+ }
}
if($trySave && $this->getAutoSave()){
@@ -2881,6 +3014,7 @@ public function unloadChunk(int $x, int $z, bool $safe = true, bool $trySave = t
unset($this->chunks[$chunkHash]);
unset($this->blockCache[$chunkHash]);
+ unset($this->blockCollisionBoxCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
unset($this->registeredTickingChunks[$chunkHash]);
$this->markTickingChunkForRecheck($x, $z);
@@ -2945,7 +3079,7 @@ function() use ($resolver) : void{
* @throws WorldException if the terrain is not generated
*/
public function getSafeSpawn(?Vector3 $spawn = null) : Position{
- if(!($spawn instanceof Vector3) || $spawn->y < 1){
+ if(!($spawn instanceof Vector3) || $spawn->y <= $this->minY){
$spawn = $this->getSpawnLocation();
}
@@ -3069,6 +3203,7 @@ public function setDifficulty(int $difficulty) : void{
if($difficulty < 0 || $difficulty > 3){
throw new \InvalidArgumentException("Invalid difficulty level $difficulty");
}
+ (new WorldDifficultyChangeEvent($this, $this->getDifficulty(), $difficulty))->call();
$this->provider->getWorldData()->setDifficulty($difficulty);
foreach($this->players as $player){
@@ -3303,7 +3438,9 @@ private function generateChunkCallback(ChunkLockId $chunkLockId, int $x, int $z,
}
if(($oldChunk === null || !$oldChunk->isPopulated()) && $chunk->isPopulated()){
- (new ChunkPopulateEvent($this, $x, $z, $chunk))->call();
+ if(ChunkPopulateEvent::hasHandlers()){
+ (new ChunkPopulateEvent($this, $x, $z, $chunk))->call();
+ }
foreach($this->getChunkListeners($x, $z) as $listener){
$listener->onChunkPopulated($x, $z, $chunk);
diff --git a/src/world/WorldManager.php b/src/world/WorldManager.php
index bd968f9042c..ff603a2dfcb 100644
--- a/src/world/WorldManager.php
+++ b/src/world/WorldManager.php
@@ -129,10 +129,6 @@ public function unloadWorld(World $world, bool $forceUnload = false) : bool{
}
$ev = new WorldUnloadEvent($world);
- if($world === $this->defaultWorld && !$forceUnload){
- $ev->cancel();
- }
-
$ev->call();
if(!$forceUnload && $ev->isCancelled()){
diff --git a/src/world/WorldTimings.php b/src/world/WorldTimings.php
index 5c1a5601123..9a43828c2ad 100644
--- a/src/world/WorldTimings.php
+++ b/src/world/WorldTimings.php
@@ -23,7 +23,6 @@
namespace pocketmine\world;
-use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
class WorldTimings{
@@ -34,6 +33,7 @@ class WorldTimings{
public TimingsHandler $doChunkUnload;
public TimingsHandler $scheduledBlockUpdates;
+ public TimingsHandler $neighbourBlockUpdates;
public TimingsHandler $randomChunkUpdates;
public TimingsHandler $randomChunkUpdatesChunkSelection;
public TimingsHandler $doChunkGC;
@@ -65,7 +65,7 @@ class WorldTimings{
private static function newTimer(string $worldName, string $timerName) : TimingsHandler{
$aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table
- return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN);
+ return new TimingsHandler("$worldName - $timerName", $aggregator);
}
public function __construct(World $world){
@@ -77,6 +77,7 @@ public function __construct(World $world){
$this->doChunkUnload = self::newTimer($name, "Unload Chunks");
$this->scheduledBlockUpdates = self::newTimer($name, "Scheduled Block Updates");
+ $this->neighbourBlockUpdates = self::newTimer($name, "Neighbour Block Updates");
$this->randomChunkUpdates = self::newTimer($name, "Random Chunk Updates");
$this->randomChunkUpdatesChunkSelection = self::newTimer($name, "Random Chunk Updates - Chunk Selection");
$this->doChunkGC = self::newTimer($name, "Garbage Collection");
diff --git a/src/world/biome/BiomeRegistry.php b/src/world/biome/BiomeRegistry.php
index 64818420952..1af60fcd592 100644
--- a/src/world/biome/BiomeRegistry.php
+++ b/src/world/biome/BiomeRegistry.php
@@ -54,7 +54,7 @@ public function __construct(){
$this->register(BiomeIds::EXTREME_HILLS_EDGE, new SmallMountainsBiome());
- $this->register(BiomeIds::BIRCH_FOREST, new ForestBiome(TreeType::BIRCH()));
+ $this->register(BiomeIds::BIRCH_FOREST, new ForestBiome(TreeType::BIRCH));
}
public function register(int $id, Biome $biome) : void{
diff --git a/src/world/biome/ForestBiome.php b/src/world/biome/ForestBiome.php
index 8f80cb474af..94d3e567ccf 100644
--- a/src/world/biome/ForestBiome.php
+++ b/src/world/biome/ForestBiome.php
@@ -33,7 +33,7 @@ class ForestBiome extends GrassyBiome{
public function __construct(?TreeType $type = null){
parent::__construct();
- $this->type = $type ?? TreeType::OAK();
+ $this->type = $type ?? TreeType::OAK;
$trees = new Tree($type);
$trees->setBaseAmount(5);
@@ -46,7 +46,7 @@ public function __construct(?TreeType $type = null){
$this->setElevation(63, 81);
- if($this->type->equals(TreeType::BIRCH())){
+ if($this->type === TreeType::BIRCH){
$this->temperature = 0.6;
$this->rainfall = 0.5;
}else{
diff --git a/src/world/biome/TaigaBiome.php b/src/world/biome/TaigaBiome.php
index cd68c974867..979ca66ec25 100644
--- a/src/world/biome/TaigaBiome.php
+++ b/src/world/biome/TaigaBiome.php
@@ -32,7 +32,7 @@ class TaigaBiome extends SnowyBiome{
public function __construct(){
parent::__construct();
- $trees = new Tree(TreeType::SPRUCE());
+ $trees = new Tree(TreeType::SPRUCE);
$trees->setBaseAmount(10);
$this->addPopulator($trees);
diff --git a/src/world/format/io/BaseWorldProvider.php b/src/world/format/io/BaseWorldProvider.php
index a4d8651a67a..f863fdf747b 100644
--- a/src/world/format/io/BaseWorldProvider.php
+++ b/src/world/format/io/BaseWorldProvider.php
@@ -31,7 +31,9 @@
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
use pocketmine\world\format\PalettedBlockArray;
use pocketmine\world\WorldException;
+use function count;
use function file_exists;
+use function implode;
abstract class BaseWorldProvider implements WorldProvider{
protected WorldData $worldData;
@@ -62,27 +64,35 @@ public function __construct(
*/
abstract protected function loadLevelData() : WorldData;
- private function translatePalette(PalettedBlockArray $blockArray) : PalettedBlockArray{
+ private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{
$palette = $blockArray->getPalette();
$newPalette = [];
+ $blockDecodeErrors = [];
foreach($palette as $k => $legacyIdMeta){
//TODO: remember data for unknown states so we can implement them later
+ $id = $legacyIdMeta >> 4;
+ $meta = $legacyIdMeta & 0xf;
try{
- $newStateData = $this->blockDataUpgrader->upgradeIntIdMeta($legacyIdMeta >> 4, $legacyIdMeta & 0xf);
+ $newStateData = $this->blockDataUpgrader->upgradeIntIdMeta($id, $meta);
}catch(BlockStateDeserializeException $e){
+ $blockDecodeErrors[] = "Palette offset $k / Failed to upgrade legacy ID/meta $id:$meta: " . $e->getMessage();
$newStateData = GlobalBlockStateHandlers::getUnknownBlockStateData();
}
try{
$newPalette[$k] = $this->blockStateDeserializer->deserialize($newStateData);
- }catch(BlockStateDeserializeException){
- //TODO: this needs to be logged
- //TODO: maybe we can remember unknown states for later saving instead of discarding them and destroying maps...
+ }catch(BlockStateDeserializeException $e){
+ //this should never happen anyway - if the upgrader returned an invalid state, we have bigger problems
+ $blockDecodeErrors[] = "Palette offset $k / Failed to deserialize upgraded state $id:$meta: " . $e->getMessage();
$newPalette[$k] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
}
}
+ if(count($blockDecodeErrors) > 0){
+ $logger->error("Errors decoding/upgrading blocks:\n - " . implode("\n - ", $blockDecodeErrors));
+ }
+
//TODO: this is sub-optimal since it reallocates the offset table multiple times
return PalettedBlockArray::fromData(
$blockArray->getBitsPerBlock(),
@@ -91,16 +101,16 @@ private function translatePalette(PalettedBlockArray $blockArray) : PalettedBloc
);
}
- protected function palettizeLegacySubChunkXZY(string $idArray, string $metaArray) : PalettedBlockArray{
- return $this->translatePalette(SubChunkConverter::convertSubChunkXZY($idArray, $metaArray));
+ protected function palettizeLegacySubChunkXZY(string $idArray, string $metaArray, \Logger $logger) : PalettedBlockArray{
+ return $this->translatePalette(SubChunkConverter::convertSubChunkXZY($idArray, $metaArray), $logger);
}
- protected function palettizeLegacySubChunkYZX(string $idArray, string $metaArray) : PalettedBlockArray{
- return $this->translatePalette(SubChunkConverter::convertSubChunkYZX($idArray, $metaArray));
+ protected function palettizeLegacySubChunkYZX(string $idArray, string $metaArray, \Logger $logger) : PalettedBlockArray{
+ return $this->translatePalette(SubChunkConverter::convertSubChunkYZX($idArray, $metaArray), $logger);
}
- protected function palettizeLegacySubChunkFromColumn(string $idArray, string $metaArray, int $yOffset) : PalettedBlockArray{
- return $this->translatePalette(SubChunkConverter::convertSubChunkFromLegacyColumn($idArray, $metaArray, $yOffset));
+ protected function palettizeLegacySubChunkFromColumn(string $idArray, string $metaArray, int $yOffset, \Logger $logger) : PalettedBlockArray{
+ return $this->translatePalette(SubChunkConverter::convertSubChunkFromLegacyColumn($idArray, $metaArray, $yOffset), $logger);
}
public function getPath() : string{
diff --git a/src/world/format/io/GlobalBlockStateHandlers.php b/src/world/format/io/GlobalBlockStateHandlers.php
index ed162c8cae3..c1d3934cf37 100644
--- a/src/world/format/io/GlobalBlockStateHandlers.php
+++ b/src/world/format/io/GlobalBlockStateHandlers.php
@@ -70,7 +70,7 @@ public static function getUpgrader() : BlockDataUpgrader{
BlockIdMetaUpgrader::loadFromString(
Filesystem::fileGetContents(Path::join(
BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH,
- '1.12.0_to_1.18.10_blockstate_map.bin'
+ 'id_meta_to_nbt/1.12.0.bin'
)),
LegacyBlockIdToStringIdMap::getInstance(),
$blockStateUpgrader
diff --git a/src/world/format/io/GlobalItemDataHandlers.php b/src/world/format/io/GlobalItemDataHandlers.php
index ea5568c7cbe..f622584784a 100644
--- a/src/world/format/io/GlobalItemDataHandlers.php
+++ b/src/world/format/io/GlobalItemDataHandlers.php
@@ -23,6 +23,7 @@
namespace pocketmine\world\format\io;
+use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\ItemDeserializer;
use pocketmine\data\bedrock\item\ItemSerializer;
use pocketmine\data\bedrock\item\upgrade\ItemDataUpgrader;
@@ -30,6 +31,7 @@
use pocketmine\data\bedrock\item\upgrade\ItemIdMetaUpgradeSchemaUtils;
use pocketmine\data\bedrock\item\upgrade\LegacyItemIdToStringIdMap;
use pocketmine\data\bedrock\item\upgrade\R12ItemIdToBlockIdMap;
+use pocketmine\network\mcpe\convert\TypeConverter;
use Symfony\Component\Filesystem\Path;
use const PHP_INT_MAX;
use const pocketmine\BEDROCK_ITEM_UPGRADE_SCHEMA_PATH;
@@ -54,7 +56,9 @@ public static function getUpgrader() : ItemDataUpgrader{
new ItemIdMetaUpgrader(ItemIdMetaUpgradeSchemaUtils::loadSchemas(Path::join(BEDROCK_ITEM_UPGRADE_SCHEMA_PATH, 'id_meta_upgrade_schema'), PHP_INT_MAX)),
LegacyItemIdToStringIdMap::getInstance(),
R12ItemIdToBlockIdMap::getInstance(),
- GlobalBlockStateHandlers::getUpgrader()
+ GlobalBlockStateHandlers::getUpgrader(),
+ BlockItemIdMap::getInstance(),
+ TypeConverter::getInstance()->getBlockTranslator()->getBlockStateDictionary()
);
}
}
diff --git a/src/world/format/io/WorldProviderManager.php b/src/world/format/io/WorldProviderManager.php
index 8a30bcb5757..1767c539a78 100644
--- a/src/world/format/io/WorldProviderManager.php
+++ b/src/world/format/io/WorldProviderManager.php
@@ -41,13 +41,13 @@ final class WorldProviderManager{
private WritableWorldProviderManagerEntry $default;
public function __construct(){
- $leveldb = new WritableWorldProviderManagerEntry(\Closure::fromCallable([LevelDB::class, 'isValid']), fn(string $path, \Logger $logger) => new LevelDB($path, $logger), \Closure::fromCallable([LevelDB::class, 'generate']));
+ $leveldb = new WritableWorldProviderManagerEntry(LevelDB::isValid(...), fn(string $path, \Logger $logger) => new LevelDB($path, $logger), LevelDB::generate(...));
$this->default = $leveldb;
$this->addProvider($leveldb, "leveldb");
- $this->addProvider(new ReadOnlyWorldProviderManagerEntry(\Closure::fromCallable([Anvil::class, 'isValid']), fn(string $path, \Logger $logger) => new Anvil($path, $logger)), "anvil");
- $this->addProvider(new ReadOnlyWorldProviderManagerEntry(\Closure::fromCallable([McRegion::class, 'isValid']), fn(string $path, \Logger $logger) => new McRegion($path, $logger)), "mcregion");
- $this->addProvider(new ReadOnlyWorldProviderManagerEntry(\Closure::fromCallable([PMAnvil::class, 'isValid']), fn(string $path, \Logger $logger) => new PMAnvil($path, $logger)), "pmanvil");
+ $this->addProvider(new ReadOnlyWorldProviderManagerEntry(Anvil::isValid(...), fn(string $path, \Logger $logger) => new Anvil($path, $logger)), "anvil");
+ $this->addProvider(new ReadOnlyWorldProviderManagerEntry(McRegion::isValid(...), fn(string $path, \Logger $logger) => new McRegion($path, $logger)), "mcregion");
+ $this->addProvider(new ReadOnlyWorldProviderManagerEntry(PMAnvil::isValid(...), fn(string $path, \Logger $logger) => new PMAnvil($path, $logger)), "pmanvil");
}
/**
diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php
index faef3ab21a6..b2e079124af 100644
--- a/src/world/format/io/data/BedrockWorldData.php
+++ b/src/world/format/io/data/BedrockWorldData.php
@@ -43,6 +43,7 @@
use Symfony\Component\Filesystem\Path;
use function array_map;
use function file_put_contents;
+use function sprintf;
use function strlen;
use function substr;
use function time;
@@ -50,12 +51,12 @@
class BedrockWorldData extends BaseNbtWorldData{
public const CURRENT_STORAGE_VERSION = 10;
- public const CURRENT_STORAGE_NETWORK_VERSION = 582;
+ public const CURRENT_STORAGE_NETWORK_VERSION = 748;
public const CURRENT_CLIENT_VERSION_TARGET = [
1, //major
- 19, //minor
- 80, //patch
- 0, //revision
+ 21, //minor
+ 40, //patch
+ 1, //revision
0 //is beta
];
@@ -154,12 +155,18 @@ protected function load() : CompoundTag{
}
$version = $worldData->getInt(self::TAG_STORAGE_VERSION, Limits::INT32_MAX);
+ if($version === Limits::INT32_MAX){
+ throw new CorruptedWorldException(sprintf("Missing '%s' tag in level.dat", self::TAG_STORAGE_VERSION));
+ }
if($version > self::CURRENT_STORAGE_VERSION){
throw new UnsupportedWorldFormatException("LevelDB world format version $version is currently unsupported");
}
//StorageVersion is rarely updated - instead, the game relies on the NetworkVersion tag, which is synced with
//the network protocol version for that version.
$protocolVersion = $worldData->getInt(self::TAG_NETWORK_VERSION, Limits::INT32_MAX);
+ if($protocolVersion === Limits::INT32_MAX){
+ throw new CorruptedWorldException(sprintf("Missing '%s' tag in level.dat", self::TAG_NETWORK_VERSION));
+ }
if($protocolVersion > self::CURRENT_STORAGE_NETWORK_VERSION){
throw new UnsupportedWorldFormatException("LevelDB world protocol version $protocolVersion is currently unsupported");
}
diff --git a/src/world/format/io/leveldb/ChunkVersion.php b/src/world/format/io/leveldb/ChunkVersion.php
index e399570de97..a69e4ff4598 100644
--- a/src/world/format/io/leveldb/ChunkVersion.php
+++ b/src/world/format/io/leveldb/ChunkVersion.php
@@ -71,4 +71,5 @@ private function __construct(){
public const v1_18_0_24_unused = 38;
public const v1_18_0_25_beta = 39;
public const v1_18_30 = 40;
+ public const v1_21_40 = 41;
}
diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php
index a42d8d53ebe..dda489d3170 100644
--- a/src/world/format/io/leveldb/LevelDB.php
+++ b/src/world/format/io/leveldb/LevelDB.php
@@ -26,7 +26,9 @@
use pocketmine\block\Block;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
+use pocketmine\data\bedrock\block\convert\UnsupportedBlockStateException;
use pocketmine\nbt\LittleEndianNbtSerializer;
+use pocketmine\nbt\NBT;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
@@ -57,6 +59,7 @@
use function defined;
use function extension_loaded;
use function file_exists;
+use function implode;
use function is_dir;
use function mkdir;
use function ord;
@@ -75,7 +78,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
protected const ENTRY_FLAT_WORLD_LAYERS = "game_flatworldlayers";
- protected const CURRENT_LEVEL_CHUNK_VERSION = ChunkVersion::v1_18_30;
+ protected const CURRENT_LEVEL_CHUNK_VERSION = ChunkVersion::v1_21_40;
protected const CURRENT_LEVEL_SUBCHUNK_VERSION = SubChunkVersion::PALETTED_MULTI;
private const CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET = 4;
@@ -155,7 +158,35 @@ protected function deserializeBlockPalette(BinaryStream $stream, \Logger $logger
$nbt = new LittleEndianNbtSerializer();
$palette = [];
- $paletteSize = $bitsPerBlock === 0 ? 1 : $stream->getLInt();
+ if($bitsPerBlock === 0){
+ $paletteSize = 1;
+ /*
+ * Due to code copy-paste in a public plugin, some PM4 worlds have 0 bpb palettes with a length prefix.
+ * This is invalid and does not happen in vanilla.
+ * These palettes were accepted by PM4 despite being invalid, but PM5 considered them corrupt, causing loss
+ * of data. Since many users were affected by this, a workaround is therefore necessary to allow PM5 to read
+ * these worlds without data loss.
+ *
+ * References:
+ * - https://github.com/Refaltor77/CustomItemAPI/issues/68
+ * - https://github.com/pmmp/PocketMine-MP/issues/5911
+ */
+ $offset = $stream->getOffset();
+ $byte1 = $stream->getByte();
+ $stream->setOffset($offset); //reset offset
+
+ if($byte1 !== NBT::TAG_Compound){ //normally the first byte would be the NBT of the blockstate
+ $susLength = $stream->getLInt();
+ if($susLength !== 1){ //make sure the data isn't complete garbage
+ throw new CorruptedChunkException("CustomItemAPI borked 0 bpb palette should always have a length of 1");
+ }
+ $logger->error("Unexpected palette size for 0 bpb palette");
+ }
+ }else{
+ $paletteSize = $stream->getLInt();
+ }
+
+ $blockDecodeErrors = [];
for($i = 0; $i < $paletteSize; ++$i){
try{
@@ -172,18 +203,25 @@ protected function deserializeBlockPalette(BinaryStream $stream, \Logger $logger
$blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt);
}catch(BlockStateDeserializeException $e){
//while not ideal, this is not a fatal error
- $logger->error("Failed to upgrade blockstate: " . $e->getMessage() . " offset $i in palette, blockstate NBT: " . $blockStateNbt->toString());
+ $blockDecodeErrors[] = "Palette offset $i / Upgrade error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
continue;
}
try{
$palette[] = $this->blockStateDeserializer->deserialize($blockStateData);
+ }catch(UnsupportedBlockStateException $e){
+ $blockDecodeErrors[] = "Palette offset $i / " . $e->getMessage();
+ $palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
}catch(BlockStateDeserializeException $e){
- $logger->error("Failed to deserialize blockstate: " . $e->getMessage() . " offset $i in palette, blockstate NBT: " . $blockStateNbt->toString());
+ $blockDecodeErrors[] = "Palette offset $i / Deserialize error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
}
}
+ if(count($blockDecodeErrors) > 0){
+ $logger->error("Errors decoding blocks:\n - " . implode("\n - ", $blockDecodeErrors));
+ }
+
//TODO: exceptions
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
}
@@ -276,6 +314,10 @@ private static function deserialize3dBiomes(BinaryStream $stream, int $chunkVers
$previous = $decoded;
if($nextIndex <= Chunk::MAX_SUBCHUNK_INDEX){ //older versions wrote additional superfluous biome palettes
$result[$nextIndex++] = $decoded;
+ }elseif($stream->feof()){
+ //not enough padding biome arrays for the given version - this is non-critical since we discard the excess anyway, but this should be logged
+ $logger->error("Wrong number of 3D biome palettes for this chunk version: expected $expectedCount, but got " . ($i + 1) . " - this is not a problem, but may indicate a corrupted chunk");
+ break;
}
}catch(BinaryDataException $e){
throw new CorruptedChunkException("Failed to deserialize biome palette $i: " . $e->getMessage(), 0, $e);
@@ -412,7 +454,7 @@ private function deserializeLegacyTerrainData(string $index, int $chunkVersion,
$subChunks = [];
for($yy = 0; $yy < 8; ++$yy){
- $storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy)];
+ $storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy, new \PrefixedLogger($logger, "Subchunk y=$yy"))];
if(isset($convertedLegacyExtraData[$yy])){
$storages[] = $convertedLegacyExtraData[$yy];
}
@@ -451,7 +493,7 @@ private function deserializeNonPalettedSubChunkData(BinaryStream $binaryStream,
}
}
- $storages = [$this->palettizeLegacySubChunkXZY($blocks, $blockData)];
+ $storages = [$this->palettizeLegacySubChunkXZY($blocks, $blockData, $logger)];
if($convertedLegacyExtraData !== null){
$storages[] = $convertedLegacyExtraData;
}
@@ -612,6 +654,8 @@ public function loadChunk(int $chunkX, int $chunkZ) : ?LoadedChunkData{
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
switch($chunkVersion){
+ case ChunkVersion::v1_21_40:
+ //TODO: BiomeStates became shorts instead of bytes
case ChunkVersion::v1_18_30:
case ChunkVersion::v1_18_0_25_beta:
case ChunkVersion::v1_18_0_24_unused:
diff --git a/src/world/format/io/region/Anvil.php b/src/world/format/io/region/Anvil.php
index abefc9f2557..2c14b54e87a 100644
--- a/src/world/format/io/region/Anvil.php
+++ b/src/world/format/io/region/Anvil.php
@@ -31,10 +31,11 @@
class Anvil extends RegionWorldProvider{
use LegacyAnvilChunkTrait;
- protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk{
+ protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk{
return new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkYZX(
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
- self::readFixedSizeByteArray($subChunk, "Data", 2048)
+ self::readFixedSizeByteArray($subChunk, "Data", 2048),
+ $logger
)], $biomes3d);
//ignore legacy light information
}
diff --git a/src/world/format/io/region/LegacyAnvilChunkTrait.php b/src/world/format/io/region/LegacyAnvilChunkTrait.php
index ba97d43b5e8..6e2f4c8f8f1 100644
--- a/src/world/format/io/region/LegacyAnvilChunkTrait.php
+++ b/src/world/format/io/region/LegacyAnvilChunkTrait.php
@@ -54,7 +54,7 @@ trait LegacyAnvilChunkTrait{
/**
* @throws CorruptedChunkException
*/
- protected function deserializeChunk(string $data) : ?LoadedChunkData{
+ protected function deserializeChunk(string $data, \Logger $logger) : ?LoadedChunkData{
$decompressed = @zlib_decode($data);
if($decompressed === false){
throw new CorruptedChunkException("Failed to decompress chunk NBT");
@@ -90,7 +90,8 @@ protected function deserializeChunk(string $data) : ?LoadedChunkData{
$subChunksTag = $chunk->getListTag("Sections") ?? [];
foreach($subChunksTag as $subChunk){
if($subChunk instanceof CompoundTag){
- $subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk, clone $biomes3d);
+ $y = $subChunk->getByte("Y");
+ $subChunks[$y] = $this->deserializeSubChunk($subChunk, clone $biomes3d, new \PrefixedLogger($logger, "Subchunk y=$y"));
}
}
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
@@ -111,6 +112,6 @@ protected function deserializeChunk(string $data) : ?LoadedChunkData{
);
}
- abstract protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk;
+ abstract protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk;
}
diff --git a/src/world/format/io/region/McRegion.php b/src/world/format/io/region/McRegion.php
index 878c84df474..ad6a2d7f2d2 100644
--- a/src/world/format/io/region/McRegion.php
+++ b/src/world/format/io/region/McRegion.php
@@ -46,7 +46,7 @@ class McRegion extends RegionWorldProvider{
/**
* @throws CorruptedChunkException
*/
- protected function deserializeChunk(string $data) : ?LoadedChunkData{
+ protected function deserializeChunk(string $data, \Logger $logger) : ?LoadedChunkData{
$decompressed = @zlib_decode($data);
if($decompressed === false){
throw new CorruptedChunkException("Failed to decompress chunk NBT");
@@ -90,7 +90,12 @@ protected function deserializeChunk(string $data) : ?LoadedChunkData{
$fullData = self::readFixedSizeByteArray($chunk, "Data", 16384);
for($y = 0; $y < 8; ++$y){
- $subChunks[$y] = new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $y)], clone $biomes3d);
+ $subChunks[$y] = new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkFromColumn(
+ $fullIds,
+ $fullData,
+ $y,
+ new \PrefixedLogger($logger, "Subchunk y=$y"),
+ )], clone $biomes3d);
}
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
if(!isset($subChunks[$y])){
diff --git a/src/world/format/io/region/PMAnvil.php b/src/world/format/io/region/PMAnvil.php
index 41dc0fa80d5..4fb46312422 100644
--- a/src/world/format/io/region/PMAnvil.php
+++ b/src/world/format/io/region/PMAnvil.php
@@ -35,10 +35,11 @@
class PMAnvil extends RegionWorldProvider{
use LegacyAnvilChunkTrait;
- protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk{
+ protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk{
return new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkXZY(
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
- self::readFixedSizeByteArray($subChunk, "Data", 2048)
+ self::readFixedSizeByteArray($subChunk, "Data", 2048),
+ $logger
)], $biomes3d);
}
diff --git a/src/world/format/io/region/RegionLocationTableEntry.php b/src/world/format/io/region/RegionLocationTableEntry.php
index abb92accf24..ad0b1ce487e 100644
--- a/src/world/format/io/region/RegionLocationTableEntry.php
+++ b/src/world/format/io/region/RegionLocationTableEntry.php
@@ -27,6 +27,7 @@
class RegionLocationTableEntry{
private int $firstSector;
+ /** @phpstan-var positive-int */
private int $sectorCount;
private int $timestamp;
@@ -61,6 +62,9 @@ public function getUsedSectors() : array{
return range($this->getFirstSector(), $this->getLastSector());
}
+ /**
+ * @phpstan-return positive-int
+ */
public function getSectorCount() : int{
return $this->sectorCount;
}
diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php
index a4a3055c63e..1feca61be1d 100644
--- a/src/world/format/io/region/RegionWorldProvider.php
+++ b/src/world/format/io/region/RegionWorldProvider.php
@@ -147,7 +147,7 @@ public function close() : void{
/**
* @throws CorruptedChunkException
*/
- abstract protected function deserializeChunk(string $data) : ?LoadedChunkData;
+ abstract protected function deserializeChunk(string $data, \Logger $logger) : ?LoadedChunkData;
/**
* @return CompoundTag[]
@@ -200,7 +200,7 @@ public function loadChunk(int $chunkX, int $chunkZ) : ?LoadedChunkData{
$chunkData = $this->loadRegion($regionX, $regionZ)->readChunk($chunkX & 0x1f, $chunkZ & 0x1f);
if($chunkData !== null){
- return $this->deserializeChunk($chunkData);
+ return $this->deserializeChunk($chunkData, new \PrefixedLogger($this->logger, "Loading chunk x=$chunkX z=$chunkZ"));
}
return null;
diff --git a/src/world/generator/GeneratorManager.php b/src/world/generator/GeneratorManager.php
index 180450b72a1..291ea91de0f 100644
--- a/src/world/generator/GeneratorManager.php
+++ b/src/world/generator/GeneratorManager.php
@@ -52,9 +52,9 @@ public function __construct(){
}
});
$this->addGenerator(Normal::class, "normal", fn() => null);
- $this->addGenerator(Normal::class, "default", fn() => null);
- $this->addGenerator(Nether::class, "hell", fn() => null);
+ $this->addAlias("normal", "default");
$this->addGenerator(Nether::class, "nether", fn() => null);
+ $this->addAlias("nether", "hell");
}
/**
@@ -80,6 +80,22 @@ public function addGenerator(string $class, string $name, \Closure $presetValida
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator);
}
+ /**
+ * Aliases an already-registered generator name to another name. Useful if you want to map a generator name to an
+ * existing generator without having to replicate the parameters.
+ */
+ public function addAlias(string $name, string $alias) : void{
+ $name = strtolower($name);
+ $alias = strtolower($alias);
+ if(!isset($this->list[$name])){
+ throw new \InvalidArgumentException("Alias \"$name\" is not assigned");
+ }
+ if(isset($this->list[$alias])){
+ throw new \InvalidArgumentException("Alias \"$alias\" is already assigned");
+ }
+ $this->list[$alias] = $this->list[$name];
+ }
+
/**
* Returns a list of names for registered generators.
*
diff --git a/src/world/generator/noise/Noise.php b/src/world/generator/noise/Noise.php
index af9cefe14e0..d91a58350ad 100644
--- a/src/world/generator/noise/Noise.php
+++ b/src/world/generator/noise/Noise.php
@@ -208,6 +208,7 @@ public function getFastNoise1D(int $xSize, int $samplingRate, int $x, int $y, in
throw new \InvalidArgumentException("xSize % samplingRate must return 0");
}
+ /** @phpstan-var \SplFixedArray $noiseArray */
$noiseArray = new \SplFixedArray($xSize + 1);
for($xx = 0; $xx <= $xSize; $xx += $samplingRate){
@@ -217,7 +218,13 @@ public function getFastNoise1D(int $xSize, int $samplingRate, int $x, int $y, in
for($xx = 0; $xx < $xSize; ++$xx){
if($xx % $samplingRate !== 0){
$nx = (int) ($xx / $samplingRate) * $samplingRate;
- $noiseArray[$xx] = self::linearLerp($xx, $nx, $nx + $samplingRate, $noiseArray[$nx], $noiseArray[$nx + $samplingRate]);
+ $noiseArray[$xx] = self::linearLerp(
+ x: $xx,
+ x1: $nx,
+ x2: $nx + $samplingRate,
+ q0: $noiseArray[$nx],
+ q1: $noiseArray[$nx + $samplingRate]
+ );
}
}
@@ -234,6 +241,7 @@ public function getFastNoise2D(int $xSize, int $zSize, int $samplingRate, int $x
assert($xSize % $samplingRate === 0, new \InvalidArgumentException("xSize % samplingRate must return 0"));
assert($zSize % $samplingRate === 0, new \InvalidArgumentException("zSize % samplingRate must return 0"));
+ /** @phpstan-var \SplFixedArray<\SplFixedArray> $noiseArray */
$noiseArray = new \SplFixedArray($xSize + 1);
for($xx = 0; $xx <= $xSize; $xx += $samplingRate){
@@ -253,9 +261,16 @@ public function getFastNoise2D(int $xSize, int $zSize, int $samplingRate, int $x
$nx = (int) ($xx / $samplingRate) * $samplingRate;
$nz = (int) ($zz / $samplingRate) * $samplingRate;
$noiseArray[$xx][$zz] = Noise::bilinearLerp(
- $xx, $zz, $noiseArray[$nx][$nz], $noiseArray[$nx][$nz + $samplingRate],
- $noiseArray[$nx + $samplingRate][$nz], $noiseArray[$nx + $samplingRate][$nz + $samplingRate],
- $nx, $nx + $samplingRate, $nz, $nz + $samplingRate
+ x: $xx,
+ y: $zz,
+ q00: $noiseArray[$nx][$nz],
+ q01: $noiseArray[$nx][$nz + $samplingRate],
+ q10: $noiseArray[$nx + $samplingRate][$nz],
+ q11: $noiseArray[$nx + $samplingRate][$nz + $samplingRate],
+ x1: $nx,
+ x2: $nx + $samplingRate,
+ y1: $nz,
+ y2: $nz + $samplingRate
);
}
}
diff --git a/src/world/generator/object/AcaciaTree.php b/src/world/generator/object/AcaciaTree.php
new file mode 100644
index 00000000000..75e58a9b357
--- /dev/null
+++ b/src/world/generator/object/AcaciaTree.php
@@ -0,0 +1,132 @@
+nextRange(0, 2) + $random->nextRange(0, 2);
+ }
+
+ protected function placeTrunk(int $x, int $y, int $z, Random $random, int $trunkHeight, BlockTransaction $transaction) : void{
+ // The base dirt block
+ $transaction->addBlockAt($x, $y - 1, $z, VanillaBlocks::DIRT());
+
+ $firstBranchHeight = $trunkHeight - 1 - $random->nextRange(0, 3);
+
+ for($yy = 0; $yy <= $firstBranchHeight; ++$yy){
+ $transaction->addBlockAt($x, $y + $yy, $z, $this->trunkBlock);
+ }
+
+ $mainBranchFacing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)];
+
+ //this branch may grow a second trunk if the diagonal length is less than the max length
+ $this->mainBranchTip = $this->placeBranch(
+ $transaction,
+ new Vector3($x, $y + $firstBranchHeight, $z),
+ $mainBranchFacing,
+ $random->nextRange(1, 3),
+ $trunkHeight - $firstBranchHeight
+ );
+
+ $secondBranchFacing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)];
+ if($secondBranchFacing !== $mainBranchFacing){
+ $secondBranchLength = $random->nextRange(1, 3);
+ $this->secondBranchTip = $this->placeBranch(
+ $transaction,
+ new Vector3($x, $y + ($firstBranchHeight - $random->nextRange(0, 1)), $z),
+ $secondBranchFacing,
+ $secondBranchLength,
+ $secondBranchLength //the secondary branch may not form a second trunk
+ );
+ }
+ }
+
+ protected function placeBranch(BlockTransaction $transaction, Vector3 $start, int $branchFacing, int $maxDiagonal, int $length) : Vector3{
+ $diagonalPlaced = 0;
+
+ $nextBlockPos = $start;
+ for($yy = 0; $yy < $length; $yy++){
+ $nextBlockPos = $nextBlockPos->up();
+ if($diagonalPlaced < $maxDiagonal){
+ $nextBlockPos = $nextBlockPos->getSide($branchFacing);
+ $diagonalPlaced++;
+ }
+ $transaction->addBlock($nextBlockPos, $this->trunkBlock);
+ }
+
+ return $nextBlockPos;
+ }
+
+ protected function placeCanopyLayer(BlockTransaction $transaction, Vector3 $center, int $radius, int $maxTaxicabDistance) : void{
+ $centerX = $center->getFloorX();
+ $centerY = $center->getFloorY();
+ $centerZ = $center->getFloorZ();
+
+ for($x = $centerX - $radius; $x <= $centerX + $radius; ++$x){
+ for($z = $centerZ - $radius; $z <= $centerZ + $radius; ++$z){
+ if(
+ abs($x - $centerX) + abs($z - $centerZ) <= $maxTaxicabDistance &&
+ $transaction->fetchBlockAt($x, $centerY, $z)->canBeReplaced()
+ ){
+ $transaction->addBlockAt($x, $centerY, $z, $this->leafBlock);
+ }
+ }
+ }
+ }
+
+ protected function placeCanopy(int $x, int $y, int $z, Random $random, BlockTransaction $transaction) : void{
+ $mainBranchTip = $this->mainBranchTip;
+ if($mainBranchTip !== null){
+ $this->placeCanopyLayer($transaction, $mainBranchTip, radius: 3, maxTaxicabDistance: 5);
+ $this->placeCanopyLayer($transaction, $mainBranchTip->up(), radius: 2, maxTaxicabDistance: 2);
+ }
+ $secondBranchTip = $this->secondBranchTip;
+ if($secondBranchTip !== null){
+ $this->placeCanopyLayer($transaction, $secondBranchTip, radius: 2, maxTaxicabDistance: 3);
+ $this->placeCanopyLayer($transaction, $secondBranchTip->up(), radius: 1, maxTaxicabDistance: 2);
+ }
+ }
+}
diff --git a/src/world/generator/object/TreeFactory.php b/src/world/generator/object/TreeFactory.php
index ecab73c7978..d1f3dbebb35 100644
--- a/src/world/generator/object/TreeFactory.php
+++ b/src/world/generator/object/TreeFactory.php
@@ -31,25 +31,13 @@ final class TreeFactory{
* @param TreeType|null $type default oak
*/
public static function get(Random $random, ?TreeType $type = null) : ?Tree{
- $type = $type ?? TreeType::OAK();
- if($type->equals(TreeType::SPRUCE())){
- return new SpruceTree();
- }elseif($type->equals(TreeType::BIRCH())){
- if($random->nextBoundedInt(39) === 0){
- return new BirchTree(true);
- }else{
- return new BirchTree();
- }
- }elseif($type->equals(TreeType::JUNGLE())){
- return new JungleTree();
- }elseif($type->equals(TreeType::OAK())){ //default
- return new OakTree();
- /*if($random->nextRange(0, 9) === 0){
- $tree = new BigTree();
- }else{*/
-
- //}
- }
- return null;
+ return match($type){
+ null, TreeType::OAK => new OakTree(), //TODO: big oak has a 1/10 chance
+ TreeType::SPRUCE => new SpruceTree(),
+ TreeType::JUNGLE => new JungleTree(),
+ TreeType::ACACIA => new AcaciaTree(),
+ TreeType::BIRCH => new BirchTree($random->nextBoundedInt(39) === 0),
+ default => null,
+ };
}
}
diff --git a/src/world/generator/object/TreeType.php b/src/world/generator/object/TreeType.php
index 1e8bf56e9a9..d4d62946a5a 100644
--- a/src/world/generator/object/TreeType.php
+++ b/src/world/generator/object/TreeType.php
@@ -23,13 +23,11 @@
namespace pocketmine\world\generator\object;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static TreeType ACACIA()
* @method static TreeType BIRCH()
@@ -38,34 +36,27 @@
* @method static TreeType OAK()
* @method static TreeType SPRUCE()
*/
-final class TreeType{
- use EnumTrait {
- register as Enum_register;
- __construct as Enum___construct;
- }
-
- protected static function setup() : void{
- self::registerAll(
- new TreeType("oak", "Oak"),
- new TreeType("spruce", "Spruce"),
- new TreeType("birch", "Birch"),
- new TreeType("jungle", "Jungle"),
- new TreeType("acacia", "Acacia"),
- new TreeType("dark_oak", "Dark Oak"),
- //TODO: cherry blossom, mangrove, azalea
- //TODO: do crimson and warped "trees" belong here? I'm not sure if they're actually trees or just fungi
- //TODO: perhaps huge mushrooms should be here too???
- );
- }
-
- private function __construct(
- string $enumName,
- private string $displayName
- ){
- $this->Enum___construct($enumName);
- }
+enum TreeType{
+ use LegacyEnumShimTrait;
+
+ case OAK;
+ case SPRUCE;
+ case BIRCH;
+ case JUNGLE;
+ case ACACIA;
+ case DARK_OAK;
+ //TODO: cherry blossom, mangrove, azalea
+ //TODO: do crimson and warped "trees" belong here? I'm not sure if they're actually trees or just fungi
+ //TODO: perhaps huge mushrooms should be here too???
public function getDisplayName() : string{
- return $this->displayName;
+ return match($this){
+ self::OAK => "Oak",
+ self::SPRUCE => "Spruce",
+ self::BIRCH => "Birch",
+ self::JUNGLE => "Jungle",
+ self::ACACIA => "Acacia",
+ self::DARK_OAK => "Dark Oak",
+ };
}
}
diff --git a/src/world/generator/populator/Tree.php b/src/world/generator/populator/Tree.php
index 3e5aef0a777..e470c46f0bd 100644
--- a/src/world/generator/populator/Tree.php
+++ b/src/world/generator/populator/Tree.php
@@ -40,7 +40,7 @@ class Tree implements Populator{
* @param TreeType|null $type default oak
*/
public function __construct(?TreeType $type = null){
- $this->type = $type ?? TreeType::OAK();
+ $this->type = $type ?? TreeType::OAK;
}
public function setRandomAmount(int $amount) : void{
diff --git a/src/world/light/LightPropagationContext.php b/src/world/light/LightPropagationContext.php
index 637535344e4..c3d45b3c63d 100644
--- a/src/world/light/LightPropagationContext.php
+++ b/src/world/light/LightPropagationContext.php
@@ -28,8 +28,8 @@ final class LightPropagationContext{
/** @phpstan-var \SplQueue */
public \SplQueue $spreadQueue;
/**
- * @var true[]
- * @phpstan-var array
+ * @var int[]|true[]
+ * @phpstan-var array
*/
public array $spreadVisited = [];
diff --git a/src/world/light/LightUpdate.php b/src/world/light/LightUpdate.php
index 1a82bdcef29..b7455c6cc3c 100644
--- a/src/world/light/LightUpdate.php
+++ b/src/world/light/LightUpdate.php
@@ -23,6 +23,7 @@
namespace pocketmine\world\light;
+use pocketmine\math\Facing;
use pocketmine\world\format\LightArray;
use pocketmine\world\format\SubChunk;
use pocketmine\world\utils\SubChunkExplorer;
@@ -33,15 +34,6 @@
//TODO: make light updates asynchronous
abstract class LightUpdate{
- private const ADJACENTS = [
- [ 1, 0, 0],
- [-1, 0, 0],
- [ 0, 1, 0],
- [ 0, -1, 0],
- [ 0, 0, 1],
- [ 0, 0, -1]
- ];
-
public const BASE_LIGHT_FILTER = 1;
/**
@@ -78,7 +70,7 @@ protected function getEffectiveLight(int $x, int $y, int $z) : int{
protected function getHighestAdjacentLight(int $x, int $y, int $z) : int{
$adjacent = 0;
- foreach(self::ADJACENTS as [$ox, $oy, $oz]){
+ foreach(Facing::OFFSET as [$ox, $oy, $oz]){
if(($adjacent = max($adjacent, $this->getEffectiveLight($x + $ox, $y + $oy, $z + $oz))) === 15){
break;
}
@@ -123,7 +115,7 @@ public function execute() : int{
$touched++;
[$x, $y, $z, $oldAdjacentLight] = $context->removalQueue->dequeue();
- foreach(self::ADJACENTS as [$ox, $oy, $oz]){
+ foreach(Facing::OFFSET as [$ox, $oy, $oz]){
$cx = $x + $ox;
$cy = $y + $oy;
$cz = $z + $oz;
@@ -145,6 +137,7 @@ public function execute() : int{
while(!$context->spreadQueue->isEmpty()){
$touched++;
[$x, $y, $z] = $context->spreadQueue->dequeue();
+ $from = $context->spreadVisited[World::blockHash($x, $y, $z)];
unset($context->spreadVisited[World::blockHash($x, $y, $z)]);
@@ -163,7 +156,11 @@ public function execute() : int{
continue;
}
- foreach(self::ADJACENTS as [$ox, $oy, $oz]){
+ foreach(Facing::OFFSET as $side => [$ox, $oy, $oz]){
+ if($from === $side){
+ //don't check the side that this node received its initial light from
+ continue;
+ }
$cx = $x + $ox;
$cy = $y + $oy;
$cz = $z + $oz;
@@ -177,7 +174,7 @@ public function execute() : int{
$lightArray = $this->getCurrentLightArray();
}
assert($subChunk !== null);
- $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight, $context, $lightArray, $subChunk);
+ $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight, $context, $lightArray, $subChunk, $side);
}
}
@@ -207,7 +204,7 @@ protected function computeRemoveLight(int $x, int $y, int $z, int $oldAdjacentLe
}
}
- protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLevel, LightPropagationContext $context, LightArray $lightArray, SubChunk $subChunk) : void{
+ protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLevel, LightPropagationContext $context, LightArray $lightArray, SubChunk $subChunk, int $side) : void{
$lx = $x & SubChunk::COORD_MASK;
$ly = $y & SubChunk::COORD_MASK;
$lz = $z & SubChunk::COORD_MASK;
@@ -218,7 +215,11 @@ protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLe
$lightArray->set($lx, $ly, $lz, $potentialLight);
if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)]) && $potentialLight > 1){
- $context->spreadVisited[$index] = true;
+ //Track where this node was lit from, to avoid checking the source again when we propagate from here
+ //TODO: In the future it might be worth tracking more than one adjacent source face in case multiple
+ //nodes try to light the same node. However, this is a rare case since the vast majority of calls are
+ //basic propagation with only one source anyway.
+ $context->spreadVisited[$index] = Facing::opposite($side);
$context->spreadQueue->enqueue([$x, $y, $z]);
}
}
diff --git a/src/world/sound/ArmorEquipChainSound.php b/src/world/sound/ArmorEquipChainSound.php
new file mode 100644
index 00000000000..efcb4f982e3
--- /dev/null
+++ b/src/world/sound/ArmorEquipChainSound.php
@@ -0,0 +1,35 @@
+goatHornType){
+ GoatHornType::PONDER => LevelSoundEvent::HORN_CALL0,
+ GoatHornType::SING => LevelSoundEvent::HORN_CALL1,
+ GoatHornType::SEEK => LevelSoundEvent::HORN_CALL2,
+ GoatHornType::FEEL => LevelSoundEvent::HORN_CALL3,
+ GoatHornType::ADMIRE => LevelSoundEvent::HORN_CALL4,
+ GoatHornType::CALL => LevelSoundEvent::HORN_CALL5,
+ GoatHornType::YEARN => LevelSoundEvent::HORN_CALL6,
+ GoatHornType::DREAM => LevelSoundEvent::HORN_CALL7
+ }, $pos, false)];
+ }
+}
diff --git a/src/world/sound/NoteInstrument.php b/src/world/sound/NoteInstrument.php
index 87e5d496d9a..a91d52cf700 100644
--- a/src/world/sound/NoteInstrument.php
+++ b/src/world/sound/NoteInstrument.php
@@ -23,13 +23,11 @@
namespace pocketmine\world\sound;
-use pocketmine\utils\EnumTrait;
+use pocketmine\utils\LegacyEnumShimTrait;
/**
- * This doc-block is generated automatically, do not modify it manually.
- * This must be regenerated whenever registry members are added, removed or changed.
- * @see build/generate-registry-annotations.php
- * @generate-registry-docblock
+ * TODO: These tags need to be removed once we get rid of LegacyEnumShimTrait (PM6)
+ * These are retained for backwards compatibility only.
*
* @method static NoteInstrument BANJO()
* @method static NoteInstrument BASS_DRUM()
@@ -48,27 +46,23 @@
* @method static NoteInstrument SNARE()
* @method static NoteInstrument XYLOPHONE()
*/
-final class NoteInstrument{
- use EnumTrait;
+enum NoteInstrument{
+ use LegacyEnumShimTrait;
- protected static function setup() : void{
- self::registerAll(
- new self("piano"),
- new self("bass_drum"),
- new self("snare"),
- new self("clicks_and_sticks"),
- new self("double_bass"),
- new self("bell"),
- new self("flute"),
- new self("chime"),
- new self("guitar"),
- new self("xylophone"),
- new self("iron_xylophone"),
- new self("cow_bell"),
- new self("didgeridoo"),
- new self("bit"),
- new self("banjo"),
- new self("pling")
- );
- }
+ case PIANO;
+ case BASS_DRUM;
+ case SNARE;
+ case CLICKS_AND_STICKS;
+ case DOUBLE_BASS;
+ case BELL;
+ case FLUTE;
+ case CHIME;
+ case GUITAR;
+ case XYLOPHONE;
+ case IRON_XYLOPHONE;
+ case COW_BELL;
+ case DIDGERIDOO;
+ case BIT;
+ case BANJO;
+ case PLING;
}
diff --git a/src/world/sound/PressurePlateActivateSound.php b/src/world/sound/PressurePlateActivateSound.php
new file mode 100644
index 00000000000..fac24e285c7
--- /dev/null
+++ b/src/world/sound/PressurePlateActivateSound.php
@@ -0,0 +1,46 @@
+getBlockTranslator()->internalIdToNetworkId($this->block->getStateId())
+ )];
+ }
+}
diff --git a/src/world/sound/PressurePlateDeactivateSound.php b/src/world/sound/PressurePlateDeactivateSound.php
new file mode 100644
index 00000000000..895bb8b8a03
--- /dev/null
+++ b/src/world/sound/PressurePlateDeactivateSound.php
@@ -0,0 +1,46 @@
+getBlockTranslator()->internalIdToNetworkId($this->block->getStateId())
+ )];
+ }
+}
diff --git a/src/world/sound/SweetBerriesPickSound.php b/src/world/sound/SweetBerriesPickSound.php
new file mode 100644
index 00000000000..fb8f3c8cf9b
--- /dev/null
+++ b/src/world/sound/SweetBerriesPickSound.php
@@ -0,0 +1,35 @@
+ /dev/null; do
case ${OPTION} in
@@ -44,6 +44,27 @@ fi
LOOPS=0
+handle_exit_code() {
+ local exitcode=$1
+ if [ "$exitcode" -eq 134 ] || [ "$exitcode" -eq 139 ]; then #SIGABRT/SIGSEGV
+ echo ""
+ echo "ERROR: The server process was killed due to a critical error (code $exitcode) which could indicate a problem with PHP."
+ echo "Updating your PHP binary is recommended."
+ echo "If this keeps happening, please open a bug report."
+ echo ""
+ elif [ "$exitcode" -eq 143 ]; then #SIGKILL, maybe user intervention
+ echo ""
+ echo "WARNING: Server was forcibly killed!"
+ echo "If you didn't kill the server manually, this probably means the server used too much memory and was killed by the system's OOM Killer."
+ echo "Please ensure your system has enough available RAM."
+ echo ""
+ elif [ "$exitcode" -ne 0 ] && [ "$exitcode" -ne 137 ]; then #normal exit / SIGTERM
+ echo ""
+ echo "WARNING: Server did not shut down correctly! (code $exitcode)"
+ echo ""
+ fi
+}
+
set +e
if [ "$DO_LOOP" == "yes" ]; then
@@ -52,11 +73,15 @@ if [ "$DO_LOOP" == "yes" ]; then
echo "Restarted $LOOPS times"
fi
"$PHP_BINARY" "$POCKETMINE_FILE" "$@"
+ handle_exit_code $?
echo "To escape the loop, press CTRL+C now. Otherwise, wait 5 seconds for the server to restart."
echo ""
sleep 5
((LOOPS++))
done
else
- exec "$PHP_BINARY" "$POCKETMINE_FILE" "$@"
+ "$PHP_BINARY" "$POCKETMINE_FILE" "$@"
+ exitcode=$?
+ handle_exit_code $exitcode
+ exit $exitcode
fi
diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon
index 6f43a99727f..e778cf0047f 100644
--- a/tests/phpstan/configs/actual-problems.neon
+++ b/tests/phpstan/configs/actual-problems.neon
@@ -32,7 +32,7 @@ parameters:
-
message: "#^Cannot access offset 'git' on mixed\\.$#"
- count: 2
+ count: 1
path: ../../../src/VersionInfo.php
-
@@ -45,11 +45,26 @@ parameters:
count: 1
path: ../../../src/VersionInfo.php
+ -
+ message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
+ count: 3
+ path: ../../../src/block/Block.php
+
+ -
+ message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
+ count: 3
+ path: ../../../src/block/Block.php
+
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:setBlockStateId\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Block.php
+ -
+ message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
+ count: 3
+ path: ../../../src/block/Block.php
+
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -140,21 +155,6 @@ parameters:
count: 1
path: ../../../src/block/DragonEgg.php
- -
- message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
- count: 1
- path: ../../../src/block/Farmland.php
-
- -
- message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
- count: 1
- path: ../../../src/block/Farmland.php
-
- -
- message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
- count: 1
- path: ../../../src/block/Farmland.php
-
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -285,31 +285,16 @@ parameters:
count: 1
path: ../../../src/block/Leaves.php
- -
- message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
- count: 11
- path: ../../../src/block/Liquid.php
-
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Liquid.php
- -
- message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
- count: 11
- path: ../../../src/block/Liquid.php
-
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Liquid.php
- -
- message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
- count: 11
- path: ../../../src/block/Liquid.php
-
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -440,6 +425,21 @@ parameters:
count: 3
path: ../../../src/block/tile/Spawnable.php
+ -
+ message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#"
+ count: 1
+ path: ../../../src/block/utils/CropGrowthHelper.php
+
+ -
+ message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#"
+ count: 1
+ path: ../../../src/block/utils/CropGrowthHelper.php
+
+ -
+ message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#"
+ count: 1
+ path: ../../../src/block/utils/CropGrowthHelper.php
+
-
message: "#^Cannot call method addParticle\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#"
count: 1
@@ -650,6 +650,11 @@ parameters:
count: 2
path: ../../../src/network/mcpe/NetworkSession.php
+ -
+ message: "#^Parameter \\#1 \\$playerInfo of class pocketmine\\\\event\\\\player\\\\PlayerResourcePackOfferEvent constructor expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#"
+ count: 1
+ path: ../../../src/network/mcpe/NetworkSession.php
+
-
message: "#^Parameter \\#1 \\$target of method pocketmine\\\\command\\\\Command\\:\\:testPermissionSilent\\(\\) expects pocketmine\\\\command\\\\CommandSender, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
@@ -780,11 +785,6 @@ parameters:
count: 1
path: ../../../src/resourcepacks/ZippedResourcePack.php
- -
- message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#"
- count: 1
- path: ../../../src/resourcepacks/ZippedResourcePack.php
-
-
message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$fileResource \\(resource\\) does not accept resource\\|false\\.$#"
count: 1
@@ -807,7 +807,7 @@ parameters:
-
message: "#^Cannot access offset string on mixed\\.$#"
- count: 3
+ count: 2
path: ../../../src/utils/Config.php
-
@@ -845,6 +845,16 @@ parameters:
count: 1
path: ../../../src/utils/Utils.php
+ -
+ message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#"
+ count: 1
+ path: ../../../src/utils/Utils.php
+
+ -
+ message: "#^Parameter \\#3 \\$line of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects int, mixed given\\.$#"
+ count: 1
+ path: ../../../src/utils/Utils.php
+
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -915,11 +925,6 @@ parameters:
count: 1
path: ../../../src/world/World.php
- -
- message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
- count: 1
- path: ../../../src/world/World.php
-
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -927,12 +932,12 @@ parameters:
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
- count: 3
+ count: 2
path: ../../../src/world/World.php
-
message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#"
- count: 3
+ count: 2
path: ../../../src/world/World.php
-
@@ -945,11 +950,6 @@ parameters:
count: 1
path: ../../../src/world/World.php
- -
- message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
- count: 1
- path: ../../../src/world/World.php
-
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -957,17 +957,12 @@ parameters:
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
- count: 3
+ count: 2
path: ../../../src/world/World.php
-
message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#"
- count: 3
- path: ../../../src/world/World.php
-
- -
- message: "#^Parameter \\#3 \\$chunk of method pocketmine\\\\player\\\\Player\\:\\:onChunkChanged\\(\\) expects pocketmine\\\\world\\\\format\\\\Chunk, pocketmine\\\\world\\\\format\\\\Chunk\\|null given\\.$#"
- count: 1
+ count: 2
path: ../../../src/world/World.php
-
@@ -980,11 +975,6 @@ parameters:
count: 1
path: ../../../src/world/World.php
- -
- message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
- count: 1
- path: ../../../src/world/World.php
-
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@@ -992,12 +982,12 @@ parameters:
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
- count: 3
+ count: 2
path: ../../../src/world/World.php
-
message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#"
- count: 3
+ count: 2
path: ../../../src/world/World.php
-
@@ -1040,11 +1030,6 @@ parameters:
count: 1
path: ../../../src/world/format/io/region/RegionLoader.php
- -
- message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#"
- count: 1
- path: ../../../src/world/format/io/region/RegionLoader.php
-
-
message: "#^Parameter \\#2 \\$size of function ftruncate expects int\\<0, max\\>, int given\\.$#"
count: 1
@@ -1080,6 +1065,21 @@ parameters:
count: 1
path: ../../../src/world/generator/hell/Nether.php
+ -
+ message: "#^Offset int does not exist on SplFixedArray\\\\|null\\.$#"
+ count: 4
+ path: ../../../src/world/generator/noise/Noise.php
+
+ -
+ message: "#^Parameter \\$q0 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#"
+ count: 1
+ path: ../../../src/world/generator/noise/Noise.php
+
+ -
+ message: "#^Parameter \\$q1 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#"
+ count: 1
+ path: ../../../src/world/generator/noise/Noise.php
+
-
message: "#^Cannot call method getBiomeId\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#"
count: 1
@@ -1180,28 +1180,3 @@ parameters:
count: 1
path: ../../../src/world/light/SkyLightUpdate.php
- -
- message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
- count: 1
- path: ../../phpunit/block/BlockTest.php
-
- -
- message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
- count: 1
- path: ../../phpunit/block/regenerate_consistency_check.php
-
- -
- message: "#^Property pocketmine\\\\event\\\\HandlerListManagerTest\\:\\:\\$isValidFunc \\(Closure\\(ReflectionClass\\\\)\\: bool\\) does not accept Closure\\|null\\.$#"
- count: 1
- path: ../../phpunit/event/HandlerListManagerTest.php
-
- -
- message: "#^Property pocketmine\\\\event\\\\HandlerListManagerTest\\:\\:\\$resolveParentFunc \\(Closure\\(ReflectionClass\\\\)\\: ReflectionClass\\\\|null\\) does not accept Closure\\|null\\.$#"
- count: 1
- path: ../../phpunit/event/HandlerListManagerTest.php
-
- -
- message: "#^Parameter \\#1 \\$logFile of class pocketmine\\\\utils\\\\MainLogger constructor expects string, string\\|false given\\.$#"
- count: 1
- path: ../../phpunit/scheduler/AsyncPoolTest.php
-
diff --git a/tests/phpstan/configs/php-bugs.neon b/tests/phpstan/configs/php-bugs.neon
index 4475ec46848..13a42a53bb9 100644
--- a/tests/phpstan/configs/php-bugs.neon
+++ b/tests/phpstan/configs/php-bugs.neon
@@ -1,6 +1,11 @@
parameters:
ignoreErrors:
-
- message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\(string, bool\\=\\)\\: mixed\\) does not accept Closure\\|null\\.$#"
+ message: "#^Property pocketmine\\\\event\\\\HandlerListManagerTest\\:\\:\\$isValidFunc \\(Closure\\(ReflectionClass\\\\)\\: bool\\) does not accept Closure\\|null\\.$#"
count: 1
- path: ../../phpunit/network/mcpe/handler/StupidJsonDecodeTest.php
+ path: ../../phpunit/event/HandlerListManagerTest.php
+
+ -
+ message: "#^Property pocketmine\\\\event\\\\HandlerListManagerTest\\:\\:\\$resolveParentFunc \\(Closure\\(ReflectionClass\\\\)\\: \\(ReflectionClass\\\\|null\\)\\) does not accept Closure\\|null\\.$#"
+ count: 1
+ path: ../../phpunit/event/HandlerListManagerTest.php
diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon
index 42d682f686a..e9de04a39ac 100644
--- a/tests/phpstan/configs/phpstan-bugs.neon
+++ b/tests/phpstan/configs/phpstan-bugs.neon
@@ -1,24 +1,79 @@
parameters:
ignoreErrors:
-
- message: "#^Instanceof between pocketmine\\\\block\\\\utils\\\\BannerPatternLayer and pocketmine\\\\block\\\\utils\\\\BannerPatternLayer will always evaluate to true\\.$#"
+ message: "#^Method pocketmine\\\\block\\\\CakeWithCandle\\:\\:onInteractCandle\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#"
count: 1
- path: ../../../src/block/BaseBanner.php
+ path: ../../../src/block/CakeWithCandle.php
-
- message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
+ message: "#^Method pocketmine\\\\block\\\\CopperDoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#"
count: 1
- path: ../../../src/entity/projectile/Projectile.php
+ path: ../../../src/block/CopperDoor.php
+
+ -
+ message: "#^Method pocketmine\\\\block\\\\CopperTrapdoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: ../../../src/block/CopperTrapdoor.php
+
+ -
+ message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#"
+ count: 1
+ path: ../../../src/block/DoubleTallGrass.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:ACACIA_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:BIRCH_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:CHERRY_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:CRIMSON_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:DARK_OAK_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
-
- message: "#^Match arm comparison between 4 and 4 is always true\\.$#"
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:JUNGLE_SIGN\\(\\)\\.$#"
count: 1
- path: ../../../src/network/mcpe/handler/InGamePacketHandler.php
+ path: ../../../src/block/VanillaBlocks.php
-
- message: "#^Match arm is unreachable because previous comparison is always true\\.$#"
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:MANGROVE_SIGN\\(\\)\\.$#"
count: 1
- path: ../../../src/network/mcpe/handler/InGamePacketHandler.php
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:OAK_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:SPRUCE_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:WARPED_SIGN\\(\\)\\.$#"
+ count: 1
+ path: ../../../src/block/VanillaBlocks.php
+
+ -
+ message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
+ count: 1
+ path: ../../../src/entity/projectile/Projectile.php
-
message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\PthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#"
@@ -36,12 +91,22 @@ parameters:
path: ../../../src/plugin/PluginManager.php
-
- message: "#^Static property pocketmine\\\\scheduler\\\\AsyncTask\\:\\:\\$threadLocalStorage \\(ArrayObject\\\\>\\|null\\) does not accept non\\-empty\\-array\\\\>\\|ArrayObject\\\\>\\.$#"
+ message: "#^Casting to int something that's already int\\.$#"
count: 1
- path: ../../../src/scheduler/AsyncTask.php
+ path: ../../../src/world/generator/normal/Normal.php
-
- message: "#^Casting to int something that's already int\\.$#"
+ message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFalse\\(\\) with false will always evaluate to true\\.$#"
count: 1
- path: ../../../src/world/generator/normal/Normal.php
+ path: ../../phpunit/promise/PromiseTest.php
+
+ -
+ message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false and 'All promise should…' will always evaluate to false\\.$#"
+ count: 1
+ path: ../../phpunit/promise/PromiseTest.php
+
+ -
+ message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false will always evaluate to false\\.$#"
+ count: 2
+ path: ../../phpunit/promise/PromiseTest.php
diff --git a/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php b/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php
new file mode 100644
index 00000000000..4fa76702247
--- /dev/null
+++ b/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php
@@ -0,0 +1,79 @@
+
+ */
+final class DeprecatedLegacyEnumAccessRule implements Rule{
+
+ public function getNodeType() : string{
+ return StaticCall::class;
+ }
+
+ public function processNode(Node $node, Scope $scope) : array{
+ /** @var StaticCall $node */
+ if(!$node->name instanceof Node\Identifier){
+ return [];
+ }
+ $caseName = $node->name->name;
+ $classType = $node->class instanceof Node\Name ?
+ $scope->resolveTypeByName($node->class) :
+ $scope->getType($node->class);
+
+ if(!$classType instanceof TypeWithClassName){
+ return [];
+ }
+
+ $reflection = $classType->getClassReflection();
+ if($reflection === null || !$reflection->hasTraitUse(LegacyEnumShimTrait::class) || !$reflection->implementsInterface(\UnitEnum::class)){
+ return [];
+ }
+
+ if(!$reflection->hasNativeMethod($caseName)){
+ return [
+ RuleErrorBuilder::message(sprintf(
+ 'Use of legacy enum case accessor %s::%s().',
+ $reflection->getName(),
+ $caseName
+ ))->tip(sprintf(
+ 'Access the enum constant directly instead (remove the brackets), e.g. %s::%s',
+ $reflection->getName(),
+ $caseName
+ ))->build()
+ ];
+ }
+
+ return [];
+ }
+}
diff --git a/tests/phpstan/rules/DisallowForeachByReferenceRule.php b/tests/phpstan/rules/DisallowForeachByReferenceRule.php
new file mode 100644
index 00000000000..79124d3283e
--- /dev/null
+++ b/tests/phpstan/rules/DisallowForeachByReferenceRule.php
@@ -0,0 +1,53 @@
+
+ */
+final class DisallowForeachByReferenceRule implements Rule{
+
+ public function getNodeType() : string{
+ return Foreach_::class;
+ }
+
+ public function processNode(Node $node, Scope $scope) : array{
+ /** @var Foreach_ $node */
+ if($node->byRef){
+ return [
+ RuleErrorBuilder::message("Foreach by-reference is not allowed, because it has surprising behaviour.")
+ ->tip("If the value variable is used outside of the foreach construct (e.g. in a second foreach), the iterable's contents will be unexpectedly altered.")
+ ->build()
+ ];
+ }
+
+ return [];
+ }
+}
diff --git a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php
index d53baa36519..e42d3292755 100644
--- a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php
+++ b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php
@@ -78,7 +78,7 @@ public function processNode(Node $node, Scope $scope) : array{
return $type;
});
if($hasCastableKeyTypes && !$expectsIntKeyTypes){
- $func = \Closure::fromCallable([Utils::class, 'stringifyKeys']);
+ $func = Utils::stringifyKeys(...);
return [
RuleErrorBuilder::message(sprintf(
"Unsafe foreach on array with key type %s (they might be casted to int).",
diff --git a/tests/phpstan/stubs/phpasn1.stub b/tests/phpstan/stubs/phpasn1.stub
deleted file mode 100644
index b459289efb3..00000000000
--- a/tests/phpstan/stubs/phpasn1.stub
+++ /dev/null
@@ -1,22 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FG\ASN1\Universal;
-
-class Integer
-{
- /**
- * @param int|string $value
- */
- public function __construct($value){}
-
- /** @return int|string */
- public function getContent(){}
-}
diff --git a/tests/phpunit/block/BlockTest.php b/tests/phpunit/block/BlockTest.php
index 3b84b8021f5..6ade2bffe2a 100644
--- a/tests/phpunit/block/BlockTest.php
+++ b/tests/phpunit/block/BlockTest.php
@@ -24,12 +24,16 @@
namespace pocketmine\block;
use PHPUnit\Framework\TestCase;
-use function asort;
-use function file_get_contents;
+use pocketmine\utils\AssumptionFailedError;
+use pocketmine\utils\Filesystem;
+use pocketmine\utils\Utils;
+use function implode;
use function is_array;
+use function is_int;
+use function is_string;
use function json_decode;
-use function print_r;
-use const SORT_STRING;
+use function log;
+use const JSON_THROW_ON_ERROR;
class BlockTest extends TestCase{
@@ -90,34 +94,71 @@ public function testLightFiltersValid() : void{
}
}
- public function testConsistency() : void{
- $list = json_decode(file_get_contents(__DIR__ . '/block_factory_consistency_check.json'), true);
- if(!is_array($list)){
- throw new \pocketmine\utils\AssumptionFailedError("Old table should be array{knownStates: array, stateDataBits: int}");
+ /**
+ * @return int[]
+ * @phpstan-return array
+ */
+ public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{
+ $newTable = [];
+
+ $idNameLookup = [];
+ //if we ever split up block registration into multiple registries (e.g. separating chemistry blocks),
+ //we'll need to ensure those additional registries are also included here
+ foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $blockType){
+ $id = $blockType->getTypeId();
+ if(isset($idNameLookup[$id])){
+ throw new AssumptionFailedError("TypeID $name collides with " . $idNameLookup[$id]);
+ }
+ $idNameLookup[$id] = $name;
}
- $knownStates = [];
- /**
- * @var string $name
- * @var int[] $stateIds
- */
- foreach($list["knownStates"] as $name => $stateIds){
- foreach($stateIds as $stateId){
- $knownStates[$stateId] = $name;
+
+ foreach($blockStateRegistry->getAllKnownStates() as $index => $block){
+ if($index !== $block->getStateId()){
+ throw new AssumptionFailedError("State index should always match state ID");
}
+ $idName = $idNameLookup[$block->getTypeId()];
+ $newTable[$idName] = ($newTable[$idName] ?? 0) + 1;
}
- $oldStateDataSize = $list["stateDataBits"];
- self::assertSame($oldStateDataSize, Block::INTERNAL_STATE_DATA_BITS, "Changed number of state data bits - consistency check probably need regenerating");
- $states = $this->blockFactory->getAllKnownStates();
- foreach($states as $stateId => $state){
- self::assertArrayHasKey($stateId, $knownStates, "New block state $stateId (" . print_r($state, true) . ") - consistency check may need regenerating");
- self::assertSame($knownStates[$stateId], $state->getName());
+ return $newTable;
+ }
+
+ /**
+ * @phpstan-param array $actual
+ *
+ * @return string[]
+ */
+ public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{
+ $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR);
+ if(!is_array($expected)){
+ throw new AssumptionFailedError("Old table should be array");
}
- asort($knownStates, SORT_STRING);
- foreach($knownStates as $k => $name){
- self::assertArrayHasKey($k, $states, "Missing previously-known block state $k " . ($k >> Block::INTERNAL_STATE_DATA_BITS) . ":" . ($k & Block::INTERNAL_STATE_DATA_MASK) . " ($name)");
- self::assertSame($name, $states[$k]->getName());
+
+ $errors = [];
+ foreach($expected as $typeName => $numStates){
+ if(!is_string($typeName) || !is_int($numStates)){
+ throw new AssumptionFailedError("Old table should be array");
+ }
+ if(!isset($actual[$typeName])){
+ $errors[] = "Removed block type $typeName ($numStates permutations)";
+ }elseif($actual[$typeName] !== $numStates){
+ $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName];
+ }
}
+ foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){
+ if(!isset($expected[$typeName])){
+ $errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)";
+ }
+ }
+
+ return $errors;
+ }
+
+ public function testConsistency() : void{
+ $newTable = self::computeConsistencyCheckTable($this->blockFactory);
+ $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable);
+
+ self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors));
}
public function testEmptyStateId() : void{
@@ -125,6 +166,14 @@ public function testEmptyStateId() : void{
self::assertInstanceOf(Air::class, $block);
}
+ public function testStateDataSizeNotTooLarge() : void{
+ $typeIdBitsMin = ((int) log(BlockTypeIds::FIRST_UNUSED_BLOCK_ID, 2)) + 1;
+
+ $typeIdBitsMin++; //for custom blocks
+
+ self::assertLessThanOrEqual(32, Block::INTERNAL_STATE_DATA_BITS + $typeIdBitsMin, "State data size cannot be larger than " . (32 - $typeIdBitsMin) . " bits (need at least $typeIdBitsMin bits for block type ID)");
+ }
+
public function testAsItemFromItem() : void{
$block = VanillaBlocks::FLOWER_POT();
$item = $block->asItem();
diff --git a/tests/phpunit/block/BlockTypeIdsTest.php b/tests/phpunit/block/BlockTypeIdsTest.php
index 5d38c740ad0..ce21a89ab68 100644
--- a/tests/phpunit/block/BlockTypeIdsTest.php
+++ b/tests/phpunit/block/BlockTypeIdsTest.php
@@ -24,6 +24,7 @@
namespace pocketmine\block;
use PHPUnit\Framework\TestCase;
+use pocketmine\utils\Utils;
use function array_unique;
use function max;
@@ -43,4 +44,15 @@ public function testNoDuplicates() : void{
self::assertSameSize($idTable, array_unique($idTable), "Every BlockTypeID must be unique");
}
+
+ public function testVanillaBlocksParity() : void{
+ $reflect = new \ReflectionClass(BlockTypeIds::class);
+
+ foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $block){
+ $expected = $block->getTypeId();
+ $actual = $reflect->getConstant($name);
+ self::assertNotFalse($actual, "VanillaBlocks::$name() does not have a BlockTypeIds constant");
+ self::assertSame($expected, $actual, "VanillaBlocks::$name() does not match BlockTypeIds::$name");
+ }
+ }
}
diff --git a/tests/phpunit/block/BrewingStandTest.php b/tests/phpunit/block/BrewingStandTest.php
index b3363b98cd3..85cdd90e113 100644
--- a/tests/phpunit/block/BrewingStandTest.php
+++ b/tests/phpunit/block/BrewingStandTest.php
@@ -25,7 +25,6 @@
use PHPUnit\Framework\TestCase;
use pocketmine\block\utils\BrewingStandSlot;
-use function array_values;
use function count;
class BrewingStandTest extends TestCase{
@@ -34,9 +33,9 @@ class BrewingStandTest extends TestCase{
* @phpstan-return \Generator}, void, void>
*/
public static function slotsProvider() : \Generator{
- yield [array_values(BrewingStandSlot::getAll())];
- yield [[BrewingStandSlot::EAST()]];
- yield [[BrewingStandSlot::EAST(), BrewingStandSlot::NORTHWEST()]];
+ yield [BrewingStandSlot::cases()];
+ yield [[BrewingStandSlot::EAST]];
+ yield [[BrewingStandSlot::EAST, BrewingStandSlot::NORTHWEST]];
}
/**
diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json
index ae5662abca5..79804d8cbd8 100644
--- a/tests/phpunit/block/block_factory_consistency_check.json
+++ b/tests/phpunit/block/block_factory_consistency_check.json
@@ -1 +1,712 @@
-{"knownStates":{"???":[2624010],"Acacia Button":[2560272,2560273,2560274,2560275,2560276,2560277,2560280,2560281,2560282,2560283,2560284,2560285],"Acacia Door":[2560512,2560513,2560514,2560515,2560516,2560517,2560518,2560519,2560520,2560521,2560522,2560523,2560524,2560525,2560526,2560527,2560528,2560529,2560530,2560531,2560532,2560533,2560534,2560535,2560536,2560537,2560538,2560539,2560540,2560541,2560542,2560543],"Acacia Fence":[2560787],"Acacia Fence Gate":[2561040,2561041,2561042,2561043,2561044,2561045,2561046,2561047,2561048,2561049,2561050,2561051,2561052,2561053,2561054,2561055],"Acacia Leaves":[2561300,2561301,2561302,2561303],"Acacia Log":[2561554,2561555,2561556,2561557,2561558,2561559],"Acacia Planks":[2561815],"Acacia Pressure Plate":[2562072,2562073],"Acacia Sapling":[2562328,2562329],"Acacia Sign":[2562576,2562577,2562578,2562579,2562580,2562581,2562582,2562583,2562584,2562585,2562586,2562587,2562588,2562589,2562590,2562591],"Acacia Slab":[2562841,2562842,2562843],"Acacia Stairs":[2563096,2563097,2563098,2563099,2563100,2563101,2563102,2563103],"Acacia Trapdoor":[2563344,2563345,2563346,2563347,2563348,2563349,2563350,2563351,2563352,2563353,2563354,2563355,2563356,2563357,2563358,2563359],"Acacia Wall Sign":[2563612,2563613,2563614,2563615],"Acacia Wood":[2563866,2563867,2563868,2563869,2563870,2563871],"Actinium":[2594197],"Activator Rail":[2564128,2564129,2564130,2564131,2564132,2564133,2564136,2564137,2564138,2564139,2564140,2564141],"Air":[2560016],"All Sided Mushroom Stem":[2564385],"Allium":[2564642],"Aluminum":[2594454],"Americium":[2594711],"Amethyst":[2698284],"Ancient Debris":[2698541],"Andesite":[2564899],"Andesite Slab":[2565156,2565157,2565158],"Andesite Stairs":[2565408,2565409,2565410,2565411,2565412,2565413,2565414,2565415],"Andesite Wall":[2565632,2565633,2565634,2565635,2565636,2565637,2565638,2565639,2565640,2565641,2565642,2565643,2565644,2565645,2565646,2565647,2565648,2565649,2565650,2565651,2565652,2565653,2565654,2565655,2565656,2565657,2565658,2565659,2565660,2565661,2565662,2565663,2565664,2565665,2565666,2565667,2565668,2565669,2565670,2565671,2565672,2565673,2565674,2565675,2565676,2565677,2565678,2565679,2565680,2565681,2565682,2565683,2565684,2565685,2565686,2565687,2565688,2565689,2565690,2565691,2565692,2565693,2565694,2565695,2565728,2565729,2565730,2565731,2565732,2565733,2565734,2565735,2565736,2565737,2565738,2565739,2565740,2565741,2565742,2565743,2565750,2565760,2565761,2565762,2565763,2565764,2565765,2565766,2565767,2565768,2565769,2565770,2565771,2565772,2565773,2565774,2565775,2565776,2565777,2565778,2565779,2565780,2565781,2565782,2565783,2565784,2565785,2565786,2565787,2565788,2565789,2565790,2565791,2565792,2565793,2565794,2565795,2565796,2565797,2565798,2565799,2565800,2565801,2565802,2565803,2565804,2565805,2565806,2565807,2565808,2565809,2565810,2565811,2565812,2565813,2565814,2565815,2565816,2565817,2565818,2565819,2565820,2565821,2565822,2565823,2565856,2565857,2565858,2565859,2565860,2565861,2565862,2565863,2565864,2565865,2565866,2565867,2565868,2565869,2565870,2565871,2565878],"Antimony":[2594968],"Anvil":[2565921,2565922,2565923,2565925,2565926,2565927,2565929,2565930,2565931,2565933,2565934,2565935],"Argon":[2595225],"Arsenic":[2595482],"Astatine":[2595739],"Azalea Leaves":[2735804,2735805,2735806,2735807],"Azure Bluet":[2566184],"Bamboo":[2566432,2566433,2566435,2566436,2566437,2566439,2566440,2566441,2566443,2566444,2566445,2566447],"Bamboo Sapling":[2566698,2566699],"Banner":[2566912,2566913,2566914,2566915,2566916,2566917,2566918,2566919,2566920,2566921,2566922,2566923,2566924,2566925,2566926,2566927,2566928,2566929,2566930,2566931,2566932,2566933,2566934,2566935,2566936,2566937,2566938,2566939,2566940,2566941,2566942,2566943,2566944,2566945,2566946,2566947,2566948,2566949,2566950,2566951,2566952,2566953,2566954,2566955,2566956,2566957,2566958,2566959,2566960,2566961,2566962,2566963,2566964,2566965,2566966,2566967,2566968,2566969,2566970,2566971,2566972,2566973,2566974,2566975,2566976,2566977,2566978,2566979,2566980,2566981,2566982,2566983,2566984,2566985,2566986,2566987,2566988,2566989,2566990,2566991,2566992,2566993,2566994,2566995,2566996,2566997,2566998,2566999,2567000,2567001,2567002,2567003,2567004,2567005,2567006,2567007,2567008,2567009,2567010,2567011,2567012,2567013,2567014,2567015,2567016,2567017,2567018,2567019,2567020,2567021,2567022,2567023,2567024,2567025,2567026,2567027,2567028,2567029,2567030,2567031,2567032,2567033,2567034,2567035,2567036,2567037,2567038,2567039,2567040,2567041,2567042,2567043,2567044,2567045,2567046,2567047,2567048,2567049,2567050,2567051,2567052,2567053,2567054,2567055,2567056,2567057,2567058,2567059,2567060,2567061,2567062,2567063,2567064,2567065,2567066,2567067,2567068,2567069,2567070,2567071,2567072,2567073,2567074,2567075,2567076,2567077,2567078,2567079,2567080,2567081,2567082,2567083,2567084,2567085,2567086,2567087,2567088,2567089,2567090,2567091,2567092,2567093,2567094,2567095,2567096,2567097,2567098,2567099,2567100,2567101,2567102,2567103,2567104,2567105,2567106,2567107,2567108,2567109,2567110,2567111,2567112,2567113,2567114,2567115,2567116,2567117,2567118,2567119,2567120,2567121,2567122,2567123,2567124,2567125,2567126,2567127,2567128,2567129,2567130,2567131,2567132,2567133,2567134,2567135,2567136,2567137,2567138,2567139,2567140,2567141,2567142,2567143,2567144,2567145,2567146,2567147,2567148,2567149,2567150,2567151,2567152,2567153,2567154,2567155,2567156,2567157,2567158,2567159,2567160,2567161,2567162,2567163,2567164,2567165,2567166,2567167],"Barium":[2595996],"Barrel":[2567200,2567201,2567204,2567205,2567206,2567207,2567208,2567209,2567212,2567213,2567214,2567215],"Barrier":[2567469],"Basalt":[2698796,2698798,2698799],"Beacon":[2567726],"Bed Block":[2567936,2567937,2567938,2567939,2567940,2567941,2567942,2567943,2567944,2567945,2567946,2567947,2567948,2567949,2567950,2567951,2567952,2567953,2567954,2567955,2567956,2567957,2567958,2567959,2567960,2567961,2567962,2567963,2567964,2567965,2567966,2567967,2567968,2567969,2567970,2567971,2567972,2567973,2567974,2567975,2567976,2567977,2567978,2567979,2567980,2567981,2567982,2567983,2567984,2567985,2567986,2567987,2567988,2567989,2567990,2567991,2567992,2567993,2567994,2567995,2567996,2567997,2567998,2567999,2568000,2568001,2568002,2568003,2568004,2568005,2568006,2568007,2568008,2568009,2568010,2568011,2568012,2568013,2568014,2568015,2568016,2568017,2568018,2568019,2568020,2568021,2568022,2568023,2568024,2568025,2568026,2568027,2568028,2568029,2568030,2568031,2568032,2568033,2568034,2568035,2568036,2568037,2568038,2568039,2568040,2568041,2568042,2568043,2568044,2568045,2568046,2568047,2568048,2568049,2568050,2568051,2568052,2568053,2568054,2568055,2568056,2568057,2568058,2568059,2568060,2568061,2568062,2568063,2568064,2568065,2568066,2568067,2568068,2568069,2568070,2568071,2568072,2568073,2568074,2568075,2568076,2568077,2568078,2568079,2568080,2568081,2568082,2568083,2568084,2568085,2568086,2568087,2568088,2568089,2568090,2568091,2568092,2568093,2568094,2568095,2568096,2568097,2568098,2568099,2568100,2568101,2568102,2568103,2568104,2568105,2568106,2568107,2568108,2568109,2568110,2568111,2568112,2568113,2568114,2568115,2568116,2568117,2568118,2568119,2568120,2568121,2568122,2568123,2568124,2568125,2568126,2568127,2568128,2568129,2568130,2568131,2568132,2568133,2568134,2568135,2568136,2568137,2568138,2568139,2568140,2568141,2568142,2568143,2568144,2568145,2568146,2568147,2568148,2568149,2568150,2568151,2568152,2568153,2568154,2568155,2568156,2568157,2568158,2568159,2568160,2568161,2568162,2568163,2568164,2568165,2568166,2568167,2568168,2568169,2568170,2568171,2568172,2568173,2568174,2568175,2568176,2568177,2568178,2568179,2568180,2568181,2568182,2568183,2568184,2568185,2568186,2568187,2568188,2568189,2568190,2568191],"Bedrock":[2568240,2568241],"Beetroot Block":[2568496,2568497,2568498,2568499,2568500,2568501,2568502,2568503],"Bell":[2568752,2568753,2568754,2568755,2568756,2568757,2568758,2568759,2568760,2568761,2568762,2568763,2568764,2568765,2568766,2568767],"Berkelium":[2596253],"Beryllium":[2596510],"Birch Button":[2569008,2569009,2569010,2569011,2569014,2569015,2569016,2569017,2569018,2569019,2569022,2569023],"Birch Door":[2569248,2569249,2569250,2569251,2569252,2569253,2569254,2569255,2569256,2569257,2569258,2569259,2569260,2569261,2569262,2569263,2569264,2569265,2569266,2569267,2569268,2569269,2569270,2569271,2569272,2569273,2569274,2569275,2569276,2569277,2569278,2569279],"Birch Fence":[2569525],"Birch Fence Gate":[2569776,2569777,2569778,2569779,2569780,2569781,2569782,2569783,2569784,2569785,2569786,2569787,2569788,2569789,2569790,2569791],"Birch Leaves":[2570036,2570037,2570038,2570039],"Birch Log":[2570296,2570297,2570298,2570299,2570300,2570301],"Birch Planks":[2570553],"Birch Pressure Plate":[2570810,2570811],"Birch Sapling":[2571066,2571067],"Birch Sign":[2571312,2571313,2571314,2571315,2571316,2571317,2571318,2571319,2571320,2571321,2571322,2571323,2571324,2571325,2571326,2571327],"Birch Slab":[2571580,2571581,2571583],"Birch Stairs":[2571832,2571833,2571834,2571835,2571836,2571837,2571838,2571839],"Birch Trapdoor":[2572080,2572081,2572082,2572083,2572084,2572085,2572086,2572087,2572088,2572089,2572090,2572091,2572092,2572093,2572094,2572095],"Birch Wall Sign":[2572352,2572353,2572354,2572355],"Birch Wood":[2572608,2572609,2572610,2572611,2572612,2572613],"Bismuth":[2596767],"Blackstone":[2699569],"Blackstone Slab":[2699824,2699826,2699827],"Blackstone Stairs":[2700080,2700081,2700082,2700083,2700084,2700085,2700086,2700087],"Blackstone Wall":[2700288,2700289,2700290,2700291,2700292,2700293,2700294,2700295,2700296,2700297,2700298,2700299,2700300,2700301,2700302,2700303,2700304,2700305,2700306,2700307,2700308,2700309,2700310,2700311,2700312,2700313,2700314,2700315,2700316,2700317,2700318,2700319,2700320,2700321,2700322,2700323,2700324,2700325,2700326,2700327,2700328,2700329,2700330,2700331,2700332,2700333,2700334,2700335,2700336,2700337,2700338,2700339,2700340,2700341,2700342,2700343,2700344,2700345,2700346,2700347,2700348,2700349,2700350,2700351,2700388,2700400,2700401,2700402,2700403,2700404,2700405,2700406,2700407,2700408,2700409,2700410,2700411,2700412,2700413,2700414,2700415,2700416,2700417,2700418,2700419,2700420,2700421,2700422,2700423,2700424,2700425,2700426,2700427,2700428,2700429,2700430,2700431,2700432,2700433,2700434,2700435,2700436,2700437,2700438,2700439,2700440,2700441,2700442,2700443,2700444,2700445,2700446,2700447,2700448,2700449,2700450,2700451,2700452,2700453,2700454,2700455,2700456,2700457,2700458,2700459,2700460,2700461,2700462,2700463,2700464,2700465,2700466,2700467,2700468,2700469,2700470,2700471,2700472,2700473,2700474,2700475,2700476,2700477,2700478,2700479,2700516,2700528,2700529,2700530,2700531,2700532,2700533,2700534,2700535,2700536,2700537,2700538,2700539,2700540,2700541,2700542,2700543],"Blast Furnace":[2573120,2573121,2573122,2573123,2573124,2573125,2573126,2573127],"Blue Ice":[2573637],"Blue Orchid":[2573894],"Blue Torch":[2574146,2574147,2574148,2574149,2574150],"Bohrium":[2597024],"Bone Block":[2574408,2574409,2574410],"Bookshelf":[2574665],"Boron":[2597281],"Brewing Stand":[2574920,2574921,2574922,2574923,2574924,2574925,2574926,2574927],"Brick Slab":[2575177,2575178,2575179],"Brick Stairs":[2575432,2575433,2575434,2575435,2575436,2575437,2575438,2575439],"Brick Wall":[2575616,2575617,2575618,2575619,2575620,2575621,2575622,2575623,2575624,2575625,2575626,2575627,2575628,2575629,2575630,2575631,2575645,2575680,2575681,2575682,2575683,2575684,2575685,2575686,2575687,2575688,2575689,2575690,2575691,2575692,2575693,2575694,2575695,2575696,2575697,2575698,2575699,2575700,2575701,2575702,2575703,2575704,2575705,2575706,2575707,2575708,2575709,2575710,2575711,2575712,2575713,2575714,2575715,2575716,2575717,2575718,2575719,2575720,2575721,2575722,2575723,2575724,2575725,2575726,2575727,2575728,2575729,2575730,2575731,2575732,2575733,2575734,2575735,2575736,2575737,2575738,2575739,2575740,2575741,2575742,2575743,2575744,2575745,2575746,2575747,2575748,2575749,2575750,2575751,2575752,2575753,2575754,2575755,2575756,2575757,2575758,2575759,2575773,2575808,2575809,2575810,2575811,2575812,2575813,2575814,2575815,2575816,2575817,2575818,2575819,2575820,2575821,2575822,2575823,2575824,2575825,2575826,2575827,2575828,2575829,2575830,2575831,2575832,2575833,2575834,2575835,2575836,2575837,2575838,2575839,2575840,2575841,2575842,2575843,2575844,2575845,2575846,2575847,2575848,2575849,2575850,2575851,2575852,2575853,2575854,2575855,2575856,2575857,2575858,2575859,2575860,2575861,2575862,2575863,2575864,2575865,2575866,2575867,2575868,2575869,2575870,2575871],"Bricks":[2575950],"Bromine":[2597538],"Brown Mushroom":[2576464],"Brown Mushroom Block":[2576720,2576721,2576722,2576723,2576724,2576725,2576726,2576727,2576728,2576729,2576731],"Cactus":[2576976,2576977,2576978,2576979,2576980,2576981,2576982,2576983,2576984,2576985,2576986,2576987,2576988,2576989,2576990,2576991],"Cadmium":[2597795],"Cake":[2577232,2577233,2577234,2577235,2577237,2577238,2577239],"Cake With Candle":[2729638,2729639],"Cake With Dyed Candle":[2729888,2729889,2729890,2729891,2729892,2729893,2729894,2729895,2729896,2729897,2729898,2729899,2729900,2729901,2729902,2729903,2729904,2729905,2729906,2729907,2729908,2729909,2729910,2729911,2729912,2729913,2729914,2729915,2729916,2729917,2729918,2729919],"Calcite":[2704709],"Calcium":[2598052],"Californium":[2598309],"Candle":[2729120,2729121,2729122,2729123,2729124,2729125,2729126,2729127],"Carbon":[2598566],"Carpet":[2577488,2577489,2577490,2577491,2577492,2577493,2577494,2577495,2577496,2577497,2577498,2577499,2577500,2577501,2577502,2577503],"Carrot Block":[2577744,2577745,2577746,2577747,2577748,2577749,2577750,2577751],"Cartography Table":[2730666],"Carved Pumpkin":[2578004,2578005,2578006,2578007],"Cauldron":[2731694],"Cave Vines":[2736512,2736513,2736514,2736515,2736516,2736517,2736518,2736519,2736520,2736521,2736522,2736523,2736524,2736525,2736526,2736527,2736528,2736529,2736530,2736531,2736532,2736533,2736534,2736535,2736536,2736537,2736544,2736545,2736546,2736547,2736548,2736549,2736550,2736551,2736552,2736553,2736554,2736555,2736556,2736557,2736558,2736559,2736560,2736561,2736562,2736563,2736564,2736565,2736566,2736567,2736568,2736569,2736576,2736577,2736578,2736579,2736580,2736581,2736582,2736583,2736584,2736585,2736586,2736587,2736588,2736589,2736590,2736591,2736592,2736593,2736594,2736595,2736596,2736597,2736598,2736599,2736600,2736601,2736608,2736609,2736610,2736611,2736612,2736613,2736614,2736615,2736616,2736617,2736618,2736619,2736620,2736621,2736622,2736623,2736624,2736625,2736626,2736627,2736628,2736629,2736630,2736631,2736632,2736633],"Cerium":[2598823],"Cesium":[2599080],"Chain":[2734776,2734778,2734779],"Chest":[2578520,2578521,2578522,2578523],"Chiseled Deepslate":[2710106],"Chiseled Nether Bricks":[2710363],"Chiseled Polished Blackstone":[2702139],"Chiseled Quartz Block":[2578776,2578777,2578779],"Chiseled Red Sandstone":[2579034],"Chiseled Sandstone":[2579291],"Chiseled Stone Bricks":[2579548],"Chlorine":[2599337],"Chorus Flower":[2732976,2732977,2732978,2732979,2732982,2732983],"Chorus Plant":[2733236],"Chromium":[2599594],"Clay Block":[2579805],"Coal Block":[2580062],"Coal Ore":[2580319],"Cobalt":[2599851],"Cobbled Deepslate":[2707793],"Cobbled Deepslate Slab":[2708048,2708050,2708051],"Cobbled Deepslate Stairs":[2708304,2708305,2708306,2708307,2708308,2708309,2708310,2708311],"Cobbled Deepslate Wall":[2708484,2708496,2708497,2708498,2708499,2708500,2708501,2708502,2708503,2708504,2708505,2708506,2708507,2708508,2708509,2708510,2708511,2708544,2708545,2708546,2708547,2708548,2708549,2708550,2708551,2708552,2708553,2708554,2708555,2708556,2708557,2708558,2708559,2708560,2708561,2708562,2708563,2708564,2708565,2708566,2708567,2708568,2708569,2708570,2708571,2708572,2708573,2708574,2708575,2708576,2708577,2708578,2708579,2708580,2708581,2708582,2708583,2708584,2708585,2708586,2708587,2708588,2708589,2708590,2708591,2708592,2708593,2708594,2708595,2708596,2708597,2708598,2708599,2708600,2708601,2708602,2708603,2708604,2708605,2708606,2708607,2708612,2708624,2708625,2708626,2708627,2708628,2708629,2708630,2708631,2708632,2708633,2708634,2708635,2708636,2708637,2708638,2708639,2708672,2708673,2708674,2708675,2708676,2708677,2708678,2708679,2708680,2708681,2708682,2708683,2708684,2708685,2708686,2708687,2708688,2708689,2708690,2708691,2708692,2708693,2708694,2708695,2708696,2708697,2708698,2708699,2708700,2708701,2708702,2708703,2708704,2708705,2708706,2708707,2708708,2708709,2708710,2708711,2708712,2708713,2708714,2708715,2708716,2708717,2708718,2708719,2708720,2708721,2708722,2708723,2708724,2708725,2708726,2708727,2708728,2708729,2708730,2708731,2708732,2708733,2708734,2708735],"Cobblestone":[2580576],"Cobblestone Slab":[2580832,2580833,2580835],"Cobblestone Stairs":[2581088,2581089,2581090,2581091,2581092,2581093,2581094,2581095],"Cobblestone Wall":[2581280,2581281,2581282,2581283,2581284,2581285,2581286,2581287,2581288,2581289,2581290,2581291,2581292,2581293,2581294,2581295,2581299,2581312,2581313,2581314,2581315,2581316,2581317,2581318,2581319,2581320,2581321,2581322,2581323,2581324,2581325,2581326,2581327,2581328,2581329,2581330,2581331,2581332,2581333,2581334,2581335,2581336,2581337,2581338,2581339,2581340,2581341,2581342,2581343,2581344,2581345,2581346,2581347,2581348,2581349,2581350,2581351,2581352,2581353,2581354,2581355,2581356,2581357,2581358,2581359,2581360,2581361,2581362,2581363,2581364,2581365,2581366,2581367,2581368,2581369,2581370,2581371,2581372,2581373,2581374,2581375,2581408,2581409,2581410,2581411,2581412,2581413,2581414,2581415,2581416,2581417,2581418,2581419,2581420,2581421,2581422,2581423,2581427,2581440,2581441,2581442,2581443,2581444,2581445,2581446,2581447,2581448,2581449,2581450,2581451,2581452,2581453,2581454,2581455,2581456,2581457,2581458,2581459,2581460,2581461,2581462,2581463,2581464,2581465,2581466,2581467,2581468,2581469,2581470,2581471,2581472,2581473,2581474,2581475,2581476,2581477,2581478,2581479,2581480,2581481,2581482,2581483,2581484,2581485,2581486,2581487,2581488,2581489,2581490,2581491,2581492,2581493,2581494,2581495,2581496,2581497,2581498,2581499,2581500,2581501,2581502,2581503],"Cobweb":[2581604],"Cocoa Block":[2581856,2581857,2581858,2581859,2581860,2581861,2581862,2581863,2581868,2581869,2581870,2581871],"Compound Creator":[2582116,2582117,2582118,2582119],"Concrete":[2582368,2582369,2582370,2582371,2582372,2582373,2582374,2582375,2582376,2582377,2582378,2582379,2582380,2582381,2582382,2582383],"Concrete Powder":[2582624,2582625,2582626,2582627,2582628,2582629,2582630,2582631,2582632,2582633,2582634,2582635,2582636,2582637,2582638,2582639],"Copernicium":[2600365],"Copper":[2600622],"Copper Block":[2728096,2728097,2728098,2728099,2728100,2728101,2728102,2728103],"Copper Ore":[2725012],"Coral":[2582880,2582881,2582882,2582883,2582885,2582888,2582889,2582890,2582891,2582893],"Coral Block":[2583136,2583137,2583138,2583139,2583142,2583144,2583145,2583146,2583147,2583150],"Coral Fan":[2583392,2583393,2583394,2583395,2583399,2583400,2583401,2583402,2583403,2583407,2583408,2583409,2583410,2583411,2583415,2583416,2583417,2583418,2583419,2583423],"Cornflower":[2583660],"Cracked Deepslate Bricks":[2706251],"Cracked Deepslate Tiles":[2707536],"Cracked Nether Bricks":[2710620],"Cracked Polished Blackstone Bricks":[2703424],"Cracked Stone Bricks":[2583917],"Crafting Table":[2584174],"Crimson Button":[2717298,2717299,2717300,2717301,2717302,2717303,2717306,2717307,2717308,2717309,2717310,2717311],"Crimson Door":[2718816,2718817,2718818,2718819,2718820,2718821,2718822,2718823,2718824,2718825,2718826,2718827,2718828,2718829,2718830,2718831,2718832,2718833,2718834,2718835,2718836,2718837,2718838,2718839,2718840,2718841,2718842,2718843,2718844,2718845,2718846,2718847],"Crimson Fence":[2713447],"Crimson Fence Gate":[2719600,2719601,2719602,2719603,2719604,2719605,2719606,2719607,2719608,2719609,2719610,2719611,2719612,2719613,2719614,2719615],"Crimson Hyphae":[2715760,2715761,2715762,2715763,2715764,2715765],"Crimson Planks":[2712676],"Crimson Pressure Plate":[2718072,2718073],"Crimson Sign":[2721152,2721153,2721154,2721155,2721156,2721157,2721158,2721159,2721160,2721161,2721162,2721163,2721164,2721165,2721166,2721167],"Crimson Slab":[2714216,2714218,2714219],"Crimson Stairs":[2720384,2720385,2720386,2720387,2720388,2720389,2720390,2720391],"Crimson Stem":[2714984,2714985,2714988,2714989,2714990,2714991],"Crimson Trapdoor":[2716528,2716529,2716530,2716531,2716532,2716533,2716534,2716535,2716536,2716537,2716538,2716539,2716540,2716541,2716542,2716543],"Crimson Wall Sign":[2721928,2721929,2721930,2721931],"Crying Obsidian":[2727325],"Curium":[2600879],"Cut Copper Block":[2728352,2728353,2728354,2728355,2728356,2728357,2728358,2728359],"Cut Copper Slab Slab":[2728608,2728609,2728610,2728611,2728612,2728613,2728614,2728615,2728616,2728617,2728618,2728619,2728620,2728621,2728622,2728623,2728624,2728625,2728626,2728627,2728628,2728629,2728630,2728631],"Cut Copper Stairs":[2728832,2728833,2728834,2728835,2728836,2728837,2728838,2728839,2728840,2728841,2728842,2728843,2728844,2728845,2728846,2728847,2728848,2728849,2728850,2728851,2728852,2728853,2728854,2728855,2728856,2728857,2728858,2728859,2728860,2728861,2728862,2728863,2728864,2728865,2728866,2728867,2728868,2728869,2728870,2728871,2728872,2728873,2728874,2728875,2728876,2728877,2728878,2728879,2728880,2728881,2728882,2728883,2728884,2728885,2728886,2728887,2728888,2728889,2728890,2728891,2728892,2728893,2728894,2728895],"Cut Red Sandstone":[2584431],"Cut Red Sandstone Slab":[2584688,2584689,2584690],"Cut Sandstone":[2584945],"Cut Sandstone Slab":[2585200,2585202,2585203],"Dandelion":[2585716],"Dark Oak Button":[2585968,2585969,2585972,2585973,2585974,2585975,2585976,2585977,2585980,2585981,2585982,2585983],"Dark Oak Door":[2586208,2586209,2586210,2586211,2586212,2586213,2586214,2586215,2586216,2586217,2586218,2586219,2586220,2586221,2586222,2586223,2586224,2586225,2586226,2586227,2586228,2586229,2586230,2586231,2586232,2586233,2586234,2586235,2586236,2586237,2586238,2586239],"Dark Oak Fence":[2586487],"Dark Oak Fence Gate":[2586736,2586737,2586738,2586739,2586740,2586741,2586742,2586743,2586744,2586745,2586746,2586747,2586748,2586749,2586750,2586751],"Dark Oak Leaves":[2587000,2587001,2587002,2587003],"Dark Oak Log":[2587256,2587257,2587258,2587259,2587262,2587263],"Dark Oak Planks":[2587515],"Dark Oak Pressure Plate":[2587772,2587773],"Dark Oak Sapling":[2588028,2588029],"Dark Oak Sign":[2588272,2588273,2588274,2588275,2588276,2588277,2588278,2588279,2588280,2588281,2588282,2588283,2588284,2588285,2588286,2588287],"Dark Oak Slab":[2588541,2588542,2588543],"Dark Oak Stairs":[2588800,2588801,2588802,2588803,2588804,2588805,2588806,2588807],"Dark Oak Trapdoor":[2589056,2589057,2589058,2589059,2589060,2589061,2589062,2589063,2589064,2589065,2589066,2589067,2589068,2589069,2589070,2589071],"Dark Oak Wall Sign":[2589312,2589313,2589314,2589315],"Dark Oak Wood":[2589568,2589569,2589570,2589571,2589574,2589575],"Dark Prismarine":[2589828],"Dark Prismarine Slab":[2590084,2590085,2590087],"Dark Prismarine Stairs":[2590336,2590337,2590338,2590339,2590340,2590341,2590342,2590343],"Darmstadtium":[2601136],"Daylight Sensor":[2590592,2590593,2590594,2590595,2590596,2590597,2590598,2590599,2590600,2590601,2590602,2590603,2590604,2590605,2590606,2590607,2590608,2590609,2590610,2590611,2590612,2590613,2590614,2590615,2590616,2590617,2590618,2590619,2590620,2590621,2590622,2590623],"Dead Bush":[2590856],"Deepslate":[2704964,2704966,2704967],"Deepslate Brick Slab":[2705480,2705481,2705482],"Deepslate Brick Stairs":[2705736,2705737,2705738,2705739,2705740,2705741,2705742,2705743],"Deepslate Brick Wall":[2705920,2705921,2705922,2705923,2705924,2705925,2705926,2705927,2705928,2705929,2705930,2705931,2705932,2705933,2705934,2705935,2705946,2705984,2705985,2705986,2705987,2705988,2705989,2705990,2705991,2705992,2705993,2705994,2705995,2705996,2705997,2705998,2705999,2706000,2706001,2706002,2706003,2706004,2706005,2706006,2706007,2706008,2706009,2706010,2706011,2706012,2706013,2706014,2706015,2706016,2706017,2706018,2706019,2706020,2706021,2706022,2706023,2706024,2706025,2706026,2706027,2706028,2706029,2706030,2706031,2706032,2706033,2706034,2706035,2706036,2706037,2706038,2706039,2706040,2706041,2706042,2706043,2706044,2706045,2706046,2706047,2706048,2706049,2706050,2706051,2706052,2706053,2706054,2706055,2706056,2706057,2706058,2706059,2706060,2706061,2706062,2706063,2706074,2706112,2706113,2706114,2706115,2706116,2706117,2706118,2706119,2706120,2706121,2706122,2706123,2706124,2706125,2706126,2706127,2706128,2706129,2706130,2706131,2706132,2706133,2706134,2706135,2706136,2706137,2706138,2706139,2706140,2706141,2706142,2706143,2706144,2706145,2706146,2706147,2706148,2706149,2706150,2706151,2706152,2706153,2706154,2706155,2706156,2706157,2706158,2706159,2706160,2706161,2706162,2706163,2706164,2706165,2706166,2706167,2706168,2706169,2706170,2706171,2706172,2706173,2706174,2706175],"Deepslate Bricks":[2705223],"Deepslate Coal Ore":[2722956],"Deepslate Copper Ore":[2724755],"Deepslate Diamond Ore":[2723213],"Deepslate Emerald Ore":[2723470],"Deepslate Gold Ore":[2724498],"Deepslate Iron Ore":[2724241],"Deepslate Lapis Lazuli Ore":[2723727],"Deepslate Redstone Ore":[2723984,2723985],"Deepslate Tile Slab":[2706764,2706765,2706767],"Deepslate Tile Stairs":[2707016,2707017,2707018,2707019,2707020,2707021,2707022,2707023],"Deepslate Tile Wall":[2707200,2707201,2707202,2707203,2707204,2707205,2707206,2707207,2707208,2707209,2707210,2707211,2707212,2707213,2707214,2707215,2707231,2707264,2707265,2707266,2707267,2707268,2707269,2707270,2707271,2707272,2707273,2707274,2707275,2707276,2707277,2707278,2707279,2707280,2707281,2707282,2707283,2707284,2707285,2707286,2707287,2707288,2707289,2707290,2707291,2707292,2707293,2707294,2707295,2707296,2707297,2707298,2707299,2707300,2707301,2707302,2707303,2707304,2707305,2707306,2707307,2707308,2707309,2707310,2707311,2707312,2707313,2707314,2707315,2707316,2707317,2707318,2707319,2707320,2707321,2707322,2707323,2707324,2707325,2707326,2707327,2707328,2707329,2707330,2707331,2707332,2707333,2707334,2707335,2707336,2707337,2707338,2707339,2707340,2707341,2707342,2707343,2707359,2707392,2707393,2707394,2707395,2707396,2707397,2707398,2707399,2707400,2707401,2707402,2707403,2707404,2707405,2707406,2707407,2707408,2707409,2707410,2707411,2707412,2707413,2707414,2707415,2707416,2707417,2707418,2707419,2707420,2707421,2707422,2707423,2707424,2707425,2707426,2707427,2707428,2707429,2707430,2707431,2707432,2707433,2707434,2707435,2707436,2707437,2707438,2707439,2707440,2707441,2707442,2707443,2707444,2707445,2707446,2707447,2707448,2707449,2707450,2707451,2707452,2707453,2707454,2707455],"Deepslate Tiles":[2706508],"Detector Rail":[2591104,2591105,2591106,2591107,2591108,2591109,2591112,2591113,2591114,2591115,2591116,2591117],"Diamond Block":[2591370],"Diamond Ore":[2591627],"Diorite":[2591884],"Diorite Slab":[2592140,2592141,2592143],"Diorite Stairs":[2592392,2592393,2592394,2592395,2592396,2592397,2592398,2592399],"Diorite Wall":[2592512,2592513,2592514,2592515,2592516,2592517,2592518,2592519,2592520,2592521,2592522,2592523,2592524,2592525,2592526,2592527,2592528,2592529,2592530,2592531,2592532,2592533,2592534,2592535,2592536,2592537,2592538,2592539,2592540,2592541,2592542,2592543,2592544,2592545,2592546,2592547,2592548,2592549,2592550,2592551,2592552,2592553,2592554,2592555,2592556,2592557,2592558,2592559,2592560,2592561,2592562,2592563,2592564,2592565,2592566,2592567,2592568,2592569,2592570,2592571,2592572,2592573,2592574,2592575,2592576,2592577,2592578,2592579,2592580,2592581,2592582,2592583,2592584,2592585,2592586,2592587,2592588,2592589,2592590,2592591,2592607,2592640,2592641,2592642,2592643,2592644,2592645,2592646,2592647,2592648,2592649,2592650,2592651,2592652,2592653,2592654,2592655,2592656,2592657,2592658,2592659,2592660,2592661,2592662,2592663,2592664,2592665,2592666,2592667,2592668,2592669,2592670,2592671,2592672,2592673,2592674,2592675,2592676,2592677,2592678,2592679,2592680,2592681,2592682,2592683,2592684,2592685,2592686,2592687,2592688,2592689,2592690,2592691,2592692,2592693,2592694,2592695,2592696,2592697,2592698,2592699,2592700,2592701,2592702,2592703,2592704,2592705,2592706,2592707,2592708,2592709,2592710,2592711,2592712,2592713,2592714,2592715,2592716,2592717,2592718,2592719,2592735],"Dirt":[2592912,2592913,2592914],"Double Tallgrass":[2593168,2593169],"Dragon Egg":[2593426],"Dried Kelp Block":[2593683],"Dubnium":[2601393],"Dyed Candle":[2729344,2729345,2729346,2729347,2729348,2729349,2729350,2729351,2729352,2729353,2729354,2729355,2729356,2729357,2729358,2729359,2729360,2729361,2729362,2729363,2729364,2729365,2729366,2729367,2729368,2729369,2729370,2729371,2729372,2729373,2729374,2729375,2729376,2729377,2729378,2729379,2729380,2729381,2729382,2729383,2729384,2729385,2729386,2729387,2729388,2729389,2729390,2729391,2729392,2729393,2729394,2729395,2729396,2729397,2729398,2729399,2729400,2729401,2729402,2729403,2729404,2729405,2729406,2729407,2729408,2729409,2729410,2729411,2729412,2729413,2729414,2729415,2729416,2729417,2729418,2729419,2729420,2729421,2729422,2729423,2729424,2729425,2729426,2729427,2729428,2729429,2729430,2729431,2729432,2729433,2729434,2729435,2729436,2729437,2729438,2729439,2729440,2729441,2729442,2729443,2729444,2729445,2729446,2729447,2729448,2729449,2729450,2729451,2729452,2729453,2729454,2729455,2729456,2729457,2729458,2729459,2729460,2729461,2729462,2729463,2729464,2729465,2729466,2729467,2729468,2729469,2729470,2729471],"Dyed Shulker Box":[2593936,2593937,2593938,2593939,2593940,2593941,2593942,2593943,2593944,2593945,2593946,2593947,2593948,2593949,2593950,2593951],"Dysprosium":[2601650],"Einsteinium":[2601907],"Element Constructor":[2600108,2600109,2600110,2600111],"Emerald Block":[2624781],"Emerald Ore":[2625038],"Enchanting Table":[2625295],"End Portal Frame":[2625552,2625553,2625554,2625555,2625556,2625557,2625558,2625559],"End Rod":[2625808,2625809,2625810,2625811,2625812,2625813],"End Stone":[2626066],"End Stone Brick Slab":[2626321,2626322,2626323],"End Stone Brick Stairs":[2626576,2626577,2626578,2626579,2626580,2626581,2626582,2626583],"End Stone Brick Wall":[2626816,2626817,2626818,2626819,2626820,2626821,2626822,2626823,2626824,2626825,2626826,2626827,2626828,2626829,2626830,2626831,2626832,2626833,2626834,2626835,2626836,2626837,2626838,2626839,2626840,2626841,2626842,2626843,2626844,2626845,2626846,2626847,2626848,2626849,2626850,2626851,2626852,2626853,2626854,2626855,2626856,2626857,2626858,2626859,2626860,2626861,2626862,2626863,2626864,2626865,2626866,2626867,2626868,2626869,2626870,2626871,2626872,2626873,2626874,2626875,2626876,2626877,2626878,2626879,2626885,2626896,2626897,2626898,2626899,2626900,2626901,2626902,2626903,2626904,2626905,2626906,2626907,2626908,2626909,2626910,2626911,2626944,2626945,2626946,2626947,2626948,2626949,2626950,2626951,2626952,2626953,2626954,2626955,2626956,2626957,2626958,2626959,2626960,2626961,2626962,2626963,2626964,2626965,2626966,2626967,2626968,2626969,2626970,2626971,2626972,2626973,2626974,2626975,2626976,2626977,2626978,2626979,2626980,2626981,2626982,2626983,2626984,2626985,2626986,2626987,2626988,2626989,2626990,2626991,2626992,2626993,2626994,2626995,2626996,2626997,2626998,2626999,2627000,2627001,2627002,2627003,2627004,2627005,2627006,2627007,2627013,2627024,2627025,2627026,2627027,2627028,2627029,2627030,2627031,2627032,2627033,2627034,2627035,2627036,2627037,2627038,2627039],"End Stone Bricks":[2627094],"Ender Chest":[2627348,2627349,2627350,2627351],"Erbium":[2602164],"Europium":[2602421],"Fake Wooden Slab":[2627608,2627609,2627610],"Farmland":[2627864,2627865,2627866,2627867,2627868,2627869,2627870,2627871],"Fermium":[2602678],"Fern":[2628122],"Fire Block":[2628368,2628369,2628370,2628371,2628372,2628373,2628374,2628375,2628376,2628377,2628378,2628379,2628380,2628381,2628382,2628383],"Flerovium":[2602935],"Fletching Table":[2628636],"Flower Pot":[2628893],"Flowering Azalea Leaves":[2736060,2736061,2736062,2736063],"Fluorine":[2603192],"Francium":[2603449],"Froglight":[2734001,2734002,2734003,2734005,2734006,2734007,2734013,2734014,2734015],"Frosted Ice":[2629148,2629149,2629150,2629151],"Furnace":[2629400,2629401,2629402,2629403,2629404,2629405,2629406,2629407],"Gadolinium":[2603706],"Gallium":[2603963],"Germanium":[2604220],"Gilded Blackstone":[2727582],"Glass":[2629664],"Glass Pane":[2629921],"Glazed Terracotta":[2697984,2697985,2697986,2697987,2697988,2697989,2697990,2697991,2697992,2697993,2697994,2697995,2697996,2697997,2697998,2697999,2698000,2698001,2698002,2698003,2698004,2698005,2698006,2698007,2698008,2698009,2698010,2698011,2698012,2698013,2698014,2698015,2698016,2698017,2698018,2698019,2698020,2698021,2698022,2698023,2698024,2698025,2698026,2698027,2698028,2698029,2698030,2698031,2698032,2698033,2698034,2698035,2698036,2698037,2698038,2698039,2698040,2698041,2698042,2698043,2698044,2698045,2698046,2698047],"Glow Item Frame":[2735280,2735281,2735284,2735285,2735286,2735287,2735288,2735289,2735292,2735293,2735294,2735295],"Glow Lichen":[2736832,2736833,2736834,2736835,2736836,2736837,2736838,2736839,2736840,2736841,2736842,2736843,2736844,2736845,2736846,2736847,2736848,2736849,2736850,2736851,2736852,2736853,2736854,2736855,2736856,2736857,2736858,2736859,2736860,2736861,2736862,2736863,2736864,2736865,2736866,2736867,2736868,2736869,2736870,2736871,2736872,2736873,2736874,2736875,2736876,2736877,2736878,2736879,2736880,2736881,2736882,2736883,2736884,2736885,2736886,2736887,2736888,2736889,2736890,2736891,2736892,2736893,2736894,2736895],"Glowing Obsidian":[2630178],"Glowstone":[2630435],"Gold":[2604477],"Gold Block":[2630692],"Gold Ore":[2630949],"Granite":[2631206],"Granite Slab":[2631461,2631462,2631463],"Granite Stairs":[2631720,2631721,2631722,2631723,2631724,2631725,2631726,2631727],"Granite Wall":[2631936,2631937,2631938,2631939,2631940,2631941,2631942,2631943,2631944,2631945,2631946,2631947,2631948,2631949,2631950,2631951,2631952,2631953,2631954,2631955,2631956,2631957,2631958,2631959,2631960,2631961,2631962,2631963,2631964,2631965,2631966,2631967,2631968,2631969,2631970,2631971,2631972,2631973,2631974,2631975,2631976,2631977,2631978,2631979,2631980,2631981,2631982,2631983,2631984,2631985,2631986,2631987,2631988,2631989,2631990,2631991,2631992,2631993,2631994,2631995,2631996,2631997,2631998,2631999,2632032,2632033,2632034,2632035,2632036,2632037,2632038,2632039,2632040,2632041,2632042,2632043,2632044,2632045,2632046,2632047,2632057,2632064,2632065,2632066,2632067,2632068,2632069,2632070,2632071,2632072,2632073,2632074,2632075,2632076,2632077,2632078,2632079,2632080,2632081,2632082,2632083,2632084,2632085,2632086,2632087,2632088,2632089,2632090,2632091,2632092,2632093,2632094,2632095,2632096,2632097,2632098,2632099,2632100,2632101,2632102,2632103,2632104,2632105,2632106,2632107,2632108,2632109,2632110,2632111,2632112,2632113,2632114,2632115,2632116,2632117,2632118,2632119,2632120,2632121,2632122,2632123,2632124,2632125,2632126,2632127,2632160,2632161,2632162,2632163,2632164,2632165,2632166,2632167,2632168,2632169,2632170,2632171,2632172,2632173,2632174,2632175,2632185],"Grass":[2632234],"Grass Path":[2632491],"Gravel":[2632748],"Green Torch":[2633514,2633515,2633516,2633517,2633518],"Hafnium":[2604734],"Hanging Roots":[2730409],"Hardened Clay":[2633776],"Hardened Glass":[2634033],"Hardened Glass Pane":[2634290],"Hassium":[2604991],"Hay Bale":[2634545,2634546,2634547],"Heat Block":[2578263],"Helium":[2605248],"Holmium":[2605505],"Honeycomb Block":[2722699],"Hopper":[2634800,2634801,2634804,2634806,2634807,2634808,2634809,2634812,2634814,2634815],"Hydrogen":[2605762],"Ice":[2635061],"Indium":[2606019],"Infested Chiseled Stone Brick":[2635318],"Infested Cobblestone":[2635575],"Infested Cracked Stone Brick":[2635832],"Infested Mossy Stone Brick":[2636089],"Infested Stone":[2636346],"Infested Stone Brick":[2636603],"Invisible Bedrock":[2637374],"Iodine":[2606276],"Iridium":[2606533],"Iron":[2606790],"Iron Bars":[2637888],"Iron Block":[2637631],"Iron Door":[2638144,2638145,2638146,2638147,2638148,2638149,2638150,2638151,2638152,2638153,2638154,2638155,2638156,2638157,2638158,2638159,2638160,2638161,2638162,2638163,2638164,2638165,2638166,2638167,2638168,2638169,2638170,2638171,2638172,2638173,2638174,2638175],"Iron Ore":[2638402],"Iron Trapdoor":[2638656,2638657,2638658,2638659,2638660,2638661,2638662,2638663,2638664,2638665,2638666,2638667,2638668,2638669,2638670,2638671],"Item Frame":[2638912,2638913,2638916,2638917,2638918,2638919,2638920,2638921,2638924,2638925,2638926,2638927],"Jack o'Lantern":[2647396,2647397,2647398,2647399],"Jukebox":[2639173],"Jungle Button":[2639426,2639427,2639428,2639429,2639430,2639431,2639434,2639435,2639436,2639437,2639438,2639439],"Jungle Door":[2639680,2639681,2639682,2639683,2639684,2639685,2639686,2639687,2639688,2639689,2639690,2639691,2639692,2639693,2639694,2639695,2639696,2639697,2639698,2639699,2639700,2639701,2639702,2639703,2639704,2639705,2639706,2639707,2639708,2639709,2639710,2639711],"Jungle Fence":[2639944],"Jungle Fence Gate":[2640192,2640193,2640194,2640195,2640196,2640197,2640198,2640199,2640200,2640201,2640202,2640203,2640204,2640205,2640206,2640207],"Jungle Leaves":[2640456,2640457,2640458,2640459],"Jungle Log":[2640712,2640713,2640714,2640715,2640718,2640719],"Jungle Planks":[2640972],"Jungle Pressure Plate":[2641228,2641229],"Jungle Sapling":[2641486,2641487],"Jungle Sign":[2641728,2641729,2641730,2641731,2641732,2641733,2641734,2641735,2641736,2641737,2641738,2641739,2641740,2641741,2641742,2641743],"Jungle Slab":[2642000,2642001,2642002],"Jungle Stairs":[2642256,2642257,2642258,2642259,2642260,2642261,2642262,2642263],"Jungle Trapdoor":[2642512,2642513,2642514,2642515,2642516,2642517,2642518,2642519,2642520,2642521,2642522,2642523,2642524,2642525,2642526,2642527],"Jungle Wall Sign":[2642768,2642769,2642770,2642771],"Jungle Wood":[2643024,2643025,2643028,2643029,2643030,2643031],"Krypton":[2607047],"Lab Table":[2643284,2643285,2643286,2643287],"Ladder":[2643540,2643541,2643542,2643543],"Lantern":[2643798,2643799],"Lanthanum":[2607304],"Lapis Lazuli Block":[2644056],"Lapis Lazuli Ore":[2644313],"Large Fern":[2644570,2644571],"Lava":[2644800,2644801,2644802,2644803,2644804,2644805,2644806,2644807,2644808,2644809,2644810,2644811,2644812,2644813,2644814,2644815,2644816,2644817,2644818,2644819,2644820,2644821,2644822,2644823,2644824,2644825,2644826,2644827,2644828,2644829,2644830,2644831],"Lava Cauldron":[2732208,2732209,2732210,2732211,2732212,2732213],"Lawrencium":[2607561],"Lead":[2607818],"Lectern":[2645080,2645081,2645082,2645083,2645084,2645085,2645086,2645087],"Legacy Stonecutter":[2645341],"Lever":[2645584,2645585,2645586,2645587,2645588,2645589,2645590,2645591,2645592,2645593,2645594,2645595,2645596,2645597,2645598,2645599],"Light Block":[2703680,2703681,2703682,2703683,2703684,2703685,2703686,2703687,2703688,2703689,2703690,2703691,2703692,2703693,2703694,2703695],"Lightning Rod":[2727834,2727835,2727836,2727837,2727838,2727839],"Lilac":[2646368,2646369],"Lily Pad":[2646883],"Lily of the Valley":[2646626],"Lithium":[2608075],"Livermorium":[2608332],"Loom":[2647652,2647653,2647654,2647655],"Lutetium":[2608589],"Magma Block":[2648168],"Magnesium":[2608846],"Manganese":[2609103],"Mangrove Button":[2717040,2717041,2717044,2717045,2717046,2717047,2717048,2717049,2717052,2717053,2717054,2717055],"Mangrove Door":[2718560,2718561,2718562,2718563,2718564,2718565,2718566,2718567,2718568,2718569,2718570,2718571,2718572,2718573,2718574,2718575,2718576,2718577,2718578,2718579,2718580,2718581,2718582,2718583,2718584,2718585,2718586,2718587,2718588,2718589,2718590,2718591],"Mangrove Fence":[2713190],"Mangrove Fence Gate":[2719344,2719345,2719346,2719347,2719348,2719349,2719350,2719351,2719352,2719353,2719354,2719355,2719356,2719357,2719358,2719359],"Mangrove Leaves":[2735548,2735549,2735550,2735551],"Mangrove Log":[2714728,2714729,2714732,2714733,2714734,2714735],"Mangrove Planks":[2712419],"Mangrove Pressure Plate":[2717816,2717817],"Mangrove Roots":[2733493],"Mangrove Sign":[2720896,2720897,2720898,2720899,2720900,2720901,2720902,2720903,2720904,2720905,2720906,2720907,2720908,2720909,2720910,2720911],"Mangrove Slab":[2713960,2713961,2713963],"Mangrove Stairs":[2720128,2720129,2720130,2720131,2720132,2720133,2720134,2720135],"Mangrove Trapdoor":[2716272,2716273,2716274,2716275,2716276,2716277,2716278,2716279,2716280,2716281,2716282,2716283,2716284,2716285,2716286,2716287],"Mangrove Wall Sign":[2721668,2721669,2721670,2721671],"Mangrove Wood":[2715498,2715499,2715500,2715501,2715502,2715503],"Material Reducer":[2648424,2648425,2648426,2648427],"Meitnerium":[2609360],"Melon Block":[2648682],"Melon Stem":[2648936,2648937,2648938,2648939,2648940,2648941,2648942,2648943],"Mendelevium":[2609617],"Mercury":[2609874],"Mob Head":[2649152,2649153,2649156,2649157,2649158,2649159,2649160,2649161,2649164,2649165,2649166,2649167,2649184,2649185,2649188,2649189,2649190,2649191,2649200,2649201,2649204,2649205,2649206,2649207,2649208,2649209,2649212,2649213,2649214,2649215],"Molybdenum":[2610131],"Monster Spawner":[2649453],"Moscovium":[2610388],"Mossy Cobblestone":[2649710],"Mossy Cobblestone Slab":[2649965,2649966,2649967],"Mossy Cobblestone Stairs":[2650224,2650225,2650226,2650227,2650228,2650229,2650230,2650231],"Mossy Cobblestone Wall":[2650401,2650416,2650417,2650418,2650419,2650420,2650421,2650422,2650423,2650424,2650425,2650426,2650427,2650428,2650429,2650430,2650431,2650432,2650433,2650434,2650435,2650436,2650437,2650438,2650439,2650440,2650441,2650442,2650443,2650444,2650445,2650446,2650447,2650448,2650449,2650450,2650451,2650452,2650453,2650454,2650455,2650456,2650457,2650458,2650459,2650460,2650461,2650462,2650463,2650464,2650465,2650466,2650467,2650468,2650469,2650470,2650471,2650472,2650473,2650474,2650475,2650476,2650477,2650478,2650479,2650480,2650481,2650482,2650483,2650484,2650485,2650486,2650487,2650488,2650489,2650490,2650491,2650492,2650493,2650494,2650495,2650529,2650544,2650545,2650546,2650547,2650548,2650549,2650550,2650551,2650552,2650553,2650554,2650555,2650556,2650557,2650558,2650559,2650560,2650561,2650562,2650563,2650564,2650565,2650566,2650567,2650568,2650569,2650570,2650571,2650572,2650573,2650574,2650575,2650576,2650577,2650578,2650579,2650580,2650581,2650582,2650583,2650584,2650585,2650586,2650587,2650588,2650589,2650590,2650591,2650592,2650593,2650594,2650595,2650596,2650597,2650598,2650599,2650600,2650601,2650602,2650603,2650604,2650605,2650606,2650607,2650608,2650609,2650610,2650611,2650612,2650613,2650614,2650615,2650616,2650617,2650618,2650619,2650620,2650621,2650622,2650623],"Mossy Stone Brick Slab":[2650736,2650738,2650739],"Mossy Stone Brick Stairs":[2650992,2650993,2650994,2650995,2650996,2650997,2650998,2650999],"Mossy Stone Brick Wall":[2651172,2651184,2651185,2651186,2651187,2651188,2651189,2651190,2651191,2651192,2651193,2651194,2651195,2651196,2651197,2651198,2651199,2651200,2651201,2651202,2651203,2651204,2651205,2651206,2651207,2651208,2651209,2651210,2651211,2651212,2651213,2651214,2651215,2651216,2651217,2651218,2651219,2651220,2651221,2651222,2651223,2651224,2651225,2651226,2651227,2651228,2651229,2651230,2651231,2651232,2651233,2651234,2651235,2651236,2651237,2651238,2651239,2651240,2651241,2651242,2651243,2651244,2651245,2651246,2651247,2651248,2651249,2651250,2651251,2651252,2651253,2651254,2651255,2651256,2651257,2651258,2651259,2651260,2651261,2651262,2651263,2651300,2651312,2651313,2651314,2651315,2651316,2651317,2651318,2651319,2651320,2651321,2651322,2651323,2651324,2651325,2651326,2651327,2651328,2651329,2651330,2651331,2651332,2651333,2651334,2651335,2651336,2651337,2651338,2651339,2651340,2651341,2651342,2651343,2651344,2651345,2651346,2651347,2651348,2651349,2651350,2651351,2651352,2651353,2651354,2651355,2651356,2651357,2651358,2651359,2651360,2651361,2651362,2651363,2651364,2651365,2651366,2651367,2651368,2651369,2651370,2651371,2651372,2651373,2651374,2651375,2651376,2651377,2651378,2651379,2651380,2651381,2651382,2651383,2651384,2651385,2651386,2651387,2651388,2651389,2651390,2651391],"Mossy Stone Bricks":[2651509],"Mud":[2725526],"Mud Brick Slab":[2726040,2726041,2726042],"Mud Brick Stairs":[2726296,2726297,2726298,2726299,2726300,2726301,2726302,2726303],"Mud Brick Wall":[2726400,2726401,2726402,2726403,2726404,2726405,2726406,2726407,2726408,2726409,2726410,2726411,2726412,2726413,2726414,2726415,2726416,2726417,2726418,2726419,2726420,2726421,2726422,2726423,2726424,2726425,2726426,2726427,2726428,2726429,2726430,2726431,2726432,2726433,2726434,2726435,2726436,2726437,2726438,2726439,2726440,2726441,2726442,2726443,2726444,2726445,2726446,2726447,2726448,2726449,2726450,2726451,2726452,2726453,2726454,2726455,2726456,2726457,2726458,2726459,2726460,2726461,2726462,2726463,2726474,2726480,2726481,2726482,2726483,2726484,2726485,2726486,2726487,2726488,2726489,2726490,2726491,2726492,2726493,2726494,2726495,2726528,2726529,2726530,2726531,2726532,2726533,2726534,2726535,2726536,2726537,2726538,2726539,2726540,2726541,2726542,2726543,2726544,2726545,2726546,2726547,2726548,2726549,2726550,2726551,2726552,2726553,2726554,2726555,2726556,2726557,2726558,2726559,2726560,2726561,2726562,2726563,2726564,2726565,2726566,2726567,2726568,2726569,2726570,2726571,2726572,2726573,2726574,2726575,2726576,2726577,2726578,2726579,2726580,2726581,2726582,2726583,2726584,2726585,2726586,2726587,2726588,2726589,2726590,2726591,2726602,2726608,2726609,2726610,2726611,2726612,2726613,2726614,2726615,2726616,2726617,2726618,2726619,2726620,2726621,2726622,2726623],"Mud Bricks":[2725783],"Muddy Mangrove Roots":[2733748,2733750,2733751],"Mushroom Stem":[2651766],"Mycelium":[2652023],"Neodymium":[2610645],"Neon":[2610902],"Neptunium":[2611159],"Nether Brick Fence":[2652280],"Nether Brick Slab":[2652536,2652537,2652539],"Nether Brick Stairs":[2652792,2652793,2652794,2652795,2652796,2652797,2652798,2652799],"Nether Brick Wall":[2652971,2652976,2652977,2652978,2652979,2652980,2652981,2652982,2652983,2652984,2652985,2652986,2652987,2652988,2652989,2652990,2652991,2652992,2652993,2652994,2652995,2652996,2652997,2652998,2652999,2653000,2653001,2653002,2653003,2653004,2653005,2653006,2653007,2653008,2653009,2653010,2653011,2653012,2653013,2653014,2653015,2653016,2653017,2653018,2653019,2653020,2653021,2653022,2653023,2653024,2653025,2653026,2653027,2653028,2653029,2653030,2653031,2653032,2653033,2653034,2653035,2653036,2653037,2653038,2653039,2653040,2653041,2653042,2653043,2653044,2653045,2653046,2653047,2653048,2653049,2653050,2653051,2653052,2653053,2653054,2653055,2653099,2653104,2653105,2653106,2653107,2653108,2653109,2653110,2653111,2653112,2653113,2653114,2653115,2653116,2653117,2653118,2653119,2653120,2653121,2653122,2653123,2653124,2653125,2653126,2653127,2653128,2653129,2653130,2653131,2653132,2653133,2653134,2653135,2653136,2653137,2653138,2653139,2653140,2653141,2653142,2653143,2653144,2653145,2653146,2653147,2653148,2653149,2653150,2653151,2653152,2653153,2653154,2653155,2653156,2653157,2653158,2653159,2653160,2653161,2653162,2653163,2653164,2653165,2653166,2653167,2653168,2653169,2653170,2653171,2653172,2653173,2653174,2653175,2653176,2653177,2653178,2653179,2653180,2653181,2653182,2653183],"Nether Bricks":[2653308],"Nether Gold Ore":[2725269],"Nether Portal":[2653564,2653565],"Nether Quartz Ore":[2653822],"Nether Reactor Core":[2654079],"Nether Wart":[2654336,2654337,2654338,2654339],"Nether Wart Block":[2654593],"Netherite Block":[2731180],"Netherrack":[2654850],"Nickel":[2611416],"Nihonium":[2611673],"Niobium":[2611930],"Nitrogen":[2612187],"Nobelium":[2612444],"Note Block":[2655107],"Oak Button":[2655360,2655361,2655364,2655365,2655366,2655367,2655368,2655369,2655372,2655373,2655374,2655375],"Oak Door":[2655616,2655617,2655618,2655619,2655620,2655621,2655622,2655623,2655624,2655625,2655626,2655627,2655628,2655629,2655630,2655631,2655632,2655633,2655634,2655635,2655636,2655637,2655638,2655639,2655640,2655641,2655642,2655643,2655644,2655645,2655646,2655647],"Oak Fence":[2655878],"Oak Fence Gate":[2656128,2656129,2656130,2656131,2656132,2656133,2656134,2656135,2656136,2656137,2656138,2656139,2656140,2656141,2656142,2656143],"Oak Leaves":[2656392,2656393,2656394,2656395],"Oak Log":[2656648,2656649,2656650,2656651,2656652,2656653],"Oak Planks":[2656906],"Oak Pressure Plate":[2657162,2657163],"Oak Sapling":[2657420,2657421],"Oak Sign":[2657664,2657665,2657666,2657667,2657668,2657669,2657670,2657671,2657672,2657673,2657674,2657675,2657676,2657677,2657678,2657679],"Oak Slab":[2657932,2657934,2657935],"Oak Stairs":[2658184,2658185,2658186,2658187,2658188,2658189,2658190,2658191],"Oak Trapdoor":[2658448,2658449,2658450,2658451,2658452,2658453,2658454,2658455,2658456,2658457,2658458,2658459,2658460,2658461,2658462,2658463],"Oak Wall Sign":[2658704,2658705,2658706,2658707],"Oak Wood":[2658960,2658961,2658962,2658963,2658966,2658967],"Obsidian":[2659219],"Oganesson":[2612701],"Orange Tulip":[2659733],"Osmium":[2612958],"Oxeye Daisy":[2659990],"Oxygen":[2613215],"Packed Ice":[2660247],"Packed Mud":[2726811],"Palladium":[2613472],"Peony":[2660504,2660505],"Phosphorus":[2613729],"Pink Tulip":[2661018],"Platinum":[2613986],"Plutonium":[2614243],"Podzol":[2661275],"Polished Andesite":[2661532],"Polished Andesite Slab":[2661788,2661789,2661791],"Polished Andesite Stairs":[2662040,2662041,2662042,2662043,2662044,2662045,2662046,2662047],"Polished Basalt":[2699053,2699054,2699055],"Polished Blackstone":[2700597],"Polished Blackstone Brick Slab":[2702652,2702653,2702655],"Polished Blackstone Brick Stairs":[2702904,2702905,2702906,2702907,2702908,2702909,2702910,2702911],"Polished Blackstone Brick Wall":[2703104,2703105,2703106,2703107,2703108,2703109,2703110,2703111,2703112,2703113,2703114,2703115,2703116,2703117,2703118,2703119,2703120,2703121,2703122,2703123,2703124,2703125,2703126,2703127,2703128,2703129,2703130,2703131,2703132,2703133,2703134,2703135,2703136,2703137,2703138,2703139,2703140,2703141,2703142,2703143,2703144,2703145,2703146,2703147,2703148,2703149,2703150,2703151,2703152,2703153,2703154,2703155,2703156,2703157,2703158,2703159,2703160,2703161,2703162,2703163,2703164,2703165,2703166,2703167,2703215,2703216,2703217,2703218,2703219,2703220,2703221,2703222,2703223,2703224,2703225,2703226,2703227,2703228,2703229,2703230,2703231,2703232,2703233,2703234,2703235,2703236,2703237,2703238,2703239,2703240,2703241,2703242,2703243,2703244,2703245,2703246,2703247,2703248,2703249,2703250,2703251,2703252,2703253,2703254,2703255,2703256,2703257,2703258,2703259,2703260,2703261,2703262,2703263,2703264,2703265,2703266,2703267,2703268,2703269,2703270,2703271,2703272,2703273,2703274,2703275,2703276,2703277,2703278,2703279,2703280,2703281,2703282,2703283,2703284,2703285,2703286,2703287,2703288,2703289,2703290,2703291,2703292,2703293,2703294,2703295,2703343,2703344,2703345,2703346,2703347,2703348,2703349,2703350,2703351,2703352,2703353,2703354,2703355,2703356,2703357,2703358,2703359],"Polished Blackstone Bricks":[2702396],"Polished Blackstone Button":[2700850,2700851,2700852,2700853,2700854,2700855,2700858,2700859,2700860,2700861,2700862,2700863],"Polished Blackstone Pressure Plate":[2701110,2701111],"Polished Blackstone Slab":[2701368,2701369,2701370],"Polished Blackstone Stairs":[2701624,2701625,2701626,2701627,2701628,2701629,2701630,2701631],"Polished Blackstone Wall":[2701824,2701825,2701826,2701827,2701828,2701829,2701830,2701831,2701832,2701833,2701834,2701835,2701836,2701837,2701838,2701839,2701840,2701841,2701842,2701843,2701844,2701845,2701846,2701847,2701848,2701849,2701850,2701851,2701852,2701853,2701854,2701855,2701856,2701857,2701858,2701859,2701860,2701861,2701862,2701863,2701864,2701865,2701866,2701867,2701868,2701869,2701870,2701871,2701872,2701873,2701874,2701875,2701876,2701877,2701878,2701879,2701880,2701881,2701882,2701883,2701884,2701885,2701886,2701887,2701930,2701936,2701937,2701938,2701939,2701940,2701941,2701942,2701943,2701944,2701945,2701946,2701947,2701948,2701949,2701950,2701951,2701952,2701953,2701954,2701955,2701956,2701957,2701958,2701959,2701960,2701961,2701962,2701963,2701964,2701965,2701966,2701967,2701968,2701969,2701970,2701971,2701972,2701973,2701974,2701975,2701976,2701977,2701978,2701979,2701980,2701981,2701982,2701983,2701984,2701985,2701986,2701987,2701988,2701989,2701990,2701991,2701992,2701993,2701994,2701995,2701996,2701997,2701998,2701999,2702000,2702001,2702002,2702003,2702004,2702005,2702006,2702007,2702008,2702009,2702010,2702011,2702012,2702013,2702014,2702015,2702058,2702064,2702065,2702066,2702067,2702068,2702069,2702070,2702071,2702072,2702073,2702074,2702075,2702076,2702077,2702078,2702079],"Polished Deepslate":[2708821],"Polished Deepslate Slab":[2709076,2709078,2709079],"Polished Deepslate Stairs":[2709328,2709329,2709330,2709331,2709332,2709333,2709334,2709335],"Polished Deepslate Wall":[2709512,2709520,2709521,2709522,2709523,2709524,2709525,2709526,2709527,2709528,2709529,2709530,2709531,2709532,2709533,2709534,2709535,2709568,2709569,2709570,2709571,2709572,2709573,2709574,2709575,2709576,2709577,2709578,2709579,2709580,2709581,2709582,2709583,2709584,2709585,2709586,2709587,2709588,2709589,2709590,2709591,2709592,2709593,2709594,2709595,2709596,2709597,2709598,2709599,2709600,2709601,2709602,2709603,2709604,2709605,2709606,2709607,2709608,2709609,2709610,2709611,2709612,2709613,2709614,2709615,2709616,2709617,2709618,2709619,2709620,2709621,2709622,2709623,2709624,2709625,2709626,2709627,2709628,2709629,2709630,2709631,2709640,2709648,2709649,2709650,2709651,2709652,2709653,2709654,2709655,2709656,2709657,2709658,2709659,2709660,2709661,2709662,2709663,2709696,2709697,2709698,2709699,2709700,2709701,2709702,2709703,2709704,2709705,2709706,2709707,2709708,2709709,2709710,2709711,2709712,2709713,2709714,2709715,2709716,2709717,2709718,2709719,2709720,2709721,2709722,2709723,2709724,2709725,2709726,2709727,2709728,2709729,2709730,2709731,2709732,2709733,2709734,2709735,2709736,2709737,2709738,2709739,2709740,2709741,2709742,2709743,2709744,2709745,2709746,2709747,2709748,2709749,2709750,2709751,2709752,2709753,2709754,2709755,2709756,2709757,2709758,2709759],"Polished Diorite":[2662303],"Polished Diorite Slab":[2662560,2662561,2662562],"Polished Diorite Stairs":[2662816,2662817,2662818,2662819,2662820,2662821,2662822,2662823],"Polished Granite":[2663074],"Polished Granite Slab":[2663329,2663330,2663331],"Polished Granite Stairs":[2663584,2663585,2663586,2663587,2663588,2663589,2663590,2663591],"Polonium":[2614500],"Poppy":[2663845],"Potassium":[2614757],"Potato Block":[2664096,2664097,2664098,2664099,2664100,2664101,2664102,2664103],"Potion Cauldron":[2732464,2732465,2732466,2732467,2732468,2732469],"Powered Rail":[2664354,2664355,2664356,2664357,2664358,2664359,2664362,2664363,2664364,2664365,2664366,2664367],"Praseodymium":[2615014],"Prismarine":[2664616],"Prismarine Bricks":[2664873],"Prismarine Bricks Slab":[2665128,2665130,2665131],"Prismarine Bricks Stairs":[2665384,2665385,2665386,2665387,2665388,2665389,2665390,2665391],"Prismarine Slab":[2665644,2665645,2665646],"Prismarine Stairs":[2665896,2665897,2665898,2665899,2665900,2665901,2665902,2665903],"Prismarine Wall":[2665984,2665985,2665986,2665987,2665988,2665989,2665990,2665991,2665992,2665993,2665994,2665995,2665996,2665997,2665998,2665999,2666000,2666001,2666002,2666003,2666004,2666005,2666006,2666007,2666008,2666009,2666010,2666011,2666012,2666013,2666014,2666015,2666016,2666017,2666018,2666019,2666020,2666021,2666022,2666023,2666024,2666025,2666026,2666027,2666028,2666029,2666030,2666031,2666032,2666033,2666034,2666035,2666036,2666037,2666038,2666039,2666040,2666041,2666042,2666043,2666044,2666045,2666046,2666047,2666080,2666081,2666082,2666083,2666084,2666085,2666086,2666087,2666088,2666089,2666090,2666091,2666092,2666093,2666094,2666095,2666110,2666112,2666113,2666114,2666115,2666116,2666117,2666118,2666119,2666120,2666121,2666122,2666123,2666124,2666125,2666126,2666127,2666128,2666129,2666130,2666131,2666132,2666133,2666134,2666135,2666136,2666137,2666138,2666139,2666140,2666141,2666142,2666143,2666144,2666145,2666146,2666147,2666148,2666149,2666150,2666151,2666152,2666153,2666154,2666155,2666156,2666157,2666158,2666159,2666160,2666161,2666162,2666163,2666164,2666165,2666166,2666167,2666168,2666169,2666170,2666171,2666172,2666173,2666174,2666175,2666208,2666209,2666210,2666211,2666212,2666213,2666214,2666215,2666216,2666217,2666218,2666219,2666220,2666221,2666222,2666223,2666238],"Promethium":[2615271],"Protactinium":[2615528],"Pumpkin":[2666415],"Pumpkin Stem":[2666672,2666673,2666674,2666675,2666676,2666677,2666678,2666679],"Purple Torch":[2667184,2667185,2667187,2667190,2667191],"Purpur Block":[2667443],"Purpur Pillar":[2667700,2667701,2667702],"Purpur Slab":[2667956,2667957,2667959],"Purpur Stairs":[2668208,2668209,2668210,2668211,2668212,2668213,2668214,2668215],"Quartz Block":[2668471],"Quartz Bricks":[2709849],"Quartz Pillar":[2668728,2668729,2668730],"Quartz Slab":[2668984,2668985,2668987],"Quartz Stairs":[2669240,2669241,2669242,2669243,2669244,2669245,2669246,2669247],"Radium":[2615785],"Radon":[2616042],"Rail":[2669490,2669491,2669496,2669497,2669498,2669499,2669500,2669501,2669502,2669503],"Raw Copper Block":[2703938],"Raw Gold Block":[2704195],"Raw Iron Block":[2704452],"Red Mushroom":[2670013],"Red Mushroom Block":[2670260,2670262,2670263,2670264,2670265,2670266,2670267,2670268,2670269,2670270,2670271],"Red Nether Brick Slab":[2670525,2670526,2670527],"Red Nether Brick Stairs":[2670784,2670785,2670786,2670787,2670788,2670789,2670790,2670791],"Red Nether Brick Wall":[2670848,2670849,2670850,2670851,2670852,2670853,2670854,2670855,2670856,2670857,2670858,2670859,2670860,2670861,2670862,2670863,2670865,2670912,2670913,2670914,2670915,2670916,2670917,2670918,2670919,2670920,2670921,2670922,2670923,2670924,2670925,2670926,2670927,2670928,2670929,2670930,2670931,2670932,2670933,2670934,2670935,2670936,2670937,2670938,2670939,2670940,2670941,2670942,2670943,2670944,2670945,2670946,2670947,2670948,2670949,2670950,2670951,2670952,2670953,2670954,2670955,2670956,2670957,2670958,2670959,2670960,2670961,2670962,2670963,2670964,2670965,2670966,2670967,2670968,2670969,2670970,2670971,2670972,2670973,2670974,2670975,2670976,2670977,2670978,2670979,2670980,2670981,2670982,2670983,2670984,2670985,2670986,2670987,2670988,2670989,2670990,2670991,2670993,2671040,2671041,2671042,2671043,2671044,2671045,2671046,2671047,2671048,2671049,2671050,2671051,2671052,2671053,2671054,2671055,2671056,2671057,2671058,2671059,2671060,2671061,2671062,2671063,2671064,2671065,2671066,2671067,2671068,2671069,2671070,2671071,2671072,2671073,2671074,2671075,2671076,2671077,2671078,2671079,2671080,2671081,2671082,2671083,2671084,2671085,2671086,2671087,2671088,2671089,2671090,2671091,2671092,2671093,2671094,2671095,2671096,2671097,2671098,2671099,2671100,2671101,2671102,2671103],"Red Nether Bricks":[2671298],"Red Sand":[2671555],"Red Sandstone":[2671812],"Red Sandstone Slab":[2672068,2672069,2672071],"Red Sandstone Stairs":[2672320,2672321,2672322,2672323,2672324,2672325,2672326,2672327],"Red Sandstone Wall":[2672384,2672385,2672386,2672387,2672388,2672389,2672390,2672391,2672392,2672393,2672394,2672395,2672396,2672397,2672398,2672399,2672407,2672448,2672449,2672450,2672451,2672452,2672453,2672454,2672455,2672456,2672457,2672458,2672459,2672460,2672461,2672462,2672463,2672464,2672465,2672466,2672467,2672468,2672469,2672470,2672471,2672472,2672473,2672474,2672475,2672476,2672477,2672478,2672479,2672480,2672481,2672482,2672483,2672484,2672485,2672486,2672487,2672488,2672489,2672490,2672491,2672492,2672493,2672494,2672495,2672496,2672497,2672498,2672499,2672500,2672501,2672502,2672503,2672504,2672505,2672506,2672507,2672508,2672509,2672510,2672511,2672512,2672513,2672514,2672515,2672516,2672517,2672518,2672519,2672520,2672521,2672522,2672523,2672524,2672525,2672526,2672527,2672535,2672576,2672577,2672578,2672579,2672580,2672581,2672582,2672583,2672584,2672585,2672586,2672587,2672588,2672589,2672590,2672591,2672592,2672593,2672594,2672595,2672596,2672597,2672598,2672599,2672600,2672601,2672602,2672603,2672604,2672605,2672606,2672607,2672608,2672609,2672610,2672611,2672612,2672613,2672614,2672615,2672616,2672617,2672618,2672619,2672620,2672621,2672622,2672623,2672624,2672625,2672626,2672627,2672628,2672629,2672630,2672631,2672632,2672633,2672634,2672635,2672636,2672637,2672638,2672639],"Red Torch":[2672841,2672842,2672843,2672844,2672845],"Red Tulip":[2673097],"Redstone":[2674896,2674897,2674898,2674899,2674900,2674901,2674902,2674903,2674904,2674905,2674906,2674907,2674908,2674909,2674910,2674911],"Redstone Block":[2673354],"Redstone Comparator":[2673600,2673601,2673602,2673603,2673604,2673605,2673606,2673607,2673608,2673609,2673610,2673611,2673612,2673613,2673614,2673615],"Redstone Lamp":[2673868,2673869],"Redstone Ore":[2674124,2674125],"Redstone Repeater":[2674368,2674369,2674370,2674371,2674372,2674373,2674374,2674375,2674376,2674377,2674378,2674379,2674380,2674381,2674382,2674383,2674384,2674385,2674386,2674387,2674388,2674389,2674390,2674391,2674392,2674393,2674394,2674395,2674396,2674397,2674398,2674399],"Redstone Torch":[2674626,2674627,2674628,2674629,2674630,2674634,2674635,2674636,2674637,2674638],"Reinforced Deepslate":[2736320],"Rhenium":[2616299],"Rhodium":[2616556],"Roentgenium":[2616813],"Rose Bush":[2675410,2675411],"Rubidium":[2617070],"Ruthenium":[2617327],"Rutherfordium":[2617584],"Samarium":[2617841],"Sand":[2675667],"Sandstone":[2675924],"Sandstone Slab":[2676180,2676181,2676183],"Sandstone Stairs":[2676432,2676433,2676434,2676435,2676436,2676437,2676438,2676439],"Sandstone Wall":[2676487,2676496,2676497,2676498,2676499,2676500,2676501,2676502,2676503,2676504,2676505,2676506,2676507,2676508,2676509,2676510,2676511,2676544,2676545,2676546,2676547,2676548,2676549,2676550,2676551,2676552,2676553,2676554,2676555,2676556,2676557,2676558,2676559,2676560,2676561,2676562,2676563,2676564,2676565,2676566,2676567,2676568,2676569,2676570,2676571,2676572,2676573,2676574,2676575,2676576,2676577,2676578,2676579,2676580,2676581,2676582,2676583,2676584,2676585,2676586,2676587,2676588,2676589,2676590,2676591,2676592,2676593,2676594,2676595,2676596,2676597,2676598,2676599,2676600,2676601,2676602,2676603,2676604,2676605,2676606,2676607,2676615,2676624,2676625,2676626,2676627,2676628,2676629,2676630,2676631,2676632,2676633,2676634,2676635,2676636,2676637,2676638,2676639,2676672,2676673,2676674,2676675,2676676,2676677,2676678,2676679,2676680,2676681,2676682,2676683,2676684,2676685,2676686,2676687,2676688,2676689,2676690,2676691,2676692,2676693,2676694,2676695,2676696,2676697,2676698,2676699,2676700,2676701,2676702,2676703,2676704,2676705,2676706,2676707,2676708,2676709,2676710,2676711,2676712,2676713,2676714,2676715,2676716,2676717,2676718,2676719,2676720,2676721,2676722,2676723,2676724,2676725,2676726,2676727,2676728,2676729,2676730,2676731,2676732,2676733,2676734,2676735],"Scandium":[2618098],"Sculk":[2735035],"Sea Lantern":[2676952],"Sea Pickle":[2677208,2677209,2677210,2677211,2677212,2677213,2677214,2677215],"Seaborgium":[2618355],"Selenium":[2618612],"Shroomlight":[2712162],"Shulker Box":[2677466],"Silicon":[2618869],"Silver":[2619126],"Slime Block":[2677723],"Smithing Table":[2730923],"Smoker":[2677976,2677977,2677978,2677979,2677980,2677981,2677982,2677983],"Smooth Basalt":[2699312],"Smooth Quartz Block":[2678237],"Smooth Quartz Slab":[2678492,2678494,2678495],"Smooth Quartz Stairs":[2678744,2678745,2678746,2678747,2678748,2678749,2678750,2678751],"Smooth Red Sandstone":[2679008],"Smooth Red Sandstone Slab":[2679264,2679265,2679267],"Smooth Red Sandstone Stairs":[2679520,2679521,2679522,2679523,2679524,2679525,2679526,2679527],"Smooth Sandstone":[2679779],"Smooth Sandstone Slab":[2680036,2680037,2680038],"Smooth Sandstone Stairs":[2680288,2680289,2680290,2680291,2680292,2680293,2680294,2680295],"Smooth Stone":[2680550],"Smooth Stone Slab":[2680805,2680806,2680807],"Snow Block":[2681064],"Snow Layer":[2681320,2681321,2681322,2681323,2681324,2681325,2681326,2681327],"Sodium":[2619383],"Soul Fire":[2711905],"Soul Lantern":[2711390,2711391],"Soul Sand":[2681578],"Soul Soil":[2711648],"Soul Torch":[2711130,2711131,2711132,2711133,2711135],"Sponge":[2681834,2681835],"Spore Blossom":[2731437],"Spruce Button":[2682080,2682081,2682084,2682085,2682086,2682087,2682088,2682089,2682092,2682093,2682094,2682095],"Spruce Door":[2682336,2682337,2682338,2682339,2682340,2682341,2682342,2682343,2682344,2682345,2682346,2682347,2682348,2682349,2682350,2682351,2682352,2682353,2682354,2682355,2682356,2682357,2682358,2682359,2682360,2682361,2682362,2682363,2682364,2682365,2682366,2682367],"Spruce Fence":[2682606],"Spruce Fence Gate":[2682848,2682849,2682850,2682851,2682852,2682853,2682854,2682855,2682856,2682857,2682858,2682859,2682860,2682861,2682862,2682863],"Spruce Leaves":[2683120,2683121,2683122,2683123],"Spruce Log":[2683376,2683377,2683378,2683379,2683380,2683381],"Spruce Planks":[2683634],"Spruce Pressure Plate":[2683890,2683891],"Spruce Sapling":[2684148,2684149],"Spruce Sign":[2684400,2684401,2684402,2684403,2684404,2684405,2684406,2684407,2684408,2684409,2684410,2684411,2684412,2684413,2684414,2684415],"Spruce Slab":[2684660,2684662,2684663],"Spruce Stairs":[2684912,2684913,2684914,2684915,2684916,2684917,2684918,2684919],"Spruce Trapdoor":[2685168,2685169,2685170,2685171,2685172,2685173,2685174,2685175,2685176,2685177,2685178,2685179,2685180,2685181,2685182,2685183],"Spruce Wall Sign":[2685432,2685433,2685434,2685435],"Spruce Wood":[2685688,2685689,2685690,2685691,2685694,2685695],"Stained Clay":[2685936,2685937,2685938,2685939,2685940,2685941,2685942,2685943,2685944,2685945,2685946,2685947,2685948,2685949,2685950,2685951],"Stained Glass":[2686192,2686193,2686194,2686195,2686196,2686197,2686198,2686199,2686200,2686201,2686202,2686203,2686204,2686205,2686206,2686207],"Stained Glass Pane":[2686448,2686449,2686450,2686451,2686452,2686453,2686454,2686455,2686456,2686457,2686458,2686459,2686460,2686461,2686462,2686463],"Stained Hardened Glass":[2686704,2686705,2686706,2686707,2686708,2686709,2686710,2686711,2686712,2686713,2686714,2686715,2686716,2686717,2686718,2686719],"Stained Hardened Glass Pane":[2686960,2686961,2686962,2686963,2686964,2686965,2686966,2686967,2686968,2686969,2686970,2686971,2686972,2686973,2686974,2686975],"Stone":[2686976],"Stone Brick Slab":[2687232,2687233,2687235],"Stone Brick Stairs":[2687488,2687489,2687490,2687491,2687492,2687493,2687494,2687495],"Stone Brick Wall":[2687744,2687745,2687746,2687747,2687748,2687749,2687750,2687751,2687752,2687753,2687754,2687755,2687756,2687757,2687758,2687759,2687760,2687761,2687762,2687763,2687764,2687765,2687766,2687767,2687768,2687769,2687770,2687771,2687772,2687773,2687774,2687775,2687776,2687777,2687778,2687779,2687780,2687781,2687782,2687783,2687784,2687785,2687786,2687787,2687788,2687789,2687790,2687791,2687792,2687793,2687794,2687795,2687796,2687797,2687798,2687799,2687800,2687801,2687802,2687803,2687804,2687805,2687806,2687807,2687808,2687809,2687810,2687811,2687812,2687813,2687814,2687815,2687816,2687817,2687818,2687819,2687820,2687821,2687822,2687823,2687827,2687872,2687873,2687874,2687875,2687876,2687877,2687878,2687879,2687880,2687881,2687882,2687883,2687884,2687885,2687886,2687887,2687888,2687889,2687890,2687891,2687892,2687893,2687894,2687895,2687896,2687897,2687898,2687899,2687900,2687901,2687902,2687903,2687904,2687905,2687906,2687907,2687908,2687909,2687910,2687911,2687912,2687913,2687914,2687915,2687916,2687917,2687918,2687919,2687920,2687921,2687922,2687923,2687924,2687925,2687926,2687927,2687928,2687929,2687930,2687931,2687932,2687933,2687934,2687935,2687936,2687937,2687938,2687939,2687940,2687941,2687942,2687943,2687944,2687945,2687946,2687947,2687948,2687949,2687950,2687951,2687955],"Stone Bricks":[2688004],"Stone Button":[2688256,2688257,2688260,2688261,2688262,2688263,2688264,2688265,2688268,2688269,2688270,2688271],"Stone Pressure Plate":[2688518,2688519],"Stone Slab":[2688773,2688774,2688775],"Stone Stairs":[2689032,2689033,2689034,2689035,2689036,2689037,2689038,2689039],"Stonecutter":[2689288,2689289,2689290,2689291],"Strontium":[2619640],"Sugarcane":[2692624,2692625,2692626,2692627,2692628,2692629,2692630,2692631,2692632,2692633,2692634,2692635,2692636,2692637,2692638,2692639],"Sulfur":[2619897],"Sunflower":[2692886,2692887],"Sweet Berry Bush":[2693144,2693145,2693146,2693147],"TNT":[2693656,2693657,2693658,2693659],"Tall Grass":[2693401],"Tantalum":[2620154],"Technetium":[2620411],"Tellurium":[2620668],"Tennessine":[2620925],"Terbium":[2621182],"Thallium":[2621439],"Thorium":[2621440],"Thulium":[2621697],"Tin":[2621954],"Tinted Glass":[2722442],"Titanium":[2622211],"Torch":[2693912,2693913,2693914,2693918,2693919],"Trapped Chest":[2694172,2694173,2694174,2694175],"Tripwire":[2694416,2694417,2694418,2694419,2694420,2694421,2694422,2694423,2694424,2694425,2694426,2694427,2694428,2694429,2694430,2694431],"Tripwire Hook":[2694672,2694673,2694674,2694675,2694676,2694677,2694678,2694679,2694680,2694681,2694682,2694683,2694684,2694685,2694686,2694687],"Tuff":[2710877],"Tungsten":[2622468],"Twisting Vines":[2734240,2734241,2734248,2734249,2734250,2734251,2734252,2734253,2734254,2734255,2734256,2734257,2734258,2734259,2734260,2734261,2734262,2734263,2734264,2734265,2734266,2734267,2734268,2734269,2734270,2734271],"Underwater Torch":[2694938,2694939,2694940,2694941,2694942],"Uranium":[2622725],"Vanadium":[2622982],"Vines":[2695200,2695201,2695202,2695203,2695204,2695205,2695206,2695207,2695208,2695209,2695210,2695211,2695212,2695213,2695214,2695215],"Wall Banner":[2695424,2695425,2695426,2695427,2695428,2695429,2695430,2695431,2695432,2695433,2695434,2695435,2695436,2695437,2695438,2695439,2695440,2695441,2695442,2695443,2695444,2695445,2695446,2695447,2695448,2695449,2695450,2695451,2695452,2695453,2695454,2695455,2695456,2695457,2695458,2695459,2695460,2695461,2695462,2695463,2695464,2695465,2695466,2695467,2695468,2695469,2695470,2695471,2695472,2695473,2695474,2695475,2695476,2695477,2695478,2695479,2695480,2695481,2695482,2695483,2695484,2695485,2695486,2695487],"Wall Coral Fan":[2695680,2695681,2695682,2695683,2695686,2695688,2695689,2695690,2695691,2695694,2695696,2695697,2695698,2695699,2695702,2695704,2695705,2695706,2695707,2695710,2695712,2695713,2695714,2695715,2695718,2695720,2695721,2695722,2695723,2695726,2695728,2695729,2695730,2695731,2695734,2695736,2695737,2695738,2695739,2695742],"Warped Button":[2717554,2717555,2717556,2717557,2717558,2717559,2717562,2717563,2717564,2717565,2717566,2717567],"Warped Door":[2719072,2719073,2719074,2719075,2719076,2719077,2719078,2719079,2719080,2719081,2719082,2719083,2719084,2719085,2719086,2719087,2719088,2719089,2719090,2719091,2719092,2719093,2719094,2719095,2719096,2719097,2719098,2719099,2719100,2719101,2719102,2719103],"Warped Fence":[2713704],"Warped Fence Gate":[2719872,2719873,2719874,2719875,2719876,2719877,2719878,2719879,2719880,2719881,2719882,2719883,2719884,2719885,2719886,2719887],"Warped Hyphae":[2716016,2716017,2716018,2716019,2716020,2716021],"Warped Planks":[2712933],"Warped Pressure Plate":[2718330,2718331],"Warped Sign":[2721408,2721409,2721410,2721411,2721412,2721413,2721414,2721415,2721416,2721417,2721418,2721419,2721420,2721421,2721422,2721423],"Warped Slab":[2714473,2714474,2714475],"Warped Stairs":[2720640,2720641,2720642,2720643,2720644,2720645,2720646,2720647],"Warped Stem":[2715242,2715243,2715244,2715245,2715246,2715247],"Warped Trapdoor":[2716784,2716785,2716786,2716787,2716788,2716789,2716790,2716791,2716792,2716793,2716794,2716795,2716796,2716797,2716798,2716799],"Warped Wall Sign":[2722184,2722185,2722186,2722187],"Warped Wart Block":[2727068],"Water":[2695968,2695969,2695970,2695971,2695972,2695973,2695974,2695975,2695976,2695977,2695978,2695979,2695980,2695981,2695982,2695983,2695984,2695985,2695986,2695987,2695988,2695989,2695990,2695991,2695992,2695993,2695994,2695995,2695996,2695997,2695998,2695999],"Water Cauldron":[2731946,2731947,2731948,2731949,2731950,2731951],"Weeping Vines":[2734496,2734497,2734504,2734505,2734506,2734507,2734508,2734509,2734510,2734511,2734512,2734513,2734514,2734515,2734516,2734517,2734518,2734519,2734520,2734521,2734522,2734523,2734524,2734525,2734526,2734527],"Weighted Pressure Plate Heavy":[2696224,2696225,2696226,2696227,2696228,2696229,2696230,2696231,2696232,2696233,2696234,2696235,2696236,2696237,2696238,2696239],"Weighted Pressure Plate Light":[2696480,2696481,2696482,2696483,2696484,2696485,2696486,2696487,2696488,2696489,2696490,2696491,2696492,2696493,2696494,2696495],"Wheat Block":[2696736,2696737,2696738,2696739,2696740,2696741,2696742,2696743],"White Tulip":[2697256],"Wither Rose":[2730152],"Wool":[2697504,2697505,2697506,2697507,2697508,2697509,2697510,2697511,2697512,2697513,2697514,2697515,2697516,2697517,2697518,2697519],"Xenon":[2623239],"Ytterbium":[2623496],"Yttrium":[2623753],"Zinc":[2624267],"Zirconium":[2624524],"ate!upd":[2637117],"reserved6":[2675153],"update!":[2636860]},"stateDataBits":8}
\ No newline at end of file
+{
+ "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
+}
\ 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 9930c54f0c6..e86f70d70ee 100644
--- a/tests/phpunit/block/regenerate_consistency_check.php
+++ b/tests/phpunit/block/regenerate_consistency_check.php
@@ -21,84 +21,31 @@
declare(strict_types=1);
-use pocketmine\block\Block;
+use pocketmine\block\BlockTest;
use pocketmine\block\RuntimeBlockStateRegistry;
-use pocketmine\utils\AssumptionFailedError;
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). */
-$factory = new \pocketmine\block\RuntimeBlockStateRegistry();
-$remaps = [];
-$new = [];
-foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $index => $block){
- if($index !== $block->getStateId()){
- throw new AssumptionFailedError("State index should always match state ID");
- }
- $new[$index] = $block->getName();
-}
+$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
$oldTablePath = __DIR__ . '/block_factory_consistency_check.json';
if(file_exists($oldTablePath)){
- $oldTable = json_decode(file_get_contents($oldTablePath), true);
- if(!is_array($oldTable)){
- throw new \pocketmine\utils\AssumptionFailedError("Old table should be array{knownStates: array, stateDataBits: int}");
- }
- $old = [];
- /**
- * @var string $name
- * @var int[] $stateIds
- */
- foreach($oldTable["knownStates"] as $name => $stateIds){
- foreach($stateIds as $stateId){
- $old[$stateId] = $name;
- }
- }
- $oldStateDataSize = $oldTable["stateDataBits"];
- $oldStateDataMask = ~(~0 << $oldStateDataSize);
+ $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable);
- if($oldStateDataSize !== Block::INTERNAL_STATE_DATA_BITS){
- echo "State data bits changed from $oldStateDataSize to " . Block::INTERNAL_STATE_DATA_BITS . "\n";
- }
-
- foreach($old as $k => $name){
- [$oldId, $oldStateData] = [$k >> $oldStateDataSize, $k & $oldStateDataMask];
- $reconstructedK = ($oldId << Block::INTERNAL_STATE_DATA_BITS) | $oldStateData;
- if(!isset($new[$reconstructedK])){
- echo "Removed state for $name ($oldId:$oldStateData)\n";
- }
- }
- foreach($new as $k => $name){
- [$newId, $newStateData] = [$k >> Block::INTERNAL_STATE_DATA_BITS, $k & Block::INTERNAL_STATE_DATA_MASK];
- if($newStateData > $oldStateDataMask){
- echo "Added state for $name ($newId, $newStateData)\n";
- }else{
- $reconstructedK = ($newId << $oldStateDataSize) | $newStateData;
- if(!isset($old[$reconstructedK])){
- echo "Added state for $name ($newId:$newStateData)\n";
- }elseif($old[$reconstructedK] !== $name){
- echo "Name changed ($newId:$newStateData) " . $old[$reconstructedK] . " -> " . $name . "\n";
- }
+ if(count($errors) > 0){
+ echo count($errors) . " changes detected:\n";
+ foreach($errors as $error){
+ echo $error . "\n";
}
+ }else{
+ echo "No changes detected\n";
}
}else{
echo "WARNING: Unable to calculate diff, no previous consistency check file found\n";
}
-$newTable = [];
-foreach($new as $stateId => $name){
- $newTable[$name][] = $stateId;
-}
ksort($newTable, SORT_STRING);
-foreach($newTable as &$stateIds){
- sort($stateIds, SORT_NUMERIC);
-}
-file_put_contents(__DIR__ . '/block_factory_consistency_check.json', json_encode(
- [
- "knownStates" => $newTable,
- "stateDataBits" => Block::INTERNAL_STATE_DATA_BITS
- ],
- JSON_THROW_ON_ERROR
-));
+file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
diff --git a/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php b/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php
new file mode 100644
index 00000000000..31ae2e27a4e
--- /dev/null
+++ b/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php
@@ -0,0 +1,92 @@
+
+ */
+ public static function commandStringProvider() : \Generator{
+ yield ["stop"];
+ yield ["pocketmine:status"];
+ yield [str_repeat("s", 1000)];
+ yield ["time set day"];
+ yield ["give \"Steve\" golden_apple"];
+ }
+
+ /**
+ * @dataProvider commandStringProvider
+ */
+ public function testCreateParseSymmetry(string $input) : void{
+ $counterCreate = $counterParse = mt_rand();
+ $message = ConsoleReaderChildProcessUtils::createMessage($input, $counterCreate);
+ $parsedInput = ConsoleReaderChildProcessUtils::parseMessage($message, $counterParse);
+
+ self::assertSame($input, $parsedInput);
+ }
+
+ public function testCreateMessage() : void{
+ $counter = 0;
+
+ ConsoleReaderChildProcessUtils::createMessage("", $counter);
+ self::assertSame(1, $counter, "createMessage should always bump the counter");
+ }
+
+ /**
+ * @phpstan-return \Generator
+ */
+ public static function parseMessageProvider() : \Generator{
+ $counter = 0;
+ yield [ConsoleReaderChildProcessUtils::createMessage("", $counter), true];
+
+ yield ["", false]; //keepalive message, doesn't bump counter
+
+ $counter = 1;
+ yield [ConsoleReaderChildProcessUtils::createMessage("", $counter), false]; //mismatched counter
+
+ $counter = 0;
+ yield ["a" . ConsoleReaderChildProcessUtils::TOKEN_DELIMITER . "b", false]; //message with delimiter but not a valid IPC message
+ }
+
+ /**
+ * @dataProvider parseMessageProvider
+ */
+ public static function testParseMessage(string $message, bool $valid) : void{
+ $counter = $oldCounter = 0;
+
+ $input = ConsoleReaderChildProcessUtils::parseMessage($message, $counter);
+ if(!$valid){
+ self::assertNull($input, "Result should be null on invalid message");
+ self::assertSame($oldCounter, $counter, "Counter shouldn't be bumped on invalid message");
+ }else{
+ self::assertNotNull($input, "This was a valid message, expected a result");
+ self::assertSame($oldCounter + 1, $counter, "Counter should be bumped on valid message parse");
+ }
+ }
+}
diff --git a/tests/phpunit/data/bedrock/block/BlockStateDataTest.php b/tests/phpunit/data/bedrock/block/BlockStateDataTest.php
new file mode 100644
index 00000000000..7add2eddae6
--- /dev/null
+++ b/tests/phpunit/data/bedrock/block/BlockStateDataTest.php
@@ -0,0 +1,58 @@
+getVersionId();
+ self::assertLessThanOrEqual($expected, $actual, sprintf(
+ "Schema version %d (%d.%d.%d.%d) is newer than the current version %d (%d.%d.%d.%d)",
+ $actual,
+ ($actual >> 24) & 0xff,
+ ($actual >> 16) & 0xff,
+ ($actual >> 8) & 0xff,
+ $actual & 0xff,
+ $expected,
+ ($expected >> 24) & 0xff,
+ ($expected >> 16) & 0xff,
+ ($expected >> 8) & 0xff,
+ $expected & 0xff
+ ));
+ }
+ }
+}
diff --git a/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php b/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php
index 6db39bb48a8..a47a9b155ad 100644
--- a/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php
+++ b/tests/phpunit/data/bedrock/block/convert/BlockSerializerDeserializerTest.php
@@ -28,6 +28,7 @@
use pocketmine\block\Bed;
use pocketmine\block\BlockTypeIds;
use pocketmine\block\CaveVines;
+use pocketmine\block\Farmland;
use pocketmine\block\MobHead;
use pocketmine\block\RuntimeBlockStateRegistry;
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
@@ -76,6 +77,8 @@ public function testAllKnownBlockStatesSerializableAndDeserializable() : void{
$newBlock->setMobHeadType($block->getMobHeadType());
}elseif($block instanceof CaveVines && $newBlock instanceof CaveVines && !$block->hasBerries()){
$newBlock->setHead($block->isHead());
+ }elseif($block instanceof Farmland && $newBlock instanceof Farmland){
+ $block->setWaterPositionIndex($newBlock->getWaterPositionIndex());
}
self::assertSame($block->getStateId(), $newBlock->getStateId(), "Mismatch of blockstate for " . $block->getName() . ", " . print_r($block, true) . " vs " . print_r($newBlock, true));
diff --git a/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php b/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php
index efd22212fc1..91afd8ed929 100644
--- a/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php
+++ b/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php
@@ -24,8 +24,10 @@
namespace pocketmine\data\bedrock\block\upgrade;
use PHPUnit\Framework\TestCase;
+use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\nbt\tag\IntTag;
+use pocketmine\nbt\tag\StringTag;
use const PHP_INT_MAX;
class BlockStateUpgraderTest extends TestCase{
@@ -47,11 +49,11 @@ public function setUp() : void{
}
private function getNewSchema() : BlockStateUpgradeSchema{
- return $this->getNewSchemaVersion(PHP_INT_MAX);
+ return $this->getNewSchemaVersion(PHP_INT_MAX, 0);
}
- private function getNewSchemaVersion(int $versionId) : BlockStateUpgradeSchema{
- $schema = new BlockStateUpgradeSchema(($versionId >> 24) & 0xff, ($versionId >> 16) & 0xff, ($versionId >> 8) & 0xff, $versionId & 0xff, 0);
+ private function getNewSchemaVersion(int $versionId, int $schemaId) : BlockStateUpgradeSchema{
+ $schema = new BlockStateUpgradeSchema(($versionId >> 24) & 0xff, ($versionId >> 16) & 0xff, ($versionId >> 8) & 0xff, $versionId & 0xff, $schemaId);
$this->upgrader->addSchema($schema);
return $schema;
}
@@ -210,21 +212,41 @@ public function testRemapAndRenameProperty(\Closure $getStateData, ?int $valueAf
self::assertSame($upgradedStateData->getState(self::TEST_PROPERTY_2)?->getValue(), $valueAfter);
}
+ public function testFlattenProperty() : void{
+ $schema = $this->getNewSchema();
+ $schema->flattenedProperties[self::TEST_BLOCK] = new BlockStateUpgradeSchemaFlattenInfo(
+ "minecraft:",
+ "test",
+ "_suffix",
+ [],
+ StringTag::class
+ );
+
+ $stateData = new BlockStateData(self::TEST_BLOCK, ["test" => new StringTag("value1")], 0);
+ $upgradedStateData = $this->upgrade($stateData, fn() => $stateData);
+
+ self::assertSame("minecraft:value1_suffix", $upgradedStateData->getName());
+ self::assertEmpty($upgradedStateData->getStates());
+ }
+
/**
- * @phpstan-return \Generator
+ * @phpstan-return \Generator
*/
public static function upgraderVersionCompatibilityProvider() : \Generator{
- yield [0x1_00_00_00, 0x1_00_00_00, true]; //Same version: must be altered - this may be a backwards-compatible change that Mojang didn't bother to bump for
- yield [0x1_00_01_00, 0x1_00_00_00, true]; //Schema newer than block: must be altered
- yield [0x1_00_00_00, 0x1_00_01_00, false]; //Block newer than schema: block must NOT be altered
+ yield [0x1_00_00_00, 0x1_00_00_00, true, 2]; //Same version, multiple schemas targeting version - must be altered, we don't know which schemas are applicable
+ yield [0x1_00_00_00, 0x1_00_00_00, false, 1]; //Same version, one schema targeting version - do not change
+ yield [0x1_00_01_00, 0x1_00_00_00, true, 1]; //Schema newer than block: must be altered
+ yield [0x1_00_00_00, 0x1_00_01_00, false, 1]; //Block newer than schema: block must NOT be altered
}
/**
* @dataProvider upgraderVersionCompatibilityProvider
*/
- public function testUpgraderVersionCompatibility(int $schemaVersion, int $stateVersion, bool $shouldChange) : void{
- $schema = $this->getNewSchemaVersion($schemaVersion);
- $schema->renamedIds[self::TEST_BLOCK] = self::TEST_BLOCK_2;
+ public function testUpgraderVersionCompatibility(int $schemaVersion, int $stateVersion, bool $shouldChange, int $schemaCount) : void{
+ for($i = 0; $i < $schemaCount; $i++){
+ $schema = $this->getNewSchemaVersion($schemaVersion, $i);
+ $schema->renamedIds[self::TEST_BLOCK] = self::TEST_BLOCK_2;
+ }
$getStateData = fn() => new BlockStateData(
self::TEST_BLOCK,
diff --git a/tests/phpunit/event/EventTest.php b/tests/phpunit/event/EventTest.php
new file mode 100644
index 00000000000..7410cc3bb02
--- /dev/null
+++ b/tests/phpunit/event/EventTest.php
@@ -0,0 +1,79 @@
+unregisterAll();
+
+ //TODO: this is a really bad hack and could break any time if PluginManager decides to access its Server field
+ //we really need to make it possible to register events without a Plugin or Server context
+ $mockServer = $this->createMock(Server::class);
+ $this->mockPlugin = self::createStub(Plugin::class);
+ $this->mockPlugin->method('isEnabled')->willReturn(true);
+
+ $this->pluginManager = new PluginManager($mockServer, null);
+ }
+
+ public static function tearDownAfterClass() : void{
+ HandlerListManager::global()->unregisterAll();
+ }
+
+ public function testHandlerInheritance() : void{
+ $expectedOrder = [
+ TestGrandchildEvent::class,
+ TestChildEvent::class,
+ TestParentEvent::class
+ ];
+ $actualOrder = [];
+
+ foreach($expectedOrder as $class){
+ $this->pluginManager->registerEvent(
+ $class,
+ function(TestParentEvent $event) use (&$actualOrder, $class) : void{
+ $actualOrder[] = $class;
+ },
+ EventPriority::NORMAL,
+ $this->mockPlugin
+ );
+ }
+
+ $event = new TestGrandchildEvent();
+ $event->call();
+
+ self::assertSame($expectedOrder, $actualOrder, "Expected event handlers to be called from most specific to least specific");
+ }
+}
diff --git a/tests/phpunit/event/HandlerListManagerTest.php b/tests/phpunit/event/HandlerListManagerTest.php
index edff36639dd..c61043dab88 100644
--- a/tests/phpunit/event/HandlerListManagerTest.php
+++ b/tests/phpunit/event/HandlerListManagerTest.php
@@ -24,6 +24,12 @@
namespace pocketmine\event;
use PHPUnit\Framework\TestCase;
+use pocketmine\event\fixtures\TestAbstractAllowHandleEvent;
+use pocketmine\event\fixtures\TestAbstractEvent;
+use pocketmine\event\fixtures\TestConcreteEvent;
+use pocketmine\event\fixtures\TestConcreteExtendsAbstractEvent;
+use pocketmine\event\fixtures\TestConcreteExtendsAllowHandleEvent;
+use pocketmine\event\fixtures\TestConcreteExtendsConcreteEvent;
class HandlerListManagerTest extends TestCase{
diff --git a/tests/phpunit/event/TestAbstractAllowHandleEvent.php b/tests/phpunit/event/fixtures/TestAbstractAllowHandleEvent.php
similarity index 92%
rename from tests/phpunit/event/TestAbstractAllowHandleEvent.php
rename to tests/phpunit/event/fixtures/TestAbstractAllowHandleEvent.php
index 1bac06bbb0e..3831698098e 100644
--- a/tests/phpunit/event/TestAbstractAllowHandleEvent.php
+++ b/tests/phpunit/event/fixtures/TestAbstractAllowHandleEvent.php
@@ -21,7 +21,9 @@
declare(strict_types=1);
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
+
+use pocketmine\event\Event;
/**
* @allowHandle
diff --git a/tests/phpunit/event/TestAbstractEvent.php b/tests/phpunit/event/fixtures/TestAbstractEvent.php
similarity index 92%
rename from tests/phpunit/event/TestAbstractEvent.php
rename to tests/phpunit/event/fixtures/TestAbstractEvent.php
index 92a95363e30..b48d8f5269a 100644
--- a/tests/phpunit/event/TestAbstractEvent.php
+++ b/tests/phpunit/event/fixtures/TestAbstractEvent.php
@@ -21,7 +21,9 @@
declare(strict_types=1);
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
+
+use pocketmine\event\Event;
abstract class TestAbstractEvent extends Event{
diff --git a/tests/plugins/TesterPlugin/src/event/ChildEvent.php b/tests/phpunit/event/fixtures/TestChildEvent.php
similarity index 89%
rename from tests/plugins/TesterPlugin/src/event/ChildEvent.php
rename to tests/phpunit/event/fixtures/TestChildEvent.php
index b71d2627fce..569a2c069ab 100644
--- a/tests/plugins/TesterPlugin/src/event/ChildEvent.php
+++ b/tests/phpunit/event/fixtures/TestChildEvent.php
@@ -21,8 +21,8 @@
declare(strict_types=1);
-namespace pmmp\TesterPlugin\event;
+namespace pocketmine\event\fixtures;
-class ChildEvent extends ParentEvent{
+class TestChildEvent extends TestParentEvent{
}
diff --git a/tests/phpunit/event/TestConcreteEvent.php b/tests/phpunit/event/fixtures/TestConcreteEvent.php
similarity index 92%
rename from tests/phpunit/event/TestConcreteEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteEvent.php
index 8b159df91a6..cf744eb2ced 100644
--- a/tests/phpunit/event/TestConcreteEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteEvent.php
@@ -21,7 +21,9 @@
declare(strict_types=1);
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
+
+use pocketmine\event\Event;
class TestConcreteEvent extends Event{
diff --git a/tests/phpunit/event/TestConcreteExtendsAbstractEvent.php b/tests/phpunit/event/fixtures/TestConcreteExtendsAbstractEvent.php
similarity index 95%
rename from tests/phpunit/event/TestConcreteExtendsAbstractEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteExtendsAbstractEvent.php
index 3f0fa572fef..54ec3dbb128 100644
--- a/tests/phpunit/event/TestConcreteExtendsAbstractEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteExtendsAbstractEvent.php
@@ -21,7 +21,7 @@
declare(strict_types=1);
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
class TestConcreteExtendsAbstractEvent extends TestAbstractEvent{
diff --git a/tests/phpunit/event/TestConcreteExtendsAllowHandleEvent.php b/tests/phpunit/event/fixtures/TestConcreteExtendsAllowHandleEvent.php
similarity index 95%
rename from tests/phpunit/event/TestConcreteExtendsAllowHandleEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteExtendsAllowHandleEvent.php
index e4146512116..362ce693b50 100644
--- a/tests/phpunit/event/TestConcreteExtendsAllowHandleEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteExtendsAllowHandleEvent.php
@@ -21,7 +21,7 @@
declare(strict_types=1);
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
class TestConcreteExtendsAllowHandleEvent extends TestAbstractAllowHandleEvent{
diff --git a/tests/phpunit/event/TestConcreteExtendsConcreteEvent.php b/tests/phpunit/event/fixtures/TestConcreteExtendsConcreteEvent.php
similarity index 95%
rename from tests/phpunit/event/TestConcreteExtendsConcreteEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteExtendsConcreteEvent.php
index cc955893515..3fd3a6cf0e0 100644
--- a/tests/phpunit/event/TestConcreteExtendsConcreteEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteExtendsConcreteEvent.php
@@ -21,7 +21,7 @@
declare(strict_types=1);
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
class TestConcreteExtendsConcreteEvent extends TestConcreteEvent{
diff --git a/tests/plugins/TesterPlugin/src/event/ParentEvent.php b/tests/phpunit/event/fixtures/TestGrandchildEvent.php
similarity index 89%
rename from tests/plugins/TesterPlugin/src/event/ParentEvent.php
rename to tests/phpunit/event/fixtures/TestGrandchildEvent.php
index 68f7df6300e..bfe50f9f3d4 100644
--- a/tests/plugins/TesterPlugin/src/event/ParentEvent.php
+++ b/tests/phpunit/event/fixtures/TestGrandchildEvent.php
@@ -21,8 +21,8 @@
declare(strict_types=1);
-namespace pmmp\TesterPlugin\event;
+namespace pocketmine\event\fixtures;
-class ParentEvent extends \pocketmine\event\Event{
+class TestGrandchildEvent extends TestChildEvent{
}
diff --git a/tests/plugins/TesterPlugin/src/event/GrandchildEvent.php b/tests/phpunit/event/fixtures/TestParentEvent.php
similarity index 87%
rename from tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
rename to tests/phpunit/event/fixtures/TestParentEvent.php
index 40c37c56794..c204422722b 100644
--- a/tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
+++ b/tests/phpunit/event/fixtures/TestParentEvent.php
@@ -21,8 +21,10 @@
declare(strict_types=1);
-namespace pmmp\TesterPlugin\event;
+namespace pocketmine\event\fixtures;
-class GrandchildEvent extends ChildEvent{
+use pocketmine\event\Event;
+
+class TestParentEvent extends Event{
}
diff --git a/tests/phpunit/item/BannerTest.php b/tests/phpunit/item/BannerTest.php
index c555031a093..797ca60fcaa 100644
--- a/tests/phpunit/item/BannerTest.php
+++ b/tests/phpunit/item/BannerTest.php
@@ -36,7 +36,7 @@ public function testBannerPatternSaveRestore() : void{
$item = VanillaBlocks::BANNER()->asItem();
assert($item instanceof Banner);
$item->setPatterns([
- new BannerPatternLayer(BannerPatternType::FLOWER(), DyeColor::RED())
+ new BannerPatternLayer(BannerPatternType::FLOWER, DyeColor::RED)
]);
$data = $item->nbtSerialize();
@@ -45,6 +45,6 @@ public function testBannerPatternSaveRestore() : void{
self::assertInstanceOf(Banner::class, $item2);
$patterns = $item2->getPatterns();
self::assertCount(1, $patterns);
- self::assertTrue(BannerPatternType::FLOWER()->equals($patterns[0]->getType()));
+ self::assertTrue(BannerPatternType::FLOWER === $patterns[0]->getType());
}
}
diff --git a/tests/phpunit/item/ItemTypeIdsTest.php b/tests/phpunit/item/ItemTypeIdsTest.php
index a5394bcbb7c..7336780b33f 100644
--- a/tests/phpunit/item/ItemTypeIdsTest.php
+++ b/tests/phpunit/item/ItemTypeIdsTest.php
@@ -24,6 +24,7 @@
namespace pocketmine\item;
use PHPUnit\Framework\TestCase;
+use pocketmine\utils\Utils;
use function array_unique;
use function max;
@@ -43,4 +44,18 @@ public function testNoDuplicates() : void{
self::assertSameSize($idTable, array_unique($idTable), "Every ItemTypeID must be unique");
}
+
+ public function testVanillaItemsParity() : void{
+ $reflect = new \ReflectionClass(ItemTypeIds::class);
+
+ foreach(Utils::stringifyKeys(VanillaItems::getAll()) as $name => $item){
+ if($item instanceof ItemBlock){
+ continue;
+ }
+ $expected = $item->getTypeId();
+ $actual = $reflect->getConstant($name);
+ self::assertNotFalse($actual, "VanillaItems::$name() does not have an ItemTypeIds constant");
+ self::assertSame($expected, $actual, "VanillaItems::$name() type ID does not match ItemTypeIds::$name");
+ }
+ }
}
diff --git a/tests/phpunit/promise/PromiseTest.php b/tests/phpunit/promise/PromiseTest.php
new file mode 100644
index 00000000000..682ee007035
--- /dev/null
+++ b/tests/phpunit/promise/PromiseTest.php
@@ -0,0 +1,148 @@
+resolve(null);
+ $resolver->getPromise()->onCompletion(
+ function(mixed $value) : void{
+ self::assertNull($value);
+ },
+ function() : void{
+ self::fail("Promise should not be rejected");
+ }
+ );
+ }
+
+ public function testAllPreResolved() : void{
+ $resolver = new PromiseResolver();
+ $resolver->resolve(1);
+
+ $allPromise = Promise::all([$resolver->getPromise()]);
+ $done = false;
+ $allPromise->onCompletion(
+ function($value) use (&$done) : void{
+ $done = true;
+ self::assertEquals([1], $value);
+ },
+ function() use (&$done) : void{
+ $done = true;
+ self::fail("Promise was rejected");
+ }
+ );
+ self::assertTrue($done);
+ }
+
+ public function testAllPostResolved() : void{
+ $resolver = new PromiseResolver();
+
+ $allPromise = Promise::all([$resolver->getPromise()]);
+ $done = false;
+ $allPromise->onCompletion(
+ function($value) use (&$done) : void{
+ $done = true;
+ self::assertEquals([1], $value);
+ },
+ function() use (&$done) : void{
+ $done = true;
+ self::fail("Promise was rejected");
+ }
+ );
+ self::assertFalse($done);
+ $resolver->resolve(1);
+ self::assertTrue($done);
+ }
+
+ public function testAllResolve() : void{
+ $resolver1 = new PromiseResolver();
+ $resolver2 = new PromiseResolver();
+
+ $allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]);
+ $done = false;
+ $allPromise->onCompletion(
+ function($value) use (&$done) : void{
+ $done = true;
+ self::assertEquals([1, 2], $value);
+ },
+ function() use (&$done) : void{
+ $done = true;
+ self::fail("Promise was rejected");
+ }
+ );
+ self::assertFalse($done);
+ $resolver1->resolve(1);
+ self::assertFalse($done);
+ $resolver2->resolve(2);
+ self::assertTrue($done);
+ }
+
+ public function testAllPartialReject() : void{
+ $resolver1 = new PromiseResolver();
+ $resolver2 = new PromiseResolver();
+
+ $allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]);
+ $done = false;
+ $allPromise->onCompletion(
+ function($value) use (&$done) : void{
+ $done = true;
+ self::fail("Promise was unexpectedly resolved");
+ },
+ function() use (&$done) : void{
+ $done = true;
+ }
+ );
+ self::assertFalse($done);
+ $resolver2->reject();
+ self::assertTrue($done, "All promise should be rejected immediately after the first constituent rejection");
+ $resolver1->resolve(1);
+ }
+
+ /**
+ * Promise::all() should return a rejected promise if any of the input promises were rejected at the call time
+ */
+ public function testAllPartialPreReject() : void{
+ $resolver1 = new PromiseResolver();
+ $resolver2 = new PromiseResolver();
+ $resolver2->reject();
+
+ $allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]);
+ $done = false;
+ $allPromise->onCompletion(
+ function($value) use (&$done) : void{
+ $done = true;
+ self::fail("Promise was unexpectedly resolved");
+ },
+ function() use (&$done) : void{
+ $done = true;
+ }
+ );
+ self::assertTrue($done, "All promise should be rejected immediately after the first constituent rejection");
+ $resolver1->resolve(1);
+ }
+}
diff --git a/tests/phpunit/scheduler/AsyncPoolTest.php b/tests/phpunit/scheduler/AsyncPoolTest.php
index fd7dc344acb..53ec15c12b6 100644
--- a/tests/phpunit/scheduler/AsyncPoolTest.php
+++ b/tests/phpunit/scheduler/AsyncPoolTest.php
@@ -32,8 +32,6 @@
use function define;
use function dirname;
use function microtime;
-use function sys_get_temp_dir;
-use function tempnam;
use function usleep;
class AsyncPoolTest extends TestCase{
@@ -45,13 +43,12 @@ class AsyncPoolTest extends TestCase{
public function setUp() : void{
@define('pocketmine\\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__, 3) . '/vendor/autoload.php');
- $this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog"), false, "Main", new \DateTimeZone('UTC'));
+ $this->mainLogger = new MainLogger(null, false, "Main", new \DateTimeZone('UTC'));
$this->pool = new AsyncPool(2, 1024, new ThreadSafeClassLoader(), $this->mainLogger, new SleeperHandler());
}
public function tearDown() : void{
$this->pool->shutdown();
- $this->mainLogger->shutdownLogWriterThread();
}
public function testTaskLeak() : void{
@@ -89,4 +86,55 @@ function() : void{
usleep(50 * 1000);
}
}
+
+ /**
+ * This test ensures that the fix for an exotic AsyncTask::__destruct() reentrancy bug has not regressed.
+ *
+ * Due to an unset() in the function body, other AsyncTask::__destruct() calls could be triggered during
+ * an AsyncTask's destruction. If done in the wrong way, this could lead to a crash.
+ *
+ * @doesNotPerformAssertions This test is checking for a crash condition, not a specific output.
+ */
+ public function testTaskDestructorReentrancy() : void{
+ $this->pool->submitTask(new class extends AsyncTask{
+ public function __construct(){
+ $this->storeLocal("task", new class extends AsyncTask{
+
+ public function __construct(){
+ $this->storeLocal("dummy", 1);
+ }
+
+ public function onRun() : void{
+ //dummy
+ }
+ });
+ }
+
+ public function onRun() : void{
+ //dummy
+ }
+ });
+ while($this->pool->collectTasks()){
+ usleep(50 * 1000);
+ }
+ }
+
+ public function testNullComplexDataFetch() : void{
+ $this->pool->submitTask(new class extends AsyncTask{
+ public function __construct(){
+ $this->storeLocal("null", null);
+ }
+
+ public function onRun() : void{
+ //dummy
+ }
+
+ public function onCompletion() : void{
+ AsyncPoolTest::assertNull($this->fetchLocal("null"));
+ }
+ });
+ while($this->pool->collectTasks()){
+ usleep(50 * 1000);
+ }
+ }
}
diff --git a/tests/plugins/DevTools b/tests/plugins/DevTools
index 83f0db3f9e0..c6dca357c7e 160000
--- a/tests/plugins/DevTools
+++ b/tests/plugins/DevTools
@@ -1 +1 @@
-Subproject commit 83f0db3f9e0adbf424e32ed81f7730e97b037be9
+Subproject commit c6dca357c7e8a37ce3479a1bedfe849451e072e3
diff --git a/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php b/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php
deleted file mode 100644
index efe20f8d8f9..00000000000
--- a/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php
+++ /dev/null
@@ -1,88 +0,0 @@
-getPlugin();
- $plugin->getServer()->getPluginManager()->registerEvent(
- ParentEvent::class,
- function(ParentEvent $event) : void{
- $this->callOrder[] = ParentEvent::class;
- },
- EventPriority::NORMAL,
- $plugin
- );
- $plugin->getServer()->getPluginManager()->registerEvent(
- ChildEvent::class,
- function(ChildEvent $event) : void{
- $this->callOrder[] = ChildEvent::class;
- },
- EventPriority::NORMAL,
- $plugin
- );
- $plugin->getServer()->getPluginManager()->registerEvent(
- GrandchildEvent::class,
- function(GrandchildEvent $event) : void{
- $this->callOrder[] = GrandchildEvent::class;
- },
- EventPriority::NORMAL,
- $plugin
- );
-
- $event = new GrandchildEvent();
- $event->call();
-
- if($this->callOrder === self::EXPECTED_ORDER){
- $this->setResult(Test::RESULT_OK);
- }else{
- $plugin->getLogger()->error("Expected order: " . implode(", ", self::EXPECTED_ORDER) . ", got: " . implode(", ", $this->callOrder));
- $this->setResult(Test::RESULT_FAILED);
- }
- }
-}
diff --git a/tests/plugins/TesterPlugin/src/Main.php b/tests/plugins/TesterPlugin/src/Main.php
index 26d3441f4a6..08b59dbacb6 100644
--- a/tests/plugins/TesterPlugin/src/Main.php
+++ b/tests/plugins/TesterPlugin/src/Main.php
@@ -57,7 +57,7 @@ public function onEnable() : void{
}), 10);
$this->waitingTests = [
- new EventHandlerInheritanceTest($this),
+ //Add test objects here
];
}
diff --git a/tests/travis.sh b/tests/travis.sh
index 094f6590533..a4674fb6f23 100755
--- a/tests/travis.sh
+++ b/tests/travis.sh
@@ -7,6 +7,9 @@ while getopts "t:" OPTION 2> /dev/null; do
t)
PM_WORKERS="$OPTARG"
;;
+ \?)
+ break
+ ;;
esac
done
@@ -19,7 +22,7 @@ rm PocketMine-MP.phar 2> /dev/null
mkdir "$DATA_DIR"
mkdir "$PLUGINS_DIR"
-cd tests/plugins/DevTools
+cd tests/plugins/DevTools || { echo "Couldn't change directory to $DIR"; exit 1; }
php -dphar.readonly=0 ./src/ConsoleScript.php --make ./ --relative ./ --out "$PLUGINS_DIR/DevTools.phar"
cd ../../..
composer make-server
@@ -45,7 +48,7 @@ if [ "$result" != "" ]; then
echo "$result"
echo Some tests did not complete successfully, changing build status to failed
exit 1
-elif [ $(grep -c "ERROR\|CRITICAL\|EMERGENCY" "$DATA_DIR/server.log") -ne 0 ]; then
+elif [ "$(grep -c "ERROR\|CRITICAL\|EMERGENCY" "$DATA_DIR/server.log")" -ne 0 ]; then
echo Server log contains error messages, changing build status to failed
exit 1
else
diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php
new file mode 100644
index 00000000000..b7a9a4169dc
--- /dev/null
+++ b/tools/blockstate-upgrade-schema-utils.php
@@ -0,0 +1,913 @@
+ $properties
+ */
+function encodeOrderedProperties(array $properties) : string{
+ ksort($properties, SORT_STRING);
+ return implode("", array_map(fn(Tag $tag) => encodeProperty($tag), array_values($properties)));
+}
+
+function encodeProperty(Tag $tag) : string{
+ return (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));
+}
+
+/**
+ * @param TreeRoot[] $oldNewStateList
+ * @phpstan-param list $oldNewStateList
+ *
+ * @return BlockStateMapping[][]
+ * @phpstan-return array>
+ */
+function buildUpgradeTableFromData(array $oldNewStateList, bool $reverse) : array{
+ $result = [];
+
+ for($i = 0; isset($oldNewStateList[$i]); $i += 2){
+ $oldTag = $oldNewStateList[$i]->mustGetCompoundTag();
+ $newTag = $oldNewStateList[$i + 1]->mustGetCompoundTag();
+ $old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag);
+ $new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag);
+
+ $result[$old->getName()][encodeOrderedProperties($old->getStates())] = new BlockStateMapping(
+ $old,
+ $new
+ );
+ }
+
+ return $result;
+}
+
+/**
+ * @return BlockStateMapping[][]
+ * @phpstan-return array>
+ */
+function loadUpgradeTableFromFile(string $file, bool $reverse) : array{
+ $contents = Filesystem::fileGetContents($file);
+ $data = (new NetworkNbtSerializer())->readMultiple($contents);
+
+ return buildUpgradeTableFromData($data, $reverse);
+}
+
+/**
+ * @param BlockStateData[] $states
+ * @phpstan-param array $states
+ *
+ * @return Tag[][]
+ * @phpstan-return array>
+ */
+function buildStateGroupSchema(array $states) : ?array{
+ $first = $states[array_key_first($states)];
+
+ $properties = [];
+ foreach(Utils::stringifyKeys($first->getStates()) as $propertyName => $propertyValue){
+ $properties[$propertyName][encodeProperty($propertyValue)] = $propertyValue;
+ }
+ foreach($states as $state){
+ if(count($state->getStates()) !== count($properties)){
+ return null;
+ }
+ foreach(Utils::stringifyKeys($state->getStates()) as $propertyName => $propertyValue){
+ if(!isset($properties[$propertyName])){
+ return null;
+ }
+ $properties[$propertyName][encodeProperty($propertyValue)] = $propertyValue;
+ }
+ }
+
+ return $properties;
+}
+
+/**
+ * @param BlockStateMapping[] $upgradeTable
+ * @phpstan-param array $upgradeTable
+ */
+function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgradeSchema $result) : bool{
+ $newProperties = buildStateGroupSchema(array_map(fn(BlockStateMapping $m) => $m->new, $upgradeTable));
+ if($newProperties === null){
+ \GlobalLogger::get()->warning("New states for $oldName don't all have the same set of properties - processing as remaps instead");
+ return false;
+ }
+ $oldProperties = buildStateGroupSchema(array_map(fn(BlockStateMapping $m) => $m->old, $upgradeTable));
+ if($oldProperties === null){
+ //TODO: not sure if this is actually required - we may be able to apply some transformations even if the states are not consistent
+ //however, this should never normally occur anyway
+ \GlobalLogger::get()->warning("Old states for $oldName don't all have the same set of properties - processing as remaps instead");
+ return false;
+ }
+
+ $remappedPropertyValues = [];
+ $addedProperties = [];
+ $removedProperties = [];
+ $renamedProperties = [];
+
+ $uniqueNewIds = [];
+ foreach($upgradeTable as $pair){
+ $uniqueNewIds[$pair->new->getName()] = $pair->new->getName();
+ }
+
+ foreach(Utils::stringifyKeys($newProperties) as $newPropertyName => $newPropertyValues){
+ if(count($newPropertyValues) === 1){
+ $newPropertyValue = $newPropertyValues[array_key_first($newPropertyValues)];
+ if(isset($oldProperties[$newPropertyName])){
+ //all the old values for this property were mapped to the same new value
+ //it would be more space-efficient to represent this as a remove+add, but we can't guarantee that the
+ //removal of the old value will be done before the addition of the new value
+ foreach($oldProperties[$newPropertyName] as $oldPropertyValue){
+ $remappedPropertyValues[$newPropertyName][encodeProperty($oldPropertyValue)] = $newPropertyValue;
+ }
+ }else{
+ //this property has no relation to any property value in any of the old states - it's a new property
+ $addedProperties[$newPropertyName] = $newPropertyValue;
+ }
+ }
+ }
+
+ foreach(Utils::stringifyKeys($oldProperties) as $oldPropertyName => $oldPropertyValues){
+ $mappingsContainingOldValue = [];
+ foreach($upgradeTable as $mapping){
+ $mappingOldValue = $mapping->old->getState($oldPropertyName) ?? throw new AssumptionFailedError("This should never happen");
+ foreach($oldPropertyValues as $oldPropertyValue){
+ if($mappingOldValue->equals($oldPropertyValue)){
+ $mappingsContainingOldValue[encodeProperty($oldPropertyValue)][] = $mapping;
+ break;
+ }
+ }
+ }
+
+ $candidateNewPropertyNames = [];
+ //foreach mappings by unique value, compute the diff across all the states in the list
+ foreach(Utils::stringifyKeys($mappingsContainingOldValue) as $rawOldValue => $mappingList){
+ $first = array_shift($mappingList);
+ foreach(Utils::stringifyKeys($first->new->getStates()) as $newPropertyName => $newPropertyValue){
+ if(isset($addedProperties[$newPropertyName])){
+ //this property was already determined to be unrelated to any old property
+ continue;
+ }
+ foreach($mappingList as $pair){
+ if(!($pair->new->getState($newPropertyName)?->equals($newPropertyValue) ?? false)){
+ //if the new property is different with an unchanged old value,
+ //the property may be influenced by multiple old properties, or be unrelated entirely
+ continue 2;
+ }
+ }
+ $candidateNewPropertyNames[$newPropertyName][$rawOldValue] = $newPropertyValue;
+ }
+ }
+
+ if(count($candidateNewPropertyNames) === 0){
+ $removedProperties[$oldPropertyName] = $oldPropertyName;
+ }elseif(count($candidateNewPropertyNames) === 1){
+ $newPropertyName = array_key_first($candidateNewPropertyNames);
+ $newPropertyValues = $candidateNewPropertyNames[$newPropertyName];
+
+ if($oldPropertyName !== $newPropertyName){
+ $renamedProperties[$oldPropertyName] = $newPropertyName;
+ }
+
+ foreach(Utils::stringifyKeys($newPropertyValues) as $rawOldValue => $newPropertyValue){
+ if(!$newPropertyValue->equals($oldPropertyValues[$rawOldValue])){
+ $remappedPropertyValues[$oldPropertyName][$rawOldValue] = $newPropertyValue;
+ }
+ }
+ }else{
+ $split = true;
+ if(isset($candidateNewPropertyNames[$oldPropertyName])){
+ //In 1.10, direction wasn't changed at all, but not all state permutations were present in the palette,
+ //making it appear that door_hinge_bit was correlated with direction.
+ //If a new property is present with the same name and values as an old property, we can assume that
+ //the property was unchanged, and that any extra matches properties are probably unrelated.
+ $changedValues = false;
+ foreach(Utils::stringifyKeys($candidateNewPropertyNames[$oldPropertyName]) as $rawOldValue => $newPropertyValue){
+ if(!$newPropertyValue->equals($oldPropertyValues[$rawOldValue])){
+ //if any of the new values are different, we may be dealing with a property being split into
+ //multiple new properties - hand this off to the remap handler
+ $changedValues = true;
+ break;
+ }
+ }
+ if(!$changedValues){
+ $split = false;
+ }
+ }
+ if($split){
+ \GlobalLogger::get()->warning(
+ "Multiple new properties (" . (implode(", ", array_keys($candidateNewPropertyNames))) . ") are correlated with $oldName property $oldPropertyName, processing as remaps instead"
+ );
+ return false;
+ }else{
+ //is it safe to ignore the rest?
+ }
+ }
+ }
+
+ if(count($uniqueNewIds) > 1){
+ //detect possible flattening
+ $flattenedProperty = null;
+ $flattenedPropertyType = null;
+ $flattenedPropertyMap = [];
+ foreach($removedProperties as $removedProperty){
+ $valueMap = [];
+ foreach($upgradeTable as $pair){
+ $oldValue = $pair->old->getState($removedProperty);
+ if($oldValue === null){
+ throw new AssumptionFailedError("We already checked that all states had consistent old properties");
+ }
+ if(!checkFlattenPropertySuitability($oldValue, $flattenedPropertyType, $pair->new->getName(), $valueMap)){
+ continue 2;
+ }
+ }
+
+ if($flattenedProperty !== null){
+ //found multiple candidates for flattening - fallback to remappedStates
+ return false;
+ }
+ //we found a suitable candidate
+ $flattenedProperty = $removedProperty;
+ $flattenedPropertyMap = $valueMap;
+ break;
+ }
+
+ if($flattenedProperty === null){
+ //can't figure out how the new IDs are related to the old states - fallback to remappedStates
+ return false;
+ }
+ if($flattenedPropertyType === null){
+ throw new AssumptionFailedError("This should never happen at this point");
+ }
+
+ $result->flattenedProperties[$oldName] = buildFlattenPropertyRule($flattenedPropertyMap, $flattenedProperty, $flattenedPropertyType);
+ unset($removedProperties[$flattenedProperty]);
+ }
+
+ //finally, write the results to the schema
+
+ if(count($remappedPropertyValues) !== 0){
+ foreach(Utils::stringifyKeys($remappedPropertyValues) as $oldPropertyName => $propertyValues){
+ foreach(Utils::stringifyKeys($propertyValues) as $rawOldValue => $newPropertyValue){
+ $oldPropertyValue = $oldProperties[$oldPropertyName][$rawOldValue];
+ $result->remappedPropertyValues[$oldName][$oldPropertyName][] = new BlockStateUpgradeSchemaValueRemap(
+ $oldPropertyValue,
+ $newPropertyValue
+ );
+ }
+ }
+ }
+ if(count($addedProperties) !== 0){
+ $result->addedProperties[$oldName] = $addedProperties;
+ }
+ if(count($removedProperties) !== 0){
+ $result->removedProperties[$oldName] = array_values($removedProperties);
+ }
+ if(count($renamedProperties) !== 0){
+ $result->renamedProperties[$oldName] = $renamedProperties;
+ }
+
+ return true;
+}
+
+/**
+ * @param string[] $strings
+ */
+function findCommonPrefix(array $strings) : string{
+ sort($strings, SORT_STRING);
+
+ $first = $strings[array_key_first($strings)];
+ $last = $strings[array_key_last($strings)];
+
+ $maxLength = min(strlen($first), strlen($last));
+ for($i = 0; $i < $maxLength; ++$i){
+ if($first[$i] !== $last[$i]){
+ return substr($first, 0, $i);
+ }
+ }
+
+ return substr($first, 0, $maxLength);
+}
+
+/**
+ * @param string[] $strings
+ */
+function findCommonSuffix(array $strings) : string{
+ $reversed = array_map(strrev(...), $strings);
+
+ return strrev(findCommonPrefix($reversed));
+}
+
+/**
+ * @param string[] $valueToIdMap
+ * @phpstan-param ?class-string $expectedType
+ * @phpstan-param-out class-string $expectedType
+ * @phpstan-param array $valueToIdMap
+ * @phpstan-param-out array $valueToIdMap
+ */
+function checkFlattenPropertySuitability(Tag $oldValue, ?string &$expectedType, string $actualNewId, array &$valueToIdMap) : bool{
+ //TODO: lots of similar logic to the remappedStates builder below
+ if(!$oldValue instanceof ByteTag && !$oldValue instanceof IntTag && !$oldValue instanceof StringTag){
+ //unknown property type - bad candidate for flattening
+ return false;
+ }
+ if($expectedType === null){
+ $expectedType = get_class($oldValue);
+ }elseif(!$oldValue instanceof $expectedType){
+ //property type mismatch - bad candidate for flattening
+ return false;
+ }
+
+ $rawValue = (string) $oldValue->getValue();
+ $existingNewId = $valueToIdMap[$rawValue] ?? null;
+ if($existingNewId !== null && $existingNewId !== $actualNewId){
+ //this property value is associated with multiple new IDs - bad candidate for flattening
+ return false;
+ }
+ $valueToIdMap[$rawValue] = $actualNewId;
+
+ return true;
+}
+
+/**
+ * @param string[] $valueToId
+ * @phpstan-param array $valueToId
+ * @phpstan-param class-string $propertyType
+ */
+function buildFlattenPropertyRule(array $valueToId, string $propertyName, string $propertyType) : BlockStateUpgradeSchemaFlattenInfo{
+ $ids = array_values($valueToId);
+
+ //TODO: this is a bit too enthusiastic. For example, when flattening the old "stone", it will see that
+ //"granite", "andesite", "stone" etc all have "e" as a common suffix, which works, but looks a bit daft.
+ //This also causes more remaps to be generated than necessary, since some of the values are already
+ //contained in the new ID.
+ $idPrefix = findCommonPrefix($ids);
+ $idSuffix = findCommonSuffix($ids);
+ if(strlen($idSuffix) < 2){
+ $idSuffix = "";
+ }
+
+ $valueMap = [];
+ foreach(Utils::stringifyKeys($valueToId) as $value => $newId){
+ $newValue = substr($newId, strlen($idPrefix), $idSuffix !== "" ? -strlen($idSuffix) : null);
+ if($newValue !== $value){
+ $valueMap[$value] = $newValue;
+ }
+ }
+
+ $allNumeric = true;
+ if(count($valueMap) > 0){
+ foreach(Utils::stringifyKeys($valueMap) as $value => $newValue){
+ if(!is_numeric($value)){
+ $allNumeric = false;
+ break;
+ }
+ }
+ if($allNumeric){
+ //add a dummy key to force the JSON to be an object and not a list
+ $valueMap["dummy"] = "map_not_list";
+ }
+ }
+
+ return new BlockStateUpgradeSchemaFlattenInfo(
+ $idPrefix,
+ $propertyName,
+ $idSuffix,
+ $valueMap,
+ $propertyType,
+ );
+}
+
+/**
+ * @param string[][][] $candidateFlattenedValues
+ * @phpstan-param array>> $candidateFlattenedValues
+ * @param string[] $candidateFlattenPropertyTypes
+ * @phpstan-param array> $candidateFlattenPropertyTypes
+ *
+ * @return BlockStateUpgradeSchemaFlattenInfo[][]
+ * @phpstan-return array>
+ */
+function buildFlattenPropertyRules(array $candidateFlattenedValues, array $candidateFlattenPropertyTypes) : array{
+ $flattenPropertyRules = [];
+ foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){
+ foreach(Utils::stringifyKeys($filters) as $filter => $valueToId){
+ $flattenPropertyRules[$propertyName][$filter] = buildFlattenPropertyRule($valueToId, $propertyName, $candidateFlattenPropertyTypes[$propertyName]);
+ }
+ }
+ ksort($flattenPropertyRules, SORT_STRING);
+ return $flattenPropertyRules;
+}
+
+/**
+ * Attempts to compress a list of remapped states by looking at which state properties were consistently unchanged.
+ * This significantly reduces the output size during flattening when the flattened block has many permutations
+ * (e.g. walls).
+ *
+ * @param BlockStateMapping[] $upgradeTable
+ * @phpstan-param array $upgradeTable
+ *
+ * @return BlockStateUpgradeSchemaBlockRemap[]
+ * @phpstan-return list
+ */
+function processRemappedStates(array $upgradeTable) : array{
+ $unchangedStatesByNewName = [];
+
+ foreach($upgradeTable as $pair){
+ if(count($pair->old->getStates()) === 0 || count($pair->new->getStates()) === 0){
+ //all states have changed in some way - no states are copied over
+ $unchangedStatesByNewName[$pair->new->getName()] = [];
+ continue;
+ }
+
+ $oldStates = $pair->old->getStates();
+ $newStates = $pair->new->getStates();
+ if(!isset($unchangedStatesByNewName[$pair->new->getName()])){
+ //build list of unchanged states for this new ID
+ $unchangedStatesByNewName[$pair->new->getName()] = [];
+ foreach(Utils::stringifyKeys($oldStates) as $propertyName => $propertyValue){
+ if(isset($newStates[$propertyName]) && $newStates[$propertyName]->equals($propertyValue)){
+ $unchangedStatesByNewName[$pair->new->getName()][] = $propertyName;
+ }
+ }
+ }else{
+ //we already have a list of stuff that probably didn't change - verify that this is the case, and remove
+ //any that changed in later states with the same ID
+ foreach($unchangedStatesByNewName[$pair->new->getName()] as $k => $propertyName){
+ if(
+ !isset($oldStates[$propertyName]) ||
+ !isset($newStates[$propertyName]) ||
+ !$oldStates[$propertyName]->equals($newStates[$propertyName])
+ ){
+ //this property disappeared or changed its value in another state with the same ID - we can't
+ //compress this state
+ unset($unchangedStatesByNewName[$pair->new->getName()][$k]);
+ }
+ }
+ }
+ }
+ foreach(Utils::stringifyKeys($unchangedStatesByNewName) as $newName => $unchangedStates){
+ ksort($unchangedStates);
+ $unchangedStatesByNewName[$newName] = $unchangedStates;
+ }
+
+ $notFlattenedProperties = [];
+
+ $candidateFlattenedValues = [];
+ $candidateFlattenedPropertyTypes = [];
+ foreach($upgradeTable as $pair){
+ foreach(Utils::stringifyKeys($pair->old->getStates()) as $propertyName => $propertyValue){
+ if(isset($notFlattenedProperties[$propertyName])){
+ continue;
+ }
+
+ $filter = $pair->old->getStates();
+ foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){
+ if($unchangedPropertyName === $propertyName){
+ $notFlattenedProperties[$propertyName] = true;
+ continue 2;
+ }
+ unset($filter[$unchangedPropertyName]);
+ }
+ unset($filter[$propertyName]);
+
+ $rawFilter = encodeOrderedProperties($filter);
+ $candidateFlattenedValues[$propertyName][$rawFilter] ??= [];
+ $expectedType = $candidateFlattenedPropertyTypes[$propertyName] ?? null;
+ if(!checkFlattenPropertySuitability($propertyValue, $expectedType, $pair->new->getName(), $candidateFlattenedValues[$propertyName][$rawFilter])){
+ $notFlattenedProperties[$propertyName] = true;
+ continue;
+ }
+ $candidateFlattenedPropertyTypes[$propertyName] = $expectedType;
+ }
+ }
+ foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){
+ foreach($filters as $valuesToIds){
+ if(count(array_unique($valuesToIds)) === 1){
+ //this property doesn't influence the new ID
+ $notFlattenedProperties[$propertyName] = true;
+ continue 2;
+ }
+ }
+ }
+ foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){
+ unset($candidateFlattenedValues[$propertyName]);
+ }
+
+ $flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues, $candidateFlattenedPropertyTypes);
+ $flattenProperty = array_key_first($flattenedProperties);
+ //Properties with fewer rules take up less space for the same result
+ foreach(Utils::stringifyKeys($flattenedProperties) as $propertyName => $rules){
+ if(count($rules) < count($flattenedProperties[$flattenProperty])){
+ $flattenProperty = $propertyName;
+ }
+ }
+
+ $list = [];
+
+ foreach($upgradeTable as $pair){
+ $oldState = $pair->old->getStates();
+ $newState = $pair->new->getStates();
+
+ $cleanedOldState = $oldState;
+ $cleanedNewState = $newState;
+ $newName = $pair->new->getName();
+
+ foreach($unchangedStatesByNewName[$newName] as $propertyName){
+ unset($cleanedOldState[$propertyName]);
+ unset($cleanedNewState[$propertyName]);
+ }
+ ksort($cleanedOldState);
+ ksort($cleanedNewState);
+ if($flattenProperty !== null){
+ $flattenedValue = $cleanedOldState[$flattenProperty] ?? null;
+ if(!$flattenedValue instanceof StringTag && !$flattenedValue instanceof IntTag && !$flattenedValue instanceof ByteTag){
+ throw new AssumptionFailedError("Non-flattenable type of tag ($newName $flattenProperty) but have " . get_debug_type($flattenedValue));
+ }
+ unset($cleanedOldState[$flattenProperty]);
+ }
+ $rawOldState = encodeOrderedProperties($cleanedOldState);
+ $newNameRule = $flattenProperty !== null ?
+ $flattenedProperties[$flattenProperty][$rawOldState] ?? throw new AssumptionFailedError("This should always be set") :
+ $newName;
+
+ $remap = new BlockStateUpgradeSchemaBlockRemap(
+ $cleanedOldState,
+ $newNameRule,
+ $cleanedNewState,
+ $unchangedStatesByNewName[$pair->new->getName()]
+ );
+
+ $existing = $list[$rawOldState] ?? null;
+ if($existing === null || $existing->equals($remap)){
+ $list[$rawOldState] = $remap;
+ }else{
+ //TODO: ambiguous filter - this is a bug in the unchanged states calculation
+ //this is a real pain to fix, so workaround this for now
+ //this arose in 1.20.40 with brown_mushroom_block when variants 10 and 15 were remapped to mushroom_stem
+ //while also keeping the huge_mushroom_bits property with the same value
+ //this causes huge_mushroom_bits to be considered an "unchanged" state, which is *technically* correct, but
+ //means it can't be deleted from the filter
+
+ //move stuff from newState to copiedState where possible, even if we can't delete it from the filter
+ $cleanedNewState2 = $newState;
+ $copiedState = [];
+ foreach(Utils::stringifyKeys($cleanedNewState2) as $newPropertyName => $newPropertyValue){
+ if(isset($oldState[$newPropertyName]) && $oldState[$newPropertyName]->equals($newPropertyValue)){
+ $copiedState[] = $newPropertyName;
+ unset($cleanedNewState2[$newPropertyName]);
+ }
+ }
+
+ $fallbackRawFilter = encodeOrderedProperties($oldState);
+ if(isset($list[$fallbackRawFilter])){
+ throw new AssumptionFailedError("Exact match filter collision for \"" . $pair->old->getName() . "\" - this should never happen");
+ }
+ $list[$fallbackRawFilter] = new BlockStateUpgradeSchemaBlockRemap(
+ $oldState,
+ $newName,
+ $cleanedNewState2,
+ $copiedState
+ );
+ \GlobalLogger::get()->warning("Couldn't calculate an unambiguous partial remappedStates filter for some states of \"" . $pair->old->getName() . "\" - falling back to exact match");
+ \GlobalLogger::get()->warning("The schema should still work, but may be larger than desired");
+ }
+ }
+
+ //more specific filters must come before less specific ones, in case of a remap on a certain value which is
+ //otherwise unchanged
+ usort($list, function(BlockStateUpgradeSchemaBlockRemap $a, BlockStateUpgradeSchemaBlockRemap $b) : int{
+ return count($b->oldState) <=> count($a->oldState);
+ });
+ return array_values($list);
+}
+
+/**
+ * @param BlockStateMapping[][] $upgradeTable
+ * @phpstan-param array> $upgradeTable
+ */
+function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgradeSchema{
+ $foundVersion = -1;
+ foreach(Utils::stringifyKeys($upgradeTable) as $blockStateMappings){
+ foreach($blockStateMappings as $mapping){
+ if($foundVersion === -1 || $mapping->new->getVersion() === $foundVersion){
+ $foundVersion = $mapping->new->getVersion();
+ }else{
+ $logger = \GlobalLogger::get();
+ $logger->emergency("Mismatched upgraded versions found: $foundVersion and " . $mapping->new->getVersion());
+ $logger->emergency("Mismatched old state: " . $mapping->old->toNbt());
+ $logger->emergency("Mismatched new state: " . $mapping->new->toNbt());
+ $logger->emergency("This is probably because the game didn't recognize the input blockstate, so it was returned unchanged.");
+ $logger->emergency("This is usually because the block is locked behind an experimental toggle that isn't enabled on the world you used when generating this upgrade table.");
+ $logger->emergency("You can test this in a vanilla game using the /give or /setblock commands to try and acquire the block. Keep trying different experiments until you find the right one.");
+
+ exit(1);
+ }
+ }
+ }
+
+ $result = new BlockStateUpgradeSchema(
+ ($foundVersion >> 24) & 0xff,
+ ($foundVersion >> 16) & 0xff,
+ ($foundVersion >> 8) & 0xff,
+ ($foundVersion & 0xff),
+ 0
+ );
+ foreach(Utils::stringifyKeys($upgradeTable) as $oldName => $blockStateMappings){
+ $newNameFound = [];
+
+ foreach($blockStateMappings as $mapping){
+ $newName = $mapping->new->getName();
+ $newNameFound[$newName] = true;
+ }
+ if(count($newNameFound) === 1){
+ $newName = array_key_first($newNameFound);
+ if($newName !== $oldName){
+ $result->renamedIds[$oldName] = array_key_first($newNameFound);
+ }
+ if(!processStateGroup($oldName, $blockStateMappings, $result)){
+ throw new \RuntimeException("States with the same ID should be fully consistent");
+ }
+ }else{
+ //try processing this as a regular state group first
+ //if a property was flattened into the ID, the remaining states will normally be consistent
+ //if not we fall back to remap states and state filters
+ if(!processStateGroup($oldName, $blockStateMappings, $result)){
+ //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap
+ //even if some of the states stay under the same ID, the compression techniques used by this function
+ //implicitly rely on knowing the full set of old states and their new transformations
+ $result->remappedStates[$oldName] = processRemappedStates($blockStateMappings);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * @param BlockStateMapping[][] $upgradeTable
+ * @phpstan-param array> $upgradeTable
+ */
+function testBlockStateUpgradeSchema(array $upgradeTable, BlockStateUpgradeSchema $schema) : bool{
+ //TODO: HACK!
+ //the upgrader won't apply the schema if it's the same version and there's only one schema with a matching version
+ //ID (for performance reasons), which is a problem for testing isolated schemas
+ //add a dummy schema to bypass this optimization
+ $dummySchema = new BlockStateUpgradeSchema($schema->maxVersionMajor, $schema->maxVersionMinor, $schema->maxVersionPatch, $schema->maxVersionRevision, $schema->getSchemaId() + 1);
+ $upgrader = new BlockStateUpgrader([$schema, $dummySchema]);
+
+ foreach($upgradeTable as $mappingsByOldName){
+ foreach($mappingsByOldName as $mapping){
+ $expectedNewState = $mapping->new;
+
+ $actualNewState = $upgrader->upgrade($mapping->old);
+
+ if(!$expectedNewState->equals($actualNewState)){
+ \GlobalLogger::get()->error("Expected: " . $expectedNewState->toNbt());
+ \GlobalLogger::get()->error("Actual: " . $actualNewState->toNbt());
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @param string[] $argv
+ */
+function cmdGenerate(array $argv) : int{
+ $upgradeTableFile = $argv[2];
+ $schemaFile = $argv[3];
+
+ $table = loadUpgradeTableFromFile($upgradeTableFile, false);
+
+ ksort($table, SORT_STRING);
+
+ $diff = generateBlockStateUpgradeSchema($table);
+ if($diff->isEmpty()){
+ \GlobalLogger::get()->warning("All states appear to be the same! No schema generated.");
+ return 0;
+ }
+
+ if(!testBlockStateUpgradeSchema($table, $diff)){
+ \GlobalLogger::get()->error("Generated schema does not produce the results expected by $upgradeTableFile");
+ \GlobalLogger::get()->error("This is probably a bug in the schema generation code. Please report this to the developers.");
+ return 1;
+ }
+
+ file_put_contents(
+ $schemaFile,
+ json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n"
+ );
+ \GlobalLogger::get()->info("Schema file $schemaFile generated successfully.");
+ return 0;
+}
+
+/**
+ * @param string[] $argv
+ */
+function cmdTest(array $argv) : int{
+ $upgradeTableFile = $argv[2];
+ $schemaFile = $argv[3];
+
+ $table = loadUpgradeTableFromFile($upgradeTableFile, false);
+
+ ksort($table, SORT_STRING);
+
+ $schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($schemaFile), 0);
+ if(!testBlockStateUpgradeSchema($table, $schema)){
+ \GlobalLogger::get()->error("Schema $schemaFile does not produce the results predicted by $upgradeTableFile");
+ return 1;
+ }
+ \GlobalLogger::get()->info("Schema $schemaFile is valid according to $upgradeTableFile");
+
+ return 0;
+}
+
+/**
+ * @param string[] $argv
+ */
+function cmdUpdate(array $argv) : int{
+ [, , $oldSchemaFile, $oldPaletteFile, $newSchemaFile] = $argv;
+
+ $palette = BlockStateDictionary::loadPaletteFromString(Filesystem::fileGetContents($oldPaletteFile));
+ $schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($oldSchemaFile), 0);
+ //TODO: HACK!
+ //the upgrader won't apply the schema if it's the same version and there's only one schema with a matching version
+ //ID (for performance reasons), which is a problem for testing isolated schemas
+ //add a dummy schema to bypass this optimization
+ $dummySchema = new BlockStateUpgradeSchema($schema->maxVersionMajor, $schema->maxVersionMinor, $schema->maxVersionPatch, $schema->maxVersionRevision, $schema->getSchemaId() + 1);
+ $upgrader = new BlockStateUpgrader([$schema, $dummySchema]);
+
+ $tags = [];
+ foreach($palette as $stateData){
+ $tags[] = new TreeRoot($stateData->toNbt());
+ $tags[] = new TreeRoot($upgrader->upgrade($stateData)->toNbt());
+ }
+
+ $upgradeTable = buildUpgradeTableFromData($tags, false);
+ $newSchema = generateBlockStateUpgradeSchema($upgradeTable);
+
+ if(!testBlockStateUpgradeSchema($upgradeTable, $newSchema)){
+ \GlobalLogger::get()->error("Updated schema does not produce the expected results!");
+ \GlobalLogger::get()->error("This is probably a bug in the schema generation code. Please report this to the developers.");
+ return 1;
+ }
+
+ file_put_contents(
+ $newSchemaFile,
+ json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($newSchema), JSON_PRETTY_PRINT) . "\n"
+ );
+ \GlobalLogger::get()->info("Schema file $newSchemaFile updated to new format (from $oldSchemaFile) successfully.");
+ return 0;
+}
+
+/**
+ * @param string[] $argv
+ */
+function cmdUpdateAll(array $argv) : int{
+ $oldPaletteFilenames = [
+ '1.9.0' => '1.09.0',
+ '1.19.50' => '1.19.50.23_beta',
+ '1.19.60' => '1.19.60.26_beta',
+ '1.19.70' => '1.19.70.26_beta',
+ '1.19.80' => '1.19.80.24_beta',
+ ];
+ $schemaDir = $argv[2];
+ $paletteArchiveDir = $argv[3];
+
+ $schemaFileNames = scandir($schemaDir);
+ if($schemaFileNames === false){
+ \GlobalLogger::get()->error("Failed to read schema directory $schemaDir");
+ return 1;
+ }
+ foreach($schemaFileNames as $file){
+ $schemaFile = Path::join($schemaDir, $file);
+ if(!file_exists($schemaFile) || is_dir($schemaFile)){
+ continue;
+ }
+
+ if(preg_match('/^\d{4}_(.+?)_to_(.+?).json/', $file, $matches) !== 1){
+ continue;
+ }
+ $oldPaletteFile = Path::join($paletteArchiveDir, ($oldPaletteFilenames[$matches[1]] ?? $matches[1]) . '.nbt');
+
+ //a bit clunky but it avoids having to make yet another function
+ //TODO: perhaps in the future we should write the result to a tmpfile until all schemas are updated,
+ //and then copy the results into place at the end
+ if(cmdUpdate([$argv[0], "update", $schemaFile, $oldPaletteFile, $schemaFile]) !== 0){
+ return 1;
+ }
+ }
+
+ \GlobalLogger::get()->info("All schemas updated successfully.");
+ return 0;
+}
+
+/**
+ * @param string[] $argv
+ */
+function main(array $argv) : int{
+ $options = [
+ "generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)],
+ "test" => [["palette upgrade table file", "schema output file"], cmdTest(...)],
+ "update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)],
+ "update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)]
+ ];
+
+ $selected = $argv[1] ?? null;
+ if($selected === null || !isset($options[$selected])){
+ fwrite(STDERR, "Available commands:\n");
+ foreach($options as $command => [$args, $callback]){
+ fwrite(STDERR, " - $command " . implode(" ", array_map(fn(string $a) => "<$a>", $args)) . "\n");
+ }
+ return 1;
+ }
+
+ $callback = $options[$selected][1];
+ if(count($argv) !== count($options[$selected][0]) + 2){
+ fwrite(STDERR, "Usage: {$argv[0]} $selected " . implode(" ", array_map(fn(string $a) => "<$a>", $options[$selected][0])) . "\n");
+ return 1;
+ }
+ return $callback($argv);
+}
+
+exit(main($argv));
diff --git a/tools/convert-world.php b/tools/convert-world.php
index 75483c6af37..d4d15ce57b6 100644
--- a/tools/convert-world.php
+++ b/tools/convert-world.php
@@ -21,10 +21,29 @@
declare(strict_types=1);
+namespace pocketmine\tools\convert_world;
+
use pocketmine\world\format\io\FormatConverter;
use pocketmine\world\format\io\WorldProviderManager;
use pocketmine\world\format\io\WorldProviderManagerEntry;
use pocketmine\world\format\io\WritableWorldProviderManagerEntry;
+use function array_filter;
+use function array_key_exists;
+use function array_keys;
+use function array_map;
+use function array_shift;
+use function count;
+use function dirname;
+use function fwrite;
+use function getopt;
+use function implode;
+use function is_dir;
+use function is_string;
+use function is_writable;
+use function mkdir;
+use function realpath;
+use const PHP_EOL;
+use const STDERR;
require_once dirname(__DIR__) . '/vendor/autoload.php';
@@ -76,5 +95,5 @@
$oldProviderClass = array_shift($oldProviderClasses);
$oldProvider = $oldProviderClass->fromPath($inputPath, new \PrefixedLogger(\GlobalLogger::get(), "Old World Provider"));
-$converter = new FormatConverter($oldProvider, $writableFormats[$args["format"]], $backupPath, GlobalLogger::get());
+$converter = new FormatConverter($oldProvider, $writableFormats[$args["format"]], $backupPath, \GlobalLogger::get());
$converter->execute();
diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php
index 989310e2553..0cb5ac3667e 100644
--- a/tools/generate-bedrock-data-from-packets.php
+++ b/tools/generate-bedrock-data-from-packets.php
@@ -33,6 +33,8 @@
use pocketmine\crafting\json\SmithingTransformRecipeData;
use pocketmine\crafting\json\SmithingTrimRecipeData;
use pocketmine\data\bedrock\block\BlockStateData;
+use pocketmine\data\bedrock\item\BlockItemIdMap;
+use pocketmine\data\bedrock\item\ItemTypeNames;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
@@ -40,6 +42,7 @@
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\convert\BlockStateDictionary;
use pocketmine\network\mcpe\convert\BlockTranslator;
+use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
@@ -48,12 +51,12 @@
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
-use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
-use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
+use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
+use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield;
use pocketmine\network\mcpe\protocol\types\recipe\ComplexAliasItemDescriptor;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
@@ -110,6 +113,7 @@ class ParserPacketHandler extends PacketHandler{
public ?ItemTypeDictionary $itemTypeDictionary = null;
private BlockTranslator $blockTranslator;
+ private BlockItemIdMap $blockItemIdMap;
public function __construct(private string $bedrockDataPath){
$this->blockTranslator = new BlockTranslator(
@@ -119,6 +123,7 @@ public function __construct(private string $bedrockDataPath){
),
GlobalBlockStateHandlers::getSerializer()
);
+ $this->blockItemIdMap = BlockItemIdMap::getInstance();
}
private static function blockStatePropertiesToString(BlockStateData $blockStateData) : string{
@@ -136,7 +141,8 @@ private function itemStackToJson(ItemStack $itemStack) : ItemStackData{
if($this->itemTypeDictionary === null){
throw new PacketHandlingException("Can't process item yet; haven't received item type dictionary");
}
- $data = new ItemStackData($this->itemTypeDictionary->fromIntId($itemStack->getId()));
+ $itemStringId = $this->itemTypeDictionary->fromIntId($itemStack->getId());
+ $data = new ItemStackData($itemStringId);
if($itemStack->getCount() !== 1){
$data->count = $itemStack->getCount();
@@ -146,7 +152,7 @@ private function itemStackToJson(ItemStack $itemStack) : ItemStackData{
if($meta === 32767){
$meta = 0; //kick wildcard magic bullshit
}
- if($itemStack->getBlockRuntimeId() !== 0){
+ if($this->blockItemIdMap->lookupBlockId($itemStringId) !== null){
if($meta !== 0){
throw new PacketHandlingException("Unexpected non-zero blockitem meta");
}
@@ -159,20 +165,27 @@ private function itemStackToJson(ItemStack $itemStack) : ItemStackData{
if(count($stateProperties) > 0){
$data->block_states = self::blockStatePropertiesToString($blockState);
}
+ }elseif($itemStack->getBlockRuntimeId() !== ItemTranslator::NO_BLOCK_RUNTIME_ID){
+ throw new PacketHandlingException("Non-blockitems should have a zero block runtime ID (" . $itemStack->getBlockRuntimeId() . " on " . $itemStringId . ")");
}elseif($meta !== 0){
$data->meta = $meta;
}
- $nbt = $itemStack->getNbt();
- if($nbt !== null && count($nbt) > 0){
- $data->nbt = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($nbt)));
- }
+ $rawExtraData = $itemStack->getRawExtraData();
+ if($rawExtraData !== ""){
+ $decoder = PacketSerializer::decoder($rawExtraData, 0);
+ $extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder);
+ $nbt = $extraData->getNbt();
+ if($nbt !== null && count($nbt) > 0){
+ $data->nbt = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($nbt)));
+ }
- if(count($itemStack->getCanPlaceOn()) > 0){
- $data->can_place_on = $itemStack->getCanPlaceOn();
- }
- if(count($itemStack->getCanDestroy()) > 0){
- $data->can_destroy = $itemStack->getCanDestroy();
+ if(count($extraData->getCanPlaceOn()) > 0){
+ $data->can_place_on = $extraData->getCanPlaceOn();
+ }
+ if(count($extraData->getCanDestroy()) > 0){
+ $data->can_destroy = $extraData->getCanDestroy();
+ }
}
return $data;
@@ -182,7 +195,7 @@ private function itemStackToJson(ItemStack $itemStack) : ItemStackData{
* @return mixed[]
*/
private static function objectToOrderedArray(object $object) : array{
- $result = (array) $object;
+ $result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object);
ksort($result, SORT_STRING);
foreach($result as $property => $value){
@@ -269,7 +282,7 @@ private function recipeIngredientToJson(RecipeIngredient $itemStack) : RecipeIng
$meta = $descriptor->getMeta();
if($meta !== 32767){
$blockStateId = $this->blockTranslator->getBlockStateDictionary()->lookupStateIdFromIdMeta($data->name, $meta);
- if($blockStateId !== null){
+ if($this->blockItemIdMap->lookupBlockId($data->name) !== null && $blockStateId !== null){
$blockState = $this->blockTranslator->getBlockStateDictionary()->generateDataFromStateId($blockStateId);
if($blockState !== null && count($blockState->getStates()) > 0){
$data->block_states = self::blockStatePropertiesToString($blockState);
@@ -322,21 +335,25 @@ private function shapedRecipeToJson(ShapedRecipe $entry) : ShapedRecipeData{
}
}
}
+ $unlockingIngredients = $entry->getUnlockingRequirement()->getUnlockingIngredients();
return new ShapedRecipeData(
array_map(fn(array $array) => implode('', $array), $shape),
$outputsByKey,
array_map(fn(ItemStack $output) => $this->itemStackToJson($output), $entry->getOutput()),
$entry->getBlockName(),
- $entry->getPriority()
+ $entry->getPriority(),
+ $unlockingIngredients !== null ? array_map(fn(RecipeIngredient $input) => $this->recipeIngredientToJson($input), $unlockingIngredients) : []
);
}
private function shapelessRecipeToJson(ShapelessRecipe $recipe) : ShapelessRecipeData{
+ $unlockingIngredients = $recipe->getUnlockingRequirement()->getUnlockingIngredients();
return new ShapelessRecipeData(
array_map(fn(RecipeIngredient $input) => $this->recipeIngredientToJson($input), $recipe->getInputs()),
array_map(fn(ItemStack $output) => $this->itemStackToJson($output), $recipe->getOutputs()),
$recipe->getBlockName(),
- $recipe->getPriority()
+ $recipe->getPriority(),
+ $unlockingIngredients !== null ? array_map(fn(RecipeIngredient $input) => $this->recipeIngredientToJson($input), $unlockingIngredients) : []
);
}
@@ -382,7 +399,7 @@ public function handleCraftingData(CraftingDataPacket $packet) : bool{
CraftingDataPacket::ENTRY_FURNACE => "smelting",
CraftingDataPacket::ENTRY_FURNACE_DATA => "smelting",
CraftingDataPacket::ENTRY_MULTI => "special_hardcoded",
- CraftingDataPacket::ENTRY_SHULKER_BOX => "shapeless_shulker_box",
+ CraftingDataPacket::ENTRY_USER_DATA_SHAPELESS => "shapeless_shulker_box",
CraftingDataPacket::ENTRY_SHAPELESS_CHEMISTRY => "shapeless_chemistry",
CraftingDataPacket::ENTRY_SHAPED_CHEMISTRY => "shaped_chemistry",
CraftingDataPacket::ENTRY_SMITHING_TRANSFORM => "smithing",
@@ -394,7 +411,13 @@ public function handleCraftingData(CraftingDataPacket $packet) : bool{
$mappedType = $typeMap[$entry->getTypeId()];
if($entry instanceof ShapedRecipe){
- $recipes[$mappedType][] = $this->shapedRecipeToJson($entry);
+ //all known recipes are currently symmetric and I don't feel like attaching a `symmetric` field to
+ //every shaped recipe for this - split it into a separate category instead
+ if(!$entry->isSymmetric()){
+ $recipes[$mappedType . "_asymmetric"][] = $this->shapedRecipeToJson($entry);
+ }else{
+ $recipes[$mappedType][] = $this->shapedRecipeToJson($entry);
+ }
}elseif($entry instanceof ShapelessRecipe){
$recipes[$mappedType][] = $this->shapelessRecipeToJson($entry);
}elseif($entry instanceof MultiRecipe){
@@ -414,7 +437,7 @@ public function handleCraftingData(CraftingDataPacket $packet) : bool{
$recipes["potion_type"][] = new PotionTypeRecipeData(
$this->recipeIngredientToJson(new RecipeIngredient(new IntIdMetaItemDescriptor($recipe->getInputItemId(), $recipe->getInputItemMeta()), 1)),
$this->recipeIngredientToJson(new RecipeIngredient(new IntIdMetaItemDescriptor($recipe->getIngredientItemId(), $recipe->getIngredientItemMeta()), 1)),
- $this->itemStackToJson(new ItemStack($recipe->getOutputItemId(), $recipe->getOutputItemMeta(), 1, 0, null, [], [], null)),
+ $this->itemStackToJson(new ItemStack($recipe->getOutputItemId(), $recipe->getOutputItemMeta(), 1, 0, "")),
);
}
@@ -433,17 +456,22 @@ public function handleCraftingData(CraftingDataPacket $packet) : bool{
//how the data is ordered doesn't matter as long as it's reproducible
foreach($recipes as $_type => $entries){
$_sortedRecipes = [];
+ $_seen = [];
foreach($entries as $entry){
$entry = self::sort($entry);
$_key = json_encode($entry);
- while(isset($_sortedRecipes[$_key])){
- echo "warning: duplicated $_type recipe: $_key\n";
- $_key .= "a";
- }
- $_sortedRecipes[$_key] = $entry;
+ $duplicates = $_seen[$_key] ??= 0;
+ $_seen[$_key]++;
+ $suffix = chr(ord("a") + $duplicates);
+ $_sortedRecipes[$_key . $suffix] = $entry;
}
ksort($_sortedRecipes, SORT_STRING);
$recipes[$_type] = array_values($_sortedRecipes);
+ foreach($_seen as $_key => $_seenCount){
+ if($_seenCount > 1){
+ fwrite(STDERR, "warning: $_type recipe $_key was seen $_seenCount times\n");
+ }
+ }
}
ksort($recipes, SORT_STRING);
@@ -559,10 +587,7 @@ function main(array $argv) : int{
fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]);
continue;
}
- $serializer = PacketSerializer::decoder($raw, 0, new PacketSerializerContext(
- $handler->itemTypeDictionary ??
- new ItemTypeDictionary([new ItemTypeEntry("minecraft:shield", 0, false)]))
- );
+ $serializer = PacketSerializer::decoder($raw, 0);
$pk->decode($serializer);
$pk->handle($handler);
diff --git a/tools/generate-block-palette-spec.php b/tools/generate-block-palette-spec.php
index 6217d543783..879ffd6b486 100644
--- a/tools/generate-block-palette-spec.php
+++ b/tools/generate-block-palette-spec.php
@@ -40,6 +40,7 @@
use function json_encode;
use function ksort;
use const JSON_PRETTY_PRINT;
+use const SORT_NATURAL;
use const SORT_STRING;
use const STDERR;
@@ -82,7 +83,7 @@
foreach(Utils::stringifyKeys($reportMap) as $blockName => $propertyList){
foreach(Utils::stringifyKeys($propertyList) as $propertyName => $propertyValues){
- ksort($reportMap[$blockName][$propertyName]);
+ ksort($propertyValues, SORT_NATURAL);
$reportMap[$blockName][$propertyName] = array_values($propertyValues);
}
}
diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php
deleted file mode 100644
index 847486a6687..00000000000
--- a/tools/generate-blockstate-upgrade-schema.php
+++ /dev/null
@@ -1,420 +0,0 @@
->
- */
-function loadUpgradeTable(string $file, bool $reverse) : array{
- $contents = Filesystem::fileGetContents($file);
- $data = (new NetworkNbtSerializer())->readMultiple($contents);
-
- $result = [];
-
- for($i = 0; isset($data[$i]); $i += 2){
- $oldTag = $data[$i]->mustGetCompoundTag();
- $newTag = $data[$i + 1]->mustGetCompoundTag();
- $old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag);
- $new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag);
-
- $result[$old->getName()][] = new BlockStateMapping(
- $old,
- $new
- );
- }
-
- return $result;
-}
-
-/**
- * @param true[] $removedPropertiesCache
- * @param Tag[][] $remappedPropertyValuesCache
- * @phpstan-param array $removedPropertiesCache
- * @phpstan-param array> $remappedPropertyValuesCache
- */
-function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgradeSchema $result, array &$removedPropertiesCache, array &$remappedPropertyValuesCache) : void{
-
- //new and old IDs are the same; compare states
- $oldName = $old->getName();
-
- $oldStates = $old->getStates();
- $newStates = $new->getStates();
-
- $propertyRemoved = [];
- $propertyAdded = [];
- foreach(Utils::stringifyKeys($oldStates) as $propertyName => $oldProperty){
- $newProperty = $new->getState($propertyName);
- if($newProperty === null){
- $propertyRemoved[$propertyName] = $oldProperty;
- }elseif(!$newProperty->equals($oldProperty)){
- if(!isset($remappedPropertyValuesCache[$propertyName][$oldProperty->getValue()])){
- $result->remappedPropertyValues[$oldName][$propertyName][] = new BlockStateUpgradeSchemaValueRemap(
- $oldProperty,
- $newProperty
- );
- $remappedPropertyValuesCache[$propertyName][$oldProperty->getValue()] = $newProperty;
- }
- }
- }
-
- foreach(Utils::stringifyKeys($newStates) as $propertyName => $value){
- if($old->getState($propertyName) === null){
- $propertyAdded[$propertyName] = $value;
- }
- }
-
- if(count($propertyAdded) === 0 && count($propertyRemoved) === 0){
- return;
- }
- if(count($propertyAdded) === 1 && count($propertyRemoved) === 1){
- $propertyOldName = array_key_first($propertyRemoved);
- $propertyNewName = array_key_first($propertyAdded);
-
- $propertyOldValue = $propertyRemoved[$propertyOldName];
- $propertyNewValue = $propertyAdded[$propertyNewName];
-
- $existingPropertyValueMap = $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] ?? null;
- if($propertyOldName !== $propertyNewName){
- if(!$propertyOldValue->equals($propertyNewValue) && $existingPropertyValueMap === null){
- \GlobalLogger::get()->warning("warning: guessing that $oldName has $propertyOldName renamed to $propertyNewName with a value map of $propertyOldValue mapped to $propertyNewValue");;
- }
- //this is a guess; it might not be reliable if the value changed as well
- //this will probably never be an issue, but it might rear its ugly head in the future
- $result->renamedProperties[$oldName][$propertyOldName] = $propertyNewName;
- }
- if(!$propertyOldValue->equals($propertyNewValue)){
- $mapped = true;
- if($existingPropertyValueMap !== null && !$existingPropertyValueMap->equals($propertyNewValue)){
- if($existingPropertyValueMap->equals($propertyOldValue)){
- \GlobalLogger::get()->warning("warning: guessing that the value $propertyOldValue of $propertyNewValue did not change");;
- $mapped = false;
- }else{
- \GlobalLogger::get()->warning("warning: mismatch of new value for $propertyNewName for $oldName: $propertyOldValue seen mapped to $propertyNewValue and $existingPropertyValueMap");;
- }
- }
- if($mapped && !isset($remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()])){
- //value remap
- $result->remappedPropertyValues[$oldName][$propertyOldName][] = new BlockStateUpgradeSchemaValueRemap(
- $propertyRemoved[$propertyOldName],
- $propertyAdded[$propertyNewName]
- );
- $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] = $propertyNewValue;
- }
- }elseif($existingPropertyValueMap !== null){
- \GlobalLogger::get()->warning("warning: multiple values found for value $propertyOldValue of $propertyNewName on block $oldName, guessing it did not change");;
- $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] = $propertyNewValue;
- }
- }else{
- if(count($propertyAdded) !== 0 && count($propertyRemoved) === 0){
- foreach(Utils::stringifyKeys($propertyAdded) as $propertyAddedName => $propertyAddedValue){
- $existingDefault = $result->addedProperties[$oldName][$propertyAddedName] ?? null;
- if($existingDefault !== null && !$existingDefault->equals($propertyAddedValue)){
- throw new \UnexpectedValueException("Ambiguous default value for added property $propertyAddedName on block $oldName");
- }
-
- $result->addedProperties[$oldName][$propertyAddedName] = $propertyAddedValue;
- }
- }elseif(count($propertyRemoved) !== 0 && count($propertyAdded) === 0){
- foreach(Utils::stringifyKeys($propertyRemoved) as $propertyRemovedName => $propertyRemovedValue){
- if(!isset($removedPropertiesCache[$propertyRemovedName])){
- //to avoid having useless keys in the output
- $result->removedProperties[$oldName][] = $propertyRemovedName;
- $removedPropertiesCache[$propertyRemovedName] = $propertyRemovedName;
- }
- }
- }else{
- $result->remappedStates[$oldName][] = new BlockStateUpgradeSchemaBlockRemap(
- $oldStates,
- $new->getName(),
- $newStates,
- []
- );
- \GlobalLogger::get()->warning("warning: multiple properties added and removed for $oldName; added full state remap");;
- }
- }
-}
-
-/**
- * Attempts to compress a list of remapped states by looking at which state properties were consistently unchanged.
- * This significantly reduces the output size during flattening when the flattened block has many permutations
- * (e.g. walls).
- *
- * @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps
- * @param BlockStateMapping[] $upgradeTable
- *
- * @return BlockStateUpgradeSchemaBlockRemap[]
- */
-function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{
- $unchangedStatesByNewName = [];
-
- foreach($upgradeTable as $pair){
- if(count($pair->old->getStates()) === 0 || count($pair->new->getStates()) === 0){
- //all states have changed in some way - compression not possible
- $unchangedStatesByNewName[$pair->new->getName()] = [];
- continue;
- }
-
- $oldStates = $pair->old->getStates();
- $newStates = $pair->new->getStates();
- if(!isset($unchangedStatesByNewName[$pair->new->getName()])){
- //build list of unchanged states for this new ID
- $unchangedStatesByNewName[$pair->new->getName()] = [];
- foreach(Utils::stringifyKeys($oldStates) as $propertyName => $propertyValue){
- if(isset($newStates[$propertyName]) && $newStates[$propertyName]->equals($propertyValue)){
- $unchangedStatesByNewName[$pair->new->getName()][] = $propertyName;
- }
- }
- }else{
- //we already have a list of stuff that probably didn't change - verify that this is the case, and remove
- //any that changed in later states with the same ID
- foreach($unchangedStatesByNewName[$pair->new->getName()] as $k => $propertyName){
- if(
- !isset($oldStates[$propertyName]) ||
- !isset($newStates[$propertyName]) ||
- !$oldStates[$propertyName]->equals($newStates[$propertyName])
- ){
- //this property disappeared or changed its value in another state with the same ID - we can't
- //compress this state
- unset($unchangedStatesByNewName[$pair->new->getName()][$k]);
- }
- }
- }
- }
- foreach(Utils::stringifyKeys($unchangedStatesByNewName) as $newName => $unchangedStates){
- ksort($unchangedStates);
- $unchangedStatesByNewName[$newName] = $unchangedStates;
- }
-
- $compressedRemaps = [];
-
- foreach($stateRemaps as $remap){
- $oldState = $remap->oldState;
- $newState = $remap->newState;
-
- if($oldState === null || $newState === null){
- //no unchanged states - no compression possible
- assert(!isset($unchangedStatesByNewName[$remap->newName]));
- $compressedRemaps[$remap->newName][] = $remap;
- continue;
- }
-
- $cleanedOldState = $oldState;
- $cleanedNewState = $newState;
-
- foreach($unchangedStatesByNewName[$remap->newName] as $propertyName){
- unset($cleanedOldState[$propertyName]);
- unset($cleanedNewState[$propertyName]);
- }
- ksort($cleanedOldState);
- ksort($cleanedNewState);
-
- $duplicate = false;
- $compressedRemaps[$remap->newName] ??= [];
- foreach($compressedRemaps[$remap->newName] as $k => $compressedRemap){
- assert($compressedRemap->oldState !== null && $compressedRemap->newState !== null);
-
- if(
- count($compressedRemap->oldState) !== count($cleanedOldState) ||
- count($compressedRemap->newState) !== count($cleanedNewState)
- ){
- continue;
- }
- foreach(Utils::stringifyKeys($cleanedOldState) as $propertyName => $propertyValue){
- if(!isset($compressedRemap->oldState[$propertyName]) || !$compressedRemap->oldState[$propertyName]->equals($propertyValue)){
- //different filter value
- continue 2;
- }
- }
- foreach(Utils::stringifyKeys($cleanedNewState) as $propertyName => $propertyValue){
- if(!isset($compressedRemap->newState[$propertyName]) || !$compressedRemap->newState[$propertyName]->equals($propertyValue)){
- //different replacement value
- continue 2;
- }
- }
- $duplicate = true;
- break;
- }
- if(!$duplicate){
- $compressedRemaps[$remap->newName][] = new BlockStateUpgradeSchemaBlockRemap(
- $cleanedOldState,
- $remap->newName,
- $cleanedNewState,
- $unchangedStatesByNewName[$remap->newName]
- );
- }
- }
-
- $list = array_merge(...array_values($compressedRemaps));
-
- //more specific filters must come before less specific ones, in case of a remap on a certain value which is
- //otherwise unchanged
- usort($list, function(BlockStateUpgradeSchemaBlockRemap $a, BlockStateUpgradeSchemaBlockRemap $b) : int{
- return count($b->oldState) <=> count($a->oldState);
- });
- return $list;
-}
-
-/**
- * @param BlockStateMapping[][] $upgradeTable
- * @phpstan-param array> $upgradeTable
- */
-function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgradeSchema{
- $foundVersion = -1;
- foreach(Utils::stringifyKeys($upgradeTable) as $blockStateMappings){
- foreach($blockStateMappings as $mapping){
- if($foundVersion === -1 || $mapping->new->getVersion() === $foundVersion){
- $foundVersion = $mapping->new->getVersion();
- }else{
- $logger = \GlobalLogger::get();
- $logger->emergency("Mismatched upgraded versions found: $foundVersion and " . $mapping->new->getVersion());
- $logger->emergency("Mismatched old state: " . $mapping->old->toNbt());
- $logger->emergency("Mismatched new state: " . $mapping->new->toNbt());
- $logger->emergency("This is probably because the game didn't recognize the input blockstate, so it was returned unchanged.");
- $logger->emergency("This is usually because the block is locked behind an experimental toggle that isn't enabled on the world you used when generating this upgrade table.");
- $logger->emergency("You can test this in a vanilla game using the /give or /setblock commands to try and acquire the block. Keep trying different experiments until you find the right one.");
-
- exit(1);
- }
- }
- }
-
- $result = new BlockStateUpgradeSchema(
- ($foundVersion >> 24) & 0xff,
- ($foundVersion >> 16) & 0xff,
- ($foundVersion >> 8) & 0xff,
- ($foundVersion & 0xff),
- 0
- );
- foreach(Utils::stringifyKeys($upgradeTable) as $oldName => $blockStateMappings){
- $newNameFound = [];
-
- $removedPropertiesCache = [];
- $remappedPropertyValuesCache = [];
- foreach($blockStateMappings as $mapping){
- $newName = $mapping->new->getName();
- $newNameFound[$newName] = true;
- }
- if(count($newNameFound) === 1){
- $newName = array_key_first($newNameFound);
- if($newName !== $oldName){
- $result->renamedIds[$oldName] = array_key_first($newNameFound);
- }
- foreach($blockStateMappings as $mapping){
- processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache);
- }
- }else{
- if(isset($newNameFound[$oldName])){
- //some of the states stayed under the same ID - we can process these as normal states
- foreach($blockStateMappings as $k => $mapping){
- if($mapping->new->getName() === $oldName){
- processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache);
- unset($blockStateMappings[$k]);
- }
- }
- }
- //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap
- foreach($blockStateMappings as $mapping){
- if(!$mapping->old->equals($mapping->new)){
- $result->remappedStates[$mapping->old->getName()][] = new BlockStateUpgradeSchemaBlockRemap(
- $mapping->old->getStates(),
- $mapping->new->getName(),
- $mapping->new->getStates(),
- []
- );
- }
- }
- }
- }
- foreach(Utils::stringifyKeys($result->remappedStates) as $oldName => $remap){
- $result->remappedStates[$oldName] = compressRemappedStates($upgradeTable[$oldName], $remap);
- }
-
- return $result;
-}
-
-/**
- * @param string[] $argv
- */
-function main(array $argv) : int{
- if(count($argv) !== 3){
- fwrite(STDERR, "Required arguments: input file path, output file path\n");
- return 1;
- }
-
- $input = $argv[1];
- $output = $argv[2];
-
- $table = loadUpgradeTable($input, false);
-
- ksort($table, SORT_STRING);
-
- $diff = generateBlockStateUpgradeSchema($table);
- if($diff->isEmpty()){
- \GlobalLogger::get()->warning("All states appear to be the same! No schema generated.");
- return 0;
- }
- file_put_contents(
- $output,
- json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n"
- );
- \GlobalLogger::get()->info("Schema file $output generated successfully.");
-
- return 0;
-}
-
-exit(main($argv));
diff --git a/tools/generate-item-upgrade-schema.php b/tools/generate-item-upgrade-schema.php
index c6096bafbab..4eee925392a 100644
--- a/tools/generate-item-upgrade-schema.php
+++ b/tools/generate-item-upgrade-schema.php
@@ -94,9 +94,14 @@
$newDiff = [];
foreach($target["simple"] as $oldId => $newId){
- if(($merged["simple"][$oldId] ?? null) !== $newId){
- $newDiff["renamedIds"][$oldId] = $newId;
+ $previousNewId = $merged["simple"][$oldId] ?? null;
+ if(
+ $previousNewId === $newId || //if previous schemas already accounted for this
+ ($previousNewId !== null && isset($target["simple"][$previousNewId])) //or the item's ID has been changed for a second time
+ ){
+ continue;
}
+ $newDiff["renamedIds"][$oldId] = $newId;
}
if(isset($newDiff["renamedIds"])){
ksort($newDiff["renamedIds"], SORT_STRING);