diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..e41741f364 --- /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 deleted file mode 100644 index 615ab12ef3..0000000000 --- a/.github/ISSUE_TEMPLATE/api-change-request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: API change request -about: Suggest a change, addition or removal to the plugin API -title: '' -labels: '' -assignees: '' - ---- - - -## Description - - - -## Justification - - - -## Alternative methods diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000..3a4e491005 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,84 @@ +name: Bug report +description: Report a feature of PocketMine-MP not working as expected +body: + - type: markdown + attributes: + value: | + ## Plugin information + + > [!IMPORTANT] + > It's strongly recommended to test for bugs without plugins before reporting an issue. + > This helps avoid wasting maintainers' time on bugs that are not actually caused by PocketMine-MP. + > + > If you're not sure whether a plugin might be causing your issue, please seek help on our [Discord](https://discord.gg/bmSAZBG) before writing an issue. + + - type: dropdown + attributes: + label: Plugin information + options: + - "I haven't tested without plugins" + - Bug happens without plugins + - Bug only happens with certain plugins (describe below) + validations: + required: true + + - type: markdown + attributes: + value: | + ## Bug description + + > [!TIP] + > Helpful information to include: + > - Steps to reproduce the issue + > - Error backtraces + > - Crashdumps + > - Plugin code that triggers the issue + > - List of installed plugins (use /plugins) + + > [!IMPORTANT] + > **Steps to reproduce are critical to finding the cause of the problem!** + > Without reproducing steps, the issue will probably not be solvable and may be closed. + + - type: textarea + attributes: + label: Problem description + description: Describe the problem, and how you encountered it + placeholder: e.g. Steps to reproduce the issue + validations: + required: true + - type: textarea + attributes: + label: Expected behaviour + description: What did you expect to happen? + validations: + required: true + + - type: markdown + attributes: + value: | + ## Version, OS and game info + + - type: input + attributes: + label: PocketMine-MP version + placeholder: Use the /version command in PocketMine-MP + validations: + required: true + - type: input + attributes: + label: PHP version + placeholder: Use the /version command in PocketMine-MP + validations: + required: true + - type: input + attributes: + label: Server OS + placeholder: Use the /version command in PocketMine-MP + validations: + required: true + - type: input + attributes: + label: Game version (if applicable) + placeholder: e.g. Android, iOS, Windows, Xbox, PS4, Switch + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 730d6e811b..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: Bug report -about: Unexpected non-crash behaviour (except missing gameplay features) -title: '' -labels: '' -assignees: '' - ---- - -### Issue description - -- Expected result: What were you expecting to happen? -- Actual result: What actually happened? - -### Steps to reproduce the issue -1. ... -2. ... - -### OS and versions - -* PocketMine-MP: -* PHP: -* Using JIT: yes/no (delete as appropriate) -* Server OS: -* Game version: Android/iOS/Win10/Xbox/PS4/Switch (delete as appropriate) - -### Plugins - - -- If you remove all plugins, does the issue still occur? -- If the issue is **not** reproducible without plugins: - - Have you asked for help on our forums before creating an issue? - - Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section - -### Crashdump, backtrace or other files - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d13fb4498f..d18b277e70 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,9 +3,6 @@ contact_links: - name: Help & support on Discord url: https://discord.gg/bmSAZBG about: We don't accept support requests on the issue tracker. Please try asking on Discord instead. - - name: Help & support on forums - url: https://forums.pmmp.io - about: We don't accept support requests on the issue tracker. Please try asking on forums instead. - name: Documentation url: https://pmmp.rtfd.io about: PocketMine-MP documentation diff --git a/.github/ISSUE_TEMPLATE/crash.md b/.github/ISSUE_TEMPLATE/crash.md deleted file mode 100644 index ee91d230eb..0000000000 --- a/.github/ISSUE_TEMPLATE/crash.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Crash -about: Report a crash in PocketMine-MP (not plugins) -title: Server crashed -labels: '' -assignees: '' - ---- - - - - -Link to crashdump: - - -### Additional comments (optional) diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml new file mode 100644 index 0000000000..735255de2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash.yml @@ -0,0 +1,25 @@ +name: Crash +description: Report a crash in PocketMine-MP (not plugins) +title: Server crashed +body: + - type: markdown + attributes: + value: | + > [!TIP] + > Submit crashdump `.log` files to the [Crash Archive](https://crash.pmmp.io/submit). + > If you can't submit the crashdump to the Crash Archive, paste it on a site like [GitHub Gist](https://gist.github.com) or [Pastebin](https://pastebin.com). + + > [!CAUTION] + > DON'T paste the crashdump data directly into an issue. + + - type: input + id: crashdump-url + attributes: + label: Link to crashdump + validations: + required: true + + - type: textarea + attributes: + label: Additional comments (optional) + description: Any other information that might help us solve the problem diff --git a/.github/ISSUE_TEMPLATE/feature-proposal.yml b/.github/ISSUE_TEMPLATE/feature-proposal.yml new file mode 100644 index 0000000000..e0d37ef06e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-proposal.yml @@ -0,0 +1,19 @@ +name: Feature addition, change, or removal +description: Propose adding new features, or changing/removing existing ones +body: + - type: textarea + attributes: + label: Problem description + description: Explain why a change is needed + validations: + required: true + - type: textarea + attributes: + label: Proposed solution + description: Describe what changes you think should be made + validations: + required: true + - type: textarea + attributes: + label: "Alternative solutions or workarounds" + description: "Describe other ways you've explored to achieve your goal" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 022518e813..82fd81a1de 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,43 +1,32 @@ -## Introduction + -### Relevant issues - +### Related issues & PRs ## Changes ### API changes + ### Behavioural changes + ## Backwards compatibility + ## Follow-up - + ## Tests -I tested this PR by doing the following (tick all that apply): -- [ ] Writing PHPUnit tests (commit these in the `tests/phpunit` folder) -- [ ] Playtesting using a Minecraft client (provide screenshots or a video) -- [ ] Writing a test plugin (provide the code and sample output) -- [ ] Other (provide details) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 97607ab8fa..13721f0ba9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,4 +37,7 @@ updates: - package-ecosystem: github-actions directory: "/" schedule: - interval: weekly + interval: monthly + groups: + github-actions: + patterns: ["*"] diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 4f045bdc9b..6199ad7a9e 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v6.6.1 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp @@ -66,7 +66,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.6.1 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp @@ -79,7 +79,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.6.1 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp @@ -92,7 +92,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v6.6.1 + uses: docker/build-push-action@v6.15.0 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 8d0add224e..fde5e3099c 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml new file mode 100644 index 0000000000..131c0dde25 --- /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.32.0 + 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 c47a4399b1..d2e9eb0d0e 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -1,18 +1,85 @@ name: Draft release 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" push: - tags: "*" + tags: + - "*" + +env: + PHP_VERSION: "8.2" jobs: + skip: + name: Check whether to ignore this tag + runs-on: ubuntu-20.04 + + outputs: + skip: ${{ steps.exists.outputs.exists == 'true' }} + + steps: + - name: Check if release already exists + id: exists + env: + GH_TOKEN: ${{ github.token }} + run: | + exists=false + if [[ "${{ github.ref_type }}" == "tag" ]]; then + tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)" + if gh release view "$tag" --repo "${{ github.repository }}"; then + exists=true + fi + fi + echo exists=$exists >> $GITHUB_OUTPUT + + check: + needs: [skip] + if: needs.skip.outputs.skip != 'true' + name: Check release + uses: ./.github/workflows/draft-release-pr-check.yml + + trigger-post-release-workflow: + name: Trigger post-release RestrictedActions workflow + needs: [check] + if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag + + runs-on: ubuntu-20.04 + + steps: + - name: Generate access token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} + private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} + owner: ${{ github.repository_owner }} + repositories: RestrictedActions + + - name: Dispatch post-release restricted action + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.repository_owner }}/RestrictedActions + event-type: pocketmine_mp_post_release + client-payload: '{"branch": "${{ github.ref }}"}' + draft: name: Create GitHub draft release - if: "startsWith(github.event.head_commit.message, 'Release ')" + needs: [check] + if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags + runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - php-version: [8.2] steps: - uses: actions/checkout@v4 @@ -20,9 +87,9 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: - php-version: ${{ matrix.php-version }} + php-version: ${{ env.PHP_VERSION }} - name: Restore Composer package cache uses: actions/cache@v4 @@ -40,7 +107,7 @@ jobs: - name: Calculate build number id: build-number run: | - BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins + BUILD_NUMBER=$((2300+$GITHUB_RUN_NUMBER)) #to stay above jenkins echo "Build number: $BUILD_NUMBER" echo BUILD_NUMBER=$BUILD_NUMBER >> $GITHUB_OUTPUT @@ -53,23 +120,31 @@ 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 + PM_VERSION=$(php build/dump-version-info.php base_version) + echo PM_VERSION=$PM_VERSION >> $GITHUB_OUTPUT + echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT + echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT + echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT + echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT + echo PRERELEASE=$(php build/dump-version-info.php prerelease) >> $GITHUB_OUTPUT + + if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)" + else + tag="$PM_VERSION" + fi + echo TAG_NAME=$tag >> $GITHUB_OUTPUT - name: Generate PHP binary download URL id: php-binary-url run: | - echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT + echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ env.PHP_VERSION }}-latest" >> $GITHUB_OUTPUT - name: Generate build info run: | php build/generate-build-info-json.php \ ${{ github.sha }} \ - ${{ steps.get-pm-version.outputs.PM_VERSION }} \ + ${{ steps.get-pm-version.outputs.TAG_NAME }} \ ${{ github.repository }} \ ${{ steps.build-number.outputs.BUILD_NUMBER }} \ ${{ github.run_id }} \ @@ -90,18 +165,27 @@ jobs: ${{ github.workspace }}/core-permissions.rst - name: Create draft release - uses: ncipollo/release-action@v1.14.0 + uses: ncipollo/release-action@v1.16.0 + id: create-draft with: artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst 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 }} + tag: ${{ steps.get-pm-version.outputs.TAG_NAME }} token: ${{ secrets.GITHUB_TOKEN }} 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 }}). + + :warning: Found a bug? Report it on our [issue tracker](${{ github.server_url }}/${{ github.repository }}/issues). **We can't fix bugs if you don't report them.** + + - name: Post draft release URL on PR + if: github.event_name == 'pull_request_target' + uses: thollander/actions-comment-pull-request@v3 + with: + message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it." diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml index 6d71a0e70c..e26f7c3187 100644 --- a/.github/workflows/main-php-matrix.yml +++ b/.github/workflows/main-php-matrix.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -96,7 +96,7 @@ jobs: submodules: true - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -128,7 +128,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7c92c0b8b2..5718687477 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.31.1 + uses: shivammathur/setup-php@2.32.0 with: php-version: 8.2 tools: php-cs-fixer:3.49 @@ -37,3 +37,15 @@ jobs: - 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 0000000000..eb46043bdd --- /dev/null +++ b/.github/workflows/pr-remove-waiting-label.yml @@ -0,0 +1,33 @@ +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('/'); + try { + await github.rest.issues.removeLabel({ + owner: owner, + repo: repo, + issue_number: context.payload.number, + name: "Status: Waiting on Author", + }); + } catch (error) { + if (error.status === 404) { + //probably label wasn't set on the issue + console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message); + } else { + throw error; + } + } diff --git a/.github/workflows/pr-stale.yml b/.github/workflows/pr-stale.yml new file mode 100644 index 0000000000..23518b2cfc --- /dev/null +++ b/.github/workflows/pr-stale.yml @@ -0,0 +1,29 @@ +name: 'Clean up stale PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + days-before-issue-stale: -1 + days-before-issue-close: -1 + stale-pr-message: | + This PR has been marked as "Waiting on Author", but we haven't seen any activity in 7 days. + + If there is no further activity, it will be closed in 28 days. + + Note for maintainers: Adding an assignee to the PR will prevent it from being marked as stale. + + close-pr-message: | + As this PR hasn't been updated for a while, unfortunately we'll have to close it. + + days-before-pr-stale: 7 + days-before-pr-close: 28 + only-labels: "Status: Waiting on Author" + close-pr-label: "Resolution: Abandoned" + exempt-all-assignees: true + diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml index a582be3280..0c2fdd81c0 100644 --- a/.github/workflows/team-pr-auto-approve.yml +++ b/.github/workflows/team-pr-auto-approve.yml @@ -9,34 +9,30 @@ on: pull_request_target: types: - opened - - synchronize - reopened - ready_for_review - -permissions: - pull-requests: write + - synchronize jobs: - approve: - name: Auto approve + dispatch: + name: Request approval runs-on: ubuntu-latest + if: '! github.event.pull_request.draft' steps: - - name: Check if PR author has write access - id: check-permission - uses: actions-cool/check-user-permission@v2 + - name: Generate access token + id: generate-token + uses: actions/create-github-app-token@v1 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 + app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} + private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} + owner: ${{ github.repository_owner }} + repositories: RestrictedActions - #TODO: Some way to avoid unnecessary repeated reviews would be nice here - - - name: Approve PR if authorized - if: steps.check-permission.outputs.require-result == 'true' && steps.check-permission.outputs.check-result == 'false' - uses: juliangruber/approve-pull-request-action@v2 + - name: Dispatch restricted action + uses: peter-evans/repository-dispatch@v3 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - number: ${{ github.event.pull_request.number }} + 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": "0" }' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3490542c5b..799c9d99cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t - Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones. - **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes. - This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers. - - Check out the [issues page]() for something that you could tackle without too much effort. + - Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort. - **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail. - **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE. - **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode. diff --git a/README.md b/README.md index b9e2e18887..6f2b715ab2 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ PocketMine-MP accepts community contributions! The following resources will be u * [Building and running PocketMine-MP from source](BUILDING.md) * [Contributing Guidelines](CONTRIBUTING.md) +New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase. + ## Donate PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love. diff --git a/build/dump-version-info.php b/build/dump-version-info.php new file mode 100644 index 0000000000..e13696f3da --- /dev/null +++ b/build/dump-version-info.php @@ -0,0 +1,87 @@ + $options + */ +$options = [ + "base_version" => VersionInfo::BASE_VERSION, + "major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0], + "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK, + "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD, + "changelog_file_name" => function() : string{ + $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-bedrockdata-path-consts.php b/build/generate-bedrockdata-path-consts.php index 6ad6d83fd8..f74137dd2f 100644 --- a/build/generate-bedrockdata-path-consts.php +++ b/build/generate-bedrockdata-path-consts.php @@ -28,6 +28,7 @@ use function dirname; use function fclose; use function fopen; use function fwrite; +use function is_dir; use function is_file; use function scandir; use function str_replace; @@ -59,7 +60,7 @@ foreach($files as $file){ continue; } $path = Path::join(BEDROCK_DATA_PATH, $file); - if(!is_file($path)){ + if(!is_file($path) && !is_dir($path)){ continue; } @@ -67,6 +68,7 @@ foreach($files as $file){ 'README.md', 'LICENSE', 'composer.json', + '.github' ] as $ignored){ if($file === $ignored){ continue 2; diff --git a/build/generate-biome-ids.php b/build/generate-biome-ids.php index f36591fe4d..56f871f431 100644 --- a/build/generate-biome-ids.php +++ b/build/generate-biome-ids.php @@ -122,7 +122,7 @@ if(!is_array($ids)){ throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object"); } $cleanedIds = []; -foreach($ids as $name => $id){ +foreach(Utils::promoteKeys($ids) as $name => $id){ if(!is_string($name) || !is_int($id)){ throw new \RuntimeException("Invalid biome ID map, expected string => int map"); } diff --git a/build/generate-pocketmine-yml-property-consts.php b/build/generate-pocketmine-yml-property-consts.php index dcc574f8ab..f90f3fc667 100644 --- a/build/generate-pocketmine-yml-property-consts.php +++ b/build/generate-pocketmine-yml-property-consts.php @@ -42,7 +42,7 @@ $constants = []; * @phpstan-param-out array $constants */ function collectProperties(string $prefix, array $properties, array &$constants) : void{ - foreach($properties as $propertyName => $property){ + foreach(Utils::promoteKeys($properties) as $propertyName => $property){ $fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName; $constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName)); diff --git a/build/generate-registry-annotations.php b/build/generate-registry-annotations.php index bcdaee4eba..35d0893954 100644 --- a/build/generate-registry-annotations.php +++ b/build/generate-registry-annotations.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\build\update_registry_annotations; +use pocketmine\utils\Utils; use function basename; use function class_exists; use function count; @@ -48,6 +49,7 @@ if(count($argv) !== 2){ /** * @param object[] $members + * @phpstan-param array $members */ function generateMethodAnnotations(string $namespaceName, array $members) : string{ $selfName = basename(__FILE__); @@ -60,7 +62,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri static $lineTmpl = " * @method static %2\$s %s()"; $memberLines = []; - foreach($members as $name => $member){ + foreach(Utils::stringifyKeys($members) as $name => $member){ $reflect = new \ReflectionClass($member); while($reflect !== false && $reflect->isAnonymous()){ $reflect = $reflect->getParentClass(); diff --git a/build/make-release.php b/build/make-release.php deleted file mode 100644 index 7a570eb357..0000000000 --- a/build/make-release.php +++ /dev/null @@ -1,164 +0,0 @@ - "Version to insert and tag", - "next" => "Version to put in the file after tagging", - "channel" => "Release channel to post this build into" -]; - -function systemWrapper(string $command, string $errorMessage) : void{ - system($command, $result); - if($result !== 0){ - echo "error: $errorMessage; aborting\n"; - exit(1); - } -} - -function main() : void{ - $filteredOpts = []; - foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help"])) as $optName => $optValue){ - if($optName === "help"){ - fwrite(STDOUT, "Options:\n"); - - $maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS))); - foreach(ACCEPTED_OPTS as $acceptedName => $description){ - fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n"); - } - exit(0); - } - if(!is_string($optValue)){ - fwrite(STDERR, "--$optName expects exactly 1 value\n"); - exit(1); - } - $filteredOpts[$optName] = $optValue; - } - - $channel = $filteredOpts["channel"] ?? null; - if(isset($filteredOpts["current"])){ - $currentVer = new VersionString($filteredOpts["current"]); - }else{ - $currentVer = new VersionString(VersionInfo::BASE_VERSION); - } - - $nextVer = isset($filteredOpts["next"]) ? new VersionString($filteredOpts["next"]) : null; - - $suffix = $currentVer->getSuffix(); - if($suffix !== ""){ - if($channel === "stable"){ - fwrite(STDERR, "error: cannot release a suffixed build into the stable channel\n"); - exit(1); - } - if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){ - echo "error: invalid current version suffix \"$suffix\"; aborting\n"; - exit(1); - } - $nextVer ??= new VersionString(sprintf( - "%u.%u.%u-%s%u", - $currentVer->getMajor(), - $currentVer->getMinor(), - $currentVer->getPatch(), - $matches[1], - ((int) $matches[2]) + 1 - )); - $channel ??= strtolower($matches[1]); - }else{ - $nextVer ??= new VersionString(sprintf( - "%u.%u.%u", - $currentVer->getMajor(), - $currentVer->getMinor(), - $currentVer->getPatch() + 1 - )); - $channel ??= "stable"; - } - - 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"); - - replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel); - systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit"); - systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit"); -} - -main(); diff --git a/build/php b/build/php index 084822aa9e..1549433797 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 084822aa9e381ca05591e902a2613fe971dff3fd +Subproject commit 15494337976e645499e2e3e8c8b491227522be91 diff --git a/build/server-phar-stub.php b/build/server-phar-stub.php index b4018e3a78..c713636c4b 100644 --- a/build/server-phar-stub.php +++ b/build/server-phar-stub.php @@ -25,6 +25,7 @@ namespace pocketmine\server_phar_stub; use function clearstatcache; use function copy; +use function define; use function fclose; use function fflush; use function flock; @@ -165,4 +166,5 @@ $start = hrtime(true); $cacheName = preparePharCache($tmpDir, __FILE__); echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n"; +define('pocketmine\ORIGINAL_PHAR_PATH', __FILE__); require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php'; diff --git a/build/server-phar.php b/build/server-phar.php index f6bb29d514..7560fa5dae 100644 --- a/build/server-phar.php +++ b/build/server-phar.php @@ -129,7 +129,7 @@ function buildPhar(string $pharPath, string $basePath, array $includedPaths, arr } function main() : void{ - if(ini_get("phar.readonly") == 1){ + if(ini_get("phar.readonly") === "1"){ echo "Set phar.readonly to 0 with -dphar.readonly=0" . PHP_EOL; exit(1); } diff --git a/changelogs/5.18.md b/changelogs/5.18.md index 4b7911efef..35aa237afe 100644 --- a/changelogs/5.18.md +++ b/changelogs/5.18.md @@ -17,3 +17,14 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if ## 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 0000000000..6768c38e5d --- /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.20.md b/changelogs/5.20.md new file mode 100644 index 0000000000..b0da701d67 --- /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 0000000000..54bebf5dbd --- /dev/null +++ b/changelogs/5.21.md @@ -0,0 +1,128 @@ +# 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. + +# 5.21.2 +Released 29th November 2024. + +## Fixes +- Fixed blocks destroyable by water being able to be placed inside water. +- Fixed deprecation warnings about nullable typehints on PHP 8.4. +- Fixed `Utils::getNiceClosureName()` not working correctly on PHP 8.4. +- Fixed incorrect break animations when breaking the block behind an instantly-breakable block like a torch. +- Fixed candle extinguish logic. +- Fixed various documentation issues around array key types. +- Introduced a new PHPStan rule along with `Utils::promoteKeys()` to improve PHPStan error reporting around unspecified array key types. Previously, some errors were missed due to PHPStan's BenevolentUnionType. + +## DevOps +- `pmmp/server-developers` team is now automatically requested for review on any new PR. +- `Status: Waiting on Author` label is now automatically removed from PRs when they are updated. diff --git a/changelogs/5.22.md b/changelogs/5.22.md new file mode 100644 index 0000000000..6c9ba6d7ee --- /dev/null +++ b/changelogs/5.22.md @@ -0,0 +1,16 @@ +# 5.22.0 +Released 4th December 2024. + +**For Minecraft: Bedrock Edition 1.21.50** + +This is a support release for Minecraft: Bedrock Edition 1.21.50. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.50. +- Removed support for earlier versions. diff --git a/changelogs/5.23.md b/changelogs/5.23.md new file mode 100644 index 0000000000..3a287608f3 --- /dev/null +++ b/changelogs/5.23.md @@ -0,0 +1,139 @@ +# 5.23.0 +Released 5th December 2024. + +This is a minor feature release, including new gameplay features, internals improvements, API additions and +deprecations, and improvements to timings. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load. +- Added `/xp` command. +- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors. + +## Gameplay +- Added the following new items: + - End Crystal + - Goat Horn (all variants) + - Ice Bomb (from Education Edition) + - Recovery Compass +- Added the following enchantments: + - Frost Walker +- Sugarcane now self-destructs when there is no water adjacent to the base block. +- Added basic support for middle-clicking on entities to get their spawn eggs. +- Added sounds when drinking potions. +- Eating food is now allowed in creative mode and in peaceful difficulty. + +## API +### `pocketmine\block` +- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks. +- The following API methods have been deprecated: + - `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work + +### `pocketmine\command` +- The following classes have been added: + - `ClosureCommand` - allows registering a closure to execute a command + +### `pocketmine\event` +- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions. + - This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc. + - If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed). + - The following API methods have been added: + - `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void` + - `public PlayerInteractEvent->setUseItem(bool $useItem) : void` + - `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc). + - `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc). +- The following new classes have been added: + - `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity + +### `pocketmine\inventory\transaction` +- The following API methods have been deprecated: + - `InventoryAction->onAddToTransaction()` + +### `pocketmine\permission` +- The following API methods have been deprecated: + - `PermissionManager->getPermissionSubscriptions()` + - `PermissionManager->subscribeToPermission()` + - `PermissionManager->unsubscribeFromAllPermissions()` + - `PermissionManager->unsubscribeFromPermission()` + +### `pocketmine\plugin` +- The following classes have been deprecated: + - `DiskResourceProvider` + - `ResourceProvider` + +### `pocketmine\promise` +- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array. + +### `pocketmine\scheduler` +- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`. +- The following API methods have been deprecated + - `AsyncTask->publishProgress()` + - `AsyncTask->onProgressUpdate()` + +### `pocketmine\timings` +- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected. + - The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report. + - Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings. + - PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future. +- The following API methods have been added: + - `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`. + - `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset + - `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled + - `public static TimingsHandler::requestPrintTimings() : Promise>` - asynchronously collects timing results from all threads and assembles them into a single report +- The following API methods have been deprecated: + - `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead. + - `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed +- The following constants have been deprecated: + - `Timings::GROUP_BREAKDOWN` - no longer used + +### `pocketmine\utils` +- The following API methods have been added: + - `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4. + +## Internals +- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins. +- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified. + - While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively. + - It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`. + - In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely. +- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions. +- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies. +- Fixed various deprecation warnings in PHP 8.4. +- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed. + +# 5.23.1 +Released 5th December 2024. + +## Fixes +- Fixed signs not creating a tile when placed. + +## Internals +- Improved blockstate consistency check to detect tiles disappearing during refactors. + +# 5.23.2 +Released 9th December 2024. + +## General +- Updated translations for Russian and Korean. + +## Fixes +- Fixed server build number. +- Fixed some crashes being misreported as plugin-involved. + +## Internals +- Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow. + +# 5.23.3 +Released 22nd January 2025. + +## Fixes +- Fixed crashes with PHP internal stack frames being flagged as plugin crashes. +- Fixed note block instrument sounds in 1.21.50. + +## Internals +- Updated GitHub issue templates to use issue forms. diff --git a/changelogs/5.24.md b/changelogs/5.24.md new file mode 100644 index 0000000000..a159d0e768 --- /dev/null +++ b/changelogs/5.24.md @@ -0,0 +1,108 @@ +# 5.24.0 +Released 22nd January 2025. + +This is a minor feature release, including new gameplay features, performance improvements, and minor API additions. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## Performance +- PHP garbage collection is now managed by the server, instead of being automatically triggered by PHP. + - The mechanism for GC triggering is designed to mimic PHP's to avoid behavioural changes. Only the place it's triggered from should be significantly different. + - This change also avoids unnecessary GCs during object-heavy operations, such as encoding `CraftingDataPacket`. As such, performance during server join should see an improvement. + - Timings is now able to directly measure the impact of GC. Previously, GC would show up as random spikes under random timers, skewing timing results. +- `ChunkCache` now uses `string` for completed caches directly instead of keeping them wrapped in `CompressBatchPromise`s. This reduces memory usage, improves performance, and reduces GC workload. + +## Configuration +- The following settings have been removed from `pocketmine.yml` and will no longer have any effect: + - `memory.garbage-collection.collect-async-worker` (now always `true`) + - `memory.garbage-collection.low-memory-trigger` (now always `true`) + - `memory.max-chunks.trigger-chunk-collect` (now always `true`) + - `memory.world-caches.disable-chunk-cache` (now always `true`) + - `memory.world-caches.low-memory-trigger` (now always `true`) + +## Gameplay +- Added the following new blocks: + - All types of pale oak wood, and leaves + - Resin + - Resin Bricks, Slabs, Stairs, and Walls + - Resin Clump + - Chiseled Resin Bricks +- Some blocks have had their tool tier requirements adjusted to match latest Bedrock updates. +- Added the following new items: + - Resin Brick + - Music Disc - Creator + - Music Disc - Creator (Music Box) + - Music Disc - Precipice + - Music Disc - Relic + +## API +### General +- Many places had their PHPDoc improved to address issues highlighted by PHPStan 2.x. This may cause new, previously unreported issues to be reported in plugins using PHPStan. + +### `pocketmine` +- The following methods have been deprecated: + - `MemoryManager->canUseChunkCache()` + - `MemoryManager::dumpMemory()` - relocated to `MemoryDump` class + +### `pocketmine\item` +- The following new enum cases have been added: + - `RecordType::DISK_CREATOR` + - `RecordType::DISK_CREATOR_MUSIC_BOX` + - `RecordType::DISK_PRECIPICE` + - `RecordType::DISK_RELIC` + +### `pocketmine\player` +- The following new methods have been added: + - `public Player->getFlightSpeedMultiplier() : float` - a base multiplier for player's flight speed + - `public Player->setFlightSpeedMultiplier(float $flightSpeedMultiplier) : void` - sets the player's flight speed multiplier +- The following new constants have been added: + - `Player::DEFAULT_FLIGHT_SPEED_MULTIPLIER` + +### `pocketmine\utils` +- The following new methods have been added: + - `public static TextFormat::javaToBedrock(string $string) : string` - removes unsupported Java Edition format codes to prevent them being incorrectly displayed on Bedrock +- The following methods have behavioural changes: + - `TextFormat::toHTML()` now converts `§m` to redstone red (instead of strikethrough), and `§n` to copper orange (instead of underline). This is because the codes previously used for `STRIKETHROUGH` and `UNDERLINE` conflict with the new material codes introduced by Minecraft Bedrock. + - `Terminal::toANSI()` now converts `§m` to redstone red (instead of strikethrough), and `§n` to copper orange (instead of underline), as above. However, underline and strikethrough can still be used on the terminal using `Terminal::$FORMAT_UNDERLINE` and `Terminal::$FORMAT_STRIKETHROUGH` respectively. +- The following new constants have been added: + - `TextFormat::MATERIAL_QUARTZ` + - `TextFormat::MATERIAL_IRON` + - `TextFormat::MATERIAL_NETHERITE` + - `TextFormat::MATERIAL_REDSTONE` + - `TextFormat::MATERIAL_COPPER` + - `TextFormat::MATERIAL_GOLD` + - `TextFormat::MATERIAL_EMERALD` + - `TextFormat::MATERIAL_DIAMOND` + - `TextFormat::MATERIAL_LAPIS` + - `TextFormat::MATERIAL_AMETHYST` +- The following constants have been deprecated: + - `TextFormat::STRIKETHROUGH` + - `TextFormat::UNDERLINE` +- The following static properties have been added: + - `Terminal::$COLOR_MATERIAL_QUARTZ` + - `Terminal::$COLOR_MATERIAL_IRON` + - `Terminal::$COLOR_MATERIAL_NETHERITE` + - `Terminal::$COLOR_MATERIAL_REDSTONE` + - `Terminal::$COLOR_MATERIAL_COPPER` + - `Terminal::$COLOR_MATERIAL_GOLD` + - `Terminal::$COLOR_MATERIAL_EMERALD` + - `Terminal::$COLOR_MATERIAL_DIAMOND` + - `Terminal::$COLOR_MATERIAL_LAPIS` + - `Terminal::$COLOR_MATERIAL_AMETHYST` + +## Tools +- Fixed some UI issues in `tools/convert-world.php` + +## Internals +- Block cache in `World` is now size-limited. This prevents memory exhaustion when plugins call `getBlock()` many thousands of times with cache misses. +- `RakLibServer` now disables PHP GC. As RakLib doesn't generate any unmanaged cycles, GC is just a waste of CPU time in this context. +- `MemoryManager` now has the responsibility for triggering cycle GC. It's checked every tick, but GC won't take place unless the GC threshold is exceeded, similar to PHP. + - This mechanism could probably do with alterations to better suit PocketMine-MP, but it was chosen to mimic PHP's own GC to minimize behavioural changes for now. +- `AsyncTask` now triggers cycle GC after `onRun()` completes. As with `MemoryManager`, this is based on a threshold designed to mimic PHP's own behaviour. +- `FormatConverter` now performs world provider GC periodically. This is not needed with current active providers, but was found to be a problem while developing custom providers. +- Various internal adjustments were made to avoid returning incorrectly-keyed arrays in the code. These changes shouldn't affect anything as the arrays should have been properly ordered anyway. +- Many places that previously used `==` and `!=` have been updated to use strict variants. This kind of change needs to be done carefully to avoid breaking `int|float` comparisons. diff --git a/changelogs/5.25.md b/changelogs/5.25.md new file mode 100644 index 0000000000..39862cbb7b --- /dev/null +++ b/changelogs/5.25.md @@ -0,0 +1,52 @@ +# 5.25.0 +Released 16th February 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.60. It also includes some minor API additions supporting new features in this version. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Added support for Minecraft: Bedrock Edition 1.21.60. +- Removed support for earlier versions. + +## Documentation +- Fixed the documentation of `Utils::getOS()`. It now refers to the `Utils::OS_*` constants instead of a list of hardcoded strings. + +## API +### `pocketmine\inventory` +This release allows plugins to decide which creative tab they want to add their new items to. +It also allows creating new collapsible groups of items, and modifying or removing existing ones. + +- The following new methods have been added: + - `public CreativeInventory->getAllEntries() : list` - returns an array of objects, each containing a creative item and information about its category and collapsible group (if applicable). + - `public CreativeInventory->getEntry(int $index) : ?CreativeInventoryEntry` - returns the creative item with the specified identifier, or `null` if not found +- The following methods have signature changes: + - `CreativeInventory->add()` now accepts two additional optional parameters: `CreativeCategory $category, ?CreativeGroup $group`. If not specified, the item will be added to the Items tab without a group. +- The following new classes have been added: + - `CreativeCategory` - enum of possible creative inventory categories (each has its own tab on the GUI) + - `CreativeGroup` - contains information about a collapsible group of creative items, including tooltip text and icon + - `CreativeInventoryEntry` - contains information about a creative inventory item, including its category and collapsible group (if applicable) + +## Internals +- `CreativeContentPacket` is no longer fully cached due to the requirement for translation context during construction. The individual items are still cached (which is the most expensive part), but the packet itself is now constructed on demand, and group entries are constructed on the fly. This may affect performance, but this has not been investigated. +- `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files. +- The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category. +- New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases. + +# 5.25.1 +Released 26th February 2025. + +## Fixes +- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero. +- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.) +- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join. + +# 5.25.2 +Released 4th March 2025. + +## Fixes +- Added limits to various `explode()` calls. diff --git a/composer.json b/composer.json index 5d1167b6d8..11c9ae5156 100644 --- a/composer.json +++ b/composer.json @@ -32,19 +32,19 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", - "pocketmine/netresearch-jsonmapper": "~v4.4.999", - "pocketmine/bedrock-block-upgrade-schema": "~4.3.0+bedrock-1.21.20", - "pocketmine/bedrock-data": "~2.12.0+bedrock-1.21.20", - "pocketmine/bedrock-item-upgrade-schema": "~1.11.0+bedrock-1.21.20", - "pocketmine/bedrock-protocol": "~33.0.0+bedrock-1.21.20", + "netresearch/jsonmapper": "~v5.0.0", + "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", + "pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60", + "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", + "pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", "pocketmine/errorhandler": "^0.7.0", - "pocketmine/locale-data": "~2.19.0", + "pocketmine/locale-data": "~2.24.0", "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", - "pocketmine/nbt": "~1.0.0", + "pocketmine/nbt": "~1.1.0", "pocketmine/raklib": "~1.1.0", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", @@ -52,9 +52,9 @@ "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "1.11.10", - "phpstan/phpstan-phpunit": "^1.1.0", - "phpstan/phpstan-strict-rules": "^1.2.0", + "phpstan/phpstan": "2.1.6", + "phpstan/phpstan-phpunit": "^2.0.0", + "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" }, "autoload": { diff --git a/composer.lock b/composer.lock index 5eb9a21585..521b5b3723 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": "fab1e131dfd049da39a87d4e562f1870", + "content-hash": "2a56fc6dee1dac2ade34d965aa49dc82", "packages": [ { "name": "adhocore/json-comment", @@ -67,16 +67,16 @@ }, { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.2", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40", + "reference": "901eddb1e45a8e0f689302e40af871c181ecbe40", "shasum": "" }, "require": { @@ -85,7 +85,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -115,7 +115,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.2" }, "funding": [ { @@ -123,20 +123,71 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-26T10:21:45+00:00" }, { - "name": "pocketmine/bedrock-block-upgrade-schema", - "version": "4.3.0", + "name": "netresearch/jsonmapper", + "version": "v5.0.0", "source": { "type": "git", - "url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git", - "reference": "53d3a41c37ce90d58b33130cdadad08e442d7c47" + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/53d3a41c37ce90d58b33130cdadad08e442d7c47", - "reference": "53d3a41c37ce90d58b33130cdadad08e442d7c47", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0" + }, + "time": "2024-09-08T10:20:00+00:00" + }, + { + "name": "pocketmine/bedrock-block-upgrade-schema", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git", + "reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", + "reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", "shasum": "" }, "type": "library", @@ -147,22 +198,22 @@ "description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves", "support": { "issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/4.3.0" + "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0" }, - "time": "2024-08-13T18:04:27+00:00" + "time": "2025-02-11T17:41:44+00:00" }, { "name": "pocketmine/bedrock-data", - "version": "2.12.0+bedrock-1.21.20", + "version": "4.0.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "d4ee3d08964fa16fbbdd04af1fb52bbde540b665" + "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/d4ee3d08964fa16fbbdd04af1fb52bbde540b665", - "reference": "d4ee3d08964fa16fbbdd04af1fb52bbde540b665", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e", + "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e", "shasum": "" }, "type": "library", @@ -173,22 +224,22 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.20" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60" }, - "time": "2024-08-15T12:50:26+00:00" + "time": "2025-02-16T15:56:56+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", - "version": "1.11.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git", - "reference": "35c18d093fc2b12da8737b2edb2c3ad6a14a53dd" + "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/35c18d093fc2b12da8737b2edb2c3ad6a14a53dd", - "reference": "35c18d093fc2b12da8737b2edb2c3ad6a14a53dd", + "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb", + "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb", "shasum": "" }, "type": "library", @@ -199,22 +250,22 @@ "description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves", "support": { "issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.11.0" + "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.14.0" }, - "time": "2024-08-13T18:06:25+00:00" + "time": "2024-12-04T12:22:49+00:00" }, { "name": "pocketmine/bedrock-protocol", - "version": "33.0.0+bedrock-1.21.20", + "version": "36.0.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "e2264137c5cd0522de2c6ee4921a3a803818ea32" + "reference": "2057de319c5c551001c2a544e08d1bc7727d9963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e2264137c5cd0522de2c6ee4921a3a803818ea32", - "reference": "e2264137c5cd0522de2c6ee4921a3a803818ea32", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963", + "reference": "2057de319c5c551001c2a544e08d1bc7727d9963", "shasum": "" }, "require": { @@ -227,10 +278,10 @@ "ramsey/uuid": "^4.1" }, "require-dev": { - "phpstan/phpstan": "1.11.2", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.0.0", - "phpunit/phpunit": "^9.5 || ^10.0" + "phpstan/phpstan": "2.1.0", + "phpstan/phpstan-phpunit": "^2.0.0", + "phpstan/phpstan-strict-rules": "^2.0.0", + "phpunit/phpunit": "^9.5 || ^10.0 || ^11.0" }, "type": "library", "autoload": { @@ -245,9 +296,9 @@ "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "support": { "issues": "https://github.com/pmmp/BedrockProtocol/issues", - "source": "https://github.com/pmmp/BedrockProtocol/tree/32.2.0+bedrock-1.21.2" + "source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60" }, - "time": "2024-08-10T19:23:18+00:00" + "time": "2025-02-16T15:59:08+00:00" }, { "name": "pocketmine/binaryutils", @@ -420,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.19.6", + "version": "2.24.0", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e" + "reference": "6ec5e92c77a2102b2692763733e4763012facae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/93e473e20e7f4515ecf45c5ef0f9155b9247a86e", - "reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e", + "url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9", + "reference": "6ec5e92c77a2102b2692763733e4763012facae9", "shasum": "" }, "type": "library", @@ -437,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.19.6" + "source": "https://github.com/pmmp/Language/tree/2.24.0" }, - "time": "2023-08-08T16:53:23+00:00" + "time": "2025-02-16T20:46:34+00:00" }, { "name": "pocketmine/log", @@ -525,16 +576,16 @@ }, { "name": "pocketmine/nbt", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/pmmp/NBT.git", - "reference": "20540271cb59e04672cb163dca73366f207974f1" + "reference": "cfd53a86166b851786967fc560cdb372e66fde96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/NBT/zipball/20540271cb59e04672cb163dca73366f207974f1", - "reference": "20540271cb59e04672cb163dca73366f207974f1", + "url": "https://api.github.com/repos/pmmp/NBT/zipball/cfd53a86166b851786967fc560cdb372e66fde96", + "reference": "cfd53a86166b851786967fc560cdb372e66fde96", "shasum": "" }, "require": { @@ -544,8 +595,8 @@ }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "1.10.25", - "phpstan/phpstan-strict-rules": "^1.0", + "phpstan/phpstan": "2.1.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.5" }, "type": "library", @@ -561,63 +612,9 @@ "description": "PHP library for working with Named Binary Tags", "support": { "issues": "https://github.com/pmmp/NBT/issues", - "source": "https://github.com/pmmp/NBT/tree/1.0.0" + "source": "https://github.com/pmmp/NBT/tree/1.1.0" }, - "time": "2023-07-14T13:01:49+00:00" - }, - { - "name": "pocketmine/netresearch-jsonmapper", - "version": "v4.4.999", - "source": { - "type": "git", - "url": "https://github.com/pmmp/netresearch-jsonmapper.git", - "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833", - "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" - }, - "replace": { - "netresearch/jsonmapper": "~4.2.0" - }, - "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Fork of netresearch/jsonmapper with security fixes needed by pocketmine/pocketmine-mp", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999" - }, - "time": "2024-02-23T13:17:01+00:00" + "time": "2025-02-01T21:20:26+00:00" }, { "name": "pocketmine/raklib", @@ -926,16 +923,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.9", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b51ef8059159330b74a4d52f68e671033c0fe463" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463", - "reference": "b51ef8059159330b74a4d52f68e671033c0fe463", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { @@ -972,7 +969,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.9" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -988,24 +985,24 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:49:33+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1016,8 +1013,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1051,7 +1048,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -1067,24 +1064,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1095,8 +1092,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1131,7 +1128,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1147,22 +1144,22 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" } ], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -1201,7 +1198,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -1209,20 +1206,20 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -1233,7 +1230,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -1265,9 +1262,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -1389,20 +1386,20 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.10", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "640410b32995914bde3eed26fa89552f9c2c082f" + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", - "reference": "640410b32995914bde3eed26fa89552f9c2c082f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1443,34 +1440,33 @@ "type": "github" } ], - "time": "2024-08-08T09:02:50+00:00" + "time": "2025-02-19T15:46:42+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.4.0", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + "reference": "d09e152f403c843998d7a52b5d87040c937525dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd", + "reference": "d09e152f403c843998d7a52b5d87040c937525dd", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -1493,34 +1489,33 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4" }, - "time": "2024-04-20T06:39:00+00:00" + "time": "2025-01-22T13:07:38+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -1542,38 +1537,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.6.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2025-01-21T10:52:14+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "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.18 || ^5.0", + "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" @@ -1585,7 +1580,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -1614,7 +1609,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.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -1622,7 +1617,7 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1869,16 +1864,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.24", + "version": "10.5.45", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015" + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f124e3e3e561006047b532fd0431bf5bb6b9015", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", "shasum": "" }, "require": { @@ -1888,26 +1883,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.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "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.1", - "sebastian/global-state": "^6.0.1", - "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" @@ -1950,7 +1945,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" }, "funding": [ { @@ -1966,7 +1961,7 @@ "type": "tidelift" } ], - "time": "2024-06-20T13:09:54+00:00" + "time": "2025-02-06T16:08:12+00:00" }, { "name": "sebastian/cli-parser", @@ -2138,16 +2133,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { @@ -2158,7 +2153,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -2203,7 +2198,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -2211,7 +2206,7 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/complexity", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2fde67d6ca..13f35c1218 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,14 +11,17 @@ includes: rules: - pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule + - pocketmine\phpstan\rules\DisallowDynamicNewRule - pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowForeachByReferenceRule + - pocketmine\phpstan\rules\ExplodeLimitRule - pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule parameters: level: 9 checkMissingCallableSignature: true + rememberPossiblyImpureFunctionValues: false #risky to remember these, better for performance to avoid repeated calls anyway treatPhpDocTypesAsCertain: false bootstrapFiles: - tests/phpstan/bootstrap.php @@ -31,6 +34,7 @@ parameters: paths: - build - src + - tests/phpstan/DummyPluginOwned.php - tests/phpstan/rules - tests/phpunit - tests/plugins/TesterPlugin @@ -40,15 +44,15 @@ parameters: - build/php dynamicConstantNames: - pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD + - pocketmine\VersionInfo::BUILD_CHANNEL - pocketmine\DEBUG - pocketmine\IS_DEVELOPMENT_BUILD stubFiles: + - tests/phpstan/stubs/chunkutils2.stub - tests/phpstan/stubs/JsonMapper.stub - tests/phpstan/stubs/leveldb.stub - tests/phpstan/stubs/pmmpthread.stub 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/resources/pocketmine.yml b/resources/pocketmine.yml index 408b5b95b3..5319249731 100644 --- a/resources/pocketmine.yml +++ b/resources/pocketmine.yml @@ -54,12 +54,6 @@ memory: #This only affects the main thread. Other threads should fire their own collections period: 36000 - #Fire asynchronous tasks to collect garbage from workers - collect-async-worker: true - - #Trigger on low memory - low-memory-trigger: true - #Settings controlling memory dump handling. memory-dump: #Dump memory from async workers as well as the main thread. If you have issues with segfaults when dumping memory, disable this setting. @@ -69,16 +63,6 @@ memory: #Cap maximum render distance per player when low memory is triggered. Set to 0 to disable cap. chunk-radius: 4 - #Do chunk garbage collection on trigger - trigger-chunk-collect: true - - world-caches: - #Disallow adding to world chunk-packet caches when memory is low - disable-chunk-cache: true - #Clear world caches when memory is low - low-memory-trigger: true - - network: #Threshold for batching packets, in bytes. Only these packets will be compressed #Set to 0 to compress everything, -1 to disable. diff --git a/src/GarbageCollectorManager.php b/src/GarbageCollectorManager.php new file mode 100644 index 0000000000..a8912a90de --- /dev/null +++ b/src/GarbageCollectorManager.php @@ -0,0 +1,103 @@ +logger = new \PrefixedLogger($logger, "Cyclic Garbage Collector"); + $this->timings = new TimingsHandler("Cyclic Garbage Collector", $parentTimings); + } + + private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{ + //TODO Very simple heuristic for dynamic GC buffer resizing: + //If there are "too few" collections, increase the collection threshold + //by a fixed step + //Adapted from zend_gc.c/gc_adjust_threshold() as of PHP 8.3.14 + if($cyclesCollected < self::GC_THRESHOLD_TRIGGER || $rootsAfterGC >= $this->threshold){ + $this->threshold = min(self::GC_THRESHOLD_MAX, $this->threshold + self::GC_THRESHOLD_STEP); + }elseif($this->threshold > self::GC_THRESHOLD_DEFAULT){ + $this->threshold = max(self::GC_THRESHOLD_DEFAULT, $this->threshold - self::GC_THRESHOLD_STEP); + } + } + + public function getThreshold() : int{ return $this->threshold; } + + public function getCollectionTimeTotalNs() : int{ return $this->collectionTimeTotalNs; } + + public function maybeCollectCycles() : int{ + $rootsBefore = gc_status()["roots"]; + if($rootsBefore < $this->threshold){ + return 0; + } + + $this->timings->startTiming(); + + $start = hrtime(true); + $cycles = gc_collect_cycles(); + $end = hrtime(true); + + $rootsAfter = gc_status()["roots"]; + $this->adjustGcThreshold($cycles, $rootsAfter); + + $this->timings->stopTiming(); + + $time = $end - $start; + $this->collectionTimeTotalNs += $time; + $this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->collectionTimeTotalNs) . " ns"); + + return $cycles; + } +} diff --git a/src/MemoryDump.php b/src/MemoryDump.php new file mode 100644 index 0000000000..bd1e0fc9aa --- /dev/null +++ b/src/MemoryDump.php @@ -0,0 +1,305 @@ +getProperties() as $property){ + if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){ + continue; + } + + if(!$property->isInitialized()){ + continue; + } + + $staticCount++; + $staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + + if(count($staticProperties[$className]) === 0){ + unset($staticProperties[$className]); + } + + foreach($reflection->getMethods() as $method){ + if($method->getDeclaringClass()->getName() !== $reflection->getName()){ + continue; + } + $methodStatics = []; + foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){ + $methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + if(count($methodStatics) > 0){ + $functionStaticVars[$className . "::" . $method->getName()] = $methodStatics; + $functionStaticVarsCount += count($functionStaticVars); + } + } + } + + file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + $logger->info("Wrote $staticCount static properties"); + + $globalVariables = []; + $globalCount = 0; + + $ignoredGlobals = [ + 'GLOBALS' => true, + '_SERVER' => true, + '_REQUEST' => true, + '_POST' => true, + '_GET' => true, + '_FILES' => true, + '_ENV' => true, + '_COOKIE' => true, + '_SESSION' => true + ]; + + foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){ + if(isset($ignoredGlobals[$varName])){ + continue; + } + + $globalCount++; + $globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + + file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + $logger->info("Wrote $globalCount global variables"); + + foreach(get_defined_functions()["user"] as $function){ + $reflect = new \ReflectionFunction($function); + + $vars = []; + foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){ + $vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + if(count($vars) > 0){ + $functionStaticVars[$function] = $vars; + $functionStaticVarsCount += count($vars); + } + } + file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + $logger->info("Wrote $functionStaticVarsCount function static variables"); + + $data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + + do{ + $continue = false; + foreach(Utils::stringifyKeys($objects) as $hash => $object){ + if(!is_object($object)){ + continue; + } + $continue = true; + + $className = get_class($object); + if(!isset($instanceCounts[$className])){ + $instanceCounts[$className] = 1; + }else{ + $instanceCounts[$className]++; + } + + $objects[$hash] = true; + $info = [ + "information" => "$hash@$className", + ]; + if($object instanceof \Closure){ + $info["definition"] = Utils::getNiceClosureName($object); + $info["referencedVars"] = []; + $reflect = new \ReflectionFunction($object); + if(($closureThis = $reflect->getClosureThis()) !== null){ + $info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + + foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){ + $info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + }else{ + $reflection = new \ReflectionObject($object); + + $info["properties"] = []; + + for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){ + foreach($reflection->getProperties() as $property){ + if($property->isStatic()){ + continue; + } + + $name = $property->getName(); + if($reflection !== $original){ + if($property->isPrivate()){ + $name = $reflection->getName() . ":" . $name; + }else{ + continue; + } + } + if(!$property->isInitialized($object)){ + continue; + } + + $info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize); + } + } + } + + fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n"); + } + + }while($continue); + + $logger->info("Wrote " . count($objects) . " objects"); + + fclose($obData); + + file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + + arsort($instanceCounts, SORT_NUMERIC); + file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + + $logger->info("Finished!"); + + ini_set('memory_limit', $hardLimit); + if($gcEnabled){ + gc_enable(); + } + } + + /** + * @param object[]|true[] $objects reference parameter + * @param int[] $refCounts reference parameter + * + * @phpstan-param array $objects + * @phpstan-param array $refCounts + * @phpstan-param-out array $objects + * @phpstan-param-out array $refCounts + */ + private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{ + if($maxNesting <= 0){ + return "(error) NESTING LIMIT REACHED"; + } + + --$maxNesting; + + if(is_object($from)){ + if(!isset($objects[$hash = spl_object_hash($from)])){ + $objects[$hash] = $from; + $refCounts[$hash] = 0; + } + + ++$refCounts[$hash]; + + $data = "(object) $hash"; + }elseif(is_array($from)){ + if($recursion >= 5){ + return "(error) ARRAY RECURSION LIMIT REACHED"; + } + $data = []; + $numeric = 0; + foreach(Utils::promoteKeys($from) as $key => $value){ + $data[$numeric] = [ + "k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), + "v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), + ]; + $numeric++; + } + }elseif(is_string($from)){ + $data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize); + }elseif(is_resource($from)){ + $data = "(resource) " . print_r($from, true); + }elseif(is_float($from)){ + $data = "(float) $from"; + }else{ + $data = $from; + } + + return $data; + } +} diff --git a/src/MemoryManager.php b/src/MemoryManager.php index 96e3bf49cd..2b7f5f1d3f 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -29,52 +29,24 @@ use pocketmine\scheduler\DumpWorkerMemoryTask; use pocketmine\scheduler\GarbageCollectionTask; use pocketmine\timings\Timings; use pocketmine\utils\Process; -use pocketmine\utils\Utils; use pocketmine\YmlServerProperties as Yml; -use Symfony\Component\Filesystem\Path; -use function arsort; -use function count; -use function fclose; -use function file_exists; -use function file_put_contents; -use function fopen; -use function fwrite; use function gc_collect_cycles; -use function gc_disable; -use function gc_enable; use function gc_mem_caches; -use function get_class; -use function get_declared_classes; -use function get_defined_functions; -use function ini_get; use function ini_set; use function intdiv; -use function is_array; -use function is_float; -use function is_object; -use function is_resource; -use function is_string; -use function json_encode; use function mb_strtoupper; use function min; -use function mkdir; use function preg_match; -use function print_r; use function round; -use function spl_object_hash; use function sprintf; -use function strlen; -use function substr; -use const JSON_PRETTY_PRINT; -use const JSON_THROW_ON_ERROR; -use const JSON_UNESCAPED_SLASHES; -use const SORT_NUMERIC; class MemoryManager{ private const DEFAULT_CHECK_RATE = Server::TARGET_TICKS_PER_SECOND; private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2; private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND; + private GarbageCollectorManager $cycleGcManager; + private int $memoryLimit; private int $globalMemoryLimit; private int $checkRate; @@ -88,14 +60,8 @@ class MemoryManager{ private int $garbageCollectionPeriod; private int $garbageCollectionTicker = 0; - private bool $garbageCollectionTrigger; - private bool $garbageCollectionAsync; private int $lowMemChunkRadiusOverride; - private bool $lowMemChunkGC; - - private bool $lowMemDisableChunkCache; - private bool $lowMemClearWorldCache; private bool $dumpWorkers = true; @@ -105,6 +71,7 @@ class MemoryManager{ private Server $server ){ $this->logger = new \PrefixedLogger($server->getLogger(), "Memory Manager"); + $this->cycleGcManager = new GarbageCollectorManager($this->logger, Timings::$memoryManager); $this->init($server->getConfigGroup()); } @@ -142,17 +109,10 @@ class MemoryManager{ $this->continuousTriggerRate = $config->getPropertyInt(Yml::MEMORY_CONTINUOUS_TRIGGER_RATE, self::DEFAULT_CONTINUOUS_TRIGGER_RATE); $this->garbageCollectionPeriod = $config->getPropertyInt(Yml::MEMORY_GARBAGE_COLLECTION_PERIOD, self::DEFAULT_TICKS_PER_GC); - $this->garbageCollectionTrigger = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER, true); - $this->garbageCollectionAsync = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER, true); $this->lowMemChunkRadiusOverride = $config->getPropertyInt(Yml::MEMORY_MAX_CHUNKS_CHUNK_RADIUS, 4); - $this->lowMemChunkGC = $config->getPropertyBool(Yml::MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT, true); - - $this->lowMemDisableChunkCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE, true); - $this->lowMemClearWorldCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER, true); $this->dumpWorkers = $config->getPropertyBool(Yml::MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER, true); - gc_enable(); } public function isLowMemory() : bool{ @@ -163,8 +123,11 @@ class MemoryManager{ return $this->globalMemoryLimit; } + /** + * @deprecated + */ public function canUseChunkCache() : bool{ - return !$this->lowMemory || !$this->lowMemDisableChunkCache; + return !$this->lowMemory; } /** @@ -180,26 +143,19 @@ class MemoryManager{ public function trigger(int $memory, int $limit, bool $global = false, int $triggerCount = 0) : void{ $this->logger->debug(sprintf("%sLow memory triggered, limit %gMB, using %gMB", $global ? "Global " : "", round(($limit / 1024) / 1024, 2), round(($memory / 1024) / 1024, 2))); - if($this->lowMemClearWorldCache){ - foreach($this->server->getWorldManager()->getWorlds() as $world){ - $world->clearCache(true); - } - ChunkCache::pruneCaches(); + foreach($this->server->getWorldManager()->getWorlds() as $world){ + $world->clearCache(true); } + ChunkCache::pruneCaches(); - if($this->lowMemChunkGC){ - foreach($this->server->getWorldManager()->getWorlds() as $world){ - $world->doChunkGarbageCollection(); - } + foreach($this->server->getWorldManager()->getWorlds() as $world){ + $world->doChunkGarbageCollection(); } $ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount); $ev->call(); - $cycles = 0; - if($this->garbageCollectionTrigger){ - $cycles = $this->triggerGarbageCollector(); - } + $cycles = $this->triggerGarbageCollector(); $this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2))); } @@ -239,6 +195,8 @@ class MemoryManager{ if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){ $this->garbageCollectionTicker = 0; $this->triggerGarbageCollector(); + }else{ + $this->cycleGcManager->maybeCollectCycles(); } Timings::$memoryManager->stopTiming(); @@ -247,14 +205,12 @@ class MemoryManager{ public function triggerGarbageCollector() : int{ Timings::$garbageCollector->startTiming(); - if($this->garbageCollectionAsync){ - $pool = $this->server->getAsyncPool(); - if(($w = $pool->shutdownUnusedWorkers()) > 0){ - $this->logger->debug("Shut down $w idle async pool workers"); - } - foreach($pool->getRunningWorkers() as $i){ - $pool->submitTaskToWorker(new GarbageCollectionTask(), $i); - } + $pool = $this->server->getAsyncPool(); + if(($w = $pool->shutdownUnusedWorkers()) > 0){ + $this->logger->debug("Shut down $w idle async pool workers"); + } + foreach($pool->getRunningWorkers() as $i){ + $pool->submitTaskToWorker(new GarbageCollectionTask(), $i); } $cycles = gc_collect_cycles(); @@ -271,7 +227,7 @@ class MemoryManager{ public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize) : void{ $logger = new \PrefixedLogger($this->server->getLogger(), "Memory Dump"); $logger->notice("After the memory dump is done, the server might crash"); - self::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger); + MemoryDump::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger); if($this->dumpWorkers){ $pool = $this->server->getAsyncPool(); @@ -283,239 +239,10 @@ class MemoryManager{ /** * Static memory dumper accessible from any thread. + * @deprecated + * @see MemoryDump */ public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{ - $hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist"); - ini_set('memory_limit', '-1'); - gc_disable(); - - if(!file_exists($outputFolder)){ - mkdir($outputFolder, 0777, true); - } - - $obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+")); - - $objects = []; - - $refCounts = []; - - $instanceCounts = []; - - $staticProperties = []; - $staticCount = 0; - - $functionStaticVars = []; - $functionStaticVarsCount = 0; - - foreach(get_declared_classes() as $className){ - $reflection = new \ReflectionClass($className); - $staticProperties[$className] = []; - foreach($reflection->getProperties() as $property){ - if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){ - continue; - } - - if(!$property->isInitialized()){ - continue; - } - - $staticCount++; - $staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - - if(count($staticProperties[$className]) === 0){ - unset($staticProperties[$className]); - } - - foreach($reflection->getMethods() as $method){ - if($method->getDeclaringClass()->getName() !== $reflection->getName()){ - continue; - } - $methodStatics = []; - foreach($method->getStaticVariables() as $name => $variable){ - $methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - if(count($methodStatics) > 0){ - $functionStaticVars[$className . "::" . $method->getName()] = $methodStatics; - $functionStaticVarsCount += count($functionStaticVars); - } - } - } - - file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - $logger->info("Wrote $staticCount static properties"); - - $globalVariables = []; - $globalCount = 0; - - $ignoredGlobals = [ - 'GLOBALS' => true, - '_SERVER' => true, - '_REQUEST' => true, - '_POST' => true, - '_GET' => true, - '_FILES' => true, - '_ENV' => true, - '_COOKIE' => true, - '_SESSION' => true - ]; - - foreach($GLOBALS as $varName => $value){ - if(isset($ignoredGlobals[$varName])){ - continue; - } - - $globalCount++; - $globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - - file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - $logger->info("Wrote $globalCount global variables"); - - foreach(get_defined_functions()["user"] as $function){ - $reflect = new \ReflectionFunction($function); - - $vars = []; - foreach($reflect->getStaticVariables() as $varName => $variable){ - $vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - if(count($vars) > 0){ - $functionStaticVars[$function] = $vars; - $functionStaticVarsCount += count($vars); - } - } - file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - $logger->info("Wrote $functionStaticVarsCount function static variables"); - - $data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - - do{ - $continue = false; - foreach(Utils::stringifyKeys($objects) as $hash => $object){ - if(!is_object($object)){ - continue; - } - $continue = true; - - $className = get_class($object); - if(!isset($instanceCounts[$className])){ - $instanceCounts[$className] = 1; - }else{ - $instanceCounts[$className]++; - } - - $objects[$hash] = true; - $info = [ - "information" => "$hash@$className", - ]; - if($object instanceof \Closure){ - $info["definition"] = Utils::getNiceClosureName($object); - $info["referencedVars"] = []; - $reflect = new \ReflectionFunction($object); - if(($closureThis = $reflect->getClosureThis()) !== null){ - $info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - - foreach($reflect->getStaticVariables() as $name => $variable){ - $info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - }else{ - $reflection = new \ReflectionObject($object); - - $info["properties"] = []; - - for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){ - foreach($reflection->getProperties() as $property){ - if($property->isStatic()){ - continue; - } - - $name = $property->getName(); - if($reflection !== $original){ - if($property->isPrivate()){ - $name = $reflection->getName() . ":" . $name; - }else{ - continue; - } - } - if(!$property->isInitialized($object)){ - continue; - } - - $info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize); - } - } - } - - fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n"); - } - - }while($continue); - - $logger->info("Wrote " . count($objects) . " objects"); - - fclose($obData); - - file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - - arsort($instanceCounts, SORT_NUMERIC); - file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); - - $logger->info("Finished!"); - - ini_set('memory_limit', $hardLimit); - gc_enable(); - } - - /** - * @param object[] $objects reference parameter - * @param int[] $refCounts reference parameter - * - * @phpstan-param array $objects - * @phpstan-param array $refCounts - * @phpstan-param-out array $objects - * @phpstan-param-out array $refCounts - */ - private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{ - if($maxNesting <= 0){ - return "(error) NESTING LIMIT REACHED"; - } - - --$maxNesting; - - if(is_object($from)){ - if(!isset($objects[$hash = spl_object_hash($from)])){ - $objects[$hash] = $from; - $refCounts[$hash] = 0; - } - - ++$refCounts[$hash]; - - $data = "(object) $hash"; - }elseif(is_array($from)){ - if($recursion >= 5){ - return "(error) ARRAY RECURSION LIMIT REACHED"; - } - $data = []; - $numeric = 0; - foreach($from as $key => $value){ - $data[$numeric] = [ - "k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), - "v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize), - ]; - $numeric++; - } - }elseif(is_string($from)){ - $data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize); - }elseif(is_resource($from)){ - $data = "(resource) " . print_r($from, true); - }elseif(is_float($from)){ - $data = "(float) $from"; - }else{ - $data = $from; - } - - return $data; + MemoryDump::dumpMemory($startingObject, $outputFolder, $maxNesting, $maxStringSize, $logger); } } diff --git a/src/PocketMine.php b/src/PocketMine.php index b2e1cd0469..a71c9768d4 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -264,7 +264,7 @@ JIT_WARNING $composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp'); if($composerGitHash !== null){ //we can't verify dependency versions if we were installed without using git - $currentGitHash = explode("-", VersionInfo::GIT_HASH())[0]; + $currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0]; if($currentGitHash !== $composerGitHash){ critical_error("Composer dependencies and/or autoloader are out of sync."); critical_error("- Current revision is $currentGitHash"); @@ -282,6 +282,11 @@ JIT_WARNING exit(0); } + if(defined('pocketmine\ORIGINAL_PHAR_PATH')){ + //if we're inside a phar cache, \pocketmine\PATH will not include the original phar + Filesystem::addCleanedPath(ORIGINAL_PHAR_PATH, Filesystem::CLEAN_PATH_SRC_PREFIX); + } + $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd()))); $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd; $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins"; diff --git a/src/Server.php b/src/Server.php index a34349bb5c..679a0ef0b6 100644 --- a/src/Server.php +++ b/src/Server.php @@ -36,6 +36,7 @@ use pocketmine\crafting\CraftingManager; use pocketmine\crafting\CraftingManagerFromDataHelper; use pocketmine\crash\CrashDump; use pocketmine\crash\CrashDumpRenderer; +use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\entity\EntityDataHelper; use pocketmine\entity\Location; use pocketmine\event\HandlerListManager; @@ -89,6 +90,8 @@ use pocketmine\promise\Promise; use pocketmine\promise\PromiseResolver; use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\scheduler\AsyncPool; +use pocketmine\scheduler\TimingsCollectionTask; +use pocketmine\scheduler\TimingsControlTask; use pocketmine\snooze\SleeperHandler; use pocketmine\stats\SendUsageTask; use pocketmine\thread\log\AttachableThreadSafeLogger; @@ -136,6 +139,7 @@ use function file_put_contents; use function filemtime; use function fopen; use function get_class; +use function gettype; use function ini_set; use function is_array; use function is_dir; @@ -695,7 +699,7 @@ class Server{ public function removeOp(string $name) : void{ $lowercaseName = strtolower($name); - foreach($this->operators->getAll() as $operatorName => $_){ + foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){ $operatorName = (string) $operatorName; if($lowercaseName === strtolower($operatorName)){ $this->operators->remove($operatorName); @@ -736,12 +740,15 @@ class Server{ /** * @return string[][] + * @phpstan-return array> */ public function getCommandAliases() : array{ $section = $this->configGroup->getProperty(Yml::ALIASES); $result = []; if(is_array($section)){ - foreach($section as $key => $value){ + foreach(Utils::promoteKeys($section) as $key => $value){ + //TODO: more validation needed here + //key might not be a string, value might not be list $commands = []; if(is_array($value)){ $commands = $value; @@ -749,7 +756,7 @@ class Server{ $commands[] = (string) $value; } - $result[$key] = $commands; + $result[(string) $key] = $commands; } } @@ -891,7 +898,37 @@ class Server{ $poolSize = max(1, (int) $poolSize); } + TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false)); + $this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND); + $this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper); + $this->asyncPool->addWorkerStartHook(function(int $i) : void{ + if(TimingsHandler::isEnabled()){ + $this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i); + } + }); + TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{ + foreach($this->asyncPool->getRunningWorkers() as $workerId){ + $this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId); + } + }); + TimingsHandler::getReloadCallbacks()->add(function() : void{ + foreach($this->asyncPool->getRunningWorkers() as $workerId){ + $this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId); + } + }); + TimingsHandler::getCollectCallbacks()->add(function() : array{ + $promises = []; + foreach($this->asyncPool->getRunningWorkers() as $workerId){ + /** @phpstan-var PromiseResolver> $resolver */ + $resolver = new PromiseResolver(); + $this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId); + + $promises[] = $resolver->getPromise(); + } + + return $promises; + }); $netCompressionThreshold = -1; if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){ @@ -965,14 +1002,11 @@ class Server{ ))); $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName()))); - TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false)); - $this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND); - DefaultPermissions::registerCorePermissions(); $this->commandMap = new SimpleCommandMap($this); - $this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes")); + $this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES); $this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger); @@ -982,7 +1016,11 @@ class Server{ copy(Path::join(\pocketmine\RESOURCE_PATH, 'plugin_list.yml'), $graylistFile); } try{ - $pluginGraylist = PluginGraylist::fromArray(yaml_parse(Filesystem::fileGetContents($graylistFile))); + $array = yaml_parse(Filesystem::fileGetContents($graylistFile)); + if(!is_array($array)){ + throw new \InvalidArgumentException("Expected array for root, but have " . gettype($array)); + } + $pluginGraylist = PluginGraylist::fromArray($array); }catch(\InvalidArgumentException $e){ $this->logger->emergency("Failed to load $graylistFile: " . $e->getMessage()); $this->forceShutdownExit(); @@ -1094,7 +1132,11 @@ class Server{ $anyWorldFailedToLoad = false; - foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){ + foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){ + if(!is_string($name)){ + //TODO: this probably should be an error + continue; + } if($options === null){ $options = []; }elseif(!is_array($options)){ @@ -1139,7 +1181,7 @@ class Server{ if($this->worldManager->getDefaultWorld() === null){ $default = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world"); - if(trim($default) == ""){ + if(trim($default) === ""){ $this->logger->warning("level-name cannot be null, using default"); $default = "world"; $this->configGroup->setConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world"); diff --git a/src/VersionInfo.php b/src/VersionInfo.php index b292b4c458..569e457dab 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.18.1"; + public const BASE_VERSION = "5.25.3"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/YmlServerProperties.php b/src/YmlServerProperties.php index 9bd203eef8..282b0b3cdf 100644 --- a/src/YmlServerProperties.php +++ b/src/YmlServerProperties.php @@ -75,20 +75,14 @@ final class YmlServerProperties{ public const MEMORY_CONTINUOUS_TRIGGER = 'memory.continuous-trigger'; public const MEMORY_CONTINUOUS_TRIGGER_RATE = 'memory.continuous-trigger-rate'; public const MEMORY_GARBAGE_COLLECTION = 'memory.garbage-collection'; - public const MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER = 'memory.garbage-collection.collect-async-worker'; - public const MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER = 'memory.garbage-collection.low-memory-trigger'; public const MEMORY_GARBAGE_COLLECTION_PERIOD = 'memory.garbage-collection.period'; public const MEMORY_GLOBAL_LIMIT = 'memory.global-limit'; public const MEMORY_MAIN_HARD_LIMIT = 'memory.main-hard-limit'; public const MEMORY_MAIN_LIMIT = 'memory.main-limit'; public const MEMORY_MAX_CHUNKS = 'memory.max-chunks'; public const MEMORY_MAX_CHUNKS_CHUNK_RADIUS = 'memory.max-chunks.chunk-radius'; - public const MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT = 'memory.max-chunks.trigger-chunk-collect'; public const MEMORY_MEMORY_DUMP = 'memory.memory-dump'; public const MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER = 'memory.memory-dump.dump-async-worker'; - public const MEMORY_WORLD_CACHES = 'memory.world-caches'; - public const MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE = 'memory.world-caches.disable-chunk-cache'; - public const MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER = 'memory.world-caches.low-memory-trigger'; public const NETWORK = 'network'; public const NETWORK_ASYNC_COMPRESSION = 'network.async-compression'; public const NETWORK_ASYNC_COMPRESSION_THRESHOLD = 'network.async-compression-threshold'; diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 4b4afef615..2c48f9a7c4 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\AnvilFallSound; use pocketmine\world\sound\Sound; -use function lcg_value; use function round; class Anvil extends Transparent implements Fallable{ @@ -70,9 +70,6 @@ class Anvil extends Transparent implements Fallable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->squash(Facing::axis(Facing::rotateY($this->facing, false)), 1 / 8)]; } @@ -97,7 +94,7 @@ class Anvil extends Transparent implements Fallable{ } public function onHitGround(FallingBlock $blockEntity) : bool{ - if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ + if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ if($this->damage !== self::VERY_DAMAGED){ $this->damage = $this->damage + 1; }else{ diff --git a/src/block/Bamboo.php b/src/block/Bamboo.php index 9f605bca69..fd64e10ef9 100644 --- a/src/block/Bamboo.php +++ b/src/block/Bamboo.php @@ -87,9 +87,6 @@ class Bamboo extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //this places the BB at the northwest corner, not the center $inset = 1 - (($this->thick ? 3 : 2) / 16); diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index 6b9e493d19..b563234533 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -30,7 +30,6 @@ use pocketmine\block\utils\SupportType; use pocketmine\item\Banner as ItemBanner; use pocketmine\item\Item; use pocketmine\item\VanillaItems; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; @@ -97,9 +96,6 @@ abstract class BaseBanner extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 5a905f8b86..0f5d77d582 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -34,7 +34,6 @@ use pocketmine\event\block\SignChangeEvent; use pocketmine\item\Dye; use pocketmine\item\Item; use pocketmine\item\ItemTypeIds; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\utils\TextFormat; @@ -95,9 +94,6 @@ abstract class BaseSign extends Transparent{ return 16; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/Bed.php b/src/block/Bed.php index 8efbdfe017..133c4a9cc5 100644 --- a/src/block/Bed.php +++ b/src/block/Bed.php @@ -76,9 +76,6 @@ class Bed extends Transparent{ } } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 16)]; } diff --git a/src/block/Block.php b/src/block/Block.php index dbc269c630..36e08fc0b4 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -75,7 +75,10 @@ class Block{ protected BlockTypeInfo $typeInfo; protected Position $position; - /** @var AxisAlignedBB[]|null */ + /** + * @var AxisAlignedBB[]|null + * @phpstan-var list|null + */ protected ?array $collisionBoxes = null; private int $requiredBlockItemStateDataBits; @@ -362,6 +365,8 @@ class Block{ * * 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{ return $this; @@ -905,6 +910,7 @@ class Block{ * - anti-cheat checks in plugins * * @return AxisAlignedBB[] + * @phpstan-return list */ final public function getCollisionBoxes() : array{ if($this->collisionBoxes === null){ @@ -929,6 +935,7 @@ class Block{ /** * @return AxisAlignedBB[] + * @phpstan-return list */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()]; diff --git a/src/block/BlockBreakInfo.php b/src/block/BlockBreakInfo.php index b49cb6f13d..e77e06cfd9 100644 --- a/src/block/BlockBreakInfo.php +++ b/src/block/BlockBreakInfo.php @@ -95,7 +95,7 @@ class BlockBreakInfo{ * Returns whether this block can be instantly broken. */ public function breaksInstantly() : bool{ - return $this->hardness == 0.0; + return $this->hardness === 0.0; } /** @@ -154,7 +154,7 @@ class BlockBreakInfo{ $efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0); if($efficiency <= 0){ - throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency"); + throw new \InvalidArgumentException(get_class($item) . " must have a positive mining efficiency, but got $efficiency"); } $base /= $efficiency; diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 29f4e650d4..c440cefdc6 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -760,8 +760,34 @@ final class BlockTypeIds{ 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 PALE_OAK_BUTTON = 10738; + public const PALE_OAK_DOOR = 10739; + public const PALE_OAK_FENCE = 10740; + public const PALE_OAK_FENCE_GATE = 10741; + public const PALE_OAK_LEAVES = 10742; + public const PALE_OAK_LOG = 10743; + public const PALE_OAK_PLANKS = 10744; + public const PALE_OAK_PRESSURE_PLATE = 10745; + public const PALE_OAK_SIGN = 10746; + public const PALE_OAK_SLAB = 10747; + public const PALE_OAK_STAIRS = 10748; + public const PALE_OAK_TRAPDOOR = 10749; + public const PALE_OAK_WALL_SIGN = 10750; + public const PALE_OAK_WOOD = 10751; + public const RESIN = 10752; + public const RESIN_BRICK_SLAB = 10753; + public const RESIN_BRICK_STAIRS = 10754; + public const RESIN_BRICK_WALL = 10755; + public const RESIN_BRICKS = 10756; + public const RESIN_CLUMP = 10757; + public const CHISELED_RESIN_BRICKS = 10758; - public const FIRST_UNUSED_BLOCK_ID = 10733; + public const FIRST_UNUSED_BLOCK_ID = 10759; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/BlueIce.php b/src/block/BlueIce.php index f7e9a04046..11e3498dd1 100644 --- a/src/block/BlueIce.php +++ b/src/block/BlueIce.php @@ -27,10 +27,6 @@ use pocketmine\item\Item; class BlueIce extends Opaque{ - public function getLightLevel() : int{ - return 1; - } - public function getFrictionFactor() : float{ return 0.99; } @@ -38,4 +34,8 @@ class BlueIce extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return []; } + + public function isAffectedBySilkTouch() : bool{ + return true; + } } diff --git a/src/block/Cactus.php b/src/block/Cactus.php index 6f2b04c8a3..67b15b9469 100644 --- a/src/block/Cactus.php +++ b/src/block/Cactus.php @@ -43,9 +43,6 @@ class Cactus extends Transparent{ return true; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $shrinkSize = 1 / 16; return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)]; diff --git a/src/block/Cake.php b/src/block/Cake.php index 073fc62ac9..e8c6dc93ee 100644 --- a/src/block/Cake.php +++ b/src/block/Cake.php @@ -40,9 +40,6 @@ class Cake extends BaseCake{ $w->boundedIntAuto(0, self::MAX_BITES, $this->bites); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php index 4479faee10..546843d6c3 100644 --- a/src/block/CakeWithCandle.php +++ b/src/block/CakeWithCandle.php @@ -36,9 +36,6 @@ class CakeWithCandle extends BaseCake{ onInteract as onInteractCandle; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() @@ -52,6 +49,9 @@ class CakeWithCandle extends BaseCake{ } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + if($this->lit && $face !== Facing::UP){ + return true; + } if($this->onInteractCandle($item, $face, $clickVector, $player, $returnedItems)){ return true; } diff --git a/src/block/Campfire.php b/src/block/Campfire.php index ce759ee87f..9f4c42a9cc 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -69,6 +69,10 @@ class Campfire extends Transparent{ private const UPDATE_INTERVAL_TICKS = 10; + /** + * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block + * has never been set in the world. + */ protected CampfireInventory $inventory; /** @@ -129,6 +133,10 @@ class Campfire extends Transparent{ return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)]; } + /** + * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block + * has never been set in the world. + */ public function getInventory() : CampfireInventory{ return $this->inventory; } diff --git a/src/block/Carpet.php b/src/block/Carpet.php index 1ee7240c51..2d8e7ea474 100644 --- a/src/block/Carpet.php +++ b/src/block/Carpet.php @@ -36,9 +36,6 @@ class Carpet extends Flowable{ return true; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)]; } diff --git a/src/block/Chest.php b/src/block/Chest.php index dca21576aa..7d26500074 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -36,9 +36,6 @@ use pocketmine\player\Player; class Chest extends Transparent{ use FacesOppositePlacingPlayerTrait; - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //these are slightly bigger than in PC return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)]; diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php index 89340a8f39..73c4861bf3 100644 --- a/src/block/ChiseledBookshelf.php +++ b/src/block/ChiseledBookshelf.php @@ -48,11 +48,32 @@ class ChiseledBookshelf extends Opaque{ */ 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. @@ -92,6 +113,23 @@ class ChiseledBookshelf extends Opaque{ 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; @@ -112,10 +150,12 @@ class ChiseledBookshelf extends Opaque{ $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; } diff --git a/src/block/ChorusPlant.php b/src/block/ChorusPlant.php index 9013f68256..88cf1787cd 100644 --- a/src/block/ChorusPlant.php +++ b/src/block/ChorusPlant.php @@ -34,11 +34,16 @@ use function mt_rand; final class ChorusPlant extends Flowable{ use StaticSupportTrait; + /** + * @var true[] + * @phpstan-var array + */ + protected array $connections = []; + protected function recalculateCollisionBoxes() : array{ $bb = AxisAlignedBB::one(); - foreach($this->getAllSides() as $facing => $block){ - $id = $block->getTypeId(); - if($id !== BlockTypeIds::END_STONE && $id !== BlockTypeIds::CHORUS_FLOWER && !$block->hasSameTypeId($this)){ + foreach(Facing::ALL as $facing){ + if(!isset($this->connections[$facing])){ $bb->trim($facing, 2 / 16); } } @@ -46,6 +51,26 @@ final class ChorusPlant extends Flowable{ return [$bb]; } + public function readStateFromWorld() : Block{ + parent::readStateFromWorld(); + + $this->collisionBoxes = null; + + foreach(Facing::ALL as $facing){ + $block = $this->getSide($facing); + if(match($block->getTypeId()){ + BlockTypeIds::END_STONE, BlockTypeIds::CHORUS_FLOWER, $this->getTypeId() => true, + default => false + }){ + $this->connections[$facing] = true; + }else{ + unset($this->connections[$facing]); + } + } + + return $this; + } + private function canBeSupportedBy(Block $block) : bool{ return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE; } diff --git a/src/block/CocoaBlock.php b/src/block/CocoaBlock.php index 6d8ce1adc9..83e1de34bc 100644 --- a/src/block/CocoaBlock.php +++ b/src/block/CocoaBlock.php @@ -50,9 +50,6 @@ class CocoaBlock extends Flowable{ $w->boundedIntAuto(0, self::MAX_AGE, $this->age); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/Copper.php b/src/block/Copper.php index 8e678bf426..d285e6ec0a 100644 --- a/src/block/Copper.php +++ b/src/block/Copper.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; -class Copper extends Opaque implements ICopper{ +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 0000000000..97fc209fee --- /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 0000000000..82a611206c --- /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 0000000000..d646d13334 --- /dev/null +++ b/src/block/CopperGrate.php @@ -0,0 +1,33 @@ +isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) { + return true; + } + + return parent::onInteract($item, $face, $clickVector, $player, $returnedItems); + } +} diff --git a/src/block/DaylightSensor.php b/src/block/DaylightSensor.php index 4141a2b7ed..5720af5293 100644 --- a/src/block/DaylightSensor.php +++ b/src/block/DaylightSensor.php @@ -62,9 +62,6 @@ class DaylightSensor extends Transparent{ return 300; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 10 / 16)]; } diff --git a/src/block/Door.php b/src/block/Door.php index 82ddaab518..fa88267e12 100644 --- a/src/block/Door.php +++ b/src/block/Door.php @@ -95,9 +95,6 @@ class Door extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //TODO: doors are 0.1825 blocks thick, instead of 0.1875 like JE (https://bugs.mojang.com/browse/MCPE-19214) return [AxisAlignedBB::one()->trim($this->open ? Facing::rotateY($this->facing, !$this->hingeRight) : $this->facing, 327 / 400)]; diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php index 6a6c936b22..53573d0646 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -33,9 +33,6 @@ use pocketmine\player\Player; class EnchantingTable extends Transparent{ - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 0.25)]; } diff --git a/src/block/EndPortalFrame.php b/src/block/EndPortalFrame.php index 612cf3723c..ed5b774338 100644 --- a/src/block/EndPortalFrame.php +++ b/src/block/EndPortalFrame.php @@ -50,9 +50,6 @@ class EndPortalFrame extends Opaque{ return 1; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 3 / 16)]; } diff --git a/src/block/EndRod.php b/src/block/EndRod.php index f0b28c26de..a6770f3708 100644 --- a/src/block/EndRod.php +++ b/src/block/EndRod.php @@ -52,9 +52,6 @@ class EndRod extends Flowable{ return 14; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $myAxis = Facing::axis($this->facing); diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 9004f7c79f..6a8cf108c9 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -40,9 +40,6 @@ class EnderChest extends Transparent{ return 7; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //these are slightly bigger than in PC return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)]; diff --git a/src/block/Farmland.php b/src/block/Farmland.php index a17a220f0b..83bc345611 100644 --- a/src/block/Farmland.php +++ b/src/block/Farmland.php @@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; +use pocketmine\utils\Utils; use function intdiv; -use function lcg_value; class Farmland extends Transparent{ public const MAX_WETNESS = 7; @@ -94,9 +94,6 @@ class Farmland extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)]; } @@ -148,7 +145,7 @@ class Farmland extends Transparent{ } public function onEntityLand(Entity $entity) : ?float{ - if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){ + if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){ $ev = new EntityTrampleFarmlandEvent($entity, $this); $ev->call(); if(!$ev->isCancelled()){ diff --git a/src/block/Fence.php b/src/block/Fence.php index 30caaa4cfc..52256d9f02 100644 --- a/src/block/Fence.php +++ b/src/block/Fence.php @@ -54,13 +54,9 @@ class Fence extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $inset = 0.5 - $this->getThickness() / 2; - /** @var AxisAlignedBB[] $bbs */ $bbs = []; $connectWest = isset($this->connections[Facing::WEST]); diff --git a/src/block/FenceGate.php b/src/block/FenceGate.php index 7354564495..2bbfdf8923 100644 --- a/src/block/FenceGate.php +++ b/src/block/FenceGate.php @@ -64,9 +64,6 @@ class FenceGate extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return $this->open ? [] : [AxisAlignedBB::one()->extend(Facing::UP, 0.5)->squash(Facing::axis($this->facing), 6 / 16)]; } diff --git a/src/block/Flowable.php b/src/block/Flowable.php index 795fe27561..355c9caeaf 100644 --- a/src/block/Flowable.php +++ b/src/block/Flowable.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; 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,9 +40,11 @@ abstract class Flowable extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ + public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{ + return (!$this->canBeFlowedInto() || !$blockReplace instanceof Liquid) && + parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock); + } + protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/FlowerPot.php b/src/block/FlowerPot.php index fb3e78d82a..79fb73b12d 100644 --- a/src/block/FlowerPot.php +++ b/src/block/FlowerPot.php @@ -83,9 +83,6 @@ class FlowerPot extends Flowable{ return $block->hasTypeTag(BlockTypeTags::POTTABLE_PLANTS); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->contract(3 / 16, 0, 3 / 16)->trim(Facing::UP, 5 / 8)]; } diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php index de66ccad76..a44c4d0358 100644 --- a/src/block/GlowLichen.php +++ b/src/block/GlowLichen.php @@ -24,60 +24,19 @@ declare(strict_types=1); 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\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; @@ -87,9 +46,6 @@ class GlowLichen extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } @@ -102,39 +58,11 @@ class GlowLichen extends Transparent{ 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->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 int[] + */ + protected function getInitialPlaceFaces(Block $blockReplace) : array{ + return $blockReplace instanceof GlowLichen ? $blockReplace->faces : []; } private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{ @@ -261,17 +189,4 @@ class GlowLichen extends Transparent{ public function getFlammability() : int{ return 100; } - - /** - * @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/GrassPath.php b/src/block/GrassPath.php index 5b11bd3745..ea56e4b95b 100644 --- a/src/block/GrassPath.php +++ b/src/block/GrassPath.php @@ -29,9 +29,6 @@ use pocketmine\math\Facing; class GrassPath extends Transparent{ - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)]; } diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php index b5b6093c49..c03806a3b5 100644 --- a/src/block/ItemFrame.php +++ b/src/block/ItemFrame.php @@ -31,13 +31,13 @@ use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\ItemFrameAddItemSound; use pocketmine\world\sound\ItemFrameRemoveItemSound; use pocketmine\world\sound\ItemFrameRotateItemSound; use function is_infinite; use function is_nan; -use function lcg_value; class ItemFrame extends Flowable{ use AnyFacingTrait; @@ -154,7 +154,7 @@ class ItemFrame extends Flowable{ return false; } $world = $this->position->getWorld(); - if(lcg_value() <= $this->itemDropChance){ + if(Utils::getRandomFloat() <= $this->itemDropChance){ $world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem); $world->addSound($this->position, new ItemFrameRemoveItemSound()); } @@ -185,7 +185,7 @@ class ItemFrame extends Flowable{ public function getDropsForCompatibleTool(Item $item) : array{ $drops = parent::getDropsForCompatibleTool($item); - if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){ + if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){ $drops[] = clone $this->framedItem; } diff --git a/src/block/Ladder.php b/src/block/Ladder.php index 58f133f6ec..09c0b8f6bc 100644 --- a/src/block/Ladder.php +++ b/src/block/Ladder.php @@ -58,9 +58,6 @@ class Ladder extends Transparent{ return true; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim($this->facing, 13 / 16)]; } diff --git a/src/block/Lantern.php b/src/block/Lantern.php index e9cbcc3fe9..302e69fd7f 100644 --- a/src/block/Lantern.php +++ b/src/block/Lantern.php @@ -59,9 +59,6 @@ class Lantern extends Transparent{ return $this->lightLevel; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [ AxisAlignedBB::one() diff --git a/src/block/Leaves.php b/src/block/Leaves.php index 7fe9eae74f..8475365575 100644 --- a/src/block/Leaves.php +++ b/src/block/Leaves.php @@ -157,6 +157,7 @@ class Leaves extends Transparent{ LeavesType::MANGROVE, //TODO: mangrove propagule LeavesType::AZALEA, LeavesType::FLOWERING_AZALEA => null, //TODO: azalea LeavesType::CHERRY => null, //TODO: cherry + LeavesType::PALE_OAK => null, //TODO: pale oak })?->asItem(); if($sapling !== null){ $drops[] = $sapling; diff --git a/src/block/Liquid.php b/src/block/Liquid.php index 6404cf9081..813d769042 100644 --- a/src/block/Liquid.php +++ b/src/block/Liquid.php @@ -30,12 +30,11 @@ use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; use pocketmine\event\block\BlockSpreadEvent; use pocketmine\item\Item; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +use pocketmine\utils\Utils; use pocketmine\world\sound\FizzSound; use pocketmine\world\sound\Sound; -use function lcg_value; abstract class Liquid extends Transparent{ public const MAX_DECAY = 7; @@ -89,9 +88,6 @@ abstract class Liquid extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } @@ -368,7 +364,7 @@ abstract class Liquid extends Transparent{ protected function liquidCollide(Block $cause, Block $result) : bool{ if(BlockEventHelper::form($this, $result, $cause)){ - $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8)); + $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8)); } return true; } diff --git a/src/block/Magma.php b/src/block/Magma.php index d2f309325d..7b3fa5229e 100644 --- a/src/block/Magma.php +++ b/src/block/Magma.php @@ -39,7 +39,7 @@ class Magma extends Opaque{ } public function onEntityInside(Entity $entity) : bool{ - if($entity instanceof Living && !$entity->isSneaking()){ + if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){ $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1); $entity->attack($ev); } diff --git a/src/block/MobHead.php b/src/block/MobHead.php index f4e945841d..41e816c557 100644 --- a/src/block/MobHead.php +++ b/src/block/MobHead.php @@ -104,9 +104,6 @@ class MobHead extends Flowable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ $collisionBox = AxisAlignedBB::one() ->contract(0.25, 0, 0.25) diff --git a/src/block/NetherPortal.php b/src/block/NetherPortal.php index 6a45fb7a0a..1b199c6038 100644 --- a/src/block/NetherPortal.php +++ b/src/block/NetherPortal.php @@ -28,7 +28,6 @@ use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Entity; use pocketmine\item\Item; use pocketmine\math\Axis; -use pocketmine\math\AxisAlignedBB; class NetherPortal extends Transparent{ @@ -62,9 +61,6 @@ class NetherPortal extends Transparent{ return false; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/RedstoneComparator.php b/src/block/RedstoneComparator.php index ee63a77a99..40e1ef510b 100644 --- a/src/block/RedstoneComparator.php +++ b/src/block/RedstoneComparator.php @@ -79,9 +79,6 @@ class RedstoneComparator extends Flowable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)]; } diff --git a/src/block/RedstoneRepeater.php b/src/block/RedstoneRepeater.php index 7e6e73da84..bf9d0c5da1 100644 --- a/src/block/RedstoneRepeater.php +++ b/src/block/RedstoneRepeater.php @@ -62,9 +62,6 @@ class RedstoneRepeater extends Flowable{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)]; } diff --git a/src/block/ResinClump.php b/src/block/ResinClump.php new file mode 100644 index 0000000000..75126edf3a --- /dev/null +++ b/src/block/ResinClump.php @@ -0,0 +1,54 @@ +faces : []; + } + + protected function recalculateCollisionBoxes() : array{ + return []; + } +} diff --git a/src/block/RuntimeBlockStateRegistry.php b/src/block/RuntimeBlockStateRegistry.php index 78be658bc1..d13b942ba0 100644 --- a/src/block/RuntimeBlockStateRegistry.php +++ b/src/block/RuntimeBlockStateRegistry.php @@ -28,6 +28,7 @@ use pocketmine\block\BlockIdentifier as BID; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use pocketmine\world\light\LightUpdate; +use function count; use function min; /** @@ -40,6 +41,11 @@ use function min; class RuntimeBlockStateRegistry{ use SingletonTrait; + public const COLLISION_CUSTOM = 0; + public const COLLISION_CUBE = 1; + public const COLLISION_NONE = 2; + public const COLLISION_MAY_OVERFLOW = 3; + /** * @var Block[] * @phpstan-var array @@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{ */ public array $blastResistance = []; + /** + * Map of state ID -> useful AABB info to avoid unnecessary block allocations + * @var int[] + * @phpstan-var array + */ + public array $collisionInfo = []; + public function __construct(){ foreach(VanillaBlocks::getAll() as $block){ $this->register($block); @@ -100,6 +113,70 @@ class RuntimeBlockStateRegistry{ } } + /** + * Checks if the given class method overrides a method in Block. + * Used to determine if a block might need to disable fast path optimizations. + * + * @phpstan-param anyClosure $closure + */ + private static function overridesBlockMethod(\Closure $closure) : bool{ + $declarer = (new \ReflectionFunction($closure))->getClosureScopeClass(); + return $declarer !== null && $declarer->getName() !== Block::class; + } + + /** + * A big ugly hack to set up fast paths for handling collisions on blocks with common shapes. + * The information returned here is stored in RuntimeBlockStateRegistry->collisionInfo, and is used during entity + * collision box calculations to avoid complex logic and unnecessary block object allocations. + * This hack allows significant performance improvements. + * + * TODO: We'll want to redesign block collision box handling and block shapes in the future, but that's a job for a + * major version. For now, this hack nets major performance wins. + */ + private static function calculateCollisionInfo(Block $block) : int{ + if( + self::overridesBlockMethod($block->getModelPositionOffset(...)) || + self::overridesBlockMethod($block->readStateFromWorld(...)) + ){ + //getModelPositionOffset() might cause AABBs to shift outside the cell + //readStateFromWorld() might cause overflow in ways we can't predict just by looking at known states + //TODO: excluding overriders of readStateFromWorld() also excludes blocks with tiles that don't do anything + //weird with their AABBs, but for now this is the best we can do. + return self::COLLISION_MAY_OVERFLOW; + } + + //TODO: this could blow up if any recalculateCollisionBoxes() uses the world + //it shouldn't, but that doesn't mean that custom blocks won't... + $boxes = $block->getCollisionBoxes(); + if(count($boxes) === 0){ + return self::COLLISION_NONE; + } + + if( + count($boxes) === 1 && + $boxes[0]->minX === 0.0 && + $boxes[0]->minY === 0.0 && + $boxes[0]->minZ === 0.0 && + $boxes[0]->maxX === 1.0 && + $boxes[0]->maxY === 1.0 && + $boxes[0]->maxZ === 1.0 + ){ + return self::COLLISION_CUBE; + } + + foreach($boxes as $box){ + if( + $box->minX < 0 || $box->maxX > 1 || + $box->minY < 0 || $box->maxY > 1 || + $box->minZ < 0 || $box->maxZ > 1 + ){ + return self::COLLISION_MAY_OVERFLOW; + } + } + + return self::COLLISION_CUSTOM; + } + private function fillStaticArrays(int $index, Block $block) : void{ $fullId = $block->getStateId(); if($index !== $fullId){ @@ -112,6 +189,8 @@ class RuntimeBlockStateRegistry{ if($block->blocksDirectSkyLight()){ $this->blocksDirectSkyLight[$index] = true; } + + $this->collisionInfo[$index] = self::calculateCollisionInfo($block); } } @@ -132,6 +211,7 @@ class RuntimeBlockStateRegistry{ /** * @return Block[] + * @phpstan-return array */ public function getAllKnownStates() : array{ return $this->fullList; diff --git a/src/block/SeaPickle.php b/src/block/SeaPickle.php index 627af9bacc..34f5c3e9ea 100644 --- a/src/block/SeaPickle.php +++ b/src/block/SeaPickle.php @@ -26,7 +26,6 @@ namespace pocketmine\block; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; @@ -70,9 +69,6 @@ class SeaPickle extends Transparent{ return $this->underwater ? ($this->count + 1) * 3 : 0; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return []; } diff --git a/src/block/Slab.php b/src/block/Slab.php index 6000bec39e..2bbb7528c2 100644 --- a/src/block/Slab.php +++ b/src/block/Slab.php @@ -93,9 +93,6 @@ class Slab extends Transparent{ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ if($this->slabType === SlabType::DOUBLE){ return [AxisAlignedBB::one()]; diff --git a/src/block/SnowLayer.php b/src/block/SnowLayer.php index cca8424a98..8549f0b316 100644 --- a/src/block/SnowLayer.php +++ b/src/block/SnowLayer.php @@ -65,9 +65,6 @@ class SnowLayer extends Flowable implements Fallable{ return $this->layers < self::MAX_LAYERS; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ //TODO: this zero-height BB is intended to stay in lockstep with a MCPE bug return [AxisAlignedBB::one()->trim(Facing::UP, $this->layers >= 4 ? 0.5 : 1)]; diff --git a/src/block/SoulSand.php b/src/block/SoulSand.php index 2c6453b6c9..e1285d095b 100644 --- a/src/block/SoulSand.php +++ b/src/block/SoulSand.php @@ -28,9 +28,6 @@ use pocketmine\math\Facing; class SoulSand extends Opaque{ - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 8)]; } diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index 97b4aee9c1..2da2dc9b97 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -36,7 +36,9 @@ use pocketmine\world\Position; class Sugarcane extends Flowable{ use AgeableTrait; - use StaticSupportTrait; + use StaticSupportTrait { + onNearbyBlockChange as onSupportBlockChange; + } public const MAX_AGE = 15; @@ -97,7 +99,13 @@ class Sugarcane extends Flowable{ } 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{ @@ -123,4 +131,23 @@ class Sugarcane extends Flowable{ 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/Thin.php b/src/block/Thin.php index dde2d7d847..82010697a1 100644 --- a/src/block/Thin.php +++ b/src/block/Thin.php @@ -56,7 +56,6 @@ class Thin extends Transparent{ protected function recalculateCollisionBoxes() : array{ $inset = 7 / 16; - /** @var AxisAlignedBB[] $bbs */ $bbs = []; if(isset($this->connections[Facing::WEST]) || isset($this->connections[Facing::EAST])){ diff --git a/src/block/Trapdoor.php b/src/block/Trapdoor.php index 20b6af2abd..a903e1b5e7 100644 --- a/src/block/Trapdoor.php +++ b/src/block/Trapdoor.php @@ -62,9 +62,6 @@ class Trapdoor extends Transparent{ return $this; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->trim($this->open ? $this->facing : ($this->top ? Facing::DOWN : Facing::UP), 13 / 16)]; } diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 13c7e869ac..231004dfa3 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -26,7 +26,6 @@ namespace pocketmine\block; 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; @@ -55,7 +54,9 @@ use pocketmine\block\tile\MonsterSpawner as TileMonsterSpawner; use pocketmine\block\tile\NormalFurnace as TileNormalFurnace; use pocketmine\block\tile\Note as TileNote; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; +use pocketmine\block\tile\Sign as TileSign; use pocketmine\block\tile\Smoker as TileSmoker; +use pocketmine\block\tile\Tile; use pocketmine\block\utils\AmethystTrait; use pocketmine\block\utils\LeavesType; use pocketmine\block\utils\SaplingType; @@ -64,9 +65,12 @@ use pocketmine\crafting\FurnaceType; 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\utils\CloningRegistryTrait; +use function is_int; use function mb_strtolower; +use function mb_strtoupper; use function strtolower; /** @@ -181,11 +185,13 @@ use function strtolower; * @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() * @method static SimplePillar CHISELED_QUARTZ() * @method static Opaque CHISELED_RED_SANDSTONE() + * @method static Opaque CHISELED_RESIN_BRICKS() * @method static Opaque CHISELED_SANDSTONE() * @method static Opaque CHISELED_STONE_BRICKS() * @method static Opaque CHISELED_TUFF() @@ -209,7 +215,11 @@ use function strtolower; * @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() @@ -581,6 +591,20 @@ use function strtolower; * @method static Flower OXEYE_DAISY() * @method static PackedIce PACKED_ICE() * @method static Opaque PACKED_MUD() + * @method static WoodenButton PALE_OAK_BUTTON() + * @method static WoodenDoor PALE_OAK_DOOR() + * @method static WoodenFence PALE_OAK_FENCE() + * @method static FenceGate PALE_OAK_FENCE_GATE() + * @method static Leaves PALE_OAK_LEAVES() + * @method static Wood PALE_OAK_LOG() + * @method static Planks PALE_OAK_PLANKS() + * @method static WoodenPressurePlate PALE_OAK_PRESSURE_PLATE() + * @method static FloorSign PALE_OAK_SIGN() + * @method static WoodenSlab PALE_OAK_SLAB() + * @method static WoodenStairs PALE_OAK_STAIRS() + * @method static WoodenTrapdoor PALE_OAK_TRAPDOOR() + * @method static WallSign PALE_OAK_WALL_SIGN() + * @method static Wood PALE_OAK_WOOD() * @method static DoublePlant PEONY() * @method static PinkPetals PINK_PETALS() * @method static Flower PINK_TULIP() @@ -664,6 +688,12 @@ use function strtolower; * @method static Flower RED_TULIP() * @method static Opaque REINFORCED_DEEPSLATE() * @method static Reserved6 RESERVED6() + * @method static Opaque RESIN() + * @method static Opaque RESIN_BRICKS() + * @method static Slab RESIN_BRICK_SLAB() + * @method static Stair RESIN_BRICK_STAIRS() + * @method static Wall RESIN_BRICK_WALL() + * @method static ResinClump RESIN_CLUMP() * @method static DoublePlant ROSE_BUSH() * @method static Sand SAND() * @method static Opaque SANDSTONE() @@ -788,8 +818,28 @@ final class VanillaBlocks{ //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; } /** @@ -804,11 +854,12 @@ final class VanillaBlocks{ } protected static function setup() : void{ + 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", 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("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; @@ -816,242 +867,242 @@ final class VanillaBlocks{ 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("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", 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("chiseled_bookshelf", new ChiseledBookshelf(new BID(Ids::CHISELED_BOOKSHELF, TileChiseledBookshelf::class), "Chiseled 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)))); + self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant()))); + self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0))), TileBell::class); + 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))), TileBrewingStand::class); $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("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", 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("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", new Campfire(new BID(Ids::CAMPFIRE, TileCampfire::class), "Campfire", $campfireBreakInfo)); - self::register("soul_campfire", new SoulCampfire(new BID(Ids::SOUL_CAMPFIRE, TileCampfire::class), "Soul Campfire", $campfireBreakInfo)); + 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", new Carrot(new BID(Ids::CARROTS), "Carrot Block", new Info(BreakInfo::instant()))); + 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)))); + 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)); - 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)); + $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", 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("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", 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("pink_petals", new PinkPetals(new BID(Ids::PINK_PETALS), "Pink Petals", 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("pitcher_plant", new DoublePlant(new BID(Ids::PITCHER_PLANT), "Pitcher Plant", new Info(BreakInfo::instant()))); - self::register("pitcher_crop", new PitcherCrop(new BID(Ids::PITCHER_CROP), "Pitcher Crop", new Info(BreakInfo::instant()))); - self::register("double_pitcher_crop", new DoublePitcherCrop(new BID(Ids::DOUBLE_PITCHER_CROP), "Double Pitcher Crop", new Info(BreakInfo::instant()))); - 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)))); + 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", 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("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", 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("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, blastResistance: 3000.0))), TileEnderChest::class); + self::register("farmland", fn(BID $id) => new Farmland($id, "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT]))); + self::register("fire", fn(BID $id) => new Fire($id, "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE]))); $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("torchflower", new Flower(new BID(Ids::TORCHFLOWER), "Torchflower", $flowerTypeInfo)); - self::register("torchflower_crop", new TorchflowerCrop(new BID(Ids::TORCHFLOWER_CROP), "Torchflower Crop", new Info(BreakInfo::instant()))); - 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)); + 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)); + 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)); + + self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", new Info(BreakInfo::pickaxe(5.0)))); + self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD)))); $itemFrameInfo = new Info(new BreakInfo(0.25)); - self::register("item_frame", 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("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", 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)))); + 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", new Lantern(new BID(Ids::LANTERN), "Lantern", $lanternBreakInfo, 15)); - self::register("soul_lantern", new Lantern(new BID(Ids::SOUL_LANTERN), "Soul Lantern", $lanternBreakInfo, 10)); + $lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0)); + self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15)); + self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10)); - 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]))); + 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", 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_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", 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)); + 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", 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("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", new Info(BreakInfo::axe(1.0), enchantmentTags: [EnchantmentTags::MASK]))); - self::register("lit_pumpkin", new LitPumpkin(new BID(Ids::LIT_PUMPKIN), "Jack o'Lantern", $pumpkinBreakInfo)); + 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", new PumpkinStem(new BID(Ids::PUMPKIN_STEM), "Pumpkin Stem", new Info(BreakInfo::instant()))); + 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", 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)); + 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", 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", 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", 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("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", 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("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("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", 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), enchantmentTags: [EnchantmentTags::MASK]))); - 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("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( + $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()]; } @@ -1061,110 +1112,110 @@ final class VanillaBlocks{ } } ); - 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("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)); - 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)); + $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("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", 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("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)))); //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("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", 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("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", 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("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", 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()))); + 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", new WeightedPressurePlateHeavy( - new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY), + $weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5)); + 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", new WeightedPressurePlateLight( - new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT), + 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", new Wheat(new BID(Ids::WHEAT), "Wheat Block", new Info(BreakInfo::instant()))); + 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{ @@ -1178,39 +1229,39 @@ final class VanillaBlocks{ foreach(SaplingType::cases() as $saplingType){ $name = $saplingType->getDisplayName(); - self::register(strtolower($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::cases() as $leavesType){ $name = $leavesType->getDisplayName(); - self::register(strtolower($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("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", 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("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", 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{ + 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){ @@ -1223,54 +1274,54 @@ final class VanillaBlocks{ //TODO: in the future these won't all have the same hardness; they only do now because of the old metadata crap $wallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); - self::register("cobblestone_wall", 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)); + 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)); + 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 []; } @@ -1282,6 +1333,7 @@ final class VanillaBlocks{ self::registerBlocksR17(); self::registerBlocksR18(); self::registerMudBlocks(); + self::registerResinBlocks(); self::registerTuffBlocks(); self::registerCraftingTables(); @@ -1303,300 +1355,312 @@ final class VanillaBlocks{ $name = $woodType->getDisplayName(); $idName = fn(string $suffix) => strtolower($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(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"), 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("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"), 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("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"), new WoodenButton(WoodLikeBlockIdHelper::getButtonIdentifier($woodType), $name . " Button", $woodenButtonBreakInfo, $woodType)); - self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20)); - self::register($idName("trapdoor"), new WoodenTrapdoor(WoodLikeBlockIdHelper::getTrapdoorIdentifier($woodType), $name . " Trapdoor", $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)); - [$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)); + $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(...), + WoodType::PALE_OAK => VanillaItems::PALE_OAK_SIGN(...), + }; + self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); + self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class); } } 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)); + self::register("element_zero", fn(BID $id) => new Opaque($id, "???", $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 = 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", 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); + $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))); + 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", 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("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)); - self::register("ancient_debris", new class(new BID(Ids::ANCIENT_DEBRIS), "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{ + self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{ public function isFireProofAsItem() : bool{ return true; } }); $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("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)); + 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)); + 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)), 20)); - 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)), 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", new NetherRoots(new BID(Ids::CRIMSON_ROOTS), "Crimson Roots", $netherRootsInfo)); - self::register("warped_roots", new NetherRoots(new BID(Ids::WARPED_ROOTS), "Warped Roots", $netherRootsInfo)); + 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 $amethystInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD)); - self::register("amethyst", new class(new BID(Ids::AMETHYST), "Amethyst", $amethystInfo) extends Opaque{ + self::register("amethyst", fn(BID $id) => new class($id, "Amethyst", $amethystInfo) extends Opaque{ use AmethystTrait; }); - self::register("budding_amethyst", new BuddingAmethyst(new BID(Ids::BUDDING_AMETHYST), "Budding Amethyst", $amethystInfo)); - self::register("amethyst_cluster", new AmethystCluster(new BID(Ids::AMETHYST_CLUSTER), "Amethyst Cluster", $amethystInfo)); + 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("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{ + self::register("deepslate", fn(BID $id) => new class($id, "Deepslate", $deepslateBreakInfo) extends SimplePillar{ public function getDropsForCompatibleTool(Item $item) : array{ return [VanillaBlocks::COBBLED_DEEPSLATE()->asItem()]; } @@ -1607,106 +1671,124 @@ final class VanillaBlocks{ }); //TODO: parity issue here - in Java this has a hardness of 3.0, but in bedrock it's 3.5 - self::register("chiseled_deepslate", new Opaque(new BID(Ids::CHISELED_DEEPSLATE), "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0)))); + self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 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)); + 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", 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)); + 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", 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)); + 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", 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("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", new TintedGlass(new BID(Ids::TINTED_GLASS), "Tinted Glass", new Info(new BreakInfo(0.3)))); + 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)); + self::register("lightning_rod", fn(BID $id) => new LightningRod($id, "Lightning Rod", $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)); + 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_door", fn(BID $id) => new CopperDoor($id, "Copper Door", new Info(BreakInfo::pickaxe(3.0, blastResistance: 30.0)))); + self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0)))); $candleBreakInfo = new Info(new BreakInfo(0.1)); - self::register("candle", 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", new HangingRoots(new BID(Ids::HANGING_ROOTS), "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1)))); + self::register("hanging_roots", fn(BID $id) => new HangingRoots($id, "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1)))); - self::register("cave_vines", new CaveVines(new BID(Ids::CAVE_VINES), "Cave Vines", new Info(BreakInfo::instant()))); + self::register("cave_vines", fn(BID $id) => new CaveVines($id, "Cave Vines", new Info(BreakInfo::instant()))); - self::register("small_dripleaf", new SmallDripleaf(new BID(Ids::SMALL_DRIPLEAF), "Small Dripleaf", new Info(BreakInfo::instant(ToolType::SHEARS, toolHarvestLevel: 1)))); - self::register("big_dripleaf_head", new BigDripleafHead(new BID(Ids::BIG_DRIPLEAF_HEAD), "Big Dripleaf", new Info(BreakInfo::instant()))); - self::register("big_dripleaf_stem", new BigDripleafStem(new BID(Ids::BIG_DRIPLEAF_STEM), "Big Dripleaf Stem", 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)); - 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)); + 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)); + } + + private static function registerResinBlocks() : void{ + self::register("resin", fn(BID $id) => new Opaque($id, "Block of Resin", new Info(BreakInfo::instant()))); + self::register("resin_clump", fn(BID $id) => new ResinClump($id, "Resin Clump", new Info(BreakInfo::instant()))); + + $resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD)); + self::register("resin_brick_slab", fn(BID $id) => new Slab($id, "Resin Brick", $resinBricksInfo)); + self::register("resin_brick_stairs", fn(BID $id) => new Stair($id, "Resin Brick Stairs", $resinBricksInfo)); + self::register("resin_brick_wall", fn(BID $id) => new Wall($id, "Resin Brick Wall", $resinBricksInfo)); + self::register("resin_bricks", fn(BID $id) => new Opaque($id, "Resin Bricks", $resinBricksInfo)); + self::register("chiseled_resin_bricks", fn(BID $id) => new Opaque($id, "Chiseled Resin Bricks", $resinBricksInfo)); } private static function registerTuffBlocks() : void{ $tuffBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)); - self::register("tuff", new Opaque(new BID(Ids::TUFF), "Tuff", $tuffBreakInfo)); - self::register("tuff_slab", new Slab(new BID(Ids::TUFF_SLAB), "Tuff", $tuffBreakInfo)); - self::register("tuff_stairs", new Stair(new BID(Ids::TUFF_STAIRS), "Tuff Stairs", $tuffBreakInfo)); - self::register("tuff_wall", new Wall(new BID(Ids::TUFF_WALL), "Tuff Wall", $tuffBreakInfo)); - self::register("chiseled_tuff", new Opaque(new BID(Ids::CHISELED_TUFF), "Chiseled Tuff", $tuffBreakInfo)); + 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", new Opaque(new BID(Ids::TUFF_BRICKS), "Tuff Bricks", $tuffBreakInfo)); - self::register("tuff_brick_slab", new Slab(new BID(Ids::TUFF_BRICK_SLAB), "Tuff Brick", $tuffBreakInfo)); - self::register("tuff_brick_stairs", new Stair(new BID(Ids::TUFF_BRICK_STAIRS), "Tuff Brick Stairs", $tuffBreakInfo)); - self::register("tuff_brick_wall", new Wall(new BID(Ids::TUFF_BRICK_WALL), "Tuff Brick Wall", $tuffBreakInfo)); - self::register("chiseled_tuff_bricks", new Opaque(new BID(Ids::CHISELED_TUFF_BRICKS), "Chiseled Tuff Bricks", $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", new Opaque(new BID(Ids::POLISHED_TUFF), "Polished Tuff", $tuffBreakInfo)); - self::register("polished_tuff_slab", new Slab(new BID(Ids::POLISHED_TUFF_SLAB), "Polished Tuff", $tuffBreakInfo)); - self::register("polished_tuff_stairs", new Stair(new BID(Ids::POLISHED_TUFF_STAIRS), "Polished Tuff Stairs", $tuffBreakInfo)); - self::register("polished_tuff_wall", new Wall(new BID(Ids::POLISHED_TUFF_WALL), "Polished Tuff Wall", $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)); - 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/WaterLily.php b/src/block/WaterLily.php index 5dfb0d74a1..b04b1baedf 100644 --- a/src/block/WaterLily.php +++ b/src/block/WaterLily.php @@ -33,9 +33,6 @@ class WaterLily extends Flowable{ canBePlacedAt as supportedWhenPlacedAt; } - /** - * @return AxisAlignedBB[] - */ protected function recalculateCollisionBoxes() : array{ return [AxisAlignedBB::one()->contract(1 / 16, 0, 1 / 16)->trim(Facing::UP, 63 / 64)]; } diff --git a/src/block/WoodLikeBlockIdHelper.php b/src/block/WoodLikeBlockIdHelper.php deleted file mode 100644 index 88fdff3a6e..0000000000 --- a/src/block/WoodLikeBlockIdHelper.php +++ /dev/null @@ -1,263 +0,0 @@ - Ids::OAK_PLANKS, - WoodType::SPRUCE => Ids::SPRUCE_PLANKS, - WoodType::BIRCH => Ids::BIRCH_PLANKS, - WoodType::JUNGLE => Ids::JUNGLE_PLANKS, - WoodType::ACACIA => Ids::ACACIA_PLANKS, - WoodType::DARK_OAK => Ids::DARK_OAK_PLANKS, - WoodType::MANGROVE => Ids::MANGROVE_PLANKS, - WoodType::CRIMSON => Ids::CRIMSON_PLANKS, - WoodType::WARPED => Ids::WARPED_PLANKS, - WoodType::CHERRY => Ids::CHERRY_PLANKS, - }); - } - - public static function getFenceIdentifier(WoodType $type) : BID{ - return new BID(match($type){ - WoodType::OAK => Ids::OAK_FENCE, - WoodType::SPRUCE => Ids::SPRUCE_FENCE, - WoodType::BIRCH => Ids::BIRCH_FENCE, - WoodType::JUNGLE => Ids::JUNGLE_FENCE, - WoodType::ACACIA => Ids::ACACIA_FENCE, - WoodType::DARK_OAK => Ids::DARK_OAK_FENCE, - WoodType::MANGROVE => Ids::MANGROVE_FENCE, - WoodType::CRIMSON => Ids::CRIMSON_FENCE, - WoodType::WARPED => Ids::WARPED_FENCE, - WoodType::CHERRY => Ids::CHERRY_FENCE, - }); - } - - public static function getSlabIdentifier(WoodType $type) : BID{ - return new BID(match($type){ - WoodType::OAK => Ids::OAK_SLAB, - WoodType::SPRUCE => Ids::SPRUCE_SLAB, - WoodType::BIRCH => Ids::BIRCH_SLAB, - WoodType::JUNGLE => Ids::JUNGLE_SLAB, - WoodType::ACACIA => Ids::ACACIA_SLAB, - WoodType::DARK_OAK => Ids::DARK_OAK_SLAB, - WoodType::MANGROVE => Ids::MANGROVE_SLAB, - WoodType::CRIMSON => Ids::CRIMSON_SLAB, - WoodType::WARPED => Ids::WARPED_SLAB, - WoodType::CHERRY => Ids::CHERRY_SLAB, - }); - } - - public static function getLogIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_LOG, - WoodType::SPRUCE => Ids::SPRUCE_LOG, - WoodType::BIRCH => Ids::BIRCH_LOG, - WoodType::JUNGLE => Ids::JUNGLE_LOG, - WoodType::ACACIA => Ids::ACACIA_LOG, - WoodType::DARK_OAK => Ids::DARK_OAK_LOG, - WoodType::MANGROVE => Ids::MANGROVE_LOG, - WoodType::CRIMSON => Ids::CRIMSON_STEM, - WoodType::WARPED => Ids::WARPED_STEM, - WoodType::CHERRY => Ids::CHERRY_LOG, - }); - } - - public static function getAllSidedLogIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_WOOD, - WoodType::SPRUCE => Ids::SPRUCE_WOOD, - WoodType::BIRCH => Ids::BIRCH_WOOD, - WoodType::JUNGLE => Ids::JUNGLE_WOOD, - WoodType::ACACIA => Ids::ACACIA_WOOD, - WoodType::DARK_OAK => Ids::DARK_OAK_WOOD, - WoodType::MANGROVE => Ids::MANGROVE_WOOD, - WoodType::CRIMSON => Ids::CRIMSON_HYPHAE, - WoodType::WARPED => Ids::WARPED_HYPHAE, - WoodType::CHERRY => Ids::CHERRY_WOOD, - }); - } - - public static function getLeavesIdentifier(LeavesType $leavesType) : BID{ - return new BID(match($leavesType){ - LeavesType::OAK => Ids::OAK_LEAVES, - LeavesType::SPRUCE => Ids::SPRUCE_LEAVES, - LeavesType::BIRCH => Ids::BIRCH_LEAVES, - LeavesType::JUNGLE => Ids::JUNGLE_LEAVES, - LeavesType::ACACIA => Ids::ACACIA_LEAVES, - LeavesType::DARK_OAK => Ids::DARK_OAK_LEAVES, - LeavesType::MANGROVE => Ids::MANGROVE_LEAVES, - LeavesType::AZALEA => Ids::AZALEA_LEAVES, - LeavesType::FLOWERING_AZALEA => Ids::FLOWERING_AZALEA_LEAVES, - LeavesType::CHERRY => Ids::CHERRY_LEAVES, - }); - } - - public static function getSaplingIdentifier(SaplingType $treeType) : BID{ - return new BID(match($treeType){ - SaplingType::OAK => Ids::OAK_SAPLING, - SaplingType::SPRUCE => Ids::SPRUCE_SAPLING, - SaplingType::BIRCH => Ids::BIRCH_SAPLING, - SaplingType::JUNGLE => Ids::JUNGLE_SAPLING, - SaplingType::ACACIA => Ids::ACACIA_SAPLING, - SaplingType::DARK_OAK => Ids::DARK_OAK_SAPLING, - }); - } - - /** - * @return BID[]|\Closure[] - * @phpstan-return array{BID, BID, \Closure() : \pocketmine\item\Item} - */ - public static function getSignInfo(WoodType $treeType) : array{ - $make = fn(int $floorId, int $wallId, \Closure $getItem) => [ - new BID($floorId, TileSign::class), - new BID($wallId, TileSign::class), - $getItem - ]; - return match($treeType){ - WoodType::OAK => $make(Ids::OAK_SIGN, Ids::OAK_WALL_SIGN, fn() => VanillaItems::OAK_SIGN()), - WoodType::SPRUCE => $make(Ids::SPRUCE_SIGN, Ids::SPRUCE_WALL_SIGN, fn() => VanillaItems::SPRUCE_SIGN()), - WoodType::BIRCH => $make(Ids::BIRCH_SIGN, Ids::BIRCH_WALL_SIGN, fn() => VanillaItems::BIRCH_SIGN()), - WoodType::JUNGLE => $make(Ids::JUNGLE_SIGN, Ids::JUNGLE_WALL_SIGN, fn() => VanillaItems::JUNGLE_SIGN()), - WoodType::ACACIA => $make(Ids::ACACIA_SIGN, Ids::ACACIA_WALL_SIGN, fn() => VanillaItems::ACACIA_SIGN()), - WoodType::DARK_OAK => $make(Ids::DARK_OAK_SIGN, Ids::DARK_OAK_WALL_SIGN, fn() => VanillaItems::DARK_OAK_SIGN()), - WoodType::MANGROVE => $make(Ids::MANGROVE_SIGN, Ids::MANGROVE_WALL_SIGN, fn() => VanillaItems::MANGROVE_SIGN()), - WoodType::CRIMSON => $make(Ids::CRIMSON_SIGN, Ids::CRIMSON_WALL_SIGN, fn() => VanillaItems::CRIMSON_SIGN()), - WoodType::WARPED => $make(Ids::WARPED_SIGN, Ids::WARPED_WALL_SIGN, fn() => VanillaItems::WARPED_SIGN()), - WoodType::CHERRY => $make(Ids::CHERRY_SIGN, Ids::CHERRY_WALL_SIGN, fn() => VanillaItems::CHERRY_SIGN()), - }; - } - - public static function getTrapdoorIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_TRAPDOOR, - WoodType::SPRUCE => Ids::SPRUCE_TRAPDOOR, - WoodType::BIRCH => Ids::BIRCH_TRAPDOOR, - WoodType::JUNGLE => Ids::JUNGLE_TRAPDOOR, - WoodType::ACACIA => Ids::ACACIA_TRAPDOOR, - WoodType::DARK_OAK => Ids::DARK_OAK_TRAPDOOR, - WoodType::MANGROVE => Ids::MANGROVE_TRAPDOOR, - WoodType::CRIMSON => Ids::CRIMSON_TRAPDOOR, - WoodType::WARPED => Ids::WARPED_TRAPDOOR, - WoodType::CHERRY => Ids::CHERRY_TRAPDOOR, - }); - } - - public static function getButtonIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_BUTTON, - WoodType::SPRUCE => Ids::SPRUCE_BUTTON, - WoodType::BIRCH => Ids::BIRCH_BUTTON, - WoodType::JUNGLE => Ids::JUNGLE_BUTTON, - WoodType::ACACIA => Ids::ACACIA_BUTTON, - WoodType::DARK_OAK => Ids::DARK_OAK_BUTTON, - WoodType::MANGROVE => Ids::MANGROVE_BUTTON, - WoodType::CRIMSON => Ids::CRIMSON_BUTTON, - WoodType::WARPED => Ids::WARPED_BUTTON, - WoodType::CHERRY => Ids::CHERRY_BUTTON, - }); - } - - public static function getPressurePlateIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_PRESSURE_PLATE, - WoodType::SPRUCE => Ids::SPRUCE_PRESSURE_PLATE, - WoodType::BIRCH => Ids::BIRCH_PRESSURE_PLATE, - WoodType::JUNGLE => Ids::JUNGLE_PRESSURE_PLATE, - WoodType::ACACIA => Ids::ACACIA_PRESSURE_PLATE, - WoodType::DARK_OAK => Ids::DARK_OAK_PRESSURE_PLATE, - WoodType::MANGROVE => Ids::MANGROVE_PRESSURE_PLATE, - WoodType::CRIMSON => Ids::CRIMSON_PRESSURE_PLATE, - WoodType::WARPED => Ids::WARPED_PRESSURE_PLATE, - WoodType::CHERRY => Ids::CHERRY_PRESSURE_PLATE, - }); - } - - public static function getDoorIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_DOOR, - WoodType::SPRUCE => Ids::SPRUCE_DOOR, - WoodType::BIRCH => Ids::BIRCH_DOOR, - WoodType::JUNGLE => Ids::JUNGLE_DOOR, - WoodType::ACACIA => Ids::ACACIA_DOOR, - WoodType::DARK_OAK => Ids::DARK_OAK_DOOR, - WoodType::MANGROVE => Ids::MANGROVE_DOOR, - WoodType::CRIMSON => Ids::CRIMSON_DOOR, - WoodType::WARPED => Ids::WARPED_DOOR, - WoodType::CHERRY => Ids::CHERRY_DOOR, - }); - } - - public static function getFenceGateIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_FENCE_GATE, - WoodType::SPRUCE => Ids::SPRUCE_FENCE_GATE, - WoodType::BIRCH => Ids::BIRCH_FENCE_GATE, - WoodType::JUNGLE => Ids::JUNGLE_FENCE_GATE, - WoodType::ACACIA => Ids::ACACIA_FENCE_GATE, - WoodType::DARK_OAK => Ids::DARK_OAK_FENCE_GATE, - WoodType::MANGROVE => Ids::MANGROVE_FENCE_GATE, - WoodType::CRIMSON => Ids::CRIMSON_FENCE_GATE, - WoodType::WARPED => Ids::WARPED_FENCE_GATE, - WoodType::CHERRY => Ids::CHERRY_FENCE_GATE, - }); - } - - public static function getStairsIdentifier(WoodType $treeType) : BID{ - return new BID(match($treeType){ - WoodType::OAK => Ids::OAK_STAIRS, - WoodType::SPRUCE => Ids::SPRUCE_STAIRS, - WoodType::BIRCH => Ids::BIRCH_STAIRS, - WoodType::JUNGLE => Ids::JUNGLE_STAIRS, - WoodType::ACACIA => Ids::ACACIA_STAIRS, - WoodType::DARK_OAK => Ids::DARK_OAK_STAIRS, - WoodType::MANGROVE => Ids::MANGROVE_STAIRS, - WoodType::CRIMSON => Ids::CRIMSON_STAIRS, - WoodType::WARPED => Ids::WARPED_STAIRS, - WoodType::CHERRY => Ids::CHERRY_STAIRS, - }); - } -} diff --git a/src/block/inventory/EnchantInventory.php b/src/block/inventory/EnchantInventory.php index b726dbedf3..6dffbad32b 100644 --- a/src/block/inventory/EnchantInventory.php +++ b/src/block/inventory/EnchantInventory.php @@ -39,7 +39,10 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor public const SLOT_INPUT = 0; public const SLOT_LAPIS = 1; - /** @var EnchantingOption[] $options */ + /** + * @var EnchantingOption[] $options + * @phpstan-var list + */ private array $options = []; public function __construct(Position $holder){ diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index 6455208fec..06175e27f4 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -40,8 +40,12 @@ use function count; class ChiseledBookshelf extends Tile implements Container{ use ContainerTrait; + private const TAG_LAST_INTERACTED_SLOT = "LastInteractedSlot"; //TAG_Int + private SimpleInventory $inventory; + private ?ChiseledBookshelfSlot $lastInteractedSlot = null; + public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); $this->inventory = new SimpleInventory(count(ChiseledBookshelfSlot::cases())); @@ -55,12 +59,30 @@ class ChiseledBookshelf extends Tile implements Container{ 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{ diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index 2ced414ff9..0bb21a6d3a 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -62,9 +62,10 @@ class Sign extends Spawnable{ /** * @return string[] + * @deprecated */ public static function fixTextBlob(string $blob) : array{ - return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4); + return array_slice(array_pad(explode("\n", $blob, limit: 5), 4, ""), 0, 4); } protected SignText $text; diff --git a/src/block/utils/CandleTrait.php b/src/block/utils/CandleTrait.php index c9da97ee0c..0cbd13044b 100644 --- a/src/block/utils/CandleTrait.php +++ b/src/block/utils/CandleTrait.php @@ -43,7 +43,10 @@ trait CandleTrait{ return $this->lit ? 3 : 0; } - /** @see Block::onInteract() */ + /** + * @param Item[] &$returnedItems + * @see Block::onInteract() + */ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE || $item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){ if($this->lit){ diff --git a/src/block/utils/ICopper.php b/src/block/utils/CopperMaterial.php similarity index 85% rename from src/block/utils/ICopper.php rename to src/block/utils/CopperMaterial.php index a749efe639..6df22620b7 100644 --- a/src/block/utils/ICopper.php +++ b/src/block/utils/CopperMaterial.php @@ -26,13 +26,13 @@ namespace pocketmine\block\utils; /** * Represents copper blocks that have oxidized and waxed variations. */ -interface ICopper{ +interface CopperMaterial{ public function getOxidation() : CopperOxidation; - public function setOxidation(CopperOxidation $oxidation) : ICopper; + public function setOxidation(CopperOxidation $oxidation) : CopperMaterial; public function isWaxed() : bool; - public function setWaxed(bool $waxed) : ICopper; + public function setWaxed(bool $waxed) : CopperMaterial; } diff --git a/src/block/utils/CopperTrait.php b/src/block/utils/CopperTrait.php index 5ad8aa82d1..2ed06b798d 100644 --- a/src/block/utils/CopperTrait.php +++ b/src/block/utils/CopperTrait.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block\utils; +use pocketmine\block\Block; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Axe; use pocketmine\item\Item; @@ -58,6 +59,10 @@ trait CopperTrait{ return $this; } + /** + * @param Item[] &$returnedItems + * @see Block::onInteract() + */ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if(!$this->waxed && $item->getTypeId() === ItemTypeIds::HONEYCOMB){ $this->waxed = true; diff --git a/src/block/utils/LeavesType.php b/src/block/utils/LeavesType.php index 975551ad6d..4846feed0e 100644 --- a/src/block/utils/LeavesType.php +++ b/src/block/utils/LeavesType.php @@ -53,6 +53,7 @@ enum LeavesType{ case AZALEA; case FLOWERING_AZALEA; case CHERRY; + case PALE_OAK; public function getDisplayName() : string{ return match($this){ @@ -65,7 +66,8 @@ enum LeavesType{ self::MANGROVE => "Mangrove", self::AZALEA => "Azalea", self::FLOWERING_AZALEA => "Flowering Azalea", - self::CHERRY => "Cherry" + self::CHERRY => "Cherry", + self::PALE_OAK => "Pale Oak", }; } } diff --git a/src/block/utils/MultiAnyFacingTrait.php b/src/block/utils/MultiAnyFacingTrait.php new file mode 100644 index 0000000000..66f26d9800 --- /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 0000000000..ae1da7befe --- /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/RecordType.php b/src/block/utils/RecordType.php index e63cee9204..0757db09b5 100644 --- a/src/block/utils/RecordType.php +++ b/src/block/utils/RecordType.php @@ -59,11 +59,15 @@ enum RecordType{ case DISK_CAT; case DISK_BLOCKS; case DISK_CHIRP; + case DISK_CREATOR; + case DISK_CREATOR_MUSIC_BOX; case DISK_FAR; case DISK_MALL; case DISK_MELLOHI; case DISK_OTHERSIDE; case DISK_PIGSTEP; + case DISK_PRECIPICE; + case DISK_RELIC; case DISK_STAL; case DISK_STRAD; case DISK_WARD; @@ -83,11 +87,15 @@ enum RecordType{ self::DISK_CAT => ["C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()], self::DISK_BLOCKS => ["C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()], self::DISK_CHIRP => ["C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()], + self::DISK_CREATOR => ["Lena Raine - Creator", LevelSoundEvent::RECORD_CREATOR, KnownTranslationFactory::item_record_creator_desc()], + self::DISK_CREATOR_MUSIC_BOX => ["Lena Raine - Creator (Music Box)", LevelSoundEvent::RECORD_CREATOR_MUSIC_BOX, KnownTranslationFactory::item_record_creator_music_box_desc()], self::DISK_FAR => ["C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()], self::DISK_MALL => ["C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()], self::DISK_MELLOHI => ["C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()], self::DISK_OTHERSIDE => ["Lena Raine - otherside", LevelSoundEvent::RECORD_OTHERSIDE, KnownTranslationFactory::item_record_otherside_desc()], self::DISK_PIGSTEP => ["Lena Raine - Pigstep", LevelSoundEvent::RECORD_PIGSTEP, KnownTranslationFactory::item_record_pigstep_desc()], + self::DISK_PRECIPICE => ["Aaron Cherof - Precipice", LevelSoundEvent::RECORD_PRECIPICE, KnownTranslationFactory::item_record_precipice_desc()], + self::DISK_RELIC => ["Aaron Cherof - Relic", LevelSoundEvent::RECORD_RELIC, KnownTranslationFactory::item_record_relic_desc()], self::DISK_STAL => ["C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()], self::DISK_STRAD => ["C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()], self::DISK_WARD => ["C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()], diff --git a/src/block/utils/SignText.php b/src/block/utils/SignText.php index 51285d2679..2198997610 100644 --- a/src/block/utils/SignText.php +++ b/src/block/utils/SignText.php @@ -36,13 +36,17 @@ use function str_contains; class SignText{ public const LINE_COUNT = 4; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var array{0: string, 1: string, 2: string, 3: string} + */ private array $lines; private Color $baseColor; private bool $glowing; /** * @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order + * @phpstan-param array{0?: string, 1?: string, 2?: string, 3?: string}|null $lines * * @throws \InvalidArgumentException if the array size is greater than 4 * @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array @@ -75,13 +79,14 @@ class SignText{ * @throws \InvalidArgumentException if the text is not valid UTF-8 */ public static function fromBlob(string $blob, ?Color $baseColor = null, bool $glowing = false) : SignText{ - return new self(array_slice(array_pad(explode("\n", $blob), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing); + return new self(array_slice(array_pad(explode("\n", $blob, limit: self::LINE_COUNT + 1), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing); } /** * Returns an array of lines currently on the sign. * * @return string[] + * @phpstan-return array{0: string, 1: string, 2: string, 3: string} */ public function getLines() : array{ return $this->lines; diff --git a/src/block/utils/WoodType.php b/src/block/utils/WoodType.php index f6195b9f90..c83a4ab000 100644 --- a/src/block/utils/WoodType.php +++ b/src/block/utils/WoodType.php @@ -53,6 +53,7 @@ enum WoodType{ case CRIMSON; case WARPED; case CHERRY; + case PALE_OAK; public function getDisplayName() : string{ return match($this){ @@ -66,6 +67,7 @@ enum WoodType{ self::CRIMSON => "Crimson", self::WARPED => "Warped", self::CHERRY => "Cherry", + self::PALE_OAK => "Pale Oak", }; } diff --git a/src/command/ClosureCommand.php b/src/command/ClosureCommand.php new file mode 100644 index 0000000000..289c82853e --- /dev/null +++ b/src/command/ClosureCommand.php @@ -0,0 +1,60 @@ + $args) : mixed + */ +final class ClosureCommand extends Command{ + /** @phpstan-var Execute */ + private \Closure $execute; + + /** + * @param string[] $permissions + * @phpstan-param Execute $execute + */ + public function __construct( + string $name, + \Closure $execute, + array $permissions, + Translatable|string $description = "", + Translatable|string|null $usageMessage = null, + array $aliases = [] + ){ + Utils::validateCallableSignature( + fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1, + $execute, + ); + $this->execute = $execute; + parent::__construct($name, $description, $usageMessage, $aliases); + $this->setPermissions($permissions); + } + + public function execute(CommandSender $sender, string $commandLabel, array $args){ + return ($this->execute)($sender, $this, $commandLabel, $args); + } +} diff --git a/src/command/Command.php b/src/command/Command.php index 02bae60d9e..54822d80e6 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -33,9 +33,11 @@ use pocketmine\permission\PermissionManager; use pocketmine\Server; use pocketmine\utils\BroadcastLoggerForwarder; use pocketmine\utils\TextFormat; +use function array_values; use function explode; use function implode; use function str_replace; +use const PHP_INT_MAX; abstract class Command{ @@ -44,10 +46,16 @@ abstract class Command{ private string $nextLabel; private string $label; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $aliases = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $activeAliases = []; private ?CommandMap $commandMap = null; @@ -62,6 +70,7 @@ abstract class Command{ /** * @param string[] $aliases + * @phpstan-param list $aliases */ public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){ $this->name = $name; @@ -73,6 +82,7 @@ abstract class Command{ /** * @param string[] $args + * @phpstan-param list $args * * @return mixed * @throws CommandException @@ -104,7 +114,7 @@ abstract class Command{ } public function setPermission(?string $permission) : void{ - $this->setPermissions($permission === null ? [] : explode(";", $permission)); + $this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX)); } public function testPermission(CommandSender $target, ?string $permission = null) : bool{ @@ -182,6 +192,7 @@ abstract class Command{ /** * @return string[] + * @phpstan-return list */ public function getAliases() : array{ return $this->activeAliases; @@ -201,8 +212,10 @@ abstract class Command{ /** * @param string[] $aliases + * @phpstan-param list $aliases */ public function setAliases(array $aliases) : void{ + $aliases = array_values($aliases); //because plugins can and will pass crap $this->aliases = $aliases; if(!$this->isRegistered()){ $this->activeAliases = $aliases; diff --git a/src/command/FormattedCommandAlias.php b/src/command/FormattedCommandAlias.php index 5086672f6a..b473633972 100644 --- a/src/command/FormattedCommandAlias.php +++ b/src/command/FormattedCommandAlias.php @@ -121,6 +121,7 @@ class FormattedCommandAlias extends Command{ /** * @param string[] $args + * @phpstan-param list $args */ private function buildCommand(string $formatString, array $args) : ?string{ $index = 0; diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 1268e715b7..9f54417469 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -64,13 +64,16 @@ use pocketmine\command\defaults\TransferServerCommand; 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; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\TextFormat; +use pocketmine\utils\Utils; use function array_shift; +use function array_values; use function count; use function implode; use function str_contains; @@ -80,7 +83,10 @@ use function trim; class SimpleCommandMap implements CommandMap{ - /** @var Command[] */ + /** + * @var Command[] + * @phpstan-var array + */ protected array $knownCommands = []; public function __construct(private Server $server){ @@ -128,7 +134,8 @@ class SimpleCommandMap implements CommandMap{ new TitleCommand(), new TransferServerCommand(), new VersionCommand(), - new WhitelistCommand() + new WhitelistCommand(), + new XpCommand(), ]); } @@ -157,7 +164,7 @@ class SimpleCommandMap implements CommandMap{ unset($aliases[$index]); } } - $command->setAliases($aliases); + $command->setAliases(array_values($aliases)); if(!$registered){ $command->setLabel($fallbackPrefix . ":" . $label); @@ -169,7 +176,7 @@ class SimpleCommandMap implements CommandMap{ } public function unregister(Command $command) : bool{ - foreach($this->knownCommands as $lbl => $cmd){ + foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){ if($cmd === $command){ unset($this->knownCommands[$lbl]); } @@ -237,6 +244,7 @@ class SimpleCommandMap implements CommandMap{ /** * @return Command[] + * @phpstan-return array */ public function getCommands() : array{ return $this->knownCommands; @@ -245,7 +253,7 @@ class SimpleCommandMap implements CommandMap{ public function registerServerAliases() : void{ $values = $this->server->getCommandAliases(); - foreach($values as $alias => $commandStrings){ + foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){ if(str_contains($alias, ":")){ $this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias))); continue; diff --git a/src/command/defaults/HelpCommand.php b/src/command/defaults/HelpCommand.php index 487c915f28..054585455f 100644 --- a/src/command/defaults/HelpCommand.php +++ b/src/command/defaults/HelpCommand.php @@ -39,6 +39,7 @@ use function ksort; use function min; use function sort; use function strtolower; +use const PHP_INT_MAX; use const SORT_FLAG_CASE; use const SORT_NATURAL; @@ -108,7 +109,7 @@ class HelpCommand extends VanillaCommand{ $usage = $cmd->getUsage(); $usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage; - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString))) + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX))) ->prefix(TextFormat::GOLD)); $aliases = $cmd->getAliases(); diff --git a/src/command/defaults/ParticleCommand.php b/src/command/defaults/ParticleCommand.php index f20d47ccc3..4867e3eb59 100644 --- a/src/command/defaults/ParticleCommand.php +++ b/src/command/defaults/ParticleCommand.php @@ -219,7 +219,11 @@ class ParticleCommand extends VanillaCommand{ break; case "blockdust": if($data !== null){ - $d = explode("_", $data); + //to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts + //this allows the 4th argument to be processed normally if given without forcing it to also consume + //any unexpected parts + //we probably ought to error in this case, but this will do for now + $d = explode("_", $data, limit: 5); if(count($d) >= 3){ return new DustParticle(new Color( ((int) $d[0]) & 0xff, diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php index 3c0701ea42..08a8b82aa0 100644 --- a/src/command/defaults/TimingsCommand.php +++ b/src/command/defaults/TimingsCommand.php @@ -26,28 +26,30 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\scheduler\BulkCurlTask; use pocketmine\scheduler\BulkCurlTaskOperation; use pocketmine\timings\TimingsHandler; +use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\InternetException; use pocketmine\utils\InternetRequestResult; -use pocketmine\utils\Utils; use pocketmine\YmlServerProperties; use Symfony\Component\Filesystem\Path; use function count; use function fclose; use function file_exists; use function fopen; -use function fseek; use function fwrite; use function http_build_query; +use function implode; use function is_array; +use function is_int; +use function is_string; use function json_decode; use function mkdir; -use function stream_get_contents; use function strtolower; use const CURLOPT_AUTOREFERER; use const CURLOPT_FOLLOWLOCATION; @@ -101,82 +103,91 @@ class TimingsCommand extends VanillaCommand{ TimingsHandler::reload(); Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset()); }elseif($mode === "merged" || $mode === "report" || $paste){ - $timings = ""; - if($paste){ - $fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail"); - }else{ - $index = 0; - $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); - - if(!file_exists($timingFolder)){ - mkdir($timingFolder, 0777); - } - $timings = Path::join($timingFolder, "timings.txt"); - while(file_exists($timings)){ - $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); - } - - $fileTimings = fopen($timings, "a+b"); - } - $lines = TimingsHandler::printTimings(); - foreach($lines as $line){ - fwrite($fileTimings, $line . PHP_EOL); - } - - if($paste){ - fseek($fileTimings, 0); - $data = [ - "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), - "data" => $content = stream_get_contents($fileTimings) - ]; - fclose($fileTimings); - - $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); - - $sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask( - [new BulkCurlTaskOperation( - "https://$host?upload=true", - 10, - [], - [ - CURLOPT_HTTPHEADER => [ - "User-Agent: $agent", - "Content-Type: application/x-www-form-urlencoded" - ], - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => http_build_query($data), - CURLOPT_AUTOREFERER => false, - CURLOPT_FOLLOWLOCATION => false - ] - )], - function(array $results) use ($sender, $host) : void{ - /** @phpstan-var array $results */ - if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender - return; - } - $result = $results[0]; - if($result instanceof InternetException){ - $sender->getServer()->getLogger()->logException($result); - return; - } - $response = json_decode($result->getBody(), true); - if(is_array($response) && isset($response["id"])){ - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead( - "https://" . $host . "/?id=" . $response["id"])); - }else{ - $sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody()); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError()); - } - } - )); - }else{ - fclose($fileTimings); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings)); - } + $timingsPromise = TimingsHandler::requestPrintTimings(); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect()); + $timingsPromise->onCompletion( + fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender), + fn() => throw new AssumptionFailedError("This promise is not expected to be rejected") + ); }else{ throw new InvalidCommandSyntaxException(); } return true; } + + /** + * @param string[] $lines + * @phpstan-param list $lines + */ + private function createReportFile(array $lines, CommandSender $sender) : void{ + $index = 0; + $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); + + if(!file_exists($timingFolder)){ + mkdir($timingFolder, 0777); + } + $timings = Path::join($timingFolder, "timings.txt"); + while(file_exists($timings)){ + $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); + } + + $fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b")); + foreach($lines as $line){ + fwrite($fileTimings, $line . PHP_EOL); + } + fclose($fileTimings); + + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings)); + } + + /** + * @param string[] $lines + * @phpstan-param list $lines + */ + private function uploadReport(array $lines, CommandSender $sender) : void{ + $data = [ + "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), + "data" => implode("\n", $lines) + ]; + + $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); + + $sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask( + [new BulkCurlTaskOperation( + "https://$host?upload=true", + 10, + [], + [ + CURLOPT_HTTPHEADER => [ + "User-Agent: $agent", + "Content-Type: application/x-www-form-urlencoded" + ], + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($data), + CURLOPT_AUTOREFERER => false, + CURLOPT_FOLLOWLOCATION => false + ] + )], + function(array $results) use ($sender, $host) : void{ + /** @phpstan-var array $results */ + if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender + return; + } + $result = $results[0]; + if($result instanceof InternetException){ + $sender->getServer()->getLogger()->logException($result); + return; + } + $response = json_decode($result->getBody(), true); + if(is_array($response) && isset($response["id"]) && (is_int($response["id"]) || is_string($response["id"]))){ + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead( + "https://" . $host . "/?id=" . $response["id"])); + }else{ + $sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody()); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError()); + } + } + )); + } } diff --git a/src/command/defaults/XpCommand.php b/src/command/defaults/XpCommand.php new file mode 100644 index 0000000000..cb03523657 --- /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/command/utils/CommandStringHelper.php b/src/command/utils/CommandStringHelper.php index eacc5d3d8b..76d70a9bba 100644 --- a/src/command/utils/CommandStringHelper.php +++ b/src/command/utils/CommandStringHelper.php @@ -51,9 +51,8 @@ final class CommandStringHelper{ foreach($matches[0] as $k => $_){ for($i = 1; $i <= 2; ++$i){ if($matches[$i][$k] !== ""){ - /** @var string $match */ //phpstan can't understand preg_match and friends by itself :( $match = $matches[$i][$k]; - $args[(int) $k] = preg_replace('/\\\\([\\\\"])/u', '$1', $match) ?? throw new AssumptionFailedError(preg_last_error_msg()); + $args[] = preg_replace('/\\\\([\\\\"])/u', '$1', $match) ?? throw new AssumptionFailedError(preg_last_error_msg()); break; } } diff --git a/src/console/ConsoleCommandSender.php b/src/console/ConsoleCommandSender.php index aa7ea6e699..a0c1c5200b 100644 --- a/src/console/ConsoleCommandSender.php +++ b/src/console/ConsoleCommandSender.php @@ -62,7 +62,7 @@ class ConsoleCommandSender implements CommandSender{ $message = $this->getLanguage()->translate($message); } - foreach(explode("\n", trim($message)) as $line){ + foreach(explode("\n", trim($message), limit: PHP_INT_MAX) as $line){ Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line)); } } diff --git a/src/console/ConsoleReaderChildProcess.php b/src/console/ConsoleReaderChildProcess.php index 0d5a30fdcc..c0c4f0048f 100644 --- a/src/console/ConsoleReaderChildProcess.php +++ b/src/console/ConsoleReaderChildProcess.php @@ -29,23 +29,21 @@ use pocketmine\utils\Process; 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; + +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'; -if(count($argv) !== 2){ - die("Please provide a server to connect to"); -} - @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 @@ $thread = new class($channel) extends NativeThread{ }; $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. diff --git a/src/console/ConsoleReaderChildProcessDaemon.php b/src/console/ConsoleReaderChildProcessDaemon.php index 138559f06e..f7300b7a52 100644 --- a/src/console/ConsoleReaderChildProcessDaemon.php +++ b/src/console/ConsoleReaderChildProcessDaemon.php @@ -29,19 +29,16 @@ use Symfony\Component\Filesystem\Path; 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 @@ use const STREAM_SHUT_RDWR; * 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 @@ final class ConsoleReaderChildProcessDaemon{ //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 @@ final class ConsoleReaderChildProcessDaemon{ $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 0000000000..661e9b0f7b --- /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 c7c0b10c64..93d6e1838f 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -29,8 +29,12 @@ use pocketmine\nbt\TreeRoot; use pocketmine\utils\BinaryStream; use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\ObjectSet; +use function array_shift; +use function count; +use function implode; +use function ksort; use function spl_object_id; -use function usort; +use const SORT_STRING; class CraftingManager{ use DestructorCallbackTrait; @@ -100,6 +104,7 @@ class CraftingManager{ /** * Function used to arrange Shapeless Recipe ingredient lists into a consistent order. + * @deprecated */ public static function sort(Item $i1, Item $i2) : int{ //Use spaceship operator to compare each property, then try the next one if they are equivalent. @@ -108,45 +113,30 @@ class CraftingManager{ return $retval; } - /** - * @param Item[] $items - * - * @return Item[] - */ - private static function pack(array $items) : array{ - /** @var Item[] $result */ - $result = []; + private static function hashOutput(Item $output) : string{ + $write = new BinaryStream(); + $write->putVarInt($output->getStateId()); + $write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag()))); - foreach($items as $i => $item){ - foreach($result as $otherItem){ - if($item->canStackWith($otherItem)){ - $otherItem->setCount($otherItem->getCount() + $item->getCount()); - continue 2; - } - } - - //No matching item found - $result[] = clone $item; - } - - return $result; + return $write->getBuffer(); } /** * @param Item[] $outputs */ private static function hashOutputs(array $outputs) : string{ - $outputs = self::pack($outputs); - usort($outputs, [self::class, "sort"]); - $result = new BinaryStream(); + if(count($outputs) === 1){ + return self::hashOutput(array_shift($outputs)); + } + $unique = []; foreach($outputs as $o){ //count is not written because the outputs might be from multiple repetitions of a single recipe //this reduces the accuracy of the hash, but it won't matter in most cases. - $result->putVarInt($o->getStateId()); - $result->put((new LittleEndianNbtSerializer())->write(new TreeRoot($o->getNamedTag()))); + $hash = self::hashOutput($o); + $unique[$hash] = $hash; } - - return $result->getBuffer(); + ksort($unique, SORT_STRING); + return implode("", $unique); } /** diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 616c2a4bd3..3df6bfb62a 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -193,7 +193,7 @@ final class CraftingManagerFromDataHelper{ */ private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{ $result = []; - foreach($data as $i => $item){ + foreach(Utils::promoteKeys($data) as $i => $item){ if(!is_object($item)){ throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item)); } diff --git a/src/crafting/CraftingRecipe.php b/src/crafting/CraftingRecipe.php index 02e6fce040..d7342aae31 100644 --- a/src/crafting/CraftingRecipe.php +++ b/src/crafting/CraftingRecipe.php @@ -30,6 +30,7 @@ interface CraftingRecipe{ * Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count. * * @return RecipeIngredient[] + * @phpstan-return list */ public function getIngredientList() : array; @@ -37,6 +38,7 @@ interface CraftingRecipe{ * Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed. * * @return Item[] + * @phpstan-return list */ public function getResultsFor(CraftingGrid $grid) : array; diff --git a/src/crafting/ShapedRecipe.php b/src/crafting/ShapedRecipe.php index f13ba3ae74..2af3f51346 100644 --- a/src/crafting/ShapedRecipe.php +++ b/src/crafting/ShapedRecipe.php @@ -32,11 +32,20 @@ use function str_contains; use function strlen; class ShapedRecipe implements CraftingRecipe{ - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $shape = []; - /** @var RecipeIngredient[] char => RecipeIngredient map */ + /** + * @var RecipeIngredient[] char => RecipeIngredient map + * @phpstan-var array + */ private array $ingredientList = []; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ private array $results = []; private int $height; @@ -56,6 +65,10 @@ class ShapedRecipe implements CraftingRecipe{ * @param Item[] $results List of items that this recipe produces when crafted. * * Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns. + * + * @phpstan-param list $shape + * @phpstan-param array $ingredients + * @phpstan-param list $results */ public function __construct(array $shape, array $ingredients, array $results){ $this->height = count($shape); @@ -84,7 +97,8 @@ class ShapedRecipe implements CraftingRecipe{ $this->shape = $shape; - foreach($ingredients as $char => $i){ + Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{}); + foreach(Utils::stringifyKeys($ingredients) as $char => $i){ if(!str_contains(implode($this->shape), $char)){ throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape"); } @@ -92,6 +106,7 @@ class ShapedRecipe implements CraftingRecipe{ $this->ingredientList[$char] = clone $i; } + Utils::validateArrayValueType($results, function(Item $_) : void{}); $this->results = Utils::cloneObjectArray($results); } @@ -105,6 +120,7 @@ class ShapedRecipe implements CraftingRecipe{ /** * @return Item[] + * @phpstan-return list */ public function getResults() : array{ return Utils::cloneObjectArray($this->results); @@ -112,6 +128,7 @@ class ShapedRecipe implements CraftingRecipe{ /** * @return Item[] + * @phpstan-return list */ public function getResultsFor(CraftingGrid $grid) : array{ return $this->getResults(); @@ -119,6 +136,7 @@ class ShapedRecipe implements CraftingRecipe{ /** * @return (RecipeIngredient|null)[][] + * @phpstan-return list> */ public function getIngredientMap() : array{ $ingredients = []; @@ -132,9 +150,6 @@ class ShapedRecipe implements CraftingRecipe{ return $ingredients; } - /** - * @return RecipeIngredient[] - */ public function getIngredientList() : array{ $ingredients = []; @@ -157,6 +172,7 @@ class ShapedRecipe implements CraftingRecipe{ /** * Returns an array of strings containing characters representing the recipe's shape. * @return string[] + * @phpstan-return list */ public function getShape() : array{ return $this->shape; diff --git a/src/crafting/ShapelessRecipe.php b/src/crafting/ShapelessRecipe.php index a9e1f023c5..b139439ef7 100644 --- a/src/crafting/ShapelessRecipe.php +++ b/src/crafting/ShapelessRecipe.php @@ -28,15 +28,24 @@ use pocketmine\utils\Utils; use function count; class ShapelessRecipe implements CraftingRecipe{ - /** @var RecipeIngredient[] */ + /** + * @var RecipeIngredient[] + * @phpstan-var list + */ private array $ingredients = []; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ private array $results; private ShapelessRecipeType $type; /** * @param RecipeIngredient[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array. * @param Item[] $results List of result items created by this recipe. + * + * @phpstan-param list $ingredients + * @phpstan-param list $results */ public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){ $this->type = $type; @@ -44,12 +53,15 @@ class ShapelessRecipe implements CraftingRecipe{ if(count($ingredients) > 9){ throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients"); } + Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{}); $this->ingredients = $ingredients; + Utils::validateArrayValueType($results, function(Item $_) : void{}); $this->results = Utils::cloneObjectArray($results); } /** * @return Item[] + * @phpstan-return list */ public function getResults() : array{ return Utils::cloneObjectArray($this->results); @@ -63,9 +75,6 @@ class ShapelessRecipe implements CraftingRecipe{ return $this->type; } - /** - * @return RecipeIngredient[] - */ public function getIngredientList() : array{ return $this->ingredients; } diff --git a/src/crash/CrashDumpData.php b/src/crash/CrashDumpData.php index b71e6f405b..f8a20abd5b 100644 --- a/src/crash/CrashDumpData.php +++ b/src/crash/CrashDumpData.php @@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{ public string $plugin = ""; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var array + */ public array $code = []; /** @var string[] */ @@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{ */ public array $plugins = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ public array $parameters = []; public string $serverDotProperties = ""; diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 2c9350ca3b..1ecb707cc5 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -35,16 +35,20 @@ final class BedrockDataFiles{ 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'; - public const CREATIVEITEMS_JSON = BEDROCK_DATA_PATH . '/creativeitems.json'; + public const CREATIVE = BEDROCK_DATA_PATH . '/creative'; public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json'; public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt'; + public const ENUMS = BEDROCK_DATA_PATH . '/enums'; + public const ENUMS_PY = BEDROCK_DATA_PATH . '/enums.py'; public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; - public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json'; + public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; + public const RECIPES = BEDROCK_DATA_PATH . '/recipes'; public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json'; } diff --git a/src/data/bedrock/BiomeIds.php b/src/data/bedrock/BiomeIds.php index 1169a51eae..f3c38d3ed1 100644 --- a/src/data/bedrock/BiomeIds.php +++ b/src/data/bedrock/BiomeIds.php @@ -122,4 +122,5 @@ final class BiomeIds{ public const DEEP_DARK = 190; public const MANGROVE_SWAMP = 191; public const CHERRY_GROVE = 192; + public const PALE_GARDEN = 193; } diff --git a/src/data/bedrock/EnchantmentIdMap.php b/src/data/bedrock/EnchantmentIdMap.php index e3d652b19c..90a10dc20a 100644 --- a/src/data/bedrock/EnchantmentIdMap.php +++ b/src/data/bedrock/EnchantmentIdMap.php @@ -66,5 +66,7 @@ final class EnchantmentIdMap{ $this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING()); $this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK()); + + $this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER()); } } diff --git a/src/data/bedrock/GoatHornTypeIdMap.php b/src/data/bedrock/GoatHornTypeIdMap.php new file mode 100644 index 0000000000..0510a09cee --- /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/src/data/bedrock/GoatHornTypeIds.php b/src/data/bedrock/GoatHornTypeIds.php new file mode 100644 index 0000000000..048d246fe0 --- /dev/null +++ b/src/data/bedrock/GoatHornTypeIds.php @@ -0,0 +1,35 @@ + $ids){ + foreach(Utils::promoteKeys($map) as $tagName => $ids){ if(!is_string($tagName)){ throw new AssumptionFailedError("Invalid item tag name $tagName, expected string as key"); } diff --git a/src/data/bedrock/LegacyToStringIdMap.php b/src/data/bedrock/LegacyToStringIdMap.php index 2b5375db23..1a92f2b6b8 100644 --- a/src/data/bedrock/LegacyToStringIdMap.php +++ b/src/data/bedrock/LegacyToStringIdMap.php @@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; +use pocketmine\utils\Utils; use function is_array; use function is_int; use function is_string; @@ -43,7 +44,7 @@ abstract class LegacyToStringIdMap{ if(!is_array($stringToLegacyId)){ throw new AssumptionFailedError("Invalid format of ID map"); } - foreach($stringToLegacyId as $stringId => $legacyId){ + foreach(Utils::promoteKeys($stringToLegacyId) as $stringId => $legacyId){ if(!is_string($stringId) || !is_int($legacyId)){ throw new AssumptionFailedError("ID map should have string keys and int values"); } diff --git a/src/data/bedrock/NoteInstrumentIdMap.php b/src/data/bedrock/NoteInstrumentIdMap.php index c847ecd98c..7f0c0f310b 100644 --- a/src/data/bedrock/NoteInstrumentIdMap.php +++ b/src/data/bedrock/NoteInstrumentIdMap.php @@ -39,10 +39,10 @@ final class NoteInstrumentIdMap{ NoteInstrument::SNARE => 2, NoteInstrument::CLICKS_AND_STICKS => 3, NoteInstrument::DOUBLE_BASS => 4, - NoteInstrument::BELL => 5, - NoteInstrument::FLUTE => 6, - NoteInstrument::CHIME => 7, - NoteInstrument::GUITAR => 8, + NoteInstrument::FLUTE => 5, + NoteInstrument::BELL => 6, + NoteInstrument::GUITAR => 7, + NoteInstrument::CHIME => 8, NoteInstrument::XYLOPHONE => 9, NoteInstrument::IRON_XYLOPHONE => 10, NoteInstrument::COW_BELL => 11, diff --git a/src/data/bedrock/block/BlockStateData.php b/src/data/bedrock/block/BlockStateData.php index 1973b55af1..e90410ac7f 100644 --- a/src/data/bedrock/block/BlockStateData.php +++ b/src/data/bedrock/block/BlockStateData.php @@ -45,8 +45,8 @@ final class BlockStateData{ public const CURRENT_VERSION = (1 << 24) | //major (21 << 16) | //minor - (20 << 8) | //patch - (6); //revision + (60 << 8) | //patch + (33); //revision public const TAG_NAME = "name"; public const TAG_STATES = "states"; diff --git a/src/data/bedrock/block/BlockStateNames.php b/src/data/bedrock/block/BlockStateNames.php index e9c33bee24..9fed77e4a3 100644 --- a/src/data/bedrock/block/BlockStateNames.php +++ b/src/data/bedrock/block/BlockStateNames.php @@ -34,7 +34,6 @@ final class BlockStateNames{ 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"; @@ -52,10 +51,7 @@ final class BlockStateNames{ 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_BIT = "color_bit"; public const COMPOSTER_FILL_LEVEL = "composter_fill_level"; public const CONDITIONAL_BIT = "conditional_bit"; public const CORAL_DIRECTION = "coral_direction"; @@ -63,6 +59,7 @@ final class BlockStateNames{ public const COVERED_BIT = "covered_bit"; public const CRACKED_STATE = "cracked_state"; public const CRAFTING = "crafting"; + public const CREAKING_HEART_STATE = "creaking_heart_state"; public const DEAD_BIT = "dead_bit"; public const DEPRECATED = "deprecated"; public const DIRECTION = "direction"; @@ -97,12 +94,17 @@ final class BlockStateNames{ public const MC_VERTICAL_HALF = "minecraft:vertical_half"; public const MOISTURIZED_AMOUNT = "moisturized_amount"; public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits"; + public const NATURAL = "natural"; public const OCCUPIED_BIT = "occupied_bit"; public const OMINOUS = "ominous"; public const OPEN_BIT = "open_bit"; public const ORIENTATION = "orientation"; public const OUTPUT_LIT_BIT = "output_lit_bit"; public const OUTPUT_SUBTRACT_BIT = "output_subtract_bit"; + public const PALE_MOSS_CARPET_SIDE_EAST = "pale_moss_carpet_side_east"; + public const PALE_MOSS_CARPET_SIDE_NORTH = "pale_moss_carpet_side_north"; + public const PALE_MOSS_CARPET_SIDE_SOUTH = "pale_moss_carpet_side_south"; + public const PALE_MOSS_CARPET_SIDE_WEST = "pale_moss_carpet_side_west"; public const PERSISTENT_BIT = "persistent_bit"; public const PILLAR_AXIS = "pillar_axis"; public const PORTAL_AXIS = "portal_axis"; @@ -116,13 +118,11 @@ final class BlockStateNames{ public const ROTATION = "rotation"; 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 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 TIP = "tip"; public const TOGGLE_BIT = "toggle_bit"; public const TORCH_FACING_DIRECTION = "torch_facing_direction"; public const TRIAL_SPAWNER_STATE = "trial_spawner_state"; @@ -134,7 +134,6 @@ final class BlockStateNames{ 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"; diff --git a/src/data/bedrock/block/BlockStateStringValues.php b/src/data/bedrock/block/BlockStateStringValues.php index 1794e240d1..e6ae30e640 100644 --- a/src/data/bedrock/block/BlockStateStringValues.php +++ b/src/data/bedrock/block/BlockStateStringValues.php @@ -52,20 +52,14 @@ final class BlockStateStringValues{ 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 CRACKED_STATE_CRACKED = "cracked"; public const CRACKED_STATE_MAX_CRACKED = "max_cracked"; public const CRACKED_STATE_NO_CRACKS = "no_cracks"; + public const CREAKING_HEART_STATE_AWAKE = "awake"; + public const CREAKING_HEART_STATE_DORMANT = "dormant"; + public const CREAKING_HEART_STATE_UPROOTED = "uprooted"; + public const DRIPSTONE_THICKNESS_BASE = "base"; public const DRIPSTONE_THICKNESS_FRUSTUM = "frustum"; public const DRIPSTONE_THICKNESS_MERGE = "merge"; @@ -116,6 +110,22 @@ final class BlockStateStringValues{ public const ORIENTATION_UP_WEST = "up_west"; public const ORIENTATION_WEST_UP = "west_up"; + public const PALE_MOSS_CARPET_SIDE_EAST_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_EAST_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_EAST_TALL = "tall"; + + public const PALE_MOSS_CARPET_SIDE_NORTH_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_NORTH_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_NORTH_TALL = "tall"; + + public const PALE_MOSS_CARPET_SIDE_SOUTH_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_SOUTH_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_SOUTH_TALL = "tall"; + + public const PALE_MOSS_CARPET_SIDE_WEST_NONE = "none"; + public const PALE_MOSS_CARPET_SIDE_WEST_SHORT = "short"; + public const PALE_MOSS_CARPET_SIDE_WEST_TALL = "tall"; + public const PILLAR_AXIS_X = "x"; public const PILLAR_AXIS_Y = "y"; public const PILLAR_AXIS_Z = "z"; @@ -128,9 +138,6 @@ final class BlockStateStringValues{ 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 STRUCTURE_BLOCK_TYPE_CORNER = "corner"; public const STRUCTURE_BLOCK_TYPE_DATA = "data"; public const STRUCTURE_BLOCK_TYPE_EXPORT = "export"; @@ -138,9 +145,6 @@ final class BlockStateStringValues{ 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 TORCH_FACING_DIRECTION_EAST = "east"; public const TORCH_FACING_DIRECTION_NORTH = "north"; public const TORCH_FACING_DIRECTION_SOUTH = "south"; @@ -158,21 +162,6 @@ final class BlockStateStringValues{ public const VAULT_STATE_INACTIVE = "inactive"; public const VAULT_STATE_UNLOCKING = "unlocking"; - 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 WALL_CONNECTION_TYPE_EAST_NONE = "none"; public const WALL_CONNECTION_TYPE_EAST_SHORT = "short"; public const WALL_CONNECTION_TYPE_EAST_TALL = "tall"; diff --git a/src/data/bedrock/block/BlockTypeNames.php b/src/data/bedrock/block/BlockTypeNames.php index f74f858cef..eec1ab8d19 100644 --- a/src/data/bedrock/block/BlockTypeNames.php +++ b/src/data/bedrock/block/BlockTypeNames.php @@ -59,6 +59,7 @@ final class BlockTypeNames{ 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"; @@ -154,6 +155,7 @@ final class BlockTypeNames{ 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"; @@ -190,8 +192,8 @@ final class BlockTypeNames{ public const CAVE_VINES_HEAD_WITH_BERRIES = "minecraft:cave_vines_head_with_berries"; public const CHAIN = "minecraft:chain"; public const CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block"; + public const CHALKBOARD = "minecraft:chalkboard"; public const CHEMICAL_HEAT = "minecraft:chemical_heat"; - public const 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"; @@ -218,6 +220,7 @@ final class BlockTypeNames{ public const CHISELED_POLISHED_BLACKSTONE = "minecraft:chiseled_polished_blackstone"; public const CHISELED_QUARTZ_BLOCK = "minecraft:chiseled_quartz_block"; public const CHISELED_RED_SANDSTONE = "minecraft:chiseled_red_sandstone"; + public const CHISELED_RESIN_BRICKS = "minecraft:chiseled_resin_bricks"; public const CHISELED_SANDSTONE = "minecraft:chiseled_sandstone"; public const CHISELED_STONE_BRICKS = "minecraft:chiseled_stone_bricks"; public const CHISELED_TUFF = "minecraft:chiseled_tuff"; @@ -226,6 +229,7 @@ final class BlockTypeNames{ public const CHORUS_PLANT = "minecraft:chorus_plant"; public const CLAY = "minecraft:clay"; public const CLIENT_REQUEST_PLACEHOLDER_BLOCK = "minecraft:client_request_placeholder_block"; + public const CLOSED_EYEBLOSSOM = "minecraft:closed_eyeblossom"; public const COAL_BLOCK = "minecraft:coal_block"; public const COAL_ORE = "minecraft:coal_ore"; public const COARSE_DIRT = "minecraft:coarse_dirt"; @@ -239,10 +243,13 @@ final class BlockTypeNames{ 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 COMPOUND_CREATOR = "minecraft:compound_creator"; public const CONDUIT = "minecraft:conduit"; public const COPPER_BLOCK = "minecraft:copper_block"; public const COPPER_BULB = "minecraft:copper_bulb"; @@ -258,6 +265,8 @@ final class BlockTypeNames{ public const CRACKED_STONE_BRICKS = "minecraft:cracked_stone_bricks"; public const CRAFTER = "minecraft:crafter"; public const CRAFTING_TABLE = "minecraft:crafting_table"; + public const CREAKING_HEART = "minecraft:creaking_heart"; + public const CREEPER_HEAD = "minecraft:creeper_head"; public const CRIMSON_BUTTON = "minecraft:crimson_button"; public const CRIMSON_DOOR = "minecraft:crimson_door"; public const CRIMSON_DOUBLE_SLAB = "minecraft:crimson_double_slab"; @@ -365,6 +374,8 @@ final class BlockTypeNames{ 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"; @@ -372,11 +383,13 @@ final class BlockTypeNames{ 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 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"; @@ -499,6 +512,7 @@ final class BlockTypeNames{ 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"; @@ -511,6 +525,7 @@ final class BlockTypeNames{ 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"; @@ -553,6 +568,7 @@ final class BlockTypeNames{ 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 GRANITE_WALL = "minecraft:granite_wall"; public const GRASS_BLOCK = "minecraft:grass_block"; public const GRASS_PATH = "minecraft:grass_path"; public const GRAVEL = "minecraft:gravel"; @@ -661,6 +677,7 @@ final class BlockTypeNames{ 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"; @@ -761,6 +778,7 @@ final class BlockTypeNames{ 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"; @@ -771,9 +789,11 @@ final class BlockTypeNames{ 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"; @@ -783,12 +803,14 @@ final class BlockTypeNames{ 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"; @@ -813,6 +835,7 @@ final class BlockTypeNames{ public const OBSERVER = "minecraft:observer"; public const OBSIDIAN = "minecraft:obsidian"; public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight"; + public const OPEN_EYEBLOSSOM = "minecraft:open_eyeblossom"; public const ORANGE_CANDLE = "minecraft:orange_candle"; public const ORANGE_CANDLE_CAKE = "minecraft:orange_candle_cake"; public const ORANGE_CARPET = "minecraft:orange_carpet"; @@ -838,10 +861,31 @@ final class BlockTypeNames{ public const OXIDIZED_DOUBLE_CUT_COPPER_SLAB = "minecraft:oxidized_double_cut_copper_slab"; public const PACKED_ICE = "minecraft:packed_ice"; public const PACKED_MUD = "minecraft:packed_mud"; + public const PALE_HANGING_MOSS = "minecraft:pale_hanging_moss"; + public const PALE_MOSS_BLOCK = "minecraft:pale_moss_block"; + public const PALE_MOSS_CARPET = "minecraft:pale_moss_carpet"; + public const PALE_OAK_BUTTON = "minecraft:pale_oak_button"; + public const PALE_OAK_DOOR = "minecraft:pale_oak_door"; + public const PALE_OAK_DOUBLE_SLAB = "minecraft:pale_oak_double_slab"; + public const PALE_OAK_FENCE = "minecraft:pale_oak_fence"; + public const PALE_OAK_FENCE_GATE = "minecraft:pale_oak_fence_gate"; + public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign"; + public const PALE_OAK_LEAVES = "minecraft:pale_oak_leaves"; + public const PALE_OAK_LOG = "minecraft:pale_oak_log"; + public const PALE_OAK_PLANKS = "minecraft:pale_oak_planks"; + public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate"; + public const PALE_OAK_SAPLING = "minecraft:pale_oak_sapling"; + public const PALE_OAK_SLAB = "minecraft:pale_oak_slab"; + public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs"; + public const PALE_OAK_STANDING_SIGN = "minecraft:pale_oak_standing_sign"; + public const PALE_OAK_TRAPDOOR = "minecraft:pale_oak_trapdoor"; + public const PALE_OAK_WALL_SIGN = "minecraft:pale_oak_wall_sign"; + public const PALE_OAK_WOOD = "minecraft:pale_oak_wood"; public const PEARLESCENT_FROGLIGHT = "minecraft:pearlescent_froglight"; public const PEONY = "minecraft:peony"; public const PETRIFIED_OAK_DOUBLE_SLAB = "minecraft:petrified_oak_double_slab"; 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"; @@ -859,6 +903,7 @@ final class BlockTypeNames{ public const PISTON_ARM_COLLISION = "minecraft:piston_arm_collision"; 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"; @@ -910,6 +955,7 @@ final class BlockTypeNames{ 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"; @@ -925,6 +971,7 @@ final class BlockTypeNames{ 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"; @@ -950,11 +997,13 @@ final class BlockTypeNames{ 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"; @@ -970,6 +1019,13 @@ final class BlockTypeNames{ public const REINFORCED_DEEPSLATE = "minecraft:reinforced_deepslate"; public const REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block"; public const RESERVED6 = "minecraft:reserved6"; + public const RESIN_BLOCK = "minecraft:resin_block"; + public const RESIN_BRICK_DOUBLE_SLAB = "minecraft:resin_brick_double_slab"; + public const RESIN_BRICK_SLAB = "minecraft:resin_brick_slab"; + public const RESIN_BRICK_STAIRS = "minecraft:resin_brick_stairs"; + public const RESIN_BRICK_WALL = "minecraft:resin_brick_wall"; + public const RESIN_BRICKS = "minecraft:resin_bricks"; + public const RESIN_CLUMP = "minecraft:resin_clump"; public const RESPAWN_ANCHOR = "minecraft:respawn_anchor"; public const ROSE_BUSH = "minecraft:rose_bush"; public const SAND = "minecraft:sand"; @@ -977,6 +1033,7 @@ final class BlockTypeNames{ 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 SANDSTONE_WALL = "minecraft:sandstone_wall"; public const SCAFFOLDING = "minecraft:scaffolding"; public const SCULK = "minecraft:sculk"; public const SCULK_CATALYST = "minecraft:sculk_catalyst"; @@ -989,7 +1046,7 @@ final class BlockTypeNames{ public const SHORT_GRASS = "minecraft:short_grass"; public const SHROOMLIGHT = "minecraft:shroomlight"; 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"; @@ -1047,6 +1104,7 @@ final class BlockTypeNames{ 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"; @@ -1070,6 +1128,8 @@ final class BlockTypeNames{ public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood"; public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log"; public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood"; + public const STRIPPED_PALE_OAK_LOG = "minecraft:stripped_pale_oak_log"; + public const STRIPPED_PALE_OAK_WOOD = "minecraft:stripped_pale_oak_wood"; public const STRIPPED_SPRUCE_LOG = "minecraft:stripped_spruce_log"; public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood"; public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae"; @@ -1108,6 +1168,7 @@ final class BlockTypeNames{ 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"; @@ -1192,6 +1253,7 @@ final class BlockTypeNames{ 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"; @@ -1206,6 +1268,7 @@ final class BlockTypeNames{ public const WHITE_TULIP = "minecraft:white_tulip"; public const WHITE_WOOL = "minecraft:white_wool"; public const WITHER_ROSE = "minecraft:wither_rose"; + 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"; @@ -1220,4 +1283,5 @@ final class BlockTypeNames{ 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 2b16a32dde..e41e820548 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -58,8 +58,12 @@ 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; @@ -117,6 +121,7 @@ use pocketmine\block\RedstoneOre; use pocketmine\block\RedstoneRepeater; use pocketmine\block\RedstoneTorch; use pocketmine\block\RedstoneWire; +use pocketmine\block\ResinClump; use pocketmine\block\RuntimeBlockStateRegistry; use pocketmine\block\Sapling; use pocketmine\block\SeaPickle; @@ -153,6 +158,7 @@ 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; @@ -207,6 +213,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->registerFlatWoodBlockSerializers(); $this->registerLeavesSerializers(); $this->registerSaplingSerializers(); + $this->registerMobHeadSerializers(); $this->registerSimpleSerializers(); $this->registerSerializers(); } @@ -626,17 +633,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $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->map(Blocks::CHERRY_WOOD(), function(Wood $block) : Writer{ - //we can't use the standard method for this because cherry_wood has a useless property attached to it - if(!$block->isStripped()){ - return Writer::create(Ids::CHERRY_WOOD) - ->writePillarAxis($block->getAxis()) - ->writeBool(StateNames::STRIPPED_BIT, false); //this is useless, but it has to be written - }else{ - return Writer::create(Ids::STRIPPED_CHERRY_WOOD) - ->writePillarAxis($block->getAxis()); - } - }); + $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))); @@ -692,17 +689,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $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->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->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))); @@ -718,6 +705,20 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSlab(Blocks::OAK_SLAB(), Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB); $this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS); + $this->map(Blocks::PALE_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::PALE_OAK_BUTTON))); + $this->map(Blocks::PALE_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::PALE_OAK_DOOR))); + $this->map(Blocks::PALE_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::PALE_OAK_FENCE_GATE))); + $this->map(Blocks::PALE_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::PALE_OAK_PRESSURE_PLATE))); + $this->map(Blocks::PALE_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::PALE_OAK_STANDING_SIGN))); + $this->map(Blocks::PALE_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::PALE_OAK_TRAPDOOR))); + $this->map(Blocks::PALE_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::PALE_OAK_WALL_SIGN))); + $this->mapLog(Blocks::PALE_OAK_LOG(), Ids::PALE_OAK_LOG, Ids::STRIPPED_PALE_OAK_LOG); + $this->mapLog(Blocks::PALE_OAK_WOOD(), Ids::PALE_OAK_WOOD, Ids::STRIPPED_PALE_OAK_WOOD); + $this->mapSimple(Blocks::PALE_OAK_FENCE(), Ids::PALE_OAK_FENCE); + $this->mapSimple(Blocks::PALE_OAK_PLANKS(), Ids::PALE_OAK_PLANKS); + $this->mapSlab(Blocks::PALE_OAK_SLAB(), Ids::PALE_OAK_SLAB, Ids::PALE_OAK_DOUBLE_SLAB); + $this->mapStairs(Blocks::PALE_OAK_STAIRS(), Ids::PALE_OAK_STAIRS); + $this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON))); $this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR))); $this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE))); @@ -754,6 +755,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::CHERRY_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::CHERRY_LEAVES))); $this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED))); $this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES))); + $this->map(Blocks::PALE_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::PALE_OAK_LEAVES))); //legacy mess $this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::ACACIA_LEAVES))); @@ -777,6 +779,18 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ } } + 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); @@ -797,6 +811,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::CHISELED_NETHER_BRICKS(), Ids::CHISELED_NETHER_BRICKS); $this->mapSimple(Blocks::CHISELED_POLISHED_BLACKSTONE(), Ids::CHISELED_POLISHED_BLACKSTONE); $this->mapSimple(Blocks::CHISELED_RED_SANDSTONE(), Ids::CHISELED_RED_SANDSTONE); + $this->mapSimple(Blocks::CHISELED_RESIN_BRICKS(), Ids::CHISELED_RESIN_BRICKS); $this->mapSimple(Blocks::CHISELED_SANDSTONE(), Ids::CHISELED_SANDSTONE); $this->mapSimple(Blocks::CHISELED_STONE_BRICKS(), Ids::CHISELED_STONE_BRICKS); $this->mapSimple(Blocks::CHISELED_TUFF(), Ids::CHISELED_TUFF); @@ -1038,6 +1053,8 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::RED_SANDSTONE(), Ids::RED_SANDSTONE); $this->mapSimple(Blocks::REINFORCED_DEEPSLATE(), Ids::REINFORCED_DEEPSLATE); $this->mapSimple(Blocks::RESERVED6(), Ids::RESERVED6); + $this->mapSimple(Blocks::RESIN(), Ids::RESIN_BLOCK); + $this->mapSimple(Blocks::RESIN_BRICKS(), Ids::RESIN_BRICKS); $this->mapSimple(Blocks::SAND(), Ids::SAND); $this->mapSimple(Blocks::SANDSTONE(), Ids::SANDSTONE); $this->mapSimple(Blocks::SCULK(), Ids::SCULK); @@ -1084,7 +1101,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered()) ->writeInt(StateNames::RAIL_DIRECTION, $block->getShape()); }); - $this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), fn() => Writer::create(Ids::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::AMETHYST_CLUSTER(), fn(AmethystCluster $block) => Writer::create( match($stage = $block->getStage()){ @@ -1098,7 +1115,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ); $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::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, @@ -1175,7 +1192,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS); $this->map(Blocks::BLACKSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::BLACKSTONE_WALL))); $this->map(Blocks::BLAST_FURNACE(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::BLAST_FURNACE, Ids::LIT_BLAST_FURNACE)); - $this->map(Blocks::BLUE_TORCH(), fn(Torch $block) => Helper::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) @@ -1189,7 +1206,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }); $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) @@ -1247,7 +1264,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::COBBLED_DEEPSLATE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::COBBLED_DEEPSLATE_WALL))); $this->mapSlab(Blocks::COBBLESTONE_SLAB(), Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB); $this->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS); - $this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::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() ? @@ -1255,6 +1272,40 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ 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() ? @@ -1322,12 +1373,73 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ) ); }); + $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::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); @@ -1354,7 +1466,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }); $this->mapSlab(Blocks::DIORITE_SLAB(), Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB); $this->mapStairs(Blocks::DIORITE_STAIRS(), Ids::DIORITE_STAIRS); - $this->map(Blocks::DIORITE_WALL(), fn(Wall $block) => Helper::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{ return Writer::create(match($block->getDirtType()){ DirtType::NORMAL => Ids::DIRT, @@ -1363,7 +1475,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }); }); $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, StringValues::CHEMISTRY_TABLE_TYPE_ELEMENT_CONSTRUCTOR, new Writer(Ids::CHEMISTRY_TABLE))); + $this->map(Blocks::ELEMENT_CONSTRUCTOR(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, Writer::create(Ids::ELEMENT_CONSTRUCTOR))); $this->map(Blocks::ENDER_CHEST(), function(EnderChest $block) : Writer{ return Writer::create(Ids::ENDER_CHEST) ->writeCardinalHorizontalFacing($block->getFacing()); @@ -1379,7 +1491,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ }); $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::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) @@ -1413,8 +1525,8 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::GLOWING_ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::GLOW_FRAME)); $this->mapSlab(Blocks::GRANITE_SLAB(), Ids::GRANITE_SLAB, Ids::GRANITE_DOUBLE_SLAB); $this->mapStairs(Blocks::GRANITE_STAIRS(), Ids::GRANITE_STAIRS); - $this->map(Blocks::GRANITE_WALL(), fn(Wall $block) => Helper::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) @@ -1428,7 +1540,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::IRON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::IRON_DOOR))); $this->map(Blocks::IRON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::IRON_TRAPDOOR))); $this->map(Blocks::ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::FRAME)); - $this->map(Blocks::LAB_TABLE(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, 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()); @@ -1492,28 +1604,24 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return Writer::create(Ids::LOOM) ->writeLegacyHorizontalFacing($block->getFacing()); }); - $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->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_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->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()){ @@ -1580,21 +1688,16 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapStairs(Blocks::PRISMARINE_BRICKS_STAIRS(), Ids::PRISMARINE_BRICKS_STAIRS); $this->mapSlab(Blocks::PRISMARINE_SLAB(), Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB); $this->mapStairs(Blocks::PRISMARINE_STAIRS(), Ids::PRISMARINE_STAIRS); - $this->map(Blocks::PRISMARINE_WALL(), fn(Wall $block) => Helper::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) ->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->mapSlab(Blocks::PURPUR_SLAB(), Ids::PURPUR_SLAB, Ids::PURPUR_DOUBLE_SLAB); @@ -1631,15 +1734,22 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->map(Blocks::RED_MUSHROOM_BLOCK(), fn(RedMushroomBlock $block) => Helper::encodeMushroomBlock($block, new Writer(Ids::RED_MUSHROOM_BLOCK))); $this->mapSlab(Blocks::RED_NETHER_BRICK_SLAB(), Ids::RED_NETHER_BRICK_SLAB, Ids::RED_NETHER_BRICK_DOUBLE_SLAB); $this->mapStairs(Blocks::RED_NETHER_BRICK_STAIRS(), Ids::RED_NETHER_BRICK_STAIRS); - $this->map(Blocks::RED_NETHER_BRICK_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_RED_NETHER_BRICK)); + $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_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_SANDSTONE_WALL))); + $this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_RED))); + $this->mapSlab(Blocks::RESIN_BRICK_SLAB(), Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB); + $this->map(Blocks::RESIN_BRICK_STAIRS(), fn(Stair $block) => Helper::encodeStairs($block, new Writer(Ids::RESIN_BRICK_STAIRS))); + $this->map(Blocks::RESIN_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RESIN_BRICK_WALL))); + $this->map(Blocks::RESIN_CLUMP(), function(ResinClump $block) : Writer{ + return Writer::create(Ids::RESIN_CLUMP) + ->writeFacingFlags($block->getFaces()); + }); $this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH))); $this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB); $this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS); - $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()) @@ -1681,15 +1791,12 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ 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::SPONGE(), fn(Sponge $block) => Writer::create($block->isWet() ? Ids::WET_SPONGE : Ids::SPONGE)); $this->map(Blocks::STONECUTTER(), fn(Stonecutter $block) => Writer::create(Ids::STONECUTTER_BLOCK) ->writeCardinalHorizontalFacing($block->getFacing())); $this->mapSlab(Blocks::STONE_BRICK_SLAB(), Ids::STONE_BRICK_SLAB, Ids::STONE_BRICK_DOUBLE_SLAB); $this->mapStairs(Blocks::STONE_BRICK_STAIRS(), Ids::STONE_BRICK_STAIRS); - $this->map(Blocks::STONE_BRICK_WALL(), fn(Wall $block) => Helper::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->mapSlab(Blocks::STONE_SLAB(), Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB); @@ -1703,11 +1810,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return Writer::create(Ids::SWEET_BERRY_BUSH) ->writeInt(StateNames::GROWTH, $block->getAge()); }); - $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()); diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index c7447bd531..1d7b4bb76e 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -45,12 +45,10 @@ 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\utils\ICopper; use pocketmine\block\utils\SlabType; -use pocketmine\block\VanillaBlocks; use pocketmine\block\Wall; -use pocketmine\block\WallCoralFan; use pocketmine\block\WallSign; use pocketmine\block\WeightedPressurePlate; use pocketmine\block\Wood; @@ -58,7 +56,6 @@ use pocketmine\data\bedrock\block\BlockLegacyMetadata; use pocketmine\data\bedrock\block\BlockStateDeserializeException; use pocketmine\data\bedrock\block\BlockStateNames; use pocketmine\data\bedrock\block\BlockStateNames as StateNames; -use pocketmine\data\bedrock\block\BlockStateStringValues as StringValues; use pocketmine\data\bedrock\MushroomBlockTypeIdMap; use pocketmine\math\Axis; use pocketmine\math\Facing; @@ -100,24 +97,24 @@ final class BlockStateDeserializerHelper{ } /** - * @phpstan-template TBlock of ICopper + * @phpstan-template TBlock of CopperMaterial * * @phpstan-param TBlock $block * @phpstan-return TBlock */ - public static function decodeCopper(ICopper $block, CopperOxidation $oxidation) : ICopper{ + public static function decodeCopper(CopperMaterial $block, CopperOxidation $oxidation) : CopperMaterial{ $block->setOxidation($oxidation); $block->setWaxed(false); return $block; } /** - * @phpstan-template TBlock of ICopper + * @phpstan-template TBlock of CopperMaterial * * @phpstan-param TBlock $block * @phpstan-return TBlock */ - public static function decodeWaxedCopper(ICopper $block, CopperOxidation $oxidation) : ICopper{ + public static function decodeWaxedCopper(CopperMaterial $block, CopperOxidation $oxidation) : CopperMaterial{ $block->setOxidation($oxidation); $block->setWaxed(true); return $block; @@ -134,7 +131,8 @@ final class BlockStateDeserializerHelper{ //TODO: check if these need any special treatment to get the appropriate data to both halves of the door return $block ->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT)) - ->setFacing(Facing::rotateY($in->readLegacyHorizontalFacing(), false)) + //a door facing "east" is actually facing north - thanks mojang + ->setFacing(Facing::rotateY($in->readCardinalHorizontalFacing(), clockwise: false)) ->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } @@ -148,7 +146,7 @@ final class BlockStateDeserializerHelper{ /** @throws BlockStateDeserializeException */ public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{ return $block - ->setFacing($in->readLegacyHorizontalFacing()) + ->setFacing($in->readCardinalHorizontalFacing()) ->setInWall($in->readBool(BlockStateNames::IN_WALL_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } @@ -210,8 +208,8 @@ final class BlockStateDeserializerHelper{ /** @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); @@ -286,13 +284,6 @@ final class BlockStateDeserializerHelper{ 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 @@ -303,25 +294,4 @@ final class BlockStateDeserializerHelper{ 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); - } } diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php index 9dfb17ca0c..a250441530 100644 --- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php @@ -68,9 +68,8 @@ final class BlockStateSerializerHelper{ ->writeInt(StateNames::CANDLES, $block->getCount() - 1); } - public static function encodeChemistryTable(ChemistryTable $block, string $chemistryTableType, Writer $out) : Writer{ + public static function encodeChemistryTable(ChemistryTable $block, Writer $out) : Writer{ return $out - ->writeString(BlockStateNames::CHEMISTRY_TABLE_TYPE, $chemistryTableType) ->writeLegacyHorizontalFacing(Facing::opposite($block->getFacing())); } @@ -78,9 +77,8 @@ final class BlockStateSerializerHelper{ return $out->writeInt(BlockStateNames::GROWTH, $block->getAge()); } - public static function encodeColoredTorch(Torch $block, bool $highBit, Writer $out) : Writer{ + public static function encodeTorch(Torch $block, Writer $out) : Writer{ return $out - ->writeBool(BlockStateNames::COLOR_BIT, $highBit) ->writeTorchFacing($block->getFacing()); } @@ -102,7 +100,8 @@ final class BlockStateSerializerHelper{ public static function encodeDoor(Door $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop()) - ->writeLegacyHorizontalFacing(Facing::rotateY($block->getFacing(), true)) + //a door facing north is encoded as "east" + ->writeCardinalHorizontalFacing(Facing::rotateY($block->getFacing(), clockwise: true)) ->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } @@ -114,7 +113,7 @@ final class BlockStateSerializerHelper{ public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{ return $out - ->writeLegacyHorizontalFacing($block->getFacing()) + ->writeCardinalHorizontalFacing($block->getFacing()) ->writeBool(BlockStateNames::IN_WALL_BIT, $block->isInWall()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } @@ -225,11 +224,6 @@ final class BlockStateSerializerHelper{ ->writeWallConnectionType(BlockStateNames::WALL_CONNECTION_TYPE_WEST, $block->getConnection(Facing::WEST)); } - public static function encodeLegacyWall(Wall $block, string $type) : Writer{ - return self::encodeWall($block, Writer::create(Ids::COBBLESTONE_WALL)) - ->writeString(BlockStateNames::WALL_BLOCK_TYPE, $type); - } - public static function encodeWallSign(WallSign $block, Writer $out) : Writer{ return $out ->writeHorizontalFacing($block->getFacing()); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 2c2e75e999..42d8ee0cdf 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -45,6 +45,7 @@ use pocketmine\block\utils\DripleafState; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FroglightType; use pocketmine\block\utils\LeverFacing; +use pocketmine\block\utils\MobHeadType; use pocketmine\block\VanillaBlocks as Blocks; use pocketmine\block\Wood; use pocketmine\data\bedrock\block\BlockLegacyMetadata; @@ -85,6 +86,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->registerLeavesDeserializers(); $this->registerSaplingDeserializers(); $this->registerLightDeserializers(); + $this->registerMobHeadDeserializers(); $this->registerSimpleDeserializers(); $this->registerDeserializers(); } @@ -101,10 +103,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ /** @phpstan-param \Closure(Reader) : Block $c */ public function map(string $id, \Closure $c) : void{ - if(array_key_exists($id, $this->deserializeFuncs)){ - throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\""); - } $this->deserializeFuncs[$id] = $c; + $this->simpleCache = []; + } + + /** + * Returns the existing data deserializer for the given ID, or null if none exists. + * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. + * + * @phpstan-return ?\Closure(Reader) : Block + */ + public function getDeserializerForId(string $id) : ?\Closure{ + return $this->deserializeFuncs[$id] ?? null; } /** @phpstan-param \Closure() : Block $getBlock */ @@ -531,10 +541,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $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, function(Reader $in){ - $in->ignored(StateNames::STRIPPED_BIT); //this is also ignored by vanilla - return Helper::decodeLog(Blocks::CHERRY_WOOD(), false, $in); - }); + $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)); @@ -591,10 +598,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $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, 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::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 @@ -612,6 +616,20 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSlab(Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB, fn() => Blocks::OAK_SLAB()); $this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS()); + $this->map(Ids::PALE_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::PALE_OAK_BUTTON(), $in)); + $this->map(Ids::PALE_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::PALE_OAK_DOOR(), $in)); + $this->map(Ids::PALE_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::PALE_OAK_FENCE_GATE(), $in)); + $this->map(Ids::PALE_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::PALE_OAK_PRESSURE_PLATE(), $in)); + $this->map(Ids::PALE_OAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::PALE_OAK_SIGN(), $in)); + $this->map(Ids::PALE_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::PALE_OAK_TRAPDOOR(), $in)); + $this->map(Ids::PALE_OAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::PALE_OAK_WALL_SIGN(), $in)); + $this->mapLog(Ids::PALE_OAK_LOG, Ids::STRIPPED_PALE_OAK_LOG, fn() => Blocks::PALE_OAK_LOG()); + $this->mapLog(Ids::PALE_OAK_WOOD, Ids::STRIPPED_PALE_OAK_WOOD, fn() => Blocks::PALE_OAK_WOOD()); + $this->mapSimple(Ids::PALE_OAK_FENCE, fn() => Blocks::PALE_OAK_FENCE()); + $this->mapSimple(Ids::PALE_OAK_PLANKS, fn() => Blocks::PALE_OAK_PLANKS()); + $this->mapSlab(Ids::PALE_OAK_SLAB, Ids::PALE_OAK_DOUBLE_SLAB, fn() => Blocks::PALE_OAK_SLAB()); + $this->mapStairs(Ids::PALE_OAK_STAIRS, fn() => Blocks::PALE_OAK_STAIRS()); + $this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in)); $this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in)); $this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in)); @@ -651,6 +669,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map(Ids::JUNGLE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::JUNGLE_LEAVES(), $in)); $this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in)); $this->map(Ids::OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::OAK_LEAVES(), $in)); + $this->map(Ids::PALE_OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::PALE_OAK_LEAVES(), $in)); $this->map(Ids::SPRUCE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::SPRUCE_LEAVES(), $in)); } @@ -690,6 +709,20 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ } } + 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()); @@ -710,6 +743,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::CHISELED_NETHER_BRICKS, fn() => Blocks::CHISELED_NETHER_BRICKS()); $this->mapSimple(Ids::CHISELED_POLISHED_BLACKSTONE, fn() => Blocks::CHISELED_POLISHED_BLACKSTONE()); $this->mapSimple(Ids::CHISELED_RED_SANDSTONE, fn() => Blocks::CHISELED_RED_SANDSTONE()); + $this->mapSimple(Ids::CHISELED_RESIN_BRICKS, fn() => Blocks::CHISELED_RESIN_BRICKS()); $this->mapSimple(Ids::CHISELED_SANDSTONE, fn() => Blocks::CHISELED_SANDSTONE()); $this->mapSimple(Ids::CHISELED_STONE_BRICKS, fn() => Blocks::CHISELED_STONE_BRICKS()); $this->mapSimple(Ids::CHISELED_TUFF, fn() => Blocks::CHISELED_TUFF()); @@ -947,6 +981,8 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::REDSTONE_BLOCK, fn() => Blocks::REDSTONE()); $this->mapSimple(Ids::REINFORCED_DEEPSLATE, fn() => Blocks::REINFORCED_DEEPSLATE()); $this->mapSimple(Ids::RESERVED6, fn() => Blocks::RESERVED6()); + $this->mapSimple(Ids::RESIN_BLOCK, fn() => Blocks::RESIN()); + $this->mapSimple(Ids::RESIN_BRICKS, fn() => Blocks::RESIN_BRICKS()); $this->mapSimple(Ids::SAND, fn() => Blocks::SAND()); $this->mapSimple(Ids::SANDSTONE, fn() => Blocks::SANDSTONE()); $this->mapSimple(Ids::SCULK, fn() => Blocks::SCULK()); @@ -963,6 +999,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $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()); @@ -975,6 +1012,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::WARPED_ROOTS, fn() => Blocks::WARPED_ROOTS()); $this->mapSimple(Ids::WATERLILY, fn() => Blocks::LILY_PAD()); $this->mapSimple(Ids::WEB, fn() => Blocks::COBWEB()); + $this->mapSimple(Ids::WET_SPONGE, fn() => Blocks::SPONGE()->setWet(true)); $this->mapSimple(Ids::WITHER_ROSE, fn() => Blocks::WITHER_ROSE()); $this->mapSimple(Ids::DANDELION, fn() => Blocks::DANDELION()); @@ -1004,6 +1042,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $this->mapSlab(Ids::ANDESITE_SLAB, Ids::ANDESITE_DOUBLE_SLAB, fn() => Blocks::ANDESITE_SLAB()); $this->mapStairs(Ids::ANDESITE_STAIRS, fn() => Blocks::ANDESITE_STAIRS()); + $this->map(Ids::ANDESITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::ANDESITE_WALL(), $in)); $this->map(Ids::ANVIL, function(Reader $in) : Block{ return Blocks::ANVIL() ->setDamage(Anvil::UNDAMAGED) @@ -1099,6 +1138,12 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $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() @@ -1152,19 +1197,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return $block; }); + $this->map(Ids::CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE)); $this->map(Ids::CHISELED_QUARTZ_BLOCK, function(Reader $in) : Block{ return Blocks::CHISELED_QUARTZ() ->setAxis($in->readPillarAxis()); }); - $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::CHEST, function(Reader $in) : Block{ return Blocks::CHEST() ->setFacing($in->readCardinalHorizontalFacing()); @@ -1178,23 +1215,28 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapStairs(Ids::COBBLED_DEEPSLATE_STAIRS, fn() => Blocks::COBBLED_DEEPSLATE_STAIRS()); $this->map(Ids::COBBLED_DEEPSLATE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::COBBLED_DEEPSLATE_WALL(), $in)); $this->mapSlab(Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB, fn() => Blocks::COBBLESTONE_SLAB()); - $this->map(Ids::COBBLESTONE_WALL, fn(Reader $in) => Helper::mapLegacyWallType($in)); + $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::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::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)); @@ -1224,6 +1266,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $this->mapSlab(Ids::DIORITE_SLAB, Ids::DIORITE_DOUBLE_SLAB, fn() => Blocks::DIORITE_SLAB()); $this->mapStairs(Ids::DIORITE_STAIRS, fn() => Blocks::DIORITE_STAIRS()); + $this->map(Ids::DIORITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::DIORITE_WALL(), $in)); $this->map(Ids::DIRT, fn() => Blocks::DIRT()->setDirtType(DirtType::NORMAL)); $this->map(Ids::DIRT_WITH_ROOTS, fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED)); $this->map(Ids::LARGE_FERN, fn(Reader $in) => Helper::decodeDoublePlant(Blocks::LARGE_FERN(), $in)); @@ -1232,7 +1275,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $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)) @@ -1248,9 +1295,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readCardinalHorizontalFacing()); }); $this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::EXPOSED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::EXPOSED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED)); $this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED)); $this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED)); $this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED)); + $this->map(Ids::EXPOSED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in)); + $this->map(Ids::EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in)); $this->map(Ids::FARMLAND, function(Reader $in) : Block{ return Blocks::FARMLAND() ->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7)); @@ -1284,6 +1340,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $this->mapSlab(Ids::GRANITE_SLAB, Ids::GRANITE_DOUBLE_SLAB, fn() => Blocks::GRANITE_SLAB()); $this->mapStairs(Ids::GRANITE_STAIRS, fn() => Blocks::GRANITE_STAIRS()); + $this->map(Ids::GRANITE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::GRANITE_WALL(), $in)); $this->map(Ids::HAY_BLOCK, function(Reader $in) : Block{ $in->ignored(StateNames::DEPRECATED); return Blocks::HAY_BALE()->setAxis($in->readPillarAxis()); @@ -1296,6 +1353,9 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $this->map(Ids::IRON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::IRON_DOOR(), $in)); $this->map(Ids::IRON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::IRON_TRAPDOOR(), $in)); + $this->map(Ids::LAB_TABLE, fn(Reader $in) => Blocks::LAB_TABLE() + ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) + ); $this->map(Ids::LADDER, function(Reader $in) : Block{ return Blocks::LADDER() ->setFacing($in->readHorizontalFacing()); @@ -1367,6 +1427,9 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return Blocks::LOOM() ->setFacing($in->readLegacyHorizontalFacing()); }); + $this->map(Ids::MATERIAL_REDUCER, fn(Reader $in) => Blocks::MATERIAL_REDUCER() + ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) + ); $this->map(Ids::MEDIUM_AMETHYST_BUD, function(Reader $in) : Block{ return Blocks::AMETHYST_CLUSTER() ->setStage(AmethystCluster::STAGE_MEDIUM_BUD) @@ -1375,8 +1438,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map(Ids::MELON_STEM, fn(Reader $in) => Helper::decodeStem(Blocks::MELON_STEM(), $in)); $this->mapSlab(Ids::MOSSY_COBBLESTONE_SLAB, Ids::MOSSY_COBBLESTONE_DOUBLE_SLAB, fn() => Blocks::MOSSY_COBBLESTONE_SLAB()); $this->mapStairs(Ids::MOSSY_COBBLESTONE_STAIRS, fn() => Blocks::MOSSY_COBBLESTONE_STAIRS()); + $this->map(Ids::MOSSY_COBBLESTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MOSSY_COBBLESTONE_WALL(), $in)); $this->mapSlab(Ids::MOSSY_STONE_BRICK_SLAB, Ids::MOSSY_STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::MOSSY_STONE_BRICK_SLAB()); $this->mapStairs(Ids::MOSSY_STONE_BRICK_STAIRS, fn() => Blocks::MOSSY_STONE_BRICK_STAIRS()); + $this->map(Ids::MOSSY_STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MOSSY_STONE_BRICK_WALL(), $in)); $this->mapSlab(Ids::MUD_BRICK_SLAB, Ids::MUD_BRICK_DOUBLE_SLAB, fn() => Blocks::MUD_BRICK_SLAB()); $this->mapStairs(Ids::MUD_BRICK_STAIRS, fn() => Blocks::MUD_BRICK_STAIRS()); $this->map(Ids::MUD_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::MUD_BRICK_WALL(), $in)); @@ -1386,6 +1451,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $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)); @@ -1394,9 +1460,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS()); $this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis())); $this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::OXIDIZED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED)); $this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED)); $this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED)); $this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED)); + $this->map(Ids::OXIDIZED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in)); + $this->map(Ids::OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in)); $this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis())); $this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB()); $this->map(Ids::PINK_PETALS, function(Reader $in) : Block{ @@ -1461,6 +1536,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setPowered(true)); $this->mapSlab(Ids::PRISMARINE_BRICK_SLAB, Ids::PRISMARINE_BRICK_DOUBLE_SLAB, fn() => Blocks::PRISMARINE_BRICKS_SLAB()); $this->mapStairs(Ids::PRISMARINE_BRICKS_STAIRS, fn() => Blocks::PRISMARINE_BRICKS_STAIRS()); + $this->map(Ids::PRISMARINE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::PRISMARINE_WALL(), $in)); $this->mapSlab(Ids::PRISMARINE_SLAB, Ids::PRISMARINE_DOUBLE_SLAB, fn() => Blocks::PRISMARINE_SLAB()); $this->mapStairs(Ids::PRISMARINE_STAIRS, fn() => Blocks::PRISMARINE_STAIRS()); $this->map(Ids::PUMPKIN, function(Reader $in) : Block{ @@ -1469,19 +1545,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $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) : Opaque{ @@ -1501,8 +1568,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map(Ids::RED_MUSHROOM_BLOCK, fn(Reader $in) => Helper::decodeMushroomBlock(Blocks::RED_MUSHROOM_BLOCK(), $in)); $this->mapSlab(Ids::RED_NETHER_BRICK_SLAB, Ids::RED_NETHER_BRICK_DOUBLE_SLAB, fn() => Blocks::RED_NETHER_BRICK_SLAB()); $this->mapStairs(Ids::RED_NETHER_BRICK_STAIRS, fn() => Blocks::RED_NETHER_BRICK_STAIRS()); + $this->map(Ids::RED_NETHER_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RED_NETHER_BRICK_WALL(), $in)); $this->mapSlab(Ids::RED_SANDSTONE_SLAB, Ids::RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::RED_SANDSTONE_SLAB()); $this->mapStairs(Ids::RED_SANDSTONE_STAIRS, fn() => Blocks::RED_SANDSTONE_STAIRS()); + $this->map(Ids::RED_SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RED_SANDSTONE_WALL(), $in)); $this->map(Ids::REDSTONE_LAMP, function() : Block{ return Blocks::REDSTONE_LAMP() ->setPowered(false); @@ -1524,17 +1593,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return Blocks::SUGARCANE() ->setAge($in->readBoundedInt(StateNames::AGE, 0, 15)); }); + $this->mapSlab(Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB, fn() => Blocks::RESIN_BRICK_SLAB()); + $this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS()); + $this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in)); + $this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags())); $this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB()); $this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS()); + $this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in)); $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::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->readCardinalHorizontalFacing()) @@ -1582,19 +1652,13 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ 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::STANDING_BANNER, function(Reader $in) : Block{ return Blocks::BANNER() ->setRotation($in->readBoundedInt(StateNames::GROUND_SIGN_DIRECTION, 0, 15)); }); $this->mapSlab(Ids::STONE_BRICK_SLAB, Ids::STONE_BRICK_DOUBLE_SLAB, fn() => Blocks::STONE_BRICK_SLAB()); $this->mapStairs(Ids::STONE_BRICK_STAIRS, fn() => Blocks::STONE_BRICK_STAIRS()); + $this->map(Ids::STONE_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::STONE_BRICK_WALL(), $in)); $this->map(Ids::STONE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::STONE_BUTTON(), $in)); $this->map(Ids::STONE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::STONE_PRESSURE_PLATE(), $in)); $this->mapStairs(Ids::STONE_STAIRS, fn() => Blocks::COBBLESTONE_STAIRS()); @@ -1611,7 +1675,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $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() @@ -1649,6 +1713,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ 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()); @@ -1676,25 +1745,70 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in)); $this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE)); + $this->map(Ids::WAXED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE)); + $this->map(Ids::WAXED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE)); $this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE)); $this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE)); $this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE)); + $this->map(Ids::WAXED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in)); + $this->map(Ids::WAXED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in)); $this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::WAXED_EXPOSED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::WAXED_EXPOSED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED)); $this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED)); $this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED)); $this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED)); + $this->map(Ids::WAXED_EXPOSED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in)); + $this->map(Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in)); $this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::WAXED_OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::WAXED_OXIDIZED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED)); $this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED)); $this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED)); $this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED)); + $this->map(Ids::WAXED_OXIDIZED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in)); + $this->map(Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in)); $this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WAXED_WEATHERED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WAXED_WEATHERED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED)); $this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED)); $this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED)); $this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED)); + $this->map(Ids::WAXED_WEATHERED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in)); + $this->map(Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in)); $this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WEATHERED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WEATHERED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED)); $this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED)); $this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED)); $this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED)); + $this->map(Ids::WEATHERED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in)); + $this->map(Ids::WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in)); $this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{ return Blocks::WEEPING_VINES() ->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25)); diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php index 6d280ecf75..f8894cfd27 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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,6 +59,12 @@ final class BlockStateUpgradeSchema{ */ public array $remappedPropertyValues = []; + /** + * @var FlattenInfo[] + * @phpstan-var array + */ + public array $flattenedProperties = []; + /** * @var BlockStateUpgradeSchemaBlockRemap[][] * @phpstan-var array> @@ -93,6 +100,7 @@ final class BlockStateUpgradeSchema{ $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 611ad04e25..676afbaf4e 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php @@ -40,7 +40,7 @@ final class BlockStateUpgradeSchemaBlockRemap{ */ public function __construct( public array $oldState, - public string|BlockStateUpgradeSchemaFlattenedName $newName, + public string|BlockStateUpgradeSchemaFlattenInfo $newName, public array $newState, public array $copiedState ){} @@ -48,8 +48,8 @@ final class BlockStateUpgradeSchemaBlockRemap{ public function equals(self $that) : bool{ $sameName = $this->newName === $that->newName || ( - $this->newName instanceof BlockStateUpgradeSchemaFlattenedName && - $that->newName instanceof BlockStateUpgradeSchemaFlattenedName && + $this->newName instanceof BlockStateUpgradeSchemaFlattenInfo && + $that->newName instanceof BlockStateUpgradeSchemaFlattenInfo && $this->newName->equals($that->newName) ); if(!$sameName){ diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php similarity index 74% rename from src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php rename to src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php index 1c95dd9c7f..4a14a1291f 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php @@ -23,20 +23,25 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\StringTag; use function ksort; use const SORT_STRING; -final class BlockStateUpgradeSchemaFlattenedName{ +final class BlockStateUpgradeSchemaFlattenInfo{ /** * @param string[] $flattenedValueRemaps * @phpstan-param array $flattenedValueRemaps + * @phpstan-param ?class-string $flattenedPropertyType */ public function __construct( public string $prefix, public string $flattenedProperty, public string $suffix, - public array $flattenedValueRemaps + public array $flattenedValueRemaps, + public ?string $flattenedPropertyType = null ){ ksort($this->flattenedValueRemaps, SORT_STRING); } @@ -45,6 +50,7 @@ final class BlockStateUpgradeSchemaFlattenedName{ return $this->prefix === $that->prefix && $this->flattenedProperty === $that->flattenedProperty && $this->suffix === $that->suffix && - $this->flattenedValueRemaps === $that->flattenedValueRemaps; + $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 832631490a..b4ed0dd261 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php @@ -25,7 +25,7 @@ namespace pocketmine\data\bedrock\block\upgrade; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModel; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelBlockRemap; -use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelFlattenedName; +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; @@ -37,7 +37,6 @@ use pocketmine\utils\Utils; use Symfony\Component\Filesystem\Path; use function array_key_last; use function array_map; -use function array_values; use function assert; use function count; use function get_debug_type; @@ -138,8 +137,8 @@ final class BlockStateUpgradeSchemaUtils{ $convertedRemappedValuesIndex = []; foreach(Utils::stringifyKeys($model->remappedPropertyValuesIndex ?? []) as $mappingKey => $mappingValues){ - foreach($mappingValues as $k => $oldNew){ - $convertedRemappedValuesIndex[$mappingKey][$k] = new BlockStateUpgradeSchemaValueRemap( + foreach($mappingValues as $oldNew){ + $convertedRemappedValuesIndex[$mappingKey][] = new BlockStateUpgradeSchemaValueRemap( self::jsonModelToTag($oldNew->old), self::jsonModelToTag($oldNew->new) ); @@ -155,20 +154,24 @@ final class BlockStateUpgradeSchemaUtils{ } } + 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) === isset($remap->newFlattenedName)){ + 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 ?? new BlockStateUpgradeSchemaFlattenedName( - $remap->newFlattenedName->prefix, - $remap->newFlattenedName->flattenedProperty, - $remap->newFlattenedName->suffix, - $remap->newFlattenedName->flattenedValueRemaps ?? [], - ), + $remapName, array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []), $remap->copiedState ?? [] ); @@ -254,6 +257,36 @@ final class BlockStateUpgradeSchemaUtils{ $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; @@ -292,19 +325,19 @@ final class BlockStateUpgradeSchemaUtils{ 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), - is_string($remap->newName) ? - $remap->newName : - new BlockStateUpgradeSchemaModelFlattenedName( - $remap->newName->prefix, - $remap->newName->flattenedProperty, - $remap->newName->suffix, - $remap->newName->flattenedValueRemaps - ), + is_string($remap->newName) ? $remap->newName : self::flattenRuleToJsonModel($remap->newName), array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState), $remap->copiedState ); @@ -327,7 +360,7 @@ final class BlockStateUpgradeSchemaUtils{ //remaps with the same number of criteria should be sorted alphabetically, but this is not strictly necessary return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []); }); - $result->remappedStates[$oldBlockName] = array_values($keyedRemaps); + $result->remappedStates[$oldBlockName] = $keyedRemaps; //usort strips keys, so this is already a list } if(isset($result->remappedStates)){ ksort($result->remappedStates); diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index 4a305d8bc3..2dce762b8d 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -24,10 +24,14 @@ declare(strict_types=1); 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; @@ -79,6 +83,8 @@ final class BlockStateUpgrader{ * 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; @@ -104,10 +110,21 @@ final class BlockStateUpgrader{ } $oldName = $blockStateData->getName(); - $newName = $schema->renamedIds[$oldName] ?? null; + $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; + } $stateChanges = 0; - $states = $blockStateData->getStates(); $states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges); $states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges); @@ -140,15 +157,8 @@ final class BlockStateUpgrader{ if(is_string($remap->newName)){ $newName = $remap->newName; }else{ - $flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null; - if($flattenedValue instanceof StringTag){ - $embedValue = $remap->newName->flattenedValueRemaps[$flattenedValue->getValue()] ?? $flattenedValue->getValue(); - $newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix); - unset($oldState[$remap->newName->flattenedProperty]); - }else{ - //flattened property is not a TAG_String, so this transformation is not applicable - continue; - } + //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; @@ -266,4 +276,32 @@ final class BlockStateUpgrader{ 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 1a4a14c87c..7d91438e4f 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 0f518479e5..6accf1f021 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php @@ -43,7 +43,7 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ * Either this or newName must be present * Due to technical limitations of jsonmapper, we can't use a union type here */ - public BlockStateUpgradeSchemaModelFlattenedName $newFlattenedName; + public BlockStateUpgradeSchemaModelFlattenInfo $newFlattenedName; /** * @var BlockStateUpgradeSchemaModelTag[]|null @@ -67,9 +67,9 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ * @phpstan-param array $newState * @phpstan-param list $copiedState */ - public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenedName $newNameRule, array $newState, array $copiedState){ + public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenInfo $newNameRule, array $newState, array $copiedState){ $this->oldState = count($oldState) === 0 ? null : $oldState; - if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenedName){ + if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenInfo){ $this->newFlattenedName = $newNameRule; }else{ $this->newName = $newNameRule; diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php similarity index 81% rename from src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php rename to src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php index 001192f478..6da590287b 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php @@ -25,12 +25,13 @@ namespace pocketmine\data\bedrock\block\upgrade\model; use function count; -final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializable{ +final class BlockStateUpgradeSchemaModelFlattenInfo implements \JsonSerializable{ /** @required */ public string $prefix; /** @required */ public string $flattenedProperty; + public ?string $flattenedPropertyType = null; /** @required */ public string $suffix; /** @@ -43,11 +44,12 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab * @param string[] $flattenedValueRemaps * @phpstan-param array $flattenedValueRemaps */ - public function __construct(string $prefix, string $flattenedProperty, string $suffix, 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; } /** @@ -58,6 +60,9 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab if(count($this->flattenedValueRemaps) === 0){ unset($result["flattenedValueRemaps"]); } + if($this->flattenedPropertyType === null){ + unset($result["flattenedPropertyType"]); + } return $result; } } diff --git a/src/data/bedrock/item/ItemDeserializer.php b/src/data/bedrock/item/ItemDeserializer.php index f7854313fc..ef3bee2a02 100644 --- a/src/data/bedrock/item/ItemDeserializer.php +++ b/src/data/bedrock/item/ItemDeserializer.php @@ -51,12 +51,19 @@ final class ItemDeserializer{ * @phpstan-param \Closure(Data) : Item $deserializer */ public function map(string $id, \Closure $deserializer) : void{ - if(isset($this->deserializers[$id])){ - throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\""); - } $this->deserializers[$id] = $deserializer; } + /** + * Returns the existing data deserializer for the given ID, or null if none exists. + * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. + * + * @phpstan-return ?\Closure(Data) : Item + */ + public function getDeserializerForId(string $id) : ?\Closure{ + return $this->deserializers[$id] ?? null; + } + /** * @phpstan-param \Closure(Data) : Block $deserializer */ diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index de9b5ae5e6..771154d462 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -25,19 +25,21 @@ namespace pocketmine\data\bedrock\item; 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 @@ final class ItemSerializerDeserializerRegistrar{ $this->register1to1BlockWithMetaMappings(); $this->register1to1ItemWithMetaMappings(); $this->register1ToNItemMappings(); + $this->registerMiscBlockMappings(); $this->registerMiscItemMappings(); } @@ -148,6 +151,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Block(Ids::JUNGLE_DOOR, Blocks::JUNGLE_DOOR()); $this->map1to1Block(Ids::MANGROVE_DOOR, Blocks::MANGROVE_DOOR()); $this->map1to1Block(Ids::NETHER_WART, Blocks::NETHER_WART()); + $this->map1to1Block(Ids::PALE_OAK_DOOR, Blocks::PALE_OAK_DOOR()); $this->map1to1Block(Ids::REPEATER, Blocks::REDSTONE_REPEATER()); $this->map1to1Block(Ids::SOUL_CAMPFIRE, Blocks::SOUL_CAMPFIRE()); $this->map1to1Block(Ids::SPRUCE_DOOR, Blocks::SPRUCE_DOOR()); @@ -229,6 +233,7 @@ final class ItemSerializerDeserializerRegistrar{ $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()); @@ -262,6 +267,7 @@ final class ItemSerializerDeserializerRegistrar{ $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::ICE_BOMB, Items::ICE_BOMB()); $this->map1to1Item(Ids::INK_SAC, Items::INK_SAC()); $this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE()); $this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS()); @@ -297,11 +303,15 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::MUSIC_DISC_BLOCKS, Items::RECORD_BLOCKS()); $this->map1to1Item(Ids::MUSIC_DISC_CAT, Items::RECORD_CAT()); $this->map1to1Item(Ids::MUSIC_DISC_CHIRP, Items::RECORD_CHIRP()); + $this->map1to1Item(Ids::MUSIC_DISC_CREATOR, Items::RECORD_CREATOR()); + $this->map1to1Item(Ids::MUSIC_DISC_CREATOR_MUSIC_BOX, Items::RECORD_CREATOR_MUSIC_BOX()); $this->map1to1Item(Ids::MUSIC_DISC_FAR, Items::RECORD_FAR()); $this->map1to1Item(Ids::MUSIC_DISC_MALL, Items::RECORD_MALL()); $this->map1to1Item(Ids::MUSIC_DISC_MELLOHI, Items::RECORD_MELLOHI()); $this->map1to1Item(Ids::MUSIC_DISC_OTHERSIDE, Items::RECORD_OTHERSIDE()); $this->map1to1Item(Ids::MUSIC_DISC_PIGSTEP, Items::RECORD_PIGSTEP()); + $this->map1to1Item(Ids::MUSIC_DISC_PRECIPICE, Items::RECORD_PRECIPICE()); + $this->map1to1Item(Ids::MUSIC_DISC_RELIC, Items::RECORD_RELIC()); $this->map1to1Item(Ids::MUSIC_DISC_STAL, Items::RECORD_STAL()); $this->map1to1Item(Ids::MUSIC_DISC_STRAD, Items::RECORD_STRAD()); $this->map1to1Item(Ids::MUSIC_DISC_WAIT, Items::RECORD_WAIT()); @@ -326,6 +336,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::OAK_BOAT, Items::OAK_BOAT()); $this->map1to1Item(Ids::OAK_SIGN, Items::OAK_SIGN()); $this->map1to1Item(Ids::PAINTING, Items::PAINTING()); + $this->map1to1Item(Ids::PALE_OAK_SIGN, Items::PALE_OAK_SIGN()); $this->map1to1Item(Ids::PAPER, Items::PAPER()); $this->map1to1Item(Ids::PHANTOM_MEMBRANE, Items::PHANTOM_MEMBRANE()); $this->map1to1Item(Ids::PITCHER_POD, Items::PITCHER_POD()); @@ -347,7 +358,9 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER()); $this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD()); $this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON()); + $this->map1to1Item(Ids::RECOVERY_COMPASS, Items::RECOVERY_COMPASS()); $this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST()); + $this->map1to1Item(Ids::RESIN_BRICK, Items::RESIN_BRICK()); $this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH()); $this->map1to1Item(Ids::SALMON, Items::RAW_SALMON()); @@ -466,14 +479,6 @@ final class ItemSerializerDeserializerRegistrar{ }, 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()) - ); } /** @@ -490,6 +495,14 @@ final class ItemSerializerDeserializerRegistrar{ }, 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(), @@ -538,4 +551,29 @@ final class ItemSerializerDeserializerRegistrar{ } $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 db6594c43d..b0d49fc316 100644 --- a/src/data/bedrock/item/ItemTypeNames.php +++ b/src/data/bedrock/item/ItemTypeNames.php @@ -66,13 +66,16 @@ final class ItemTypeNames{ 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 BOARD = "minecraft:board"; public const BOAT = "minecraft:boat"; public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg"; public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template"; @@ -88,6 +91,7 @@ final class ItemTypeNames{ 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"; @@ -109,6 +113,7 @@ 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"; @@ -127,6 +132,8 @@ final class ItemTypeNames{ 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"; @@ -148,12 +155,14 @@ final class ItemTypeNames{ public const CORAL_FAN = "minecraft:coral_fan"; public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead"; public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg"; + public const CREAKING_SPAWN_EGG = "minecraft:creaking_spawn_egg"; public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern"; public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg"; public const CRIMSON_DOOR = "minecraft:crimson_door"; 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"; @@ -252,7 +261,9 @@ 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"; @@ -306,8 +317,11 @@ final class ItemTypeNames{ 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"; @@ -315,6 +329,7 @@ final class ItemTypeNames{ 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"; @@ -381,9 +396,15 @@ final class ItemTypeNames{ 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 PALE_OAK_BOAT = "minecraft:pale_oak_boat"; + public const PALE_OAK_CHEST_BOAT = "minecraft:pale_oak_chest_boat"; + public const PALE_OAK_DOOR = "minecraft:pale_oak_door"; + public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign"; + public const PALE_OAK_SIGN = "minecraft:pale_oak_sign"; public const PANDA_SPAWN_EGG = "minecraft:panda_spawn_egg"; public const PAPER = "minecraft:paper"; public const PARROT_SPAWN_EGG = "minecraft:parrot_spawn_egg"; @@ -394,6 +415,7 @@ 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"; @@ -413,6 +435,7 @@ final class ItemTypeNames{ 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"; @@ -427,10 +450,12 @@ final class ItemTypeNames{ 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 RESIN_BRICK = "minecraft:resin_brick"; public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template"; public const ROTTEN_FLESH = "minecraft:rotten_flesh"; public const SADDLE = "minecraft:saddle"; @@ -534,6 +559,7 @@ final class ItemTypeNames{ 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"; @@ -553,6 +579,7 @@ final class ItemTypeNames{ 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 d7d097de82..7b34ffcb6e 100644 --- a/src/data/bedrock/item/upgrade/ItemDataUpgrader.php +++ b/src/data/bedrock/item/upgrade/ItemDataUpgrader.php @@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock\item\upgrade; 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\CompoundTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\ShortTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\utils\Binary; use function assert; @@ -46,6 +48,8 @@ final class ItemDataUpgrader{ private LegacyItemIdToStringIdMap $legacyIntToStringIdMap, private R12ItemIdToBlockIdMap $r12ItemIdToBlockIdMap, private BlockDataUpgrader $blockDataUpgrader, + private BlockItemIdMap $blockItemIdMap, + private BlockStateDictionary $blockStateDictionary ){} /** @@ -148,6 +152,17 @@ final class ItemDataUpgrader{ [$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/R12ItemIdToBlockIdMap.php b/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php index 2aac8de64e..19cfe6c78d 100644 --- a/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php +++ b/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php @@ -56,7 +56,7 @@ final class R12ItemIdToBlockIdMap{ } $builtMap = []; - foreach($map as $itemId => $blockId){ + foreach(Utils::promoteKeys($map) as $itemId => $blockId){ if(!is_string($itemId)){ throw new AssumptionFailedError("Invalid blockitem ID mapping table, expected string as key"); } diff --git a/src/data/runtime/RuntimeDataDescriber.php b/src/data/runtime/RuntimeDataDescriber.php index 6f1d35b983..6eb552a7b6 100644 --- a/src/data/runtime/RuntimeDataDescriber.php +++ b/src/data/runtime/RuntimeDataDescriber.php @@ -89,6 +89,11 @@ interface RuntimeDataDescriber extends RuntimeEnumDescriber{ 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; /** diff --git a/src/data/runtime/RuntimeEnumMetadata.php b/src/data/runtime/RuntimeEnumMetadata.php index 261b7a1bc3..45f831b194 100644 --- a/src/data/runtime/RuntimeEnumMetadata.php +++ b/src/data/runtime/RuntimeEnumMetadata.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\data\runtime; -use function array_values; use function ceil; use function count; use function log; @@ -60,7 +59,7 @@ final class RuntimeEnumMetadata{ usort($members, fn(\UnitEnum $a, \UnitEnum $b) => $a->name <=> $b->name); //sort by name to ensure consistent ordering (and thus consistent bit assignments) $this->bits = (int) ceil(log(count($members), 2)); - $this->intToEnum = array_values($members); + $this->intToEnum = $members; //usort strips keys so this is already a list $reversed = []; foreach($this->intToEnum as $int => $enum){ diff --git a/src/entity/Attribute.php b/src/entity/Attribute.php index 829e3d26cd..3e9b7d7c29 100644 --- a/src/entity/Attribute.php +++ b/src/entity/Attribute.php @@ -76,7 +76,7 @@ class Attribute{ throw new \InvalidArgumentException("Minimum $minValue is greater than the maximum $max"); } - if($this->minValue != $minValue){ + if($this->minValue !== $minValue){ $this->desynchronized = true; $this->minValue = $minValue; } @@ -95,7 +95,7 @@ class Attribute{ throw new \InvalidArgumentException("Maximum $maxValue is less than the minimum $min"); } - if($this->maxValue != $maxValue){ + if($this->maxValue !== $maxValue){ $this->desynchronized = true; $this->maxValue = $maxValue; } @@ -140,7 +140,7 @@ class Attribute{ $value = min(max($value, $this->getMinValue()), $this->getMaxValue()); } - if($this->currentValue != $value){ + if($this->currentValue !== $value){ $this->desynchronized = true; $this->currentValue = $value; }elseif($forceSend){ diff --git a/src/entity/Entity.php b/src/entity/Entity.php index c55a8716ca..7f0f6028b3 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -35,6 +35,7 @@ use pocketmine\event\entity\EntityMotionEvent; 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; @@ -71,10 +72,10 @@ use function assert; use function cos; use function count; use function deg2rad; +use function floatval; use function floor; use function fmod; use function get_class; -use function lcg_value; use function sin; use function spl_object_id; use const M_PI_2; @@ -102,7 +103,10 @@ abstract class Entity{ return self::$entityCount++; } - /** @var Player[] */ + /** + * @var Player[] + * @phpstan-var array + */ protected array $hasSpawned = []; protected int $id; @@ -588,7 +592,7 @@ abstract class Entity{ * Sets the health of the Entity. This won't send any update to the players */ public function setHealth(float $amount) : void{ - if($amount == $this->health){ + if($amount === $this->health){ return; } @@ -757,8 +761,8 @@ abstract class Entity{ $diffMotion = $this->motion->subtractVector($this->lastMotion)->lengthSquared(); - $still = $this->motion->lengthSquared() == 0.0; - $wasStill = $this->lastMotion->lengthSquared() == 0.0; + $still = $this->motion->lengthSquared() === 0.0; + $wasStill = $this->lastMotion->lengthSquared() === 0.0; if($wasStill !== $still){ //TODO: hack for client-side AI interference: prevent client sided movement when motion is 0 $this->setNoClientPredictions($still); @@ -906,7 +910,7 @@ abstract class Entity{ return false; } - $force = lcg_value() * 0.2 + 0.1; + $force = Utils::getRandomFloat() * 0.2 + 0.1; $this->motion = match($direction){ Facing::WEST => $this->motion->withComponents(-$force, null, null), @@ -1001,7 +1005,7 @@ abstract class Entity{ abs($this->motion->z) <= self::MOTION_THRESHOLD ? 0 : null ); - if($this->motion->x != 0 || $this->motion->y != 0 || $this->motion->z != 0){ + if(floatval($this->motion->x) !== 0.0 || floatval($this->motion->y) !== 0.0 || floatval($this->motion->z) !== 0.0){ $this->move($this->motion->x, $this->motion->y, $this->motion->z); } @@ -1055,9 +1059,9 @@ abstract class Entity{ public function hasMovementUpdate() : bool{ return ( $this->forceMovementUpdate || - $this->motion->x != 0 || - $this->motion->y != 0 || - $this->motion->z != 0 || + floatval($this->motion->x) !== 0.0 || + floatval($this->motion->y) !== 0.0 || + floatval($this->motion->z) !== 0.0 || !$this->onGround ); } @@ -1160,7 +1164,7 @@ abstract class Entity{ $moveBB->offset(0, $dy, 0); - $fallingFlag = ($this->onGround || ($dy != $wantedY && $wantedY < 0)); + $fallingFlag = ($this->onGround || ($dy !== $wantedY && $wantedY < 0)); foreach($list as $bb){ $dx = $bb->calculateXOffset($moveBB, $dx); @@ -1174,7 +1178,7 @@ abstract class Entity{ $moveBB->offset(0, 0, $dz); - if($this->stepHeight > 0 && $fallingFlag && ($wantedX != $dx || $wantedZ != $dz)){ + if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; @@ -1239,9 +1243,9 @@ abstract class Entity{ $postFallVerticalVelocity = $this->updateFallState($dy, $this->onGround); $this->motion = $this->motion->withComponents( - $wantedX != $dx ? 0 : null, - $postFallVerticalVelocity ?? ($wantedY != $dy ? 0 : null), - $wantedZ != $dz ? 0 : null + $wantedX !== $dx ? 0 : null, + $postFallVerticalVelocity ?? ($wantedY !== $dy ? 0 : null), + $wantedZ !== $dz ? 0 : null ); //TODO: vehicle collision events (first we need to spawn them!) @@ -1250,10 +1254,10 @@ abstract class Entity{ } protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{ - $this->isCollidedVertically = $wantedY != $dy; - $this->isCollidedHorizontally = ($wantedX != $dx || $wantedZ != $dz); + $this->isCollidedVertically = $wantedY !== $dy; + $this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz); $this->isCollided = ($this->isCollidedHorizontally || $this->isCollidedVertically); - $this->onGround = ($wantedY != $dy && $wantedY < 0); + $this->onGround = ($wantedY !== $dy && $wantedY < 0); } /** @@ -1560,6 +1564,13 @@ abstract class Entity{ $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. */ diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index d8d189cffc..03d9c03e6b 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -32,6 +32,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap; 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; @@ -42,6 +43,7 @@ use pocketmine\entity\projectile\Arrow; use pocketmine\entity\projectile\Egg; use pocketmine\entity\projectile\EnderPearl; use pocketmine\entity\projectile\ExperienceBottle; +use pocketmine\entity\projectile\IceBomb; use pocketmine\entity\projectile\Snowball; use pocketmine\entity\projectile\SplashPotion; use pocketmine\item\Item; @@ -92,6 +94,10 @@ final class EntityFactory{ 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']); @@ -115,6 +121,10 @@ final class EntityFactory{ return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt); }, ['FallingSand', 'minecraft:falling_block']); + $this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{ + return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt); + }, ['minecraft:ice_bomb']); + $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{ $itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM); if($itemTag === null){ diff --git a/src/entity/FoodSource.php b/src/entity/FoodSource.php index 98478b4a1d..028c767838 100644 --- a/src/entity/FoodSource.php +++ b/src/entity/FoodSource.php @@ -34,6 +34,7 @@ interface FoodSource extends Consumable{ /** * Returns whether a Human eating this FoodSource must have a non-full hunger bar. + * This is ignored in creative mode and in peaceful difficulty. */ public function requiresHunger() : bool; } diff --git a/src/entity/Human.php b/src/entity/Human.php index f2c4c7a740..c94b76097c 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -68,6 +68,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; use pocketmine\player\Player; use pocketmine\world\sound\TotemUseSound; +use pocketmine\world\World; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function array_fill; @@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ return $this->hungerManager; } + /** + * Returns whether the Human can eat food. This may return a different result than {@link HungerManager::isHungry()}, + * as HungerManager only handles the hunger bar. + */ + public function canEat() : bool{ + return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === World::DIFFICULTY_PEACEFUL; + } + public function consumeObject(Consumable $consumable) : bool{ - if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){ + if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){ return false; } @@ -504,6 +513,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ AbilitiesLayer::LAYER_BASE, array_fill(0, AbilitiesLayer::NUMBER_OF_ABILITIES, false), 0.0, + 0.0, 0.0 ) ])), diff --git a/src/entity/HungerManager.php b/src/entity/HungerManager.php index a318558918..7e3b40e745 100644 --- a/src/entity/HungerManager.php +++ b/src/entity/HungerManager.php @@ -88,7 +88,8 @@ class HungerManager{ } /** - * Returns whether this Human may consume objects requiring hunger. + * Returns whether the food level is below the maximum. + * This doesn't decide if the entity can eat food. Use {@link Human::canEat()} for that. */ public function isHungry() : bool{ return $this->getFood() < $this->getMaxFood(); diff --git a/src/entity/Living.php b/src/entity/Living.php index 81f46424f1..852344784a 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -25,6 +25,8 @@ namespace pocketmine\entity; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; +use pocketmine\block\VanillaBlocks; +use pocketmine\block\Water; use pocketmine\data\bedrock\EffectIdMap; use pocketmine\entity\animation\DeathAnimation; use pocketmine\entity\animation\HurtAnimation; @@ -44,6 +46,7 @@ use pocketmine\item\Durable; use pocketmine\item\enchantment\Enchantment; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; +use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\math\VoxelRayTrace; use pocketmine\nbt\tag\CompoundTag; @@ -58,18 +61,19 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; use pocketmine\player\Player; use pocketmine\timings\Timings; use pocketmine\utils\Binary; +use pocketmine\utils\Utils; use pocketmine\world\sound\BurpSound; use pocketmine\world\sound\EntityLandSound; use pocketmine\world\sound\EntityLongFallSound; use pocketmine\world\sound\EntityShortFallSound; use pocketmine\world\sound\ItemBreakSound; +use function abs; use function array_shift; use function atan2; use function ceil; use function count; use function floor; use function ksort; -use function lcg_value; use function max; use function min; use function mt_getrandmax; @@ -128,6 +132,8 @@ abstract class Living extends Entity{ protected bool $gliding = false; protected bool $swimming = false; + private ?int $frostWalkerLevel = null; + protected function getInitialDragMultiplier() : float{ return 0.02; } protected function getInitialGravity() : float{ return 0.08; } @@ -151,6 +157,14 @@ abstract class Living extends Entity{ $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this) ))); + $this->armorInventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: function(Inventory $inventory, int $slot) : void{ + if($slot === ArmorInventory::SLOT_FEET){ + $this->frostWalkerLevel = null; + } + }, + onContentChange: function() : void{ $this->frostWalkerLevel = null; } + )); $health = $this->getMaxHealth(); @@ -490,7 +504,7 @@ abstract class Living extends Entity{ $helmet = $this->armorInventory->getHelmet(); if($helmet instanceof Armor){ $finalDamage = $source->getFinalDamage(); - $this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2)); + $this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2)); $this->armorInventory->setHelmet($helmet); } } @@ -687,6 +701,47 @@ abstract class Living extends Entity{ return $hasUpdate; } + protected function move(float $dx, float $dy, float $dz) : void{ + $oldX = $this->location->x; + $oldZ = $this->location->z; + + parent::move($dx, $dy, $dz); + + $frostWalkerLevel = $this->getFrostWalkerLevel(); + if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){ + $this->applyFrostWalker($frostWalkerLevel); + } + } + + protected function applyFrostWalker(int $level) : void{ + $radius = $level + 2; + $world = $this->getWorld(); + + $baseX = $this->location->getFloorX(); + $y = $this->location->getFloorY() - 1; + $baseZ = $this->location->getFloorZ(); + + $frostedIce = VanillaBlocks::FROSTED_ICE(); + for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){ + for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){ + $block = $world->getBlockAt($x, $y, $z); + if( + !$block instanceof Water || + !$block->isSource() || + $world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR || + count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0 + ){ + continue; + } + $world->setBlockAt($x, $y, $z, $frostedIce); + } + } + } + + public function getFrostWalkerLevel() : int{ + return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->getEnchantmentLevel(VanillaEnchantments::FROST_WALKER()); + } + /** * Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water. */ @@ -697,7 +752,7 @@ abstract class Living extends Entity{ $this->setBreathing(false); if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 || - lcg_value() <= (1 / ($respirationLevel + 1)) + Utils::getRandomFloat() <= (1 / ($respirationLevel + 1)) ){ $ticks -= $tickDiff; if($ticks <= -20){ diff --git a/src/entity/Location.php b/src/entity/Location.php index 990108ac48..d9c1018822 100644 --- a/src/entity/Location.php +++ b/src/entity/Location.php @@ -66,7 +66,7 @@ class Location extends Position{ public function equals(Vector3 $v) : bool{ if($v instanceof Location){ - return parent::equals($v) && $v->yaw == $this->yaw && $v->pitch == $this->pitch; + return parent::equals($v) && $v->yaw === $this->yaw && $v->pitch === $this->pitch; } return parent::equals($v); } diff --git a/src/entity/Squid.php b/src/entity/Squid.php index 75c50061b9..a0f3fef48b 100644 --- a/src/entity/Squid.php +++ b/src/entity/Squid.php @@ -26,6 +26,7 @@ namespace pocketmine\entity; 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 @@ class Squid extends WaterAnimal{ 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 376401a5df..fee65b0143 100644 --- a/src/entity/Villager.php +++ b/src/entity/Villager.php @@ -23,6 +23,8 @@ declare(strict_types=1); 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 @@ class Villager extends Living implements Ageable{ 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 18fc2207e7..159a2dd258 100644 --- a/src/entity/Zombie.php +++ b/src/entity/Zombie.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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 @@ class Zombie extends Living{ //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/object/EndCrystal.php b/src/entity/object/EndCrystal.php new file mode 100644 index 0000000000..afaeb67693 --- /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 9d8af89347..66d4049f88 100644 --- a/src/entity/object/FallingBlock.php +++ b/src/entity/object/FallingBlock.php @@ -35,6 +35,7 @@ use pocketmine\entity\Location; 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 @@ class FallingBlock extends Entity{ 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/Painting.php b/src/entity/object/Painting.php index f6449883c7..641d040510 100644 --- a/src/entity/object/Painting.php +++ b/src/entity/object/Painting.php @@ -28,6 +28,7 @@ use pocketmine\entity\Entity; 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 @@ class Painting extends Entity{ )); } + public function getPickedItem() : ?Item{ + return VanillaItems::PAINTING(); + } + /** * Returns the painting motive (which image is displayed on the painting) */ diff --git a/src/entity/object/PrimedTNT.php b/src/entity/object/PrimedTNT.php index ec621adfb8..af3c979228 100644 --- a/src/entity/object/PrimedTNT.php +++ b/src/entity/object/PrimedTNT.php @@ -23,11 +23,13 @@ declare(strict_types=1); 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 @@ class PrimedTNT extends Entity implements Explosive{ } } + 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/IceBomb.php b/src/entity/projectile/IceBomb.php new file mode 100644 index 0000000000..69a5d46a2c --- /dev/null +++ b/src/entity/projectile/IceBomb.php @@ -0,0 +1,86 @@ +getTypeId() === BlockTypeIds::WATER){ + $pos = $block->getPosition(); + + return AxisAlignedBB::one()->offset($pos->x, $pos->y, $pos->z)->calculateIntercept($start, $end); + } + + return parent::calculateInterceptWithBlock($block, $start, $end); + } + + protected function onHit(ProjectileHitEvent $event) : void{ + $world = $this->getWorld(); + $pos = $this->location; + + $world->addSound($pos, new IceBombHitSound()); + $itemBreakParticle = new ItemBreakParticle(VanillaItems::ICE_BOMB()); + for($i = 0; $i < 6; ++$i){ + $world->addParticle($pos, $itemBreakParticle); + } + } + + protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{ + parent::onHitBlock($blockHit, $hitResult); + + $pos = $blockHit->getPosition(); + $world = $pos->getWorld(); + $posX = $pos->getFloorX(); + $posY = $pos->getFloorY(); + $posZ = $pos->getFloorZ(); + + $ice = VanillaBlocks::ICE(); + for($x = $posX - 1; $x <= $posX + 1; $x++){ + for($y = $posY - 1; $y <= $posY + 1; $y++){ + for($z = $posZ - 1; $z <= $posZ + 1; $z++){ + if($world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::WATER){ + $world->setBlockAt($x, $y, $z, $ice); + } + } + } + } + } +} diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php index 55950b6a6d..68b6c47637 100644 --- a/src/entity/projectile/Projectile.php +++ b/src/entity/projectile/Projectile.php @@ -28,6 +28,7 @@ use pocketmine\data\SavedDataLoadingException; 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; @@ -43,7 +44,6 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; use pocketmine\timings\Timings; -use function assert; use function atan2; use function ceil; use function count; @@ -96,7 +96,7 @@ abstract class Projectile extends Entity{ } 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{ @@ -169,8 +169,6 @@ abstract class Projectile extends Entity{ $start = $this->location->asVector3(); $end = $start->add($dx, $dy, $dz); - $blockHit = null; - $entityHit = null; $hitResult = null; $world = $this->getWorld(); @@ -180,8 +178,7 @@ abstract class Projectile extends Entity{ $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end); if($blockHitResult !== null){ $end = $blockHitResult->hitVector; - $blockHit = $block; - $hitResult = $blockHitResult; + $hitResult = [$block, $blockHitResult]; break; } } @@ -205,8 +202,7 @@ abstract class Projectile extends Entity{ if($distance < $entityDistance){ $entityDistance = $distance; - $entityHit = $entity; - $hitResult = $entityHitResult; + $hitResult = [$entity, $entityHitResult]; $end = $entityHitResult->hitVector; } } @@ -222,26 +218,18 @@ abstract class Projectile extends Entity{ $this->recalculateBoundingBox(); if($hitResult !== null){ - /** @var ProjectileHitEvent|null $ev */ - $ev = null; - if($entityHit !== null){ - $ev = new ProjectileHitEntityEvent($this, $hitResult, $entityHit); - }elseif($blockHit !== null){ - $ev = new ProjectileHitBlockEvent($this, $hitResult, $blockHit); + [$objectHit, $rayTraceResult] = $hitResult; + if($objectHit instanceof Entity){ + $ev = new ProjectileHitEntityEvent($this, $rayTraceResult, $objectHit); + $specificHitFunc = fn() => $this->onHitEntity($objectHit, $rayTraceResult); }else{ - assert(false, "unknown hit type"); + $ev = new ProjectileHitBlockEvent($this, $rayTraceResult, $objectHit); + $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult); } - if($ev !== null){ - $ev->call(); - $this->onHit($ev); - - if($ev instanceof ProjectileHitEntityEvent){ - $this->onHitEntity($ev->getEntityHit(), $ev->getRayTraceResult()); - }elseif($ev instanceof ProjectileHitBlockEvent){ - $this->onHitBlock($ev->getBlockHit(), $ev->getRayTraceResult()); - } - } + $ev->call(); + $this->onHit($ev); + $specificHitFunc(); $this->isCollided = $this->onGround = true; $this->motion = Vector3::zero(); @@ -289,10 +277,11 @@ abstract class Projectile extends Entity{ $damage = $this->getResultDamage(); if($damage >= 0){ - if($this->getOwningEntity() === null){ + $owner = $this->getOwningEntity(); + if($owner === null){ $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); }else{ - $ev = new EntityDamageByChildEntityEvent($this->getOwningEntity(), $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); + $ev = new EntityDamageByChildEntityEvent($owner, $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); } $entityHit->attack($ev); diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php index 2072cd5226..e7d229a3ff 100644 --- a/src/event/HandlerList.php +++ b/src/event/HandlerList.php @@ -30,7 +30,10 @@ use function spl_object_id; use const SORT_NUMERIC; class HandlerList{ - /** @var RegisteredListener[][] */ + /** + * @var RegisteredListener[][] + * @phpstan-var array> + */ private array $handlerSlots = []; /** @var RegisteredListenerCache[] */ diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index 605a387478..9437df37f9 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -119,7 +119,7 @@ class HandlerListManager{ public function getHandlersFor(string $event) : array{ $cache = $this->handlerCaches[$event] ?? null; //getListFor() will populate the cache for the next call - return $cache?->list ?? $this->getListFor($event)->getListenerList(); + return $cache->list ?? $this->getListFor($event)->getListenerList(); } /** diff --git a/src/event/entity/EntityDamageByEntityEvent.php b/src/event/entity/EntityDamageByEntityEvent.php index 5ef6c4b8e9..052be9a159 100644 --- a/src/event/entity/EntityDamageByEntityEvent.php +++ b/src/event/entity/EntityDamageByEntityEvent.php @@ -57,7 +57,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ $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); } } diff --git a/src/event/player/PlayerEntityPickEvent.php b/src/event/player/PlayerEntityPickEvent.php new file mode 100644 index 0000000000..3c742d6c48 --- /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 6119d5e99c..46daf70813 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 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{ 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/PlayerPreLoginEvent.php b/src/event/player/PlayerPreLoginEvent.php index 5a69c0e175..4af0cdd891 100644 --- a/src/event/player/PlayerPreLoginEvent.php +++ b/src/event/player/PlayerPreLoginEvent.php @@ -52,9 +52,15 @@ class PlayerPreLoginEvent extends Event{ self::KICK_FLAG_BANNED ]; - /** @var Translatable[]|string[] reason const => associated message */ + /** + * @var Translatable[]|string[] reason const => associated message + * @phpstan-var array + */ protected array $disconnectReasons = []; - /** @var Translatable[]|string[] */ + /** + * @var Translatable[]|string[] + * @phpstan-var array + */ protected array $disconnectScreenMessages = []; public function __construct( @@ -93,6 +99,7 @@ class PlayerPreLoginEvent extends Event{ * Returns an array of kick flags currently assigned. * * @return int[] + * @phpstan-return list */ public function getKickFlags() : array{ return array_keys($this->disconnectReasons); diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index dcb3c04cb2..8591cc65bf 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -23,8 +23,13 @@ declare(strict_types=1); 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 @@ class ArmorInventory extends SimpleInventory{ protected Living $holder ){ parent::__construct(4); + + $this->validators->add(new CallbackSlotValidator(self::validate(...))); } public function getHolder() : Living{ @@ -73,4 +80,20 @@ class ArmorInventory extends SimpleInventory{ 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 254e44b1ea..0d5d1ffe60 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -36,19 +36,27 @@ use function spl_object_id; /** * 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[] */ + /** + * @var Player[] + * @phpstan-var array + */ protected array $viewers = []; /** * @var InventoryListener[]|ObjectSet * @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{ @@ -281,8 +289,6 @@ abstract class BaseInventory implements Inventory{ } public function removeItem(Item ...$slots) : array{ - /** @var Item[] $searchItems */ - /** @var Item[] $slots */ $searchItems = []; foreach($slots as $slot){ if(!$slot->isNull()){ @@ -398,4 +404,8 @@ abstract class BaseInventory implements Inventory{ public function getListeners() : ObjectSet{ return $this->listeners; } + + public function getSlotValidators() : ObjectSet{ + return $this->validators; + } } diff --git a/src/inventory/CreativeCategory.php b/src/inventory/CreativeCategory.php new file mode 100644 index 0000000000..bede48b280 --- /dev/null +++ b/src/inventory/CreativeCategory.php @@ -0,0 +1,34 @@ +getText()) : strlen($name); + if($nameLength === 0){ + throw new \InvalidArgumentException("Creative group name cannot be empty"); + } + } + + public function getName() : Translatable|string{ return $this->name; } + + public function getIcon() : Item{ return clone $this->icon; } +} diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 7502a6785f..ee27068c70 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -24,19 +24,25 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\crafting\CraftingManagerFromDataHelper; -use pocketmine\crafting\json\ItemStackData; use pocketmine\data\bedrock\BedrockDataFiles; +use pocketmine\inventory\json\CreativeGroupData; use pocketmine\item\Item; +use pocketmine\lang\Translatable; use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\ObjectSet; use pocketmine\utils\SingletonTrait; -use pocketmine\utils\Utils; +use Symfony\Component\Filesystem\Path; +use function array_filter; +use function array_map; final class CreativeInventory{ use SingletonTrait; use DestructorCallbackTrait; - /** @var Item[] */ + /** + * @var CreativeInventoryEntry[] + * @phpstan-var array + */ private array $creative = []; /** @phpstan-var ObjectSet<\Closure() : void> */ @@ -44,17 +50,32 @@ final class CreativeInventory{ private function __construct(){ $this->contentChangedCallbacks = new ObjectSet(); - $creativeItems = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( - BedrockDataFiles::CREATIVEITEMS_JSON, - ItemStackData::class - ); - foreach($creativeItems as $data){ - $item = CraftingManagerFromDataHelper::deserializeItemStack($data); - if($item === null){ - //unknown item - continue; + + foreach([ + "construction" => CreativeCategory::CONSTRUCTION, + "nature" => CreativeCategory::NATURE, + "equipment" => CreativeCategory::EQUIPMENT, + "items" => CreativeCategory::ITEMS, + ] as $categoryId => $categoryEnum){ + $groups = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( + Path::join(BedrockDataFiles::CREATIVE, $categoryId . ".json"), + CreativeGroupData::class + ); + + foreach($groups as $groupData){ + $icon = $groupData->group_icon === null ? null : CraftingManagerFromDataHelper::deserializeItemStack($groupData->group_icon); + + $group = $icon === null ? null : new CreativeGroup( + new Translatable($groupData->group_name), + $icon + ); + + $items = array_filter(array_map(static fn($itemStack) => CraftingManagerFromDataHelper::deserializeItemStack($itemStack), $groupData->items)); + + foreach($items as $item){ + $this->add($item, $categoryEnum, $group); + } } - $this->add($item); } } @@ -69,18 +90,31 @@ final class CreativeInventory{ /** * @return Item[] + * @phpstan-return array */ public function getAll() : array{ - return Utils::cloneObjectArray($this->creative); + return array_map(fn(CreativeInventoryEntry $entry) => $entry->getItem(), $this->creative); + } + + /** + * @return CreativeInventoryEntry[] + * @phpstan-return array + */ + public function getAllEntries() : array{ + return $this->creative; } public function getItem(int $index) : ?Item{ - return isset($this->creative[$index]) ? clone $this->creative[$index] : null; + return $this->getEntry($index)?->getItem(); + } + + public function getEntry(int $index) : ?CreativeInventoryEntry{ + return $this->creative[$index] ?? null; } public function getItemIndex(Item $item) : int{ foreach($this->creative as $i => $d){ - if($item->equals($d, true, false)){ + if($d->matchesItem($item)){ return $i; } } @@ -92,8 +126,8 @@ final class CreativeInventory{ * Adds an item to the creative menu. * Note: Players who are already online when this is called will not see this change. */ - public function add(Item $item) : void{ - $this->creative[] = clone $item; + public function add(Item $item, CreativeCategory $category = CreativeCategory::ITEMS, ?CreativeGroup $group = null) : void{ + $this->creative[] = new CreativeInventoryEntry($item, $category, $group); $this->onContentChange(); } diff --git a/src/inventory/CreativeInventoryEntry.php b/src/inventory/CreativeInventoryEntry.php new file mode 100644 index 0000000000..a5d568ee8f --- /dev/null +++ b/src/inventory/CreativeInventoryEntry.php @@ -0,0 +1,48 @@ +item = clone $item; + } + + public function getItem() : Item{ return clone $this->item; } + + public function getCategory() : CreativeCategory{ return $this->category; } + + public function getGroup() : ?CreativeGroup{ return $this->group; } + + public function matchesItem(Item $item) : bool{ + return $item->equals($this->item, checkDamage: true, checkCompound: false); + } +} diff --git a/src/inventory/SlotValidatedInventory.php b/src/inventory/SlotValidatedInventory.php new file mode 100644 index 0000000000..f30ebf8a01 --- /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/json/CreativeGroupData.php b/src/inventory/json/CreativeGroupData.php new file mode 100644 index 0000000000..70b73d3de8 --- /dev/null +++ b/src/inventory/json/CreativeGroupData.php @@ -0,0 +1,38 @@ + + */ protected array $inputs = []; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ protected array $outputs = []; private CraftingManager $craftingManager; @@ -74,6 +80,9 @@ class CraftingTransaction extends InventoryTransaction{ /** * @param Item[] $providedItems * @return Item[] + * + * @phpstan-param list $providedItems + * @phpstan-return list */ private static function packItems(array $providedItems) : array{ $packedProvidedItems = []; @@ -94,6 +103,9 @@ class CraftingTransaction extends InventoryTransaction{ /** * @param Item[] $providedItems * @param RecipeIngredient[] $recipeIngredients + * + * @phpstan-param list $providedItems + * @phpstan-param list $recipeIngredients */ public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{ if(count($recipeIngredients) === 0){ @@ -172,6 +184,9 @@ class CraftingTransaction extends InventoryTransaction{ * @param Item[] $txItems * @param Item[] $recipeItems * + * @phpstan-param list $txItems + * @phpstan-param list $recipeItems + * * @throws TransactionValidationException */ protected function matchOutputs(array $txItems, array $recipeItems) : int{ diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 2ca00f910c..6e010c7b8f 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -29,6 +29,7 @@ use pocketmine\inventory\transaction\action\InventoryAction; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\item\Item; use pocketmine\player\Player; +use pocketmine\utils\Utils; use function array_keys; use function array_values; use function assert; @@ -57,10 +58,16 @@ use function spl_object_id; class InventoryTransaction{ protected bool $hasExecuted = false; - /** @var Inventory[] */ + /** + * @var Inventory[] + * @phpstan-var array + */ protected array $inventories = []; - /** @var InventoryAction[] */ + /** + * @var InventoryAction[] + * @phpstan-var array + */ protected array $actions = []; /** @@ -81,6 +88,7 @@ class InventoryTransaction{ /** * @return Inventory[] + * @phpstan-return array */ public function getInventories() : array{ return $this->inventories; @@ -93,6 +101,7 @@ class InventoryTransaction{ * significance and should not be relied on. * * @return InventoryAction[] + * @phpstan-return array */ public function getActions() : array{ return $this->actions; @@ -102,6 +111,9 @@ class InventoryTransaction{ if(!isset($this->actions[$hash = spl_object_id($action)])){ $this->actions[$hash] = $action; $action->onAddToTransaction($this); + if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){ + $this->inventories[$inventoryId] = $action->getInventory(); + } }else{ throw new \InvalidArgumentException("Tried to add the same action to a transaction twice"); } @@ -120,21 +132,11 @@ class InventoryTransaction{ $this->actions = $actions; } - /** - * @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions - * involving inventories. - */ - public function addInventory(Inventory $inventory) : void{ - if(!isset($this->inventories[$hash = spl_object_id($inventory)])){ - $this->inventories[$hash] = $inventory; - } - } - /** * @param Item[] $needItems * @param Item[] $haveItems - * @phpstan-param-out Item[] $needItems - * @phpstan-param-out Item[] $haveItems + * @phpstan-param-out list $needItems + * @phpstan-param-out list $haveItems * * @throws TransactionValidationException */ @@ -142,8 +144,9 @@ class InventoryTransaction{ $needItems = []; $haveItems = []; foreach($this->actions as $key => $action){ - if(!$action->getTargetItem()->isNull()){ - $needItems[] = $action->getTargetItem(); + $targetItem = $action->getTargetItem(); + if(!$targetItem->isNull()){ + $needItems[] = $targetItem; } try{ @@ -152,8 +155,9 @@ class InventoryTransaction{ throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e); } - if(!$action->getSourceItem()->isNull()){ - $haveItems[] = $action->getSourceItem(); + $sourceItem = $action->getSourceItem(); + if(!$sourceItem->isNull()){ + $haveItems[] = $sourceItem; } } @@ -188,11 +192,8 @@ class InventoryTransaction{ * wrong order), so this method also tries to chain them into order. */ protected function squashDuplicateSlotChanges() : void{ - /** @var SlotChangeAction[][] $slotChanges */ $slotChanges = []; - /** @var Inventory[] $inventories */ $inventories = []; - /** @var int[] $slots */ $slots = []; foreach($this->actions as $key => $action){ @@ -203,7 +204,7 @@ class InventoryTransaction{ } } - foreach($slotChanges as $hash => $list){ + foreach(Utils::stringifyKeys($slotChanges) as $hash => $list){ if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot continue; } @@ -233,6 +234,7 @@ class InventoryTransaction{ /** * @param SlotChangeAction[] $possibleActions + * @phpstan-param array $possibleActions */ protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{ assert(count($possibleActions) > 0); diff --git a/src/inventory/transaction/action/InventoryAction.php b/src/inventory/transaction/action/InventoryAction.php index fff3d22b80..2f0db083c9 100644 --- a/src/inventory/transaction/action/InventoryAction.php +++ b/src/inventory/transaction/action/InventoryAction.php @@ -60,6 +60,7 @@ abstract class InventoryAction{ /** * Called when the action is added to the specified InventoryTransaction. + * @deprecated */ public function onAddToTransaction(InventoryTransaction $transaction) : void{ diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 453f0c4d22..3c9b8e5cf7 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\inventory\transaction\action; use pocketmine\inventory\Inventory; -use pocketmine\inventory\transaction\InventoryTransaction; +use pocketmine\inventory\SlotValidatedInventory; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; use pocketmine\player\Player; @@ -74,13 +74,14 @@ class SlotChangeAction extends InventoryAction{ if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } - } - - /** - * Adds this action's target inventory to the transaction's inventory list. - */ - public function onAddToTransaction(InventoryTransaction $transaction) : void{ - $transaction->addInventory($this->inventory); + 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 0000000000..1670dc6232 --- /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 0000000000..1b78c91f7f --- /dev/null +++ b/src/inventory/transaction/action/validator/SlotValidator.php @@ -0,0 +1,38 @@ + 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best + if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best $negated++; } } diff --git a/src/item/ChorusFruit.php b/src/item/ChorusFruit.php index c12724d7ca..e10c519576 100644 --- a/src/item/ChorusFruit.php +++ b/src/item/ChorusFruit.php @@ -88,4 +88,8 @@ class ChorusFruit extends Food{ public function getCooldownTicks() : int{ return 20; } + + public function getCooldownTag() : ?string{ + return ItemCooldownTags::CHORUS_FRUIT; + } } diff --git a/src/item/Durable.php b/src/item/Durable.php index 164e7a2730..f826211bf7 100644 --- a/src/item/Durable.php +++ b/src/item/Durable.php @@ -25,7 +25,7 @@ namespace pocketmine\item; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\nbt\tag\CompoundTag; -use function lcg_value; +use pocketmine\utils\Utils; use function min; abstract class Durable extends Item{ @@ -87,7 +87,7 @@ abstract class Durable extends Item{ $chance = 1 / ($unbreakingLevel + 1); for($i = 0; $i < $amount; ++$i){ - if(lcg_value() > $chance){ + if(Utils::getRandomFloat() > $chance){ $negated++; } } diff --git a/src/item/EndCrystal.php b/src/item/EndCrystal.php new file mode 100644 index 0000000000..320d657e6d --- /dev/null +++ b/src/item/EndCrystal.php @@ -0,0 +1,59 @@ +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 76bcb358ea..7109d3ae06 100644 --- a/src/item/EnderPearl.php +++ b/src/item/EnderPearl.php @@ -45,4 +45,8 @@ class EnderPearl extends ProjectileItem{ public function getCooldownTicks() : int{ return 20; } + + public function getCooldownTag() : ?string{ + return ItemCooldownTags::ENDER_PEARL; + } } diff --git a/src/item/Food.php b/src/item/Food.php index 1950c4b14a..d01ce9e18d 100644 --- a/src/item/Food.php +++ b/src/item/Food.php @@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{ } public function canStartUsingItem(Player $player) : bool{ - return !$this->requiresHunger() || $player->getHungerManager()->isHungry(); + return !$this->requiresHunger() || $player->canEat(); } } diff --git a/src/item/GoatHorn.php b/src/item/GoatHorn.php new file mode 100644 index 0000000000..088701e398 --- /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 0000000000..6c0c3b2f7f --- /dev/null +++ b/src/item/GoatHornType.php @@ -0,0 +1,36 @@ + + */ protected array $enchantments = []; public function hasEnchantments() : bool{ @@ -79,6 +82,7 @@ trait ItemEnchantmentHandlingTrait{ /** * @return EnchantmentInstance[] + * @phpstan-return array */ public function getEnchantments() : array{ return $this->enchantments; diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index 66eebed744..c63046c6bb 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -324,8 +324,18 @@ final class ItemTypeIds{ public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285; public const PITCHER_POD = 20286; public const NAME_TAG = 20287; + public const GOAT_HORN = 20288; + public const END_CRYSTAL = 20289; + public const ICE_BOMB = 20290; + public const RECOVERY_COMPASS = 20291; + public const PALE_OAK_SIGN = 20292; + public const RESIN_BRICK = 20293; + public const RECORD_RELIC = 20294; + public const RECORD_CREATOR = 20295; + public const RECORD_CREATOR_MUSIC_BOX = 20296; + public const RECORD_PRECIPICE = 20297; - public const FIRST_UNUSED_ITEM_ID = 20288; + public const FIRST_UNUSED_ITEM_ID = 20298; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/LegacyStringToItemParser.php b/src/item/LegacyStringToItemParser.php index a52b6c716e..19a6d1f6cc 100644 --- a/src/item/LegacyStringToItemParser.php +++ b/src/item/LegacyStringToItemParser.php @@ -29,6 +29,7 @@ use pocketmine\data\bedrock\item\upgrade\ItemDataUpgrader; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\SingletonTrait; +use pocketmine\utils\Utils; use pocketmine\world\format\io\GlobalItemDataHandlers; use Symfony\Component\Filesystem\Path; use function explode; @@ -67,7 +68,7 @@ final class LegacyStringToItemParser{ $mappings = json_decode($mappingsRaw, true); if(!is_array($mappings)) throw new AssumptionFailedError("Invalid mappings format, expected array"); - foreach($mappings as $name => $id){ + foreach(Utils::promoteKeys($mappings) as $name => $id){ if(!is_string($id)) throw new AssumptionFailedError("Invalid mappings format, expected string values"); $result->addMapping((string) $name, $id); } @@ -110,7 +111,8 @@ final class LegacyStringToItemParser{ */ public function parse(string $input) : Item{ $key = $this->reprocess($input); - $b = explode(":", $key); + //TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given a string like 351:4:1 + $b = explode(":", $key, limit: 3); if(!isset($b[1])){ $meta = 0; diff --git a/src/item/Potion.php b/src/item/Potion.php index 41b0f634a8..192a257841 100644 --- a/src/item/Potion.php +++ b/src/item/Potion.php @@ -26,6 +26,7 @@ namespace pocketmine\item; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\Living; use pocketmine\player\Player; +use pocketmine\world\sound\BottleEmptySound; class Potion extends Item implements ConsumableItem{ @@ -50,7 +51,7 @@ class Potion extends Item implements ConsumableItem{ } public function onConsume(Living $consumer) : void{ - + $consumer->broadcastSound(new BottleEmptySound()); } public function getAdditionalEffects() : array{ diff --git a/src/item/RottenFlesh.php b/src/item/RottenFlesh.php index 4cecc67fcd..2ea3ee9559 100644 --- a/src/item/RottenFlesh.php +++ b/src/item/RottenFlesh.php @@ -25,7 +25,7 @@ namespace pocketmine\item; use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\VanillaEffects; -use function lcg_value; +use pocketmine\utils\Utils; class RottenFlesh extends Food{ @@ -38,7 +38,7 @@ class RottenFlesh extends Food{ } public function getAdditionalEffects() : array{ - if(lcg_value() <= 0.8){ + if(Utils::getRandomFloat() <= 0.8){ return [ new EffectInstance(VanillaEffects::HUNGER(), 600) ]; diff --git a/src/item/SpawnEgg.php b/src/item/SpawnEgg.php index 51dcceebd2..ab4f0e1498 100644 --- a/src/item/SpawnEgg.php +++ b/src/item/SpawnEgg.php @@ -27,15 +27,15 @@ use pocketmine\block\Block; use pocketmine\entity\Entity; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\World; -use function lcg_value; abstract class SpawnEgg extends Item{ abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity; public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{ - $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0); + $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0); if($this->hasCustomName()){ $entity->setNameTag($this->getCustomName()); diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 9f5db6950c..a3bd7b8727 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -98,9 +98,14 @@ final class StringToItemParser extends StringToTParser{ 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)); } } @@ -238,6 +243,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("chiseled_polished_blackstone", fn() => Blocks::CHISELED_POLISHED_BLACKSTONE()); $result->registerBlock("chiseled_quartz", fn() => Blocks::CHISELED_QUARTZ()); $result->registerBlock("chiseled_red_sandstone", fn() => Blocks::CHISELED_RED_SANDSTONE()); + $result->registerBlock("chiseled_resin_bricks", fn() => Blocks::CHISELED_RESIN_BRICKS()); $result->registerBlock("chiseled_sandstone", fn() => Blocks::CHISELED_SANDSTONE()); $result->registerBlock("chiseled_stone_bricks", fn() => Blocks::CHISELED_STONE_BRICKS()); $result->registerBlock("chiseled_tuff", fn() => Blocks::CHISELED_TUFF()); @@ -867,6 +873,19 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("oxeye_daisy", fn() => Blocks::OXEYE_DAISY()); $result->registerBlock("packed_ice", fn() => Blocks::PACKED_ICE()); $result->registerBlock("packed_mud", fn() => Blocks::PACKED_MUD()); + $result->registerBlock("pale_oak_button", fn() => Blocks::PALE_OAK_BUTTON()); + $result->registerBlock("pale_oak_door", fn() => Blocks::PALE_OAK_DOOR()); + $result->registerBlock("pale_oak_fence", fn() => Blocks::PALE_OAK_FENCE()); + $result->registerBlock("pale_oak_fence_gate", fn() => Blocks::PALE_OAK_FENCE_GATE()); + $result->registerBlock("pale_oak_leaves", fn() => Blocks::PALE_OAK_LEAVES()); + $result->registerBlock("pale_oak_log", fn() => Blocks::PALE_OAK_LOG()->setStripped(false)); + $result->registerBlock("pale_oak_planks", fn() => Blocks::PALE_OAK_PLANKS()); + $result->registerBlock("pale_oak_pressure_plate", fn() => Blocks::PALE_OAK_PRESSURE_PLATE()); + $result->registerBlock("pale_oak_sign", fn() => Blocks::PALE_OAK_SIGN()); + $result->registerBlock("pale_oak_slab", fn() => Blocks::PALE_OAK_SLAB()); + $result->registerBlock("pale_oak_stairs", fn() => Blocks::PALE_OAK_STAIRS()); + $result->registerBlock("pale_oak_trapdoor", fn() => Blocks::PALE_OAK_TRAPDOOR()); + $result->registerBlock("pale_oak_wood", fn() => Blocks::PALE_OAK_WOOD()->setStripped(false)); $result->registerBlock("peony", fn() => Blocks::PEONY()); $result->registerBlock("pink_petals", fn() => Blocks::PINK_PETALS()); $result->registerBlock("pink_tulip", fn() => Blocks::PINK_TULIP()); @@ -967,6 +986,13 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("repeater", fn() => Blocks::REDSTONE_REPEATER()); $result->registerBlock("repeater_block", fn() => Blocks::REDSTONE_REPEATER()); $result->registerBlock("reserved6", fn() => Blocks::RESERVED6()); + $result->registerBlock("resin", fn() => Blocks::RESIN()); + $result->registerBlock("resin_block", fn() => Blocks::RESIN()); + $result->registerBlock("resin_brick_slab", fn() => Blocks::RESIN_BRICK_SLAB()); + $result->registerBlock("resin_brick_stairs", fn() => Blocks::RESIN_BRICK_STAIRS()); + $result->registerBlock("resin_brick_wall", fn() => Blocks::RESIN_BRICK_WALL()); + $result->registerBlock("resin_bricks", fn() => Blocks::RESIN_BRICKS()); + $result->registerBlock("resin_clump", fn() => Blocks::RESIN_CLUMP()); $result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED)); $result->registerBlock("rose", fn() => Blocks::POPPY()); $result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH()); @@ -1079,6 +1105,8 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("stripped_mangrove_wood", fn() => Blocks::MANGROVE_WOOD()->setStripped(true)); $result->registerBlock("stripped_oak_log", fn() => Blocks::OAK_LOG()->setStripped(true)); $result->registerBlock("stripped_oak_wood", fn() => Blocks::OAK_WOOD()->setStripped(true)); + $result->registerBlock("stripped_pale_oak_log", fn() => Blocks::PALE_OAK_LOG()->setStripped(true)); + $result->registerBlock("stripped_pale_oak_wood", fn() => Blocks::PALE_OAK_WOOD()->setStripped(true)); $result->registerBlock("stripped_spruce_log", fn() => Blocks::SPRUCE_LOG()->setStripped(true)); $result->registerBlock("stripped_spruce_wood", fn() => Blocks::SPRUCE_WOOD()->setStripped(true)); $result->registerBlock("stripped_warped_hyphae", fn() => Blocks::WARPED_HYPHAE()->setStripped(true)); @@ -1179,6 +1207,13 @@ final class StringToItemParser extends StringToTParser{ $result->register($prefix("dye"), fn() => Items::DYE()->setColor($color)); } + + 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; @@ -1318,6 +1353,7 @@ final class StringToItemParser extends StringToTParser{ $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_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE()); @@ -1336,6 +1372,7 @@ final class StringToItemParser extends StringToTParser{ $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()); @@ -1364,6 +1401,7 @@ final class StringToItemParser extends StringToTParser{ $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("ice_bomb", fn() => Items::ICE_BOMB()); $result->register("ink_sac", fn() => Items::INK_SAC()); $result->register("iron_axe", fn() => Items::IRON_AXE()); $result->register("iron_boots", fn() => Items::IRON_BOOTS()); @@ -1457,17 +1495,23 @@ final class StringToItemParser extends StringToTParser{ $result->register("record_blocks", fn() => Items::RECORD_BLOCKS()); $result->register("record_cat", fn() => Items::RECORD_CAT()); $result->register("record_chirp", fn() => Items::RECORD_CHIRP()); + $result->register("record_creator", fn() => Items::RECORD_CREATOR()); + $result->register("record_creator_music_box", fn() => Items::RECORD_CREATOR_MUSIC_BOX()); $result->register("record_far", fn() => Items::RECORD_FAR()); $result->register("record_mall", fn() => Items::RECORD_MALL()); $result->register("record_mellohi", fn() => Items::RECORD_MELLOHI()); $result->register("record_otherside", fn() => Items::RECORD_OTHERSIDE()); $result->register("record_pigstep", fn() => Items::RECORD_PIGSTEP()); + $result->register("record_precipice", fn() => Items::RECORD_PRECIPICE()); + $result->register("record_relic", fn() => Items::RECORD_RELIC()); $result->register("record_stal", fn() => Items::RECORD_STAL()); $result->register("record_strad", fn() => Items::RECORD_STRAD()); $result->register("record_wait", fn() => Items::RECORD_WAIT()); $result->register("record_ward", fn() => Items::RECORD_WARD()); + $result->register("recovery_compass", fn() => Items::RECOVERY_COMPASS()); $result->register("redstone", fn() => Items::REDSTONE_DUST()); $result->register("redstone_dust", fn() => Items::REDSTONE_DUST()); + $result->register("resin_brick", fn() => Items::RESIN_BRICK()); $result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("rotten_flesh", fn() => Items::ROTTEN_FLESH()); $result->register("salmon", fn() => Items::RAW_SALMON()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 5115ee48a8..f76cf369f4 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -33,11 +33,12 @@ 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\CloningRegistryTrait; use pocketmine\world\World; +use function is_int; +use function mb_strtoupper; use function strtolower; /** @@ -157,6 +158,7 @@ use function strtolower; * @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() @@ -171,6 +173,7 @@ use function strtolower; * @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() @@ -189,6 +192,7 @@ use function strtolower; * @method static Item HONEYCOMB() * @method static HoneyBottle HONEY_BOTTLE() * @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE() + * @method static IceBomb ICE_BOMB() * @method static Item INK_SAC() * @method static Axe IRON_AXE() * @method static Armor IRON_BOOTS() @@ -239,6 +243,7 @@ use function strtolower; * @method static Boat OAK_BOAT() * @method static ItemBlockWallOrFloor OAK_SIGN() * @method static PaintingItem PAINTING() + * @method static ItemBlockWallOrFloor PALE_OAK_SIGN() * @method static Item PAPER() * @method static Item PHANTOM_MEMBRANE() * @method static PitcherPod PITCHER_POD() @@ -271,16 +276,22 @@ use function strtolower; * @method static Record RECORD_BLOCKS() * @method static Record RECORD_CAT() * @method static Record RECORD_CHIRP() + * @method static Record RECORD_CREATOR() + * @method static Record RECORD_CREATOR_MUSIC_BOX() * @method static Record RECORD_FAR() * @method static Record RECORD_MALL() * @method static Record RECORD_MELLOHI() * @method static Record RECORD_OTHERSIDE() * @method static Record RECORD_PIGSTEP() + * @method static Record RECORD_PRECIPICE() + * @method static Record RECORD_RELIC() * @method static Record RECORD_STAL() * @method static Record RECORD_STRAD() * @method static Record RECORD_WAIT() * @method static Record RECORD_WARD() + * @method static Item RECOVERY_COMPASS() * @method static Redstone REDSTONE_DUST() + * @method static Item RESIN_BRICK() * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static RottenFlesh ROTTEN_FLESH() * @method static Item SCUTE() @@ -339,8 +350,29 @@ final class VanillaItems{ //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; } /** @@ -360,242 +392,246 @@ final class VanillaItems{ self::registerTierToolItems(); self::registerSmithingTemplates(); - self::register("air", Blocks::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", [EnchantmentTags::ALL])); - self::register("bow", new Bow(new IID(Ids::BOW), "Bow", [EnchantmentTags::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("cherry_sign", new ItemBlockWallOrFloor(new IID(Ids::CHERRY_SIGN), Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN())); - 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", [EnchantmentTags::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_book", new EnchantedBook(new IID(Ids::ENCHANTED_BOOK), "Enchanted Book", [EnchantmentTags::ALL])); - 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", [EnchantmentTags::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", [EnchantmentTags::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("name_tag", new NameTag(new IID(Ids::NAME_TAG), "Name Tag")); - 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("ice_bomb", fn(IID $id) => new IceBomb($id, "Ice Bomb")); + 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("pitcher_pod", new PitcherPod(new IID(Ids::PITCHER_POD), "Pitcher Pod")); - 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", [EnchantmentTags::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("torchflower_seeds", new TorchflowerSeeds(new IID(Ids::TORCHFLOWER_SEEDS), "Torchflower Seeds")); - 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("pale_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::PALE_OAK_SIGN(), Blocks::PALE_OAK_WALL_SIGN())); + self::register("paper", fn(IID $id) => new Item($id, "Paper")); + self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane")); + self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod")); + 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_creator", fn(IID $id) => new Record($id, RecordType::DISK_CREATOR, "Record Creator")); + self::register("record_creator_music_box", fn(IID $id) => new Record($id, RecordType::DISK_CREATOR_MUSIC_BOX, "Record Creator (Music Box)")); + self::register("record_far", fn(IID $id) => new Record($id, RecordType::DISK_FAR, "Record Far")); + self::register("record_mall", fn(IID $id) => new Record($id, RecordType::DISK_MALL, "Record Mall")); + self::register("record_mellohi", fn(IID $id) => new Record($id, RecordType::DISK_MELLOHI, "Record Mellohi")); + self::register("record_otherside", fn(IID $id) => new Record($id, RecordType::DISK_OTHERSIDE, "Record Otherside")); + self::register("record_pigstep", fn(IID $id) => new Record($id, RecordType::DISK_PIGSTEP, "Record Pigstep")); + self::register("record_precipice", fn(IID $id) => new Record($id, RecordType::DISK_PRECIPICE, "Record Precipice")); + self::register("record_relic", fn(IID $id) => new Record($id, RecordType::DISK_RELIC, "Record Relic")); + self::register("record_stal", fn(IID $id) => new Record($id, RecordType::DISK_STAL, "Record Stal")); + self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad")); + self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait")); + self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward")); + self::register("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass")); + self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone")); + self::register("resin_brick", fn(IID $id) => new Item($id, "Resin Brick")); + self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh")); + self::register("scute", fn(IID $id) => new Item($id, "Scute")); + self::register("shears", fn(IID $id) => new Shears($id, "Shears", [EnchantmentTags::SHEARS])); + 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::cases() as $type){ //boat type is static, because different types of wood may have different properties - self::register(strtolower($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, - }), $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{ + 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{ + 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)); } @@ -603,87 +639,87 @@ final class VanillaItems{ } private static function registerTierToolItems() : void{ - self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND, [EnchantmentTags::AXE])); - self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD, [EnchantmentTags::AXE])); - self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON, [EnchantmentTags::AXE])); - self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE, [EnchantmentTags::AXE])); - self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE, [EnchantmentTags::AXE])); - self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD, [EnchantmentTags::AXE])); - self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND, [EnchantmentTags::HOE])); - self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD, [EnchantmentTags::HOE])); - self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON, [EnchantmentTags::HOE])); - self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE, [EnchantmentTags::HOE])); - self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE, [EnchantmentTags::HOE])); - self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD, [EnchantmentTags::HOE])); - self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND, [EnchantmentTags::PICKAXE])); - self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD, [EnchantmentTags::PICKAXE])); - self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON, [EnchantmentTags::PICKAXE])); - self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE, [EnchantmentTags::PICKAXE])); - self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE, [EnchantmentTags::PICKAXE])); - self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD, [EnchantmentTags::PICKAXE])); - self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND, [EnchantmentTags::SHOVEL])); - self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD, [EnchantmentTags::SHOVEL])); - self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON, [EnchantmentTags::SHOVEL])); - self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE, [EnchantmentTags::SHOVEL])); - self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE, [EnchantmentTags::SHOVEL])); - self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD, [EnchantmentTags::SHOVEL])); - self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND, [EnchantmentTags::SWORD])); - self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD, [EnchantmentTags::SWORD])); - self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON, [EnchantmentTags::SWORD])); - self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE, [EnchantmentTags::SWORD])); - self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE, [EnchantmentTags::SWORD])); - self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD, [EnchantmentTags::SWORD])); + 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, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::BOOTS])); - self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::BOOTS])); - self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [EnchantmentTags::BOOTS])); - self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [EnchantmentTags::BOOTS])); - self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [EnchantmentTags::BOOTS])); - self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::BOOTS])); + 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, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::CHESTPLATE])); - self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::CHESTPLATE])); - self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [EnchantmentTags::CHESTPLATE])); - self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [EnchantmentTags::CHESTPLATE])); - self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [EnchantmentTags::CHESTPLATE])); - self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::CHESTPLATE])); + 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, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::HELMET])); - self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::HELMET])); - self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [EnchantmentTags::HELMET])); - self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [EnchantmentTags::HELMET])); - self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [EnchantmentTags::HELMET])); - self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::HELMET])); - self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [EnchantmentTags::HELMET])); + 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", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::LEGGINGS])); - self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::LEGGINGS])); - self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [EnchantmentTags::LEGGINGS])); - self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [EnchantmentTags::LEGGINGS])); - self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [EnchantmentTags::LEGGINGS])); - self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS])); + 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])); } private static function registerSmithingTemplates() : void{ - self::register("netherite_upgrade_smithing_template", new Item(new IID(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE), "Netherite Upgrade Smithing Template")); - self::register("coast_armor_trim_smithing_template", new Item(new IID(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE), "Coast Armor Trim Smithing Template")); - self::register("dune_armor_trim_smithing_template", new Item(new IID(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE), "Dune Armor Trim Smithing Template")); - self::register("eye_armor_trim_smithing_template", new Item(new IID(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE), "Eye Armor Trim Smithing Template")); - self::register("host_armor_trim_smithing_template", new Item(new IID(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE), "Host Armor Trim Smithing Template")); - self::register("raiser_armor_trim_smithing_template", new Item(new IID(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE), "Raiser Armor Trim Smithing Template")); - self::register("rib_armor_trim_smithing_template", new Item(new IID(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE), "Rib Armor Trim Smithing Template")); - self::register("sentry_armor_trim_smithing_template", new Item(new IID(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE), "Sentry Armor Trim Smithing Template")); - self::register("shaper_armor_trim_smithing_template", new Item(new IID(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE), "Shaper Armor Trim Smithing Template")); - self::register("silence_armor_trim_smithing_template", new Item(new IID(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE), "Silence Armor Trim Smithing Template")); - self::register("snout_armor_trim_smithing_template", new Item(new IID(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE), "Snout Armor Trim Smithing Template")); - self::register("spire_armor_trim_smithing_template", new Item(new IID(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE), "Spire Armor Trim Smithing Template")); - self::register("tide_armor_trim_smithing_template", new Item(new IID(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE), "Tide Armor Trim Smithing Template")); - self::register("vex_armor_trim_smithing_template", new Item(new IID(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE), "Vex Armor Trim Smithing Template")); - self::register("ward_armor_trim_smithing_template", new Item(new IID(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE), "Ward Armor Trim Smithing Template")); - self::register("wayfinder_armor_trim_smithing_template", new Item(new IID(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE), "Wayfinder Armor Trim Smithing Template")); - self::register("wild_armor_trim_smithing_template", new Item(new IID(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE), "Wild Armor Trim Smithing Template")); + 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/WritableBookBase.php b/src/item/WritableBookBase.php index 6b7e55468c..d3b9b70610 100644 --- a/src/item/WritableBookBase.php +++ b/src/item/WritableBookBase.php @@ -101,8 +101,9 @@ abstract class WritableBookBase extends Item{ * @return $this */ public function deletePage(int $pageId) : self{ - unset($this->pages[$pageId]); - $this->pages = array_values($this->pages); + $newPages = $this->pages; + unset($newPages[$pageId]); + $this->pages = array_values($newPages); return $this; } diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php index cae94c666f..eed7bff527 100644 --- a/src/item/enchantment/AvailableEnchantmentRegistry.php +++ b/src/item/enchantment/AvailableEnchantmentRegistry.php @@ -57,6 +57,7 @@ final class AvailableEnchantmentRegistry{ $this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]); $this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []); $this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []); + $this->register(Enchantments::FROST_WALKER(), [/* no primary items */], [Tags::BOOTS]); $this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []); $this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []); $this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []); diff --git a/src/item/enchantment/ItemEnchantmentTagRegistry.php b/src/item/enchantment/ItemEnchantmentTagRegistry.php index 210cd8e864..b239f18a28 100644 --- a/src/item/enchantment/ItemEnchantmentTagRegistry.php +++ b/src/item/enchantment/ItemEnchantmentTagRegistry.php @@ -32,6 +32,7 @@ use function array_merge; use function array_search; use function array_shift; use function array_unique; +use function array_values; use function count; /** @@ -103,7 +104,8 @@ final class ItemEnchantmentTagRegistry{ foreach(Utils::stringifyKeys($this->tagMap) as $key => $nestedTags){ if(($nestedKey = array_search($tag, $nestedTags, true)) !== false){ - unset($this->tagMap[$key][$nestedKey]); + unset($nestedTags[$nestedKey]); + $this->tagMap[$key] = array_values($nestedTags); } } } @@ -115,7 +117,7 @@ final class ItemEnchantmentTagRegistry{ */ public function removeNested(string $tag, array $nestedTags) : void{ $this->assertNotInternalTag($tag); - $this->tagMap[$tag] = array_diff($this->tagMap[$tag], $nestedTags); + $this->tagMap[$tag] = array_values(array_diff($this->tagMap[$tag], $nestedTags)); } /** diff --git a/src/item/enchantment/ProtectionEnchantment.php b/src/item/enchantment/ProtectionEnchantment.php index be78a23065..8174668754 100644 --- a/src/item/enchantment/ProtectionEnchantment.php +++ b/src/item/enchantment/ProtectionEnchantment.php @@ -25,18 +25,22 @@ namespace pocketmine\item\enchantment; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\lang\Translatable; -use function array_flip; +use function array_fill_keys; use function floor; class ProtectionEnchantment extends Enchantment{ protected float $typeModifier; - /** @var int[]|null */ + /** + * @var true[]|null + * @phpstan-var array + */ protected ?array $applicableDamageTypes = null; /** * ProtectionEnchantment constructor. * * @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower + * @phpstan-param list|null $applicableDamageTypes * * @param int $primaryItemFlags @deprecated * @param int $secondaryItemFlags @deprecated @@ -48,7 +52,7 @@ class ProtectionEnchantment extends Enchantment{ $this->typeModifier = $typeModifier; if($applicableDamageTypes !== null){ - $this->applicableDamageTypes = array_flip($applicableDamageTypes); + $this->applicableDamageTypes = array_fill_keys($applicableDamageTypes, true); } } diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php index 47a750ff27..b6763e4912 100644 --- a/src/item/enchantment/StringToEnchantmentParser.php +++ b/src/item/enchantment/StringToEnchantmentParser.php @@ -44,6 +44,7 @@ final class StringToEnchantmentParser extends StringToTParser{ $result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION()); $result->register("flame", fn() => VanillaEnchantments::FLAME()); $result->register("fortune", fn() => VanillaEnchantments::FORTUNE()); + $result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER()); $result->register("infinity", fn() => VanillaEnchantments::INFINITY()); $result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK()); $result->register("mending", fn() => VanillaEnchantments::MENDING()); diff --git a/src/item/enchantment/VanillaEnchantments.php b/src/item/enchantment/VanillaEnchantments.php index 19ce397163..1132dc9c66 100644 --- a/src/item/enchantment/VanillaEnchantments.php +++ b/src/item/enchantment/VanillaEnchantments.php @@ -41,6 +41,7 @@ use pocketmine\utils\RegistryTrait; * @method static ProtectionEnchantment FIRE_PROTECTION() * @method static Enchantment FLAME() * @method static Enchantment FORTUNE() + * @method static Enchantment FROST_WALKER() * @method static Enchantment INFINITY() * @method static KnockbackEnchantment KNOCKBACK() * @method static Enchantment MENDING() @@ -145,6 +146,16 @@ final class VanillaEnchantments{ fn(int $level) : int => 10 * $level, 30 )); + + self::register("FROST_WALKER", new Enchantment( + KnownTranslationFactory::enchantment_frostwalker(), + Rarity::RARE, + 0, + 0, + 2, + fn(int $level) : int => 10 * $level, + 15 + )); self::register("AQUA_AFFINITY", new Enchantment( KnownTranslationFactory::enchantment_waterWorker(), Rarity::RARE, diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php index ea8c2952e5..4e42419ea5 100644 --- a/src/lang/KnownTranslationFactory.php +++ b/src/lang/KnownTranslationFactory.php @@ -603,6 +603,31 @@ final class KnownTranslationFactory{ 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 @@ final class KnownTranslationFactory{ ]); } + 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, @@ -866,6 +897,18 @@ final class KnownTranslationFactory{ return new Translatable(KnownTranslationKeys::ENCHANTMENT_FROSTWALKER, []); } + public static function enchantment_heavy_weapon_breach() : Translatable{ + return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_BREACH, []); + } + + public static function enchantment_heavy_weapon_density() : Translatable{ + return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_DENSITY, []); + } + + public static function enchantment_heavy_weapon_windburst() : Translatable{ + return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_WINDBURST, []); + } + public static function enchantment_knockback() : Translatable{ return new Translatable(KnownTranslationKeys::ENCHANTMENT_KNOCKBACK, []); } @@ -1025,6 +1068,14 @@ final class KnownTranslationFactory{ 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 +1096,14 @@ final class KnownTranslationFactory{ 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, []); } @@ -1061,6 +1120,318 @@ final class KnownTranslationFactory{ return new Translatable(KnownTranslationKeys::ITEM_RECORD_WARD_DESC, []); } + public static function itemGroup_name_anvil() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ANVIL, []); + } + + public static function itemGroup_name_arrow() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ARROW, []); + } + + public static function itemGroup_name_axe() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_AXE, []); + } + + public static function itemGroup_name_banner() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BANNER, []); + } + + public static function itemGroup_name_banner_pattern() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BANNER_PATTERN, []); + } + + public static function itemGroup_name_bed() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BED, []); + } + + public static function itemGroup_name_boat() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BOAT, []); + } + + public static function itemGroup_name_boots() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BOOTS, []); + } + + public static function itemGroup_name_bundles() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BUNDLES, []); + } + + public static function itemGroup_name_buttons() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BUTTONS, []); + } + + public static function itemGroup_name_candles() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CANDLES, []); + } + + public static function itemGroup_name_chalkboard() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHALKBOARD, []); + } + + public static function itemGroup_name_chest() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHEST, []); + } + + public static function itemGroup_name_chestboat() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHESTBOAT, []); + } + + public static function itemGroup_name_chestplate() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHESTPLATE, []); + } + + public static function itemGroup_name_compounds() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_COMPOUNDS, []); + } + + public static function itemGroup_name_concrete() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CONCRETE, []); + } + + public static function itemGroup_name_concretePowder() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CONCRETEPOWDER, []); + } + + public static function itemGroup_name_cookedFood() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_COOKEDFOOD, []); + } + + public static function itemGroup_name_coral() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CORAL, []); + } + + public static function itemGroup_name_coral_decorations() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CORAL_DECORATIONS, []); + } + + public static function itemGroup_name_crop() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CROP, []); + } + + public static function itemGroup_name_door() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_DOOR, []); + } + + public static function itemGroup_name_dye() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_DYE, []); + } + + public static function itemGroup_name_enchantedBook() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ENCHANTEDBOOK, []); + } + + public static function itemGroup_name_fence() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FENCE, []); + } + + public static function itemGroup_name_fenceGate() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FENCEGATE, []); + } + + public static function itemGroup_name_firework() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FIREWORK, []); + } + + public static function itemGroup_name_fireworkStars() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FIREWORKSTARS, []); + } + + public static function itemGroup_name_flower() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FLOWER, []); + } + + public static function itemGroup_name_glass() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLASS, []); + } + + public static function itemGroup_name_glassPane() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLASSPANE, []); + } + + public static function itemGroup_name_glazedTerracotta() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLAZEDTERRACOTTA, []); + } + + public static function itemGroup_name_goatHorn() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GOATHORN, []); + } + + public static function itemGroup_name_grass() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GRASS, []); + } + + public static function itemGroup_name_helmet() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HELMET, []); + } + + public static function itemGroup_name_hoe() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HOE, []); + } + + public static function itemGroup_name_horseArmor() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HORSEARMOR, []); + } + + public static function itemGroup_name_leaves() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LEAVES, []); + } + + public static function itemGroup_name_leggings() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LEGGINGS, []); + } + + public static function itemGroup_name_lingeringPotion() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LINGERINGPOTION, []); + } + + public static function itemGroup_name_log() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LOG, []); + } + + public static function itemGroup_name_minecart() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MINECART, []); + } + + public static function itemGroup_name_miscFood() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MISCFOOD, []); + } + + public static function itemGroup_name_mobEgg() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MOBEGG, []); + } + + public static function itemGroup_name_monsterStoneEgg() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MONSTERSTONEEGG, []); + } + + public static function itemGroup_name_mushroom() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MUSHROOM, []); + } + + public static function itemGroup_name_netherWartBlock() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_NETHERWARTBLOCK, []); + } + + public static function itemGroup_name_ominousBottle() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_OMINOUSBOTTLE, []); + } + + public static function itemGroup_name_ore() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ORE, []); + } + + public static function itemGroup_name_permission() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PERMISSION, []); + } + + public static function itemGroup_name_pickaxe() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PICKAXE, []); + } + + public static function itemGroup_name_planks() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PLANKS, []); + } + + public static function itemGroup_name_potion() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_POTION, []); + } + + public static function itemGroup_name_pressurePlate() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PRESSUREPLATE, []); + } + + public static function itemGroup_name_products() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PRODUCTS, []); + } + + public static function itemGroup_name_rail() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RAIL, []); + } + + public static function itemGroup_name_rawFood() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RAWFOOD, []); + } + + public static function itemGroup_name_record() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RECORD, []); + } + + public static function itemGroup_name_sandstone() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SANDSTONE, []); + } + + public static function itemGroup_name_sapling() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SAPLING, []); + } + + public static function itemGroup_name_seed() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SEED, []); + } + + public static function itemGroup_name_shovel() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SHOVEL, []); + } + + public static function itemGroup_name_shulkerBox() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SHULKERBOX, []); + } + + public static function itemGroup_name_sign() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SIGN, []); + } + + public static function itemGroup_name_skull() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SKULL, []); + } + + public static function itemGroup_name_slab() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SLAB, []); + } + + public static function itemGroup_name_splashPotion() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SPLASHPOTION, []); + } + + public static function itemGroup_name_stainedClay() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STAINEDCLAY, []); + } + + public static function itemGroup_name_stairs() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STAIRS, []); + } + + public static function itemGroup_name_stone() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STONE, []); + } + + public static function itemGroup_name_stoneBrick() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STONEBRICK, []); + } + + public static function itemGroup_name_sword() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SWORD, []); + } + + public static function itemGroup_name_trapdoor() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_TRAPDOOR, []); + } + + public static function itemGroup_name_walls() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WALLS, []); + } + + public static function itemGroup_name_wood() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOD, []); + } + + public static function itemGroup_name_wool() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOL, []); + } + + public static function itemGroup_name_woolCarpet() : Translatable{ + return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOLCARPET, []); + } + public static function kick_admin() : Translatable{ return new Translatable(KnownTranslationKeys::KICK_ADMIN, []); } @@ -1394,6 +1765,10 @@ final class KnownTranslationFactory{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []); } + public static function pocketmine_command_timings_collect() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_COLLECT, []); + } + public static function pocketmine_command_timings_description() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []); } @@ -1536,6 +1911,14 @@ final class KnownTranslationFactory{ 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 +2439,14 @@ final class KnownTranslationFactory{ 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 c834527306..6fbb32ecbb 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"; @@ -189,6 +194,9 @@ final class KnownTranslationKeys{ public const ENCHANTMENT_FIRE = "enchantment.fire"; public const ENCHANTMENT_FISHINGSPEED = "enchantment.fishingSpeed"; public const ENCHANTMENT_FROSTWALKER = "enchantment.frostwalker"; + public const ENCHANTMENT_HEAVY_WEAPON_BREACH = "enchantment.heavy_weapon.breach"; + public const ENCHANTMENT_HEAVY_WEAPON_DENSITY = "enchantment.heavy_weapon.density"; + public const ENCHANTMENT_HEAVY_WEAPON_WINDBURST = "enchantment.heavy_weapon.windburst"; public const ENCHANTMENT_KNOCKBACK = "enchantment.knockback"; public const ENCHANTMENT_LOOTBONUS = "enchantment.lootBonus"; public const ENCHANTMENT_LOOTBONUSDIGGER = "enchantment.lootBonusDigger"; @@ -227,15 +235,97 @@ 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"; public const ITEM_RECORD_WARD_DESC = "item.record_ward.desc"; + public const ITEMGROUP_NAME_ANVIL = "itemGroup.name.anvil"; + public const ITEMGROUP_NAME_ARROW = "itemGroup.name.arrow"; + public const ITEMGROUP_NAME_AXE = "itemGroup.name.axe"; + public const ITEMGROUP_NAME_BANNER = "itemGroup.name.banner"; + public const ITEMGROUP_NAME_BANNER_PATTERN = "itemGroup.name.banner_pattern"; + public const ITEMGROUP_NAME_BED = "itemGroup.name.bed"; + public const ITEMGROUP_NAME_BOAT = "itemGroup.name.boat"; + public const ITEMGROUP_NAME_BOOTS = "itemGroup.name.boots"; + public const ITEMGROUP_NAME_BUNDLES = "itemGroup.name.bundles"; + public const ITEMGROUP_NAME_BUTTONS = "itemGroup.name.buttons"; + public const ITEMGROUP_NAME_CANDLES = "itemGroup.name.candles"; + public const ITEMGROUP_NAME_CHALKBOARD = "itemGroup.name.chalkboard"; + public const ITEMGROUP_NAME_CHEST = "itemGroup.name.chest"; + public const ITEMGROUP_NAME_CHESTBOAT = "itemGroup.name.chestboat"; + public const ITEMGROUP_NAME_CHESTPLATE = "itemGroup.name.chestplate"; + public const ITEMGROUP_NAME_COMPOUNDS = "itemGroup.name.compounds"; + public const ITEMGROUP_NAME_CONCRETE = "itemGroup.name.concrete"; + public const ITEMGROUP_NAME_CONCRETEPOWDER = "itemGroup.name.concretePowder"; + public const ITEMGROUP_NAME_COOKEDFOOD = "itemGroup.name.cookedFood"; + public const ITEMGROUP_NAME_CORAL = "itemGroup.name.coral"; + public const ITEMGROUP_NAME_CORAL_DECORATIONS = "itemGroup.name.coral_decorations"; + public const ITEMGROUP_NAME_CROP = "itemGroup.name.crop"; + public const ITEMGROUP_NAME_DOOR = "itemGroup.name.door"; + public const ITEMGROUP_NAME_DYE = "itemGroup.name.dye"; + public const ITEMGROUP_NAME_ENCHANTEDBOOK = "itemGroup.name.enchantedBook"; + public const ITEMGROUP_NAME_FENCE = "itemGroup.name.fence"; + public const ITEMGROUP_NAME_FENCEGATE = "itemGroup.name.fenceGate"; + public const ITEMGROUP_NAME_FIREWORK = "itemGroup.name.firework"; + public const ITEMGROUP_NAME_FIREWORKSTARS = "itemGroup.name.fireworkStars"; + public const ITEMGROUP_NAME_FLOWER = "itemGroup.name.flower"; + public const ITEMGROUP_NAME_GLASS = "itemGroup.name.glass"; + public const ITEMGROUP_NAME_GLASSPANE = "itemGroup.name.glassPane"; + public const ITEMGROUP_NAME_GLAZEDTERRACOTTA = "itemGroup.name.glazedTerracotta"; + public const ITEMGROUP_NAME_GOATHORN = "itemGroup.name.goatHorn"; + public const ITEMGROUP_NAME_GRASS = "itemGroup.name.grass"; + public const ITEMGROUP_NAME_HELMET = "itemGroup.name.helmet"; + public const ITEMGROUP_NAME_HOE = "itemGroup.name.hoe"; + public const ITEMGROUP_NAME_HORSEARMOR = "itemGroup.name.horseArmor"; + public const ITEMGROUP_NAME_LEAVES = "itemGroup.name.leaves"; + public const ITEMGROUP_NAME_LEGGINGS = "itemGroup.name.leggings"; + public const ITEMGROUP_NAME_LINGERINGPOTION = "itemGroup.name.lingeringPotion"; + public const ITEMGROUP_NAME_LOG = "itemGroup.name.log"; + public const ITEMGROUP_NAME_MINECART = "itemGroup.name.minecart"; + public const ITEMGROUP_NAME_MISCFOOD = "itemGroup.name.miscFood"; + public const ITEMGROUP_NAME_MOBEGG = "itemGroup.name.mobEgg"; + public const ITEMGROUP_NAME_MONSTERSTONEEGG = "itemGroup.name.monsterStoneEgg"; + public const ITEMGROUP_NAME_MUSHROOM = "itemGroup.name.mushroom"; + public const ITEMGROUP_NAME_NETHERWARTBLOCK = "itemGroup.name.netherWartBlock"; + public const ITEMGROUP_NAME_OMINOUSBOTTLE = "itemGroup.name.ominousBottle"; + public const ITEMGROUP_NAME_ORE = "itemGroup.name.ore"; + public const ITEMGROUP_NAME_PERMISSION = "itemGroup.name.permission"; + public const ITEMGROUP_NAME_PICKAXE = "itemGroup.name.pickaxe"; + public const ITEMGROUP_NAME_PLANKS = "itemGroup.name.planks"; + public const ITEMGROUP_NAME_POTION = "itemGroup.name.potion"; + public const ITEMGROUP_NAME_PRESSUREPLATE = "itemGroup.name.pressurePlate"; + public const ITEMGROUP_NAME_PRODUCTS = "itemGroup.name.products"; + public const ITEMGROUP_NAME_RAIL = "itemGroup.name.rail"; + public const ITEMGROUP_NAME_RAWFOOD = "itemGroup.name.rawFood"; + public const ITEMGROUP_NAME_RECORD = "itemGroup.name.record"; + public const ITEMGROUP_NAME_SANDSTONE = "itemGroup.name.sandstone"; + public const ITEMGROUP_NAME_SAPLING = "itemGroup.name.sapling"; + public const ITEMGROUP_NAME_SEED = "itemGroup.name.seed"; + public const ITEMGROUP_NAME_SHOVEL = "itemGroup.name.shovel"; + public const ITEMGROUP_NAME_SHULKERBOX = "itemGroup.name.shulkerBox"; + public const ITEMGROUP_NAME_SIGN = "itemGroup.name.sign"; + public const ITEMGROUP_NAME_SKULL = "itemGroup.name.skull"; + public const ITEMGROUP_NAME_SLAB = "itemGroup.name.slab"; + public const ITEMGROUP_NAME_SPLASHPOTION = "itemGroup.name.splashPotion"; + public const ITEMGROUP_NAME_STAINEDCLAY = "itemGroup.name.stainedClay"; + public const ITEMGROUP_NAME_STAIRS = "itemGroup.name.stairs"; + public const ITEMGROUP_NAME_STONE = "itemGroup.name.stone"; + public const ITEMGROUP_NAME_STONEBRICK = "itemGroup.name.stoneBrick"; + public const ITEMGROUP_NAME_SWORD = "itemGroup.name.sword"; + public const ITEMGROUP_NAME_TRAPDOOR = "itemGroup.name.trapdoor"; + public const ITEMGROUP_NAME_WALLS = "itemGroup.name.walls"; + public const ITEMGROUP_NAME_WOOD = "itemGroup.name.wood"; + public const ITEMGROUP_NAME_WOOL = "itemGroup.name.wool"; + public const ITEMGROUP_NAME_WOOLCARPET = "itemGroup.name.woolCarpet"; public const KICK_ADMIN = "kick.admin"; public const KICK_ADMIN_REASON = "kick.admin.reason"; public const KICK_REASON_CHEAT = "kick.reason.cheat"; @@ -306,6 +396,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description"; public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage"; public const POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED = "pocketmine.command.timings.alreadyEnabled"; + public const POCKETMINE_COMMAND_TIMINGS_COLLECT = "pocketmine.command.timings.collect"; public const POCKETMINE_COMMAND_TIMINGS_DESCRIPTION = "pocketmine.command.timings.description"; public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable"; public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable"; @@ -336,6 +427,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 +542,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/lang/Language.php b/src/lang/Language.php index a871c820ae..59a309524c 100644 --- a/src/lang/Language.php +++ b/src/lang/Language.php @@ -71,7 +71,7 @@ class Language{ foreach($files as $file){ try{ - $code = explode(".", $file)[0]; + $code = explode(".", $file, limit: 2)[0]; $strings = self::loadLang($path, $code); if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){ $result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME]; @@ -147,7 +147,7 @@ class Language{ $baseText = $this->parseTranslation($str, $onlyPrefix); } - foreach($params as $i => $p){ + foreach(Utils::promoteKeys($params) as $i => $p){ $replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p; $baseText = str_replace("{%$i}", $replacement, $baseText); } @@ -161,7 +161,7 @@ class Language{ $baseText = $this->parseTranslation($c->getText()); } - foreach($c->getParameters() as $i => $p){ + foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){ $replacement = $p instanceof Translatable ? $this->translate($p) : $p; $baseText = str_replace("{%$i}", $replacement, $baseText); } diff --git a/src/lang/Translatable.php b/src/lang/Translatable.php index 827a11657b..8dee8f4773 100644 --- a/src/lang/Translatable.php +++ b/src/lang/Translatable.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace pocketmine\lang; +use pocketmine\utils\Utils; + final class Translatable{ /** @var string[]|Translatable[] $params */ protected array $params = []; @@ -34,7 +36,7 @@ final class Translatable{ protected string $text, array $params = [] ){ - foreach($params as $k => $param){ + foreach(Utils::promoteKeys($params) as $k => $param){ if(!($param instanceof Translatable)){ $this->params[$k] = (string) $param; }else{ diff --git a/src/network/NetworkSessionManager.php b/src/network/NetworkSessionManager.php index d8ff7fe038..aecbc646d9 100644 --- a/src/network/NetworkSessionManager.php +++ b/src/network/NetworkSessionManager.php @@ -30,10 +30,16 @@ use function spl_object_id; class NetworkSessionManager{ - /** @var NetworkSession[] */ + /** + * @var NetworkSession[] + * @phpstan-var array + */ private array $sessions = []; - /** @var NetworkSession[] */ + /** + * @var NetworkSession[] + * @phpstan-var array + */ private array $pendingLoginSessions = []; /** diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 16898283a9..2ff23a73a4 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -54,6 +54,7 @@ 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; @@ -363,6 +364,7 @@ class InventoryManager{ 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, @@ -500,16 +502,18 @@ class InventoryManager{ $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, + new FullContainerName($this->lastInventoryNetworkId), new ItemStackWrapper(0, ItemStack::null()), - 0 + new ItemStackWrapper(0, ItemStack::null()) )); } //now send the real contents $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, - $itemStackWrapper, - 0 + new FullContainerName($this->lastInventoryNetworkId), + new ItemStackWrapper(0, ItemStack::null()), + $itemStackWrapper )); } @@ -528,10 +532,11 @@ class InventoryManager{ $this->session->sendDataPacket(InventoryContentPacket::create( $windowId, array_fill_keys(array_keys($itemStackWrappers), new ItemStackWrapper(0, ItemStack::null())), - 0 + new FullContainerName($this->lastInventoryNetworkId), + new ItemStackWrapper(0, ItemStack::null()) )); //now send the real contents - $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $itemStackWrappers, 0)); + $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{ @@ -587,7 +592,6 @@ class InventoryManager{ $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; @@ -686,11 +690,12 @@ class InventoryManager{ } public function syncCreative() : void{ - $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory())); + $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->buildPacket($this->player->getCreativeInventory(), $this->session)); } /** * @param EnchantingOption[] $options + * @phpstan-param list $options */ public function syncEnchantingTableOptions(array $options) : void{ $protocolOptions = []; diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index 259a602d48..987ed6e61c 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -72,9 +72,11 @@ final class JwtUtils{ * @throws JwtException */ public static function split(string $jwt) : array{ - $v = explode(".", $jwt); + //limit of 4 allows us to detect too many parts without having to split the string up into a potentially large + //number of parts + $v = explode(".", $jwt, limit: 4); if(count($v) !== 3){ - throw new JwtException("Expected exactly 3 JWT parts, got " . count($v)); + throw new JwtException("Expected exactly 3 JWT parts delimited by a period"); } return [$v[0], $v[1], $v[2]]; //workaround phpstan bug } diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 33a9303a30..8c457ed405 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -30,6 +30,7 @@ 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; @@ -65,6 +66,7 @@ use pocketmine\network\mcpe\protocol\Packet; 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; @@ -111,10 +113,11 @@ use pocketmine\utils\BinaryDataException; use pocketmine\utils\BinaryStream; use pocketmine\utils\ObjectSet; use pocketmine\utils\TextFormat; +use pocketmine\world\format\io\GlobalItemDataHandlers; use pocketmine\world\Position; +use pocketmine\world\World; use pocketmine\YmlServerProperties; use function array_map; -use function array_values; use function base64_encode; use function bin2hex; use function count; @@ -160,7 +163,10 @@ class NetworkSession{ private ?EncryptionContext $cipher = null; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $sendBuffer = []; /** * @var PromiseResolver[] @@ -540,6 +546,7 @@ class NetworkSession{ * @phpstan-return Promise */ public function sendDataPacketWithReceipt(ClientboundPacket $packet, bool $immediate = false) : Promise{ + /** @phpstan-var PromiseResolver $resolver */ $resolver = new PromiseResolver(); if(!$this->sendDataPacketInternal($packet, $immediate, $resolver)){ @@ -787,7 +794,7 @@ class NetworkSession{ 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); } @@ -1051,8 +1058,7 @@ class NetworkSession{ ]; $layers = [ - //TODO: dynamic flying speed! FINALLY!!!!!!!!!!!!!!!!! - new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1), + new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 1, 0.1), ]; if(!$for->hasBlockCollision()){ //TODO: HACK! In 1.19.80, the client starts falling in our faux spectator mode when it clips into a @@ -1062,7 +1068,7 @@ class NetworkSession{ $layers[] = new AbilitiesLayer(AbilitiesLayer::LAYER_SPECTATOR, [ AbilitiesLayer::ABILITY_FLYING => true, - ], null, null); + ], null, null, null); } $this->sendDataPacket(UpdateAbilitiesPacket::create(new AbilitiesData( @@ -1089,7 +1095,7 @@ class NetworkSession{ public function syncAvailableCommands() : void{ $commandData = []; - foreach($this->server->getCommandMap()->getCommands() as $name => $command){ + foreach($this->server->getCommandMap()->getCommands() as $command){ if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){ continue; } @@ -1102,7 +1108,7 @@ class NetworkSession{ //work around a client bug which makes the original name not show when aliases are used $aliases[] = $lname; } - $aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", array_values($aliases)); + $aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", $aliases); } $description = $command->getDescription(); @@ -1175,6 +1181,19 @@ class NetworkSession{ $this->sendDataPacket(ClientboundCloseFormPacket::create()); } + /** + * @phpstan-param \Closure() : void $onCompletion + */ + private function sendChunkPacket(string $chunkPacket, \Closure $onCompletion, World $world) : void{ + $world->timings->syncChunkSend->startTiming(); + try{ + $this->queueCompressed($chunkPacket); + $onCompletion(); + }finally{ + $world->timings->syncChunkSend->stopTiming(); + } + } + /** * Instructs the networksession to start using the chunk at the given coordinates. This may occur asynchronously. * @param \Closure $onCompletion To be called when chunk sending has completed. @@ -1182,8 +1201,12 @@ class NetworkSession{ */ public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{ $world = $this->player->getLocation()->getWorld(); - ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve( - + $promiseOrPacket = ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ); + if(is_string($promiseOrPacket)){ + $this->sendChunkPacket($promiseOrPacket, $onCompletion, $world); + return; + } + $promiseOrPacket->onResolve( //this callback may be called synchronously or asynchronously, depending on whether the promise is resolved yet function(CompressBatchPromise $promise) use ($world, $onCompletion, $chunkX, $chunkZ) : void{ if(!$this->isConnected()){ @@ -1201,13 +1224,7 @@ class NetworkSession{ //to NEEDED if they want to be resent. return; } - $world->timings->syncChunkSend->startTiming(); - try{ - $this->queueCompressed($promise); - $onCompletion(); - }finally{ - $world->timings->syncChunkSend->stopTiming(); - } + $this->sendChunkPacket($promise->getResult(), $onCompletion, $world); } ); } @@ -1289,6 +1306,13 @@ class NetworkSession{ $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/StandardEntityEventBroadcaster.php b/src/network/mcpe/StandardEntityEventBroadcaster.php index e2a707a3db..3e2df39948 100644 --- a/src/network/mcpe/StandardEntityEventBroadcaster.php +++ b/src/network/mcpe/StandardEntityEventBroadcaster.php @@ -38,8 +38,8 @@ use pocketmine\network\mcpe\protocol\MobEquipmentPacket; 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; @@ -67,7 +67,7 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{ 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 )); } @@ -142,6 +142,13 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{ } public function onEmote(array $recipients, Human $from, string $emoteId) : void{ - $this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, "", "", EmotePacket::FLAG_SERVER | EmotePacket::FLAG_MUTE_ANNOUNCEMENT)); + $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 32afdeeb7c..7a91b397be 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -53,7 +53,6 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ $compressors = []; - /** @var NetworkSession[][] $targetsByCompressor */ $targetsByCompressor = []; foreach($recipients as $recipient){ //TODO: different compressors might be compatible, it might not be necessary to split them up by object diff --git a/src/network/mcpe/cache/ChunkCache.php b/src/network/mcpe/cache/ChunkCache.php index 6d80200857..2b4265fcfe 100644 --- a/src/network/mcpe/cache/ChunkCache.php +++ b/src/network/mcpe/cache/ChunkCache.php @@ -32,6 +32,7 @@ use pocketmine\world\ChunkListener; use pocketmine\world\ChunkListenerNoOpTrait; use pocketmine\world\format\Chunk; use pocketmine\world\World; +use function is_string; use function spl_object_id; use function strlen; @@ -69,7 +70,7 @@ class ChunkCache implements ChunkListener{ foreach(self::$instances as $compressorMap){ foreach($compressorMap as $chunkCache){ foreach($chunkCache->caches as $chunkHash => $promise){ - if($promise->hasResult()){ + if(is_string($promise)){ //Do not clear promises that are not yet fulfilled; they will have requesters waiting on them unset($chunkCache->caches[$chunkHash]); } @@ -78,58 +79,75 @@ class ChunkCache implements ChunkListener{ } } - /** @var CompressBatchPromise[] */ + /** + * @var CompressBatchPromise[]|string[] + * @phpstan-var array + */ private array $caches = []; private int $hits = 0; private int $misses = 0; + /** + * @phpstan-param DimensionIds::* $dimensionId + */ private function __construct( private World $world, - private Compressor $compressor + private Compressor $compressor, + private int $dimensionId = DimensionIds::OVERWORLD ){} - /** - * Requests asynchronous preparation of the chunk at the given coordinates. - * - * @return CompressBatchPromise a promise of resolution which will contain a compressed chunk packet. - */ - public function request(int $chunkX, int $chunkZ) : CompressBatchPromise{ + private function prepareChunkAsync(int $chunkX, int $chunkZ, int $chunkHash) : CompressBatchPromise{ $this->world->registerChunkListener($this, $chunkX, $chunkZ); $chunk = $this->world->getChunk($chunkX, $chunkZ); if($chunk === null){ throw new \InvalidArgumentException("Cannot request an unloaded chunk"); } - $chunkHash = World::chunkHash($chunkX, $chunkZ); - - if(isset($this->caches[$chunkHash])){ - ++$this->hits; - return $this->caches[$chunkHash]; - } - ++$this->misses; $this->world->timings->syncChunkSendPrepare->startTiming(); try{ - $this->caches[$chunkHash] = new CompressBatchPromise(); + $promise = new CompressBatchPromise(); $this->world->getServer()->getAsyncPool()->submitTask( new ChunkRequestTask( $chunkX, $chunkZ, - DimensionIds::OVERWORLD, //TODO: not hardcode this + $this->dimensionId, $chunk, - $this->caches[$chunkHash], + $promise, $this->compressor ) ); + $this->caches[$chunkHash] = $promise; + $promise->onResolve(function(CompressBatchPromise $promise) use ($chunkHash) : void{ + //the promise may have been discarded or replaced if the chunk was unloaded or modified in the meantime + if(($this->caches[$chunkHash] ?? null) === $promise){ + $this->caches[$chunkHash] = $promise->getResult(); + } + }); - return $this->caches[$chunkHash]; + return $promise; }finally{ $this->world->timings->syncChunkSendPrepare->stopTiming(); } } + /** + * Requests asynchronous preparation of the chunk at the given coordinates. + * + * @return CompressBatchPromise|string Compressed chunk packet, or a promise for one to be resolved asynchronously. + */ + public function request(int $chunkX, int $chunkZ) : CompressBatchPromise|string{ + $chunkHash = World::chunkHash($chunkX, $chunkZ); + if(isset($this->caches[$chunkHash])){ + ++$this->hits; + return $this->caches[$chunkHash]; + } + + return $this->prepareChunkAsync($chunkX, $chunkZ, $chunkHash); + } + private function destroy(int $chunkX, int $chunkZ) : bool{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $existing = $this->caches[$chunkHash] ?? null; @@ -145,12 +163,12 @@ class ChunkCache implements ChunkListener{ $chunkPosHash = World::chunkHash($chunkX, $chunkZ); $cache = $this->caches[$chunkPosHash] ?? null; if($cache !== null){ - if(!$cache->hasResult()){ + if(!is_string($cache)){ //some requesters are waiting for this chunk, so their request needs to be fulfilled $cache->cancel(); unset($this->caches[$chunkPosHash]); - $this->request($chunkX, $chunkZ)->onResolve(...$cache->getResolveCallbacks()); + $this->prepareChunkAsync($chunkX, $chunkZ, $chunkPosHash)->onResolve(...$cache->getResolveCallbacks()); }else{ //dump the cache, it'll be regenerated the next time it's requested $this->destroy($chunkX, $chunkZ); @@ -196,8 +214,8 @@ class ChunkCache implements ChunkListener{ public function calculateCacheSize() : int{ $result = 0; foreach($this->caches as $cache){ - if($cache->hasResult()){ - $result += strlen($cache->getResult()); + if(is_string($cache)){ + $result += strlen($cache); } } return $result; diff --git a/src/network/mcpe/cache/CreativeInventoryCache.php b/src/network/mcpe/cache/CreativeInventoryCache.php index 04fc52604c..e543f343ec 100644 --- a/src/network/mcpe/cache/CreativeInventoryCache.php +++ b/src/network/mcpe/cache/CreativeInventoryCache.php @@ -23,23 +23,30 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\cache; +use pocketmine\inventory\CreativeCategory; use pocketmine\inventory\CreativeInventory; +use pocketmine\lang\Translatable; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\CreativeContentPacket; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeItemEntry; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\utils\SingletonTrait; +use function is_string; use function spl_object_id; +use const PHP_INT_MIN; final class CreativeInventoryCache{ use SingletonTrait; /** - * @var CreativeContentPacket[] - * @phpstan-var array + * @var CreativeInventoryCacheEntry[] + * @phpstan-var array */ private array $caches = []; - public function getCache(CreativeInventory $inventory) : CreativeContentPacket{ + private function getCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{ $id = spl_object_id($inventory); if(!isset($this->caches[$id])){ $inventory->getDestructorCallbacks()->add(function() use ($id) : void{ @@ -48,7 +55,7 @@ final class CreativeInventoryCache{ $inventory->getContentChangedCallbacks()->add(function() use ($id) : void{ unset($this->caches[$id]); }); - $this->caches[$id] = $this->buildCreativeInventoryCache($inventory); + $this->caches[$id] = $this->buildCacheEntry($inventory); } return $this->caches[$id]; } @@ -56,14 +63,91 @@ final class CreativeInventoryCache{ /** * Rebuild the cache for the given inventory. */ - private function buildCreativeInventoryCache(CreativeInventory $inventory) : CreativeContentPacket{ - $entries = []; + private function buildCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{ + $categories = []; + $groups = []; + $typeConverter = TypeConverter::getInstance(); - //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent - foreach($inventory->getAll() as $k => $item){ - $entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item)); + + $nextIndex = 0; + $groupIndexes = []; + $itemGroupIndexes = []; + + foreach($inventory->getAllEntries() as $k => $entry){ + $group = $entry->getGroup(); + $category = $entry->getCategory(); + if($group === null){ + $groupId = PHP_INT_MIN; + }else{ + $groupId = spl_object_id($group); + unset($groupIndexes[$category->name][PHP_INT_MIN]); //start a new anonymous group for this category + } + + //group object may be reused by multiple categories + if(!isset($groupIndexes[$category->name][$groupId])){ + $groupIndexes[$category->name][$groupId] = $nextIndex++; + $categories[] = $category; + $groups[] = $group; + } + $itemGroupIndexes[$k] = $groupIndexes[$category->name][$groupId]; } - return CreativeContentPacket::create($entries); + //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent + $items = []; + foreach($inventory->getAllEntries() as $k => $entry){ + $items[] = new CreativeItemEntry( + $k, + $typeConverter->coreItemStackToNet($entry->getItem()), + $itemGroupIndexes[$k] + ); + } + + return new CreativeInventoryCacheEntry($categories, $groups, $items); + } + + public function buildPacket(CreativeInventory $inventory, NetworkSession $session) : CreativeContentPacket{ + $player = $session->getPlayer() ?? throw new \LogicException("Cannot prepare creative data for a session without a player"); + $language = $player->getLanguage(); + $forceLanguage = $player->getServer()->isLanguageForced(); + $typeConverter = $session->getTypeConverter(); + $cachedEntry = $this->getCacheEntry($inventory); + $translate = function(Translatable|string $translatable) use ($session, $language, $forceLanguage) : string{ + if(is_string($translatable)){ + $message = $translatable; + }elseif(!$forceLanguage){ + [$message,] = $session->prepareClientTranslatableMessage($translatable); + }else{ + $message = $language->translate($translatable); + } + return $message; + }; + + $groupEntries = []; + foreach($cachedEntry->categories as $index => $category){ + $group = $cachedEntry->groups[$index]; + $categoryId = match ($category) { + CreativeCategory::CONSTRUCTION => CreativeContentPacket::CATEGORY_CONSTRUCTION, + CreativeCategory::NATURE => CreativeContentPacket::CATEGORY_NATURE, + CreativeCategory::EQUIPMENT => CreativeContentPacket::CATEGORY_EQUIPMENT, + CreativeCategory::ITEMS => CreativeContentPacket::CATEGORY_ITEMS + }; + if($group === null){ + $groupEntries[] = new CreativeGroupEntry($categoryId, "", ItemStack::null()); + }else{ + $groupIcon = $group->getIcon(); + //TODO: HACK! In 1.21.60, Workaround glitchy behaviour when an item is used as an icon for a group it + //doesn't belong to. Without this hack, both instances of the item will show a +, but neither of them + //will actually expand the group work correctly. + $groupIcon->getNamedTag()->setInt("___GroupBugWorkaround___", $index); + $groupName = $group->getName(); + $groupEntries[] = new CreativeGroupEntry( + $categoryId, + $translate($groupName), + $typeConverter->coreItemStackToNet($groupIcon) + ); + } + } + + return CreativeContentPacket::create($groupEntries, $cachedEntry->items); } } diff --git a/src/network/mcpe/cache/CreativeInventoryCacheEntry.php b/src/network/mcpe/cache/CreativeInventoryCacheEntry.php new file mode 100644 index 0000000000..1fc0767dff --- /dev/null +++ b/src/network/mcpe/cache/CreativeInventoryCacheEntry.php @@ -0,0 +1,48 @@ + $categories + * @phpstan-param list $groups + * @phpstan-param list $items + */ + public function __construct( + public readonly array $categories, + public readonly array $groups, + public readonly array $items, + ){ + //NOOP + } +} diff --git a/src/network/mcpe/convert/BlockStateDictionaryEntry.php b/src/network/mcpe/convert/BlockStateDictionaryEntry.php index 8c6244da58..28cc3960dc 100644 --- a/src/network/mcpe/convert/BlockStateDictionaryEntry.php +++ b/src/network/mcpe/convert/BlockStateDictionaryEntry.php @@ -28,6 +28,7 @@ use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; +use pocketmine\utils\Utils; use function count; use function ksort; use const SORT_STRING; @@ -43,6 +44,7 @@ final class BlockStateDictionaryEntry{ /** * @param Tag[] $stateProperties + * @phpstan-param array $stateProperties */ public function __construct( private string $stateName, @@ -79,6 +81,7 @@ final class BlockStateDictionaryEntry{ /** * @param Tag[] $properties + * @phpstan-param array $properties */ public static function encodeStateProperties(array $properties) : string{ if(count($properties) === 0){ @@ -87,7 +90,7 @@ final class BlockStateDictionaryEntry{ //TODO: make a more efficient encoding - NBT will do for now, but it's not very compact ksort($properties, SORT_STRING); $tag = new CompoundTag(); - foreach($properties as $k => $v){ + foreach(Utils::stringifyKeys($properties) as $k => $v){ $tag->setTag($k, $v); } return (new LittleEndianNbtSerializer())->write(new TreeRoot($tag)); diff --git a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php index 5d06758ef5..ed8ae2cc73 100644 --- a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php +++ b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php @@ -23,9 +23,15 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pocketmine\errorhandler\ErrorToExceptionHandler; +use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; +use pocketmine\network\mcpe\protocol\types\CacheableNbt; use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Utils; +use function base64_decode; use function is_array; use function is_bool; use function is_int; @@ -40,12 +46,15 @@ final class ItemTypeDictionaryFromDataHelper{ throw new AssumptionFailedError("Invalid item list format"); } + $emptyNBT = new CacheableNbt(new CompoundTag()); + $nbtSerializer = new LittleEndianNbtSerializer(); + $params = []; - foreach($table as $name => $entry){ - if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){ + foreach(Utils::promoteKeys($table) as $name => $entry){ + if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"], $entry["version"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"]) || !is_int($entry["version"]) || !(is_string($componentNbt = $entry["component_nbt"] ?? null) || $componentNbt === null)){ throw new AssumptionFailedError("Invalid item list format"); } - $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]); + $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"], $entry["version"], $componentNbt === null ? $emptyNBT : new CacheableNbt($nbtSerializer->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($componentNbt, true)))->mustGetCompoundTag())); } return new ItemTypeDictionary($params); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index c92db31331..dba16e1e69 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -77,6 +77,7 @@ use pocketmine\network\mcpe\protocol\PlayerHotbarPacket; use pocketmine\network\mcpe\protocol\PlayerInputPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; +use pocketmine\network\mcpe\protocol\serializer\BitSet; use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket; @@ -135,7 +136,7 @@ class InGamePacketHandler extends PacketHandler{ protected ?Vector3 $lastPlayerAuthInputPosition = null; protected ?float $lastPlayerAuthInputYaw = null; protected ?float $lastPlayerAuthInputPitch = null; - protected ?int $lastPlayerAuthInputFlags = null; + protected ?BitSet $lastPlayerAuthInputFlags = null; public bool $forceMoveSync = false; @@ -161,9 +162,9 @@ class InGamePacketHandler extends PacketHandler{ return true; } - private function resolveOnOffInputFlags(int $inputFlags, int $startFlag, int $stopFlag) : ?bool{ - $enabled = ($inputFlags & (1 << $startFlag)) !== 0; - $disabled = ($inputFlags & (1 << $stopFlag)) !== 0; + private function resolveOnOffInputFlags(BitSet $inputFlags, int $startFlag, int $stopFlag) : ?bool{ + $enabled = $inputFlags->get($startFlag); + $disabled = $inputFlags->get($stopFlag); if($enabled !== $disabled){ return $enabled; } @@ -215,7 +216,10 @@ class InGamePacketHandler extends PacketHandler{ if($inputFlags !== $this->lastPlayerAuthInputFlags){ $this->lastPlayerAuthInputFlags = $inputFlags; - $sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING); + $sneaking = $inputFlags->get(PlayerAuthInputFlags::SNEAKING); + if($this->player->isSneaking() === $sneaking){ + $sneaking = null; + } $sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING); $swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING); $gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING); @@ -230,10 +234,10 @@ class InGamePacketHandler extends PacketHandler{ $this->player->sendData([$this->player]); } - if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ + if($inputFlags->get(PlayerAuthInputFlags::START_JUMPING)){ $this->player->jump(); } - if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){ + if($inputFlags->get(PlayerAuthInputFlags::MISSED_SWING)){ $this->player->missSwing(); } } @@ -251,7 +255,7 @@ class InGamePacketHandler extends PacketHandler{ if(count($blockActions) > 100){ throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket"); } - foreach($blockActions as $k => $blockAction){ + foreach(Utils::promoteKeys($blockActions) as $k => $blockAction){ $actionHandled = false; if($blockAction instanceof PlayerBlockActionStopBreak){ $actionHandled = $this->handlePlayerActionFromData($blockAction->getActionType(), new BlockPosition(0, 0, 0), Facing::DOWN); @@ -412,7 +416,7 @@ class InGamePacketHandler extends PacketHandler{ $droppedCount = null; foreach($data->getActions() as $networkInventoryAction){ - if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot == NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){ + if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot === NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){ $droppedCount = $networkInventoryAction->newItem->getItemStack()->getCount(); if($droppedCount <= 0){ throw new PacketHandlingException("Expected positive count for dropped item"); @@ -493,15 +497,18 @@ class InGamePacketHandler extends PacketHandler{ $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: @@ -529,9 +536,9 @@ class InGamePacketHandler extends PacketHandler{ } /** - * 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){ @@ -571,7 +578,7 @@ class InGamePacketHandler extends PacketHandler{ private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{ $this->player->selectHotbarSlot($data->getHotbarSlot()); - if($data->getActionType() == ReleaseItemTransactionData::ACTION_RELEASE){ + if($data->getActionType() === ReleaseItemTransactionData::ACTION_RELEASE){ $this->player->releaseHeldItem(); return true; } @@ -668,7 +675,7 @@ class InGamePacketHandler extends PacketHandler{ } public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{ - return false; //TODO + return $this->player->pickEntity($packet->actorUniqueId); } public function handlePlayerAction(PlayerActionPacket $packet) : bool{ @@ -682,7 +689,7 @@ class InGamePacketHandler extends PacketHandler{ case PlayerAction::START_BREAK: self::validateFacing($face); if(!$this->player->attackBlock($pos, $face)){ - $this->onFailedBlockAction($pos, $face); + $this->syncBlocksNearby($pos, $face); } break; @@ -998,7 +1005,7 @@ class InGamePacketHandler extends PacketHandler{ $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; } diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index 0807dc2bdc..b57aab31b5 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -58,6 +58,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResp use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Utils; use function array_key_first; use function count; use function spl_object_id; @@ -347,7 +348,7 @@ class ItemStackRequestExecutor{ $this->setNextCreatedItem($window->getOutput($optionId)); } }else{ - $this->beginCrafting($action->getRecipeId(), 1); + $this->beginCrafting($action->getRecipeId(), $action->getRepetitions()); } }elseif($action instanceof CraftRecipeAutoStackRequestAction){ $this->beginCrafting($action->getRecipeId(), $action->getRepetitions()); @@ -383,7 +384,7 @@ class ItemStackRequestExecutor{ * @throws ItemStackRequestProcessException */ public function generateInventoryTransaction() : InventoryTransaction{ - foreach($this->request->getActions() as $k => $action){ + foreach(Utils::promoteKeys($this->request->getActions()) as $k => $action){ try{ $this->processItemStackRequestAction($action); }catch(ItemStackRequestProcessException $e){ diff --git a/src/network/mcpe/handler/ItemStackResponseBuilder.php b/src/network/mcpe/handler/ItemStackResponseBuilder.php index a947eae726..faf479ee2d 100644 --- a/src/network/mcpe/handler/ItemStackResponseBuilder.php +++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php @@ -93,6 +93,7 @@ final class ItemStackResponseBuilder{ $item->getCount(), $itemStackInfo->getStackId(), $item->getCustomName(), + $item->getCustomName(), $item instanceof Durable ? $item->getDamage() : 0, ); } @@ -100,7 +101,7 @@ final class ItemStackResponseBuilder{ $responseContainerInfos = []; foreach($responseInfosByContainer as $containerInterfaceId => $responseInfos){ - $responseContainerInfos[] = new ItemStackResponseContainerInfo(new FullContainerName($containerInterfaceId, 0), $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 2e3a515198..c15753dad3 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -150,7 +150,7 @@ class LoginPacketHandler extends PacketHandler{ protected function fetchAuthData(JwtChain $chain) : AuthenticationData{ /** @var AuthenticationData|null $extraData */ $extraData = null; - foreach($chain->chain as $k => $jwt){ + foreach($chain->chain as $jwt){ //validate every chain element try{ [, $claims, ] = JwtUtils::parse($jwt); diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index f80bacfc18..9aa302c0c0 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -28,6 +28,7 @@ use pocketmine\network\mcpe\cache\CraftingDataCache; use pocketmine\network\mcpe\cache\StaticPacketCache; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; @@ -39,7 +40,7 @@ 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; @@ -98,7 +99,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $this->server->getMotd(), "", false, - new PlayerMovementSettings(PlayerMovementType::SERVER_AUTHORITATIVE_V1, 0, false), + new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V2, 0, false), 0, 0, "", @@ -110,9 +111,11 @@ class PreSpawnPacketHandler extends PacketHandler{ new NetworkPermissions(disableClientSounds: true), [], 0, - $typeConverter->getItemTypeDictionary()->getEntries(), )); + $this->session->getLogger()->debug("Sending items"); + $this->session->sendDataPacket(ItemRegistryPacket::create($typeConverter->getItemTypeDictionary()->getEntries())); + $this->session->getLogger()->debug("Sending actor identifiers"); $this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers()); diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index b99775886c..a1df394dae 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -37,6 +37,7 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType; use pocketmine\resourcepacks\ResourcePack; +use Ramsey\Uuid\Uuid; use function array_keys; use function array_map; use function ceil; @@ -103,7 +104,7 @@ class ResourcePacksPacketHandler extends PacketHandler{ //TODO: more stuff return new ResourcePackInfoEntry( - $pack->getPackId(), + Uuid::fromString($pack->getPackId()), $pack->getPackVersion(), $pack->getPackSize(), $this->encryptionKeys[$pack->getPackId()] ?? "", @@ -115,12 +116,11 @@ class ResourcePacksPacketHandler extends PacketHandler{ //TODO: support forcing server packs $this->session->sendDataPacket(ResourcePacksInfoPacket::create( resourcePackEntries: $resourcePackEntries, - behaviorPackEntries: [], mustAccept: $this->mustAccept, hasAddons: false, hasScripts: false, - forceServerPacks: false, - cdnUrls: [] + worldTemplateId: Uuid::fromString(Uuid::NIL), + worldTemplateVersion: "" )); $this->session->getLogger()->debug("Waiting for client to accept resource packs"); } diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index 5137b94ba2..d5e825beea 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -38,7 +38,7 @@ use raklib\server\ServerSocket; use raklib\server\SimpleProtocolAcceptor; use raklib\utils\ExceptionTraceCleaner; use raklib\utils\InternetAddress; -use function gc_enable; +use function gc_disable; use function ini_set; class RakLibServer extends Thread{ @@ -82,7 +82,10 @@ class RakLibServer extends Thread{ } protected function onRun() : void{ - gc_enable(); + //RakLib has cycles (e.g. ServerSession <-> Server) but these cycles are explicitly cleaned up anyway, and are + //very few, so it's pointless to waste CPU time on GC + gc_disable(); + ini_set("display_errors", '1'); ini_set("display_startup_errors", '1'); \GlobalLogger::set($this->logger); diff --git a/src/network/upnp/UPnP.php b/src/network/upnp/UPnP.php index 2d48a2db83..bd8e8376f3 100644 --- a/src/network/upnp/UPnP.php +++ b/src/network/upnp/UPnP.php @@ -215,6 +215,7 @@ class UPnP{ 'SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"' ]; + $err = ""; if(Internet::postURL($serviceURL, $contents, 3, $headers, $err) === null){ throw new UPnPException("Failed to portforward using UPnP: " . $err); } diff --git a/src/permission/BanEntry.php b/src/permission/BanEntry.php index 0d5ed0c76b..a766a6fcc3 100644 --- a/src/permission/BanEntry.php +++ b/src/permission/BanEntry.php @@ -101,11 +101,12 @@ class BanEntry{ } public function getString() : string{ + $expires = $this->getExpires(); return implode("|", [ $this->getName(), $this->getCreated()->format(self::$format), $this->getSource(), - $this->getExpires() === null ? "Forever" : $this->getExpires()->format(self::$format), + $expires === null ? "Forever" : $expires->format(self::$format), $this->getReason() ]); } @@ -147,7 +148,9 @@ class BanEntry{ return null; } - $parts = explode("|", trim($str)); + //we expect at most 5 parts, but accept 6 in case of an extra unexpected delimiter + //we don't want to include unexpected data into the ban reason + $parts = explode("|", trim($str), limit: 6); $entry = new BanEntry(trim(array_shift($parts))); if(count($parts) > 0){ $entry->setCreated(self::parseDate(array_shift($parts))); diff --git a/src/permission/BanList.php b/src/permission/BanList.php index b09cd98c90..36826ed66f 100644 --- a/src/permission/BanList.php +++ b/src/permission/BanList.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\permission; +use pocketmine\utils\Utils; use function fclose; use function fgets; use function fopen; @@ -32,7 +33,10 @@ use function strtolower; use function trim; class BanList{ - /** @var BanEntry[] */ + /** + * @var BanEntry[] + * @phpstan-var array + */ private array $list = []; private bool $enabled = true; @@ -101,7 +105,7 @@ class BanList{ } public function removeExpired() : void{ - foreach($this->list as $name => $entry){ + foreach(Utils::promoteKeys($this->list) as $name => $entry){ if($entry->hasExpired()){ unset($this->list[$name]); } diff --git a/src/permission/DefaultPermissionNames.php b/src/permission/DefaultPermissionNames.php index fab532e286..a916e813c0 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 c72765af6b..1030e9ff8c 100644 --- a/src/permission/DefaultPermissions.php +++ b/src/permission/DefaultPermissions.php @@ -112,5 +112,7 @@ abstract class DefaultPermissions{ 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/PermissionAttachment.php b/src/permission/PermissionAttachment.php index b01fba3070..79c66ba8a1 100644 --- a/src/permission/PermissionAttachment.php +++ b/src/permission/PermissionAttachment.php @@ -25,10 +25,14 @@ namespace pocketmine\permission; use pocketmine\plugin\Plugin; use pocketmine\plugin\PluginException; +use pocketmine\utils\Utils; use function spl_object_id; class PermissionAttachment{ - /** @var bool[] */ + /** + * @var bool[] + * @phpstan-var array + */ private array $permissions = []; /** @@ -60,6 +64,7 @@ class PermissionAttachment{ /** * @return bool[] + * @phpstan-return array */ public function getPermissions() : array{ return $this->permissions; @@ -78,9 +83,10 @@ class PermissionAttachment{ /** * @param bool[] $permissions + * @phpstan-param array $permissions */ public function setPermissions(array $permissions) : void{ - foreach($permissions as $key => $value){ + foreach(Utils::stringifyKeys($permissions) as $key => $value){ $this->permissions[$key] = $value; } $this->recalculatePermissibles(); diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php index 1291ba86bf..c9e37f5e94 100644 --- a/src/permission/PermissionManager.php +++ b/src/permission/PermissionManager.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace pocketmine\permission; +use pocketmine\Server; +use pocketmine\utils\Utils; use function count; use function spl_object_id; @@ -37,9 +39,15 @@ class PermissionManager{ return self::$instance; } - /** @var Permission[] */ + /** + * @var Permission[] + * @phpstan-var array + */ protected array $permissions = []; - /** @var PermissibleInternal[][] */ + /** + * @var PermissibleInternal[][] + * @phpstan-var array> + */ protected array $permSubs = []; public function getPermission(string $name) : ?Permission{ @@ -64,6 +72,10 @@ class PermissionManager{ } } + /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::subscribeToBroadcastChannel() + */ public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{ if(!isset($this->permSubs[$permission])){ $this->permSubs[$permission] = []; @@ -71,6 +83,10 @@ class PermissionManager{ $this->permSubs[$permission][spl_object_id($permissible)] = $permissible; } + /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::unsubscribeFromBroadcastChannel() + */ public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{ if(isset($this->permSubs[$permission][spl_object_id($permissible)])){ if(count($this->permSubs[$permission]) === 1){ @@ -81,8 +97,12 @@ class PermissionManager{ } } + /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::unsubscribeFromAllBroadcastChannels() + */ public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{ - foreach($this->permSubs as $permission => $subs){ + foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){ if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){ unset($this->permSubs[$permission]); }else{ @@ -92,6 +112,8 @@ class PermissionManager{ } /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::getBroadcastChannelSubscribers() * @return PermissibleInternal[] */ public function getPermissionSubscriptions(string $permission) : array{ diff --git a/src/player/Player.php b/src/player/Player.php index d442c6a3b2..1c67b7182c 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -57,6 +57,7 @@ use pocketmine\event\player\PlayerDisplayNameChangeEvent; 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; @@ -109,6 +110,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; +use pocketmine\network\mcpe\protocol\types\DimensionIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; @@ -145,7 +147,6 @@ use function count; use function explode; use function floor; use function get_class; -use function is_int; use function max; use function mb_strlen; use function microtime; @@ -183,6 +184,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ private const MAX_REACH_DISTANCE_SURVIVAL = 7; private const MAX_REACH_DISTANCE_ENTITY_INTERACTION = 8; + public const DEFAULT_FLIGHT_SPEED_MULTIPLIER = 0.05; + public const TAG_FIRST_PLAYED = "firstPlayed"; //TAG_Long public const TAG_LAST_PLAYED = "lastPlayed"; //TAG_Long private const TAG_GAME_MODE = "playerGameType"; //TAG_Int @@ -190,6 +193,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ private const TAG_SPAWN_X = "SpawnX"; //TAG_Int private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int + private const TAG_DEATH_WORLD = "DeathLevel"; //TAG_String + private const TAG_DEATH_X = "DeathPositionX"; //TAG_Int + private const TAG_DEATH_Y = "DeathPositionY"; //TAG_Int + private const TAG_DEATH_Z = "DeathPositionZ"; //TAG_Int public const TAG_LEVEL = "Level"; //TAG_String public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String @@ -272,18 +279,26 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ private bool $respawnLocked = false; + private ?Position $deathPosition = null; + //TODO: Abilities protected bool $autoJump = true; protected bool $allowFlight = false; protected bool $blockCollision = true; protected bool $flying = false; + protected float $flightSpeedMultiplier = self::DEFAULT_FLIGHT_SPEED_MULTIPLIER; + /** @phpstan-var positive-int|null */ protected ?int $lineHeight = null; protected string $locale = "en_US"; 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; @@ -386,6 +401,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){ $this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world); } + if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_DEATH_WORLD, ""))) instanceof World){ + $this->deathPosition = new Position($nbt->getInt(self::TAG_DEATH_X), $nbt->getInt(self::TAG_DEATH_Y), $nbt->getInt(self::TAG_DEATH_Z), $world); + } } public function getLeaveMessage() : Translatable|string{ @@ -504,6 +522,41 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return $this->flying; } + /** + * Sets the player's flight speed multiplier. + * + * Normal flying speed in blocks-per-tick is (multiplier * 10) blocks per tick. + * When sprint-flying, this is doubled to 20. + * + * If set to zero, the player will not be able to move in the xz plane when flying. + * Negative values will invert the controls. + * + * Note: Movement speed attribute does not influence flight speed. + * + * @see Player::DEFAULT_FLIGHT_SPEED_MULTIPLIER + */ + public function setFlightSpeedMultiplier(float $flightSpeedMultiplier) : void{ + if($this->flightSpeedMultiplier !== $flightSpeedMultiplier){ + $this->flightSpeedMultiplier = $flightSpeedMultiplier; + $this->getNetworkSession()->syncAbilities($this); + } + } + + /** + * Returns the player's flight speed multiplier. + * + * Normal flying speed in blocks-per-tick is (multiplier * 10) blocks per tick. + * When sprint-flying, this is doubled to 20. + * + * If set to zero, the player will not be able to move in the xz plane when flying. + * Negative values will invert the controls. + * + * @see Player::DEFAULT_FLIGHT_SPEED_MULTIPLIER + */ + public function getFlightSpeedMultiplier() : float{ + return $this->flightSpeedMultiplier; + } + public function setAutoJump(bool $value) : void{ if($this->autoJump !== $value){ $this->autoJump = $value; @@ -697,7 +750,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ */ public function getItemCooldownExpiry(Item $item) : int{ $this->checkItemCooldowns(); - return $this->usedItemsCooldown[$item->getStateId()] ?? 0; + return $this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()] ?? 0; } /** @@ -705,7 +758,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ */ public function hasItemCooldown(Item $item) : bool{ $this->checkItemCooldowns(); - return isset($this->usedItemsCooldown[$item->getStateId()]); + return isset($this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()]); } /** @@ -714,7 +767,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ 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); } } @@ -810,7 +864,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $X = null; $Z = null; World::getXZ($index, $X, $Z); - assert(is_int($X) && is_int($Z)); ++$count; @@ -1026,6 +1079,30 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } } + public function getDeathPosition() : ?Position{ + if($this->deathPosition !== null && !$this->deathPosition->isValid()){ + $this->deathPosition = null; + } + return $this->deathPosition; + } + + /** + * @param Vector3|Position|null $pos + */ + public function setDeathPosition(?Vector3 $pos) : void{ + if($pos !== null){ + if($pos instanceof Position && $pos->world !== null){ + $world = $pos->world; + }else{ + $world = $this->getWorld(); + } + $this->deathPosition = new Position($pos->x, $pos->y, $pos->z, $world); + }else{ + $this->deathPosition = null; + } + $this->networkPropertiesDirty = true; + } + /** * @return Position */ @@ -1306,7 +1383,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->nextChunkOrderRun = 0; } - if(!$revert && $distanceSquared != 0){ + if(!$revert && $distanceSquared !== 0.0){ $dx = $newPos->x - $oldPos->x; $dy = $newPos->y - $oldPos->y; $dz = $newPos->z - $oldPos->z; @@ -1465,6 +1542,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return true; } + public function canEat() : bool{ + return $this->isCreative() || parent::canEat(); + } + public function canBreathe() : bool{ return $this->isCreative() || parent::canBreathe(); } @@ -1620,7 +1701,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return false; } - $this->resetItemCooldown($item); + $this->resetItemCooldown($oldItem); $this->returnItemsFromAction($oldItem, $item, $returnedItems); $this->setUsingItem($item instanceof Releasable && $item->canStartUsingItem($this)); @@ -1649,7 +1730,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } $this->setUsingItem(false); - $this->resetItemCooldown($slot); + $this->resetItemCooldown($oldItem); $slot->pop(); $this->returnItemsFromAction($oldItem, $slot, [$slot->getResidue()]); @@ -1677,7 +1758,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $returnedItems = []; $result = $item->onReleaseUsing($this, $returnedItems); if($result === ItemUseResult::SUCCESS){ - $this->resetItemCooldown($item); + $this->resetItemCooldown($oldItem); $this->returnItemsFromAction($oldItem, $item, $returnedItems); return true; } @@ -1704,29 +1785,58 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $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. * @@ -1759,7 +1869,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return true; } - if(!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()){ + if(!$this->isCreative() && !$target->getBreakInfo()->breaksInstantly()){ $this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16); } @@ -2246,7 +2356,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $ev = new PlayerQuitEvent($this, $quitMessage ?? $this->getLeaveMessage(), $reason); $ev->call(); - if(($quitMessage = $ev->getQuitMessage()) != ""){ + if(($quitMessage = $ev->getQuitMessage()) !== ""){ $this->server->broadcastMessage($quitMessage); } $this->save(); @@ -2297,6 +2407,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ unset($this->cursorInventory); unset($this->craftingGrid); $this->spawnPosition = null; + $this->deathPosition = null; $this->blockBreakHandler = null; parent::destroyCycles(); } @@ -2338,6 +2449,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ()); } + if($this->deathPosition !== null && $this->deathPosition->isValid()){ + $nbt->setString(self::TAG_DEATH_WORLD, $this->deathPosition->getWorld()->getFolderName()); + $nbt->setInt(self::TAG_DEATH_X, $this->deathPosition->getFloorX()); + $nbt->setInt(self::TAG_DEATH_Y, $this->deathPosition->getFloorY()); + $nbt->setInt(self::TAG_DEATH_Z, $this->deathPosition->getFloorZ()); + } + $nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode)); $nbt->setLong(self::TAG_FIRST_PLAYED, $this->firstPlayed); $nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000)); @@ -2357,6 +2475,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ //main inventory and drops the rest on the ground. $this->removeCurrentWindow(); + $this->setDeathPosition($this->getPosition()); + $ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null); $ev->call(); @@ -2377,7 +2497,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->xpManager->setXpAndProgress(0, 0.0); } - if($ev->getDeathMessage() != ""){ + if($ev->getDeathMessage() !== ""){ $this->server->broadcastMessage($ev->getDeathMessage()); } @@ -2485,6 +2605,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null); $properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0)); + + if($this->deathPosition !== null && $this->deathPosition->world === $this->location->world){ + $properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, BlockPosition::fromVector3($this->deathPosition)); + //TODO: this should be updated when dimensions are implemented + $properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD); + $properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 1); + }else{ + $properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, new BlockPosition(0, 0, 0)); + $properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD); + $properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 0); + } } public function sendData(?array $targets, ?array $data = null) : void{ diff --git a/src/player/SurvivalBlockBreakHandler.php b/src/player/SurvivalBlockBreakHandler.php index e31e77ef7c..57c2842ced 100644 --- a/src/player/SurvivalBlockBreakHandler.php +++ b/src/player/SurvivalBlockBreakHandler.php @@ -25,6 +25,8 @@ namespace pocketmine\player; use pocketmine\block\Block; use pocketmine\entity\animation\ArmSwingAnimation; +use pocketmine\entity\effect\VanillaEffects; +use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\LevelEventPacket; @@ -65,11 +67,29 @@ final class SurvivalBlockBreakHandler{ if(!$this->block->getBreakInfo()->isBreakable()){ return 0.0; } - //TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211) $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20; - + if(!$this->player->isOnGround() && !$this->player->isFlying()){ + $breakTimePerTick *= 5; + } + if($this->player->isUnderwater() && !$this->player->getArmorInventory()->getHelmet()->hasEnchantment(VanillaEnchantments::AQUA_AFFINITY())){ + $breakTimePerTick *= 5; + } if($breakTimePerTick > 0){ - return 1 / $breakTimePerTick; + $progressPerTick = 1 / $breakTimePerTick; + + $haste = $this->player->getEffects()->get(VanillaEffects::HASTE()); + if($haste !== null){ + $hasteLevel = $haste->getEffectLevel(); + $progressPerTick *= (1 + 0.2 * $hasteLevel) * (1.2 ** $hasteLevel); + } + + $miningFatigue = $this->player->getEffects()->get(VanillaEffects::MINING_FATIGUE()); + if($miningFatigue !== null){ + $miningFatigueLevel = $miningFatigue->getEffectLevel(); + $progressPerTick *= 0.21 ** $miningFatigueLevel; + } + + return $progressPerTick; } return 1; } @@ -82,7 +102,10 @@ final class SurvivalBlockBreakHandler{ $newBreakSpeed = $this->calculateBreakProgressPerTick(); if(abs($newBreakSpeed - $this->breakSpeed) > 0.0001){ $this->breakSpeed = $newBreakSpeed; - //TODO: sync with client + $this->player->getWorld()->broadcastPacketToViewers( + $this->blockPos, + LevelEventPacket::create(LevelEvent::BLOCK_BREAK_SPEED, (int) (65535 * $this->breakSpeed), $this->blockPos) + ); } $this->breakProgress += $this->breakSpeed; diff --git a/src/plugin/ApiVersion.php b/src/plugin/ApiVersion.php index bcf7e59a04..d95b66576c 100644 --- a/src/plugin/ApiVersion.php +++ b/src/plugin/ApiVersion.php @@ -70,7 +70,6 @@ final class ApiVersion{ * @return string[] */ public static function checkAmbiguousVersions(array $versions) : array{ - /** @var VersionString[][] $indexedVersions */ $indexedVersions = []; foreach($versions as $str){ @@ -85,9 +84,8 @@ final class ApiVersion{ } } - /** @var VersionString[] $result */ $result = []; - foreach($indexedVersions as $major => $list){ + foreach($indexedVersions as $list){ if(count($list) > 1){ array_push($result, ...$list); } diff --git a/src/plugin/DiskResourceProvider.php b/src/plugin/DiskResourceProvider.php index efdc9cd172..9649f565f1 100644 --- a/src/plugin/DiskResourceProvider.php +++ b/src/plugin/DiskResourceProvider.php @@ -36,6 +36,8 @@ use const DIRECTORY_SEPARATOR; /** * Provides resources from the given plugin directory on disk. The path may be prefixed with a specific access protocol * to enable special types of access. + * + * @deprecated */ class DiskResourceProvider implements ResourceProvider{ private string $file; diff --git a/src/plugin/PluginDescription.php b/src/plugin/PluginDescription.php index 72f0add7fd..89ac19e05d 100644 --- a/src/plugin/PluginDescription.php +++ b/src/plugin/PluginDescription.php @@ -26,6 +26,7 @@ namespace pocketmine\plugin; use pocketmine\permission\Permission; use pocketmine\permission\PermissionParser; use pocketmine\permission\PermissionParserException; +use pocketmine\utils\Utils; use function array_map; use function array_values; use function get_debug_type; @@ -83,11 +84,20 @@ class PluginDescription{ * @phpstan-var array> */ private array $extensions = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $depend = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $softDepend = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $loadBefore = []; private string $version; /** @@ -151,7 +161,7 @@ class PluginDescription{ $this->compatibleOperatingSystems = array_map("\strval", (array) ($plugin[self::KEY_OS] ?? [])); if(isset($plugin[self::KEY_COMMANDS]) && is_array($plugin[self::KEY_COMMANDS])){ - foreach($plugin[self::KEY_COMMANDS] as $commandName => $commandData){ + foreach(Utils::promoteKeys($plugin[self::KEY_COMMANDS]) as $commandName => $commandData){ if(!is_string($commandName)){ throw new PluginDescriptionParseException("Invalid Plugin commands, key must be the name of the command"); } @@ -172,23 +182,23 @@ class PluginDescription{ } if(isset($plugin[self::KEY_DEPEND])){ - $this->depend = (array) $plugin[self::KEY_DEPEND]; + $this->depend = array_values((array) $plugin[self::KEY_DEPEND]); } if(isset($plugin[self::KEY_EXTENSIONS])){ $extensions = (array) $plugin[self::KEY_EXTENSIONS]; $isLinear = $extensions === array_values($extensions); - foreach($extensions as $k => $v){ + foreach(Utils::promoteKeys($extensions) as $k => $v){ if($isLinear){ $k = $v; $v = "*"; } - $this->extensions[(string) $k] = array_map('strval', is_array($v) ? $v : [$v]); + $this->extensions[(string) $k] = array_values(array_map('strval', is_array($v) ? $v : [$v])); } } - $this->softDepend = (array) ($plugin[self::KEY_SOFTDEPEND] ?? $this->softDepend); + $this->softDepend = array_values((array) ($plugin[self::KEY_SOFTDEPEND] ?? $this->softDepend)); - $this->loadBefore = (array) ($plugin[self::KEY_LOADBEFORE] ?? $this->loadBefore); + $this->loadBefore = array_values((array) ($plugin[self::KEY_LOADBEFORE] ?? $this->loadBefore)); $this->website = (string) ($plugin[self::KEY_WEBSITE] ?? $this->website); @@ -209,7 +219,7 @@ class PluginDescription{ $this->authors = []; if(isset($plugin[self::KEY_AUTHOR])){ if(is_array($plugin[self::KEY_AUTHOR])){ - $this->authors = $plugin[self::KEY_AUTHOR]; + $this->authors = array_values($plugin[self::KEY_AUTHOR]); }else{ $this->authors[] = $plugin[self::KEY_AUTHOR]; } @@ -283,6 +293,7 @@ class PluginDescription{ /** * @return string[] + * @phpstan-return list */ public function getDepend() : array{ return $this->depend; @@ -294,6 +305,7 @@ class PluginDescription{ /** * @return string[] + * @phpstan-return list */ public function getLoadBefore() : array{ return $this->loadBefore; @@ -323,6 +335,7 @@ class PluginDescription{ /** * @return string[] + * @phpstan-return list */ public function getSoftDepend() : array{ return $this->softDepend; diff --git a/src/plugin/PluginGraylist.php b/src/plugin/PluginGraylist.php index 84aa3f7924..f3c9cf2a3b 100644 --- a/src/plugin/PluginGraylist.php +++ b/src/plugin/PluginGraylist.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\plugin; -use function array_flip; +use pocketmine\utils\Utils; +use function array_fill_keys; +use function array_keys; use function is_array; use function is_float; use function is_int; @@ -31,23 +33,28 @@ use function is_string; class PluginGraylist{ - /** @var string[] */ + /** + * @var true[] + * @phpstan-var array + */ private array $plugins; private bool $isWhitelist = false; /** * @param string[] $plugins + * @phpstan-param list $plugins */ public function __construct(array $plugins = [], bool $whitelist = false){ - $this->plugins = array_flip($plugins); + $this->plugins = array_fill_keys($plugins, true); $this->isWhitelist = $whitelist; } /** * @return string[] + * @phpstan-return list */ public function getPlugins() : array{ - return array_flip($this->plugins); + return array_keys($this->plugins); } public function isWhitelist() : bool{ @@ -77,7 +84,7 @@ class PluginGraylist{ if(!is_array($array["plugins"])){ throw new \InvalidArgumentException("\"plugins\" must be an array"); } - foreach($array["plugins"] as $k => $v){ + foreach(Utils::promoteKeys($array["plugins"]) as $k => $v){ if(!is_string($v) && !is_int($v) && !is_float($v)){ throw new \InvalidArgumentException("\"plugins\" contains invalid element at position $k"); } diff --git a/src/plugin/PluginLoadTriage.php b/src/plugin/PluginLoadTriage.php index 77d1026684..fcf32751ef 100644 --- a/src/plugin/PluginLoadTriage.php +++ b/src/plugin/PluginLoadTriage.php @@ -31,12 +31,12 @@ final class PluginLoadTriage{ public array $plugins = []; /** * @var string[][] - * @phpstan-var array> + * @phpstan-var array> */ public array $dependencies = []; /** * @var string[][] - * @phpstan-var array> + * @phpstan-var array> */ public array $softDependencies = []; } diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index 198e4e893b..a8440f04fc 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -69,10 +69,16 @@ use function strtolower; * Manages all the plugins */ class PluginManager{ - /** @var Plugin[] */ + /** + * @var Plugin[] + * @phpstan-var array + */ protected array $plugins = []; - /** @var Plugin[] */ + /** + * @var Plugin[] + * @phpstan-var array + */ protected array $enabledPlugins = []; /** @var array> */ @@ -114,6 +120,7 @@ class PluginManager{ /** * @return Plugin[] + * @phpstan-return array */ public function getPlugins() : array{ return $this->plugins; @@ -320,12 +327,12 @@ class PluginManager{ * @param string[][] $dependencyLists * @param Plugin[] $loadedPlugins * - * @phpstan-param array> $dependencyLists - * @phpstan-param-out array> $dependencyLists + * @phpstan-param array> $dependencyLists + * @phpstan-param-out array> $dependencyLists */ private function checkDepsForTriage(string $pluginName, string $dependencyType, array &$dependencyLists, array $loadedPlugins, PluginLoadTriage $triage) : void{ if(isset($dependencyLists[$pluginName])){ - foreach($dependencyLists[$pluginName] as $key => $dependency){ + foreach(Utils::promoteKeys($dependencyLists[$pluginName]) as $key => $dependency){ if(isset($loadedPlugins[$dependency]) || $this->getPlugin($dependency) instanceof Plugin){ $this->server->getLogger()->debug("Successfully resolved $dependencyType dependency \"$dependency\" for plugin \"$pluginName\""); unset($dependencyLists[$pluginName][$key]); @@ -392,7 +399,7 @@ class PluginManager{ //check for skippable soft dependencies first, in case the dependents could resolve hard dependencies foreach(Utils::stringifyKeys($triage->plugins) as $name => $file){ if(isset($triage->softDependencies[$name]) && !isset($triage->dependencies[$name])){ - foreach($triage->softDependencies[$name] as $k => $dependency){ + foreach(Utils::promoteKeys($triage->softDependencies[$name]) as $k => $dependency){ if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){ $this->server->getLogger()->debug("Skipping resolution of missing soft dependency \"$dependency\" for plugin \"$name\""); unset($triage->softDependencies[$name][$k]); @@ -409,7 +416,7 @@ class PluginManager{ if(isset($triage->dependencies[$name])){ $unknownDependencies = []; - foreach($triage->dependencies[$name] as $k => $dependency){ + foreach($triage->dependencies[$name] as $dependency){ if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){ //assume that the plugin is never going to be loaded //by this point all soft dependencies have been ignored if they were able to be, so @@ -526,7 +533,7 @@ class PluginManager{ } public function tickSchedulers(int $currentTick) : void{ - foreach($this->enabledPlugins as $pluginName => $p){ + foreach(Utils::promoteKeys($this->enabledPlugins) as $pluginName => $p){ if(isset($this->enabledPlugins[$pluginName])){ //the plugin may have been disabled as a result of updating other plugins' schedulers, and therefore //removed from enabledPlugins; however, foreach will still see it due to copy-on-write diff --git a/src/plugin/ResourceProvider.php b/src/plugin/ResourceProvider.php index 3594d7eee8..5ff8db882a 100644 --- a/src/plugin/ResourceProvider.php +++ b/src/plugin/ResourceProvider.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\plugin; +/** + * @deprecated + */ interface ResourceProvider{ /** * Gets an embedded resource on the plugin file. diff --git a/src/promise/Promise.php b/src/promise/Promise.php index 0def7e6052..c09037158b 100644 --- a/src/promise/Promise.php +++ b/src/promise/Promise.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\promise; +use pocketmine\utils\Utils; use function count; use function spl_object_id; @@ -69,21 +70,22 @@ final class Promise{ * * @phpstan-template TPromiseValue * @phpstan-template TKey of array-key - * @phpstan-param non-empty-array> $promises + * @phpstan-param array> $promises * * @phpstan-return Promise> */ public static function all(array $promises) : Promise{ - if(count($promises) === 0){ - throw new \InvalidArgumentException("At least one promise must be provided"); - } /** @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){ + foreach(Utils::promoteKeys($promises) as $key => $promise){ $promise->onCompletion( function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{ $values[$key] = $value; diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index 2df6750def..ad44177691 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -25,6 +25,8 @@ namespace pocketmine\resourcepacks; use pocketmine\utils\Config; use pocketmine\utils\Filesystem; +use pocketmine\utils\Utils; +use Ramsey\Uuid\Uuid; use Symfony\Component\Filesystem\Path; use function array_keys; use function copy; @@ -46,10 +48,16 @@ class ResourcePackManager{ private string $path; private bool $serverForceResources = false; - /** @var ResourcePack[] */ + /** + * @var ResourcePack[] + * @phpstan-var list + */ private array $resourcePacks = []; - /** @var ResourcePack[] */ + /** + * @var ResourcePack[] + * @phpstan-var array + */ private array $uuidList = []; /** @@ -87,7 +95,7 @@ class ResourcePackManager{ throw new \InvalidArgumentException("\"resource_stack\" key should contain a list of pack names"); } - foreach($resourceStack as $pos => $pack){ + foreach(Utils::promoteKeys($resourceStack) as $pos => $pack){ if(!is_string($pack) && !is_int($pack) && !is_float($pack)){ $logger->critical("Found invalid entry in resource pack list at offset $pos of type " . gettype($pack)); continue; @@ -96,9 +104,14 @@ class ResourcePackManager{ try{ $newPack = $this->loadPackFromPath(Path::join($this->path, $pack)); - $this->resourcePacks[] = $newPack; $index = strtolower($newPack->getPackId()); + if(!Uuid::isValid($index)){ + //TODO: we should use Uuid in ResourcePack interface directly but that would break BC + //for now we need to validate this here to make sure it doesn't cause crashes later on + throw new ResourcePackException("Invalid UUID ($index)"); + } $this->uuidList[$index] = $newPack; + $this->resourcePacks[] = $newPack; $keyPath = Path::join($this->path, $pack . ".key"); if(file_exists($keyPath)){ @@ -164,6 +177,7 @@ class ResourcePackManager{ /** * Returns an array of resource packs in use, sorted in order of priority. * @return ResourcePack[] + * @phpstan-return list */ public function getResourceStack() : array{ return $this->resourcePacks; @@ -182,6 +196,11 @@ class ResourcePackManager{ $resourcePacks = []; foreach($resourceStack as $pack){ $uuid = strtolower($pack->getPackId()); + if(!Uuid::isValid($uuid)){ + //TODO: we should use Uuid in ResourcePack interface directly but that would break BC + //for now we need to validate this here to make sure it doesn't cause crashes later on + throw new \InvalidArgumentException("Invalid resource pack UUID ($uuid)"); + } if(isset($uuidList[$uuid])){ throw new \InvalidArgumentException("Cannot load two resource pack with the same UUID ($uuid)"); } diff --git a/src/resourcepacks/ZippedResourcePack.php b/src/resourcepacks/ZippedResourcePack.php index c4daeedf7b..4fcf204d94 100644 --- a/src/resourcepacks/ZippedResourcePack.php +++ b/src/resourcepacks/ZippedResourcePack.php @@ -100,7 +100,7 @@ class ZippedResourcePack implements ResourcePack{ try{ $manifest = (new CommentedJsonDecoder())->decode($manifestData); }catch(\RuntimeException $e){ - throw new ResourcePackException("Failed to parse manifest.json: " . $e->getMessage(), $e->getCode(), $e); + throw new ResourcePackException("Failed to parse manifest.json: " . $e->getMessage(), 0, $e); } if(!($manifest instanceof \stdClass)){ throw new ResourcePackException("manifest.json should contain a JSON object, not " . gettype($manifest)); diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index b0b64347ad..a9b466f40c 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -27,6 +27,7 @@ use pmmp\thread\Runnable; use pmmp\thread\ThreadSafe; use pmmp\thread\ThreadSafeArray; use pocketmine\thread\NonThreadSafeValue; +use pocketmine\timings\Timings; use function array_key_exists; use function igbinary_serialize; use function igbinary_unserialize; @@ -67,7 +68,10 @@ abstract class AsyncTask extends Runnable{ */ private static array $threadLocalStorage = []; - /** @phpstan-var ThreadSafeArray|null */ + /** + * @phpstan-var ThreadSafeArray|null + * @deprecated + */ private ?ThreadSafeArray $progressUpdates = null; private ThreadSafe|string|int|bool|null|float $result = null; @@ -78,10 +82,18 @@ abstract class AsyncTask extends Runnable{ public function run() : void{ $this->result = null; - $this->onRun(); + $timings = Timings::getAsyncTaskRunTimings($this); + $timings->startTiming(); + + try{ + $this->onRun(); + }finally{ + $timings->stopTiming(); + } $this->finished = true; AsyncWorker::getNotifier()->wakeupSleeper(); + AsyncWorker::maybeCollectCycles(); } /** @@ -153,6 +165,8 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated + * * Call this method from {@link AsyncTask::onRun} (AsyncTask execution thread) to schedule a call to * {@link AsyncTask::onProgressUpdate} from the main thread with the given progress parameter. * @@ -167,6 +181,7 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated * @internal Only call from AsyncPool.php on the main thread */ public function checkProgressUpdates() : void{ @@ -179,6 +194,8 @@ abstract class AsyncTask extends Runnable{ } /** + * @deprecated + * * Called from the main thread after {@link AsyncTask::publishProgress} is called. * All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before * {@link AsyncTask::onCompletion} is called. diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index 5fdfb1ebb7..528d632d17 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -24,12 +24,13 @@ declare(strict_types=1); namespace pocketmine\scheduler; use pmmp\thread\Thread as NativeThread; +use pocketmine\GarbageCollectorManager; use pocketmine\snooze\SleeperHandlerEntry; use pocketmine\snooze\SleeperNotifier; use pocketmine\thread\log\ThreadSafeLogger; use pocketmine\thread\Worker; +use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; -use function gc_enable; use function ini_set; class AsyncWorker extends Worker{ @@ -37,6 +38,7 @@ class AsyncWorker extends Worker{ private static array $store = []; private static ?SleeperNotifier $notifier = null; + private static ?GarbageCollectorManager $cycleGcManager = null; public function __construct( private ThreadSafeLogger $logger, @@ -52,11 +54,16 @@ class AsyncWorker extends Worker{ throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage"); } + public static function maybeCollectCycles() : void{ + if(self::$cycleGcManager === null){ + throw new AssumptionFailedError("GarbageCollectorManager not found in thread-local storage"); + } + self::$cycleGcManager->maybeCollectCycles(); + } + protected function onRun() : void{ \GlobalLogger::set($this->logger); - gc_enable(); - if($this->memoryLimit > 0){ ini_set('memory_limit', $this->memoryLimit . 'M'); $this->logger->debug("Set memory limit to " . $this->memoryLimit . " MB"); @@ -66,6 +73,8 @@ class AsyncWorker extends Worker{ } self::$notifier = $this->sleeperEntry->createNotifier(); + Timings::init(); + self::$cycleGcManager = new GarbageCollectorManager($this->logger, Timings::$asyncTaskWorkers); } public function getLogger() : ThreadSafeLogger{ diff --git a/src/scheduler/BulkCurlTask.php b/src/scheduler/BulkCurlTask.php index ccc1b24664..21f144702e 100644 --- a/src/scheduler/BulkCurlTask.php +++ b/src/scheduler/BulkCurlTask.php @@ -77,7 +77,10 @@ class BulkCurlTask extends AsyncTask{ * @phpstan-var \Closure(list) : void */ $callback = $this->fetchLocal(self::TLS_KEY_COMPLETION_CALLBACK); - /** @var InternetRequestResult[]|InternetException[] $results */ + /** + * @var InternetRequestResult[]|InternetException[] $results + * @phpstan-var list $results + */ $results = $this->getResult(); $callback($results); } diff --git a/src/scheduler/DumpWorkerMemoryTask.php b/src/scheduler/DumpWorkerMemoryTask.php index 5ef787b5b8..fac8d63680 100644 --- a/src/scheduler/DumpWorkerMemoryTask.php +++ b/src/scheduler/DumpWorkerMemoryTask.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\scheduler; use pmmp\thread\Thread as NativeThread; -use pocketmine\MemoryManager; +use pocketmine\MemoryDump; use Symfony\Component\Filesystem\Path; use function assert; @@ -41,7 +41,7 @@ class DumpWorkerMemoryTask extends AsyncTask{ public function onRun() : void{ $worker = NativeThread::getCurrentThread(); assert($worker instanceof AsyncWorker); - MemoryManager::dumpMemory( + MemoryDump::dumpMemory( $worker, Path::join($this->outputFolder, "AsyncWorker#" . $worker->getAsyncWorkerId()), $this->maxNesting, diff --git a/src/scheduler/Task.php b/src/scheduler/Task.php index bde405a637..db42b477ad 100644 --- a/src/scheduler/Task.php +++ b/src/scheduler/Task.php @@ -26,8 +26,12 @@ namespace pocketmine\scheduler; use pocketmine\utils\Utils; abstract class Task{ + /** @phpstan-var TaskHandler|null */ private ?TaskHandler $taskHandler = null; + /** + * @phpstan-return TaskHandler|null + */ final public function getHandler() : ?TaskHandler{ return $this->taskHandler; } @@ -36,6 +40,9 @@ abstract class Task{ return Utils::getNiceClassName($this); } + /** + * @phpstan-param TaskHandler|null $taskHandler + */ final public function setHandler(?TaskHandler $taskHandler) : void{ if($this->taskHandler === null || $taskHandler === null){ $this->taskHandler = $taskHandler; diff --git a/src/scheduler/TaskHandler.php b/src/scheduler/TaskHandler.php index b90c992d92..985344b649 100644 --- a/src/scheduler/TaskHandler.php +++ b/src/scheduler/TaskHandler.php @@ -26,6 +26,9 @@ namespace pocketmine\scheduler; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; +/** + * @template TTask of Task + */ class TaskHandler{ protected int $nextRun; @@ -36,6 +39,9 @@ class TaskHandler{ private string $taskName; private string $ownerName; + /** + * @phpstan-param TTask $task + */ public function __construct( protected Task $task, protected int $delay = -1, @@ -66,6 +72,9 @@ class TaskHandler{ $this->nextRun = $ticks; } + /** + * @phpstan-return TTask + */ public function getTask() : Task{ return $this->task; } diff --git a/src/scheduler/TaskScheduler.php b/src/scheduler/TaskScheduler.php index f2b7c48467..3e2b089c6c 100644 --- a/src/scheduler/TaskScheduler.php +++ b/src/scheduler/TaskScheduler.php @@ -33,12 +33,12 @@ use pocketmine\utils\ReversePriorityQueue; class TaskScheduler{ private bool $enabled = true; - /** @phpstan-var ReversePriorityQueue */ + /** @phpstan-var ReversePriorityQueue> */ protected ReversePriorityQueue $queue; /** * @var ObjectSet|TaskHandler[] - * @phpstan-var ObjectSet + * @phpstan-var ObjectSet> */ protected ObjectSet $tasks; @@ -51,18 +51,42 @@ class TaskScheduler{ $this->tasks = new ObjectSet(); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleTask(Task $task) : TaskHandler{ return $this->addTask($task, -1, -1); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleDelayedTask(Task $task, int $delay) : TaskHandler{ return $this->addTask($task, $delay, -1); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleRepeatingTask(Task $task, int $period) : TaskHandler{ return $this->addTask($task, -1, $period); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period) : TaskHandler{ return $this->addTask($task, $delay, $period); } @@ -77,10 +101,19 @@ class TaskScheduler{ } } + /** + * @phpstan-param TaskHandler $task + */ public function isQueued(TaskHandler $task) : bool{ return $this->tasks->contains($task); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ private function addTask(Task $task, int $delay, int $period) : TaskHandler{ if(!$this->enabled){ throw new \LogicException("Tried to schedule task to disabled scheduler"); @@ -99,6 +132,11 @@ class TaskScheduler{ return $this->handle(new TaskHandler($task, $delay, $period, $this->owner)); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TaskHandler $handler + * @phpstan-return TaskHandler + */ private function handle(TaskHandler $handler) : TaskHandler{ if($handler->isDelayed()){ $nextRun = $this->currentTick + $handler->getDelay(); @@ -128,7 +166,7 @@ class TaskScheduler{ } $this->currentTick = $currentTick; while($this->isReady($this->currentTick)){ - /** @var TaskHandler $task */ + /** @phpstan-var TaskHandler $task */ $task = $this->queue->extract(); if($task->isCancelled()){ $this->tasks->remove($task); diff --git a/src/scheduler/TimingsCollectionTask.php b/src/scheduler/TimingsCollectionTask.php new file mode 100644 index 0000000000..25cd41166c --- /dev/null +++ b/src/scheduler/TimingsCollectionTask.php @@ -0,0 +1,60 @@ +> + */ +final class TimingsCollectionTask extends AsyncTask{ + private const TLS_KEY_RESOLVER = "resolver"; + + /** + * @phpstan-param PromiseResolver> $promiseResolver + */ + public function __construct(PromiseResolver $promiseResolver){ + $this->storeLocal(self::TLS_KEY_RESOLVER, $promiseResolver); + } + + public function onRun() : void{ + $this->setResult(TimingsHandler::printCurrentThreadRecords()); + } + + public function onCompletion() : void{ + /** + * @var string[] $result + * @phpstan-var list $result + */ + $result = $this->getResult(); + /** + * @var PromiseResolver $promiseResolver + * @phpstan-var PromiseResolver> $promiseResolver + */ + $promiseResolver = $this->fetchLocal(self::TLS_KEY_RESOLVER); + + $promiseResolver->resolve($result); + } +} diff --git a/src/scheduler/TimingsControlTask.php b/src/scheduler/TimingsControlTask.php new file mode 100644 index 0000000000..51e906e6bc --- /dev/null +++ b/src/scheduler/TimingsControlTask.php @@ -0,0 +1,60 @@ +operation === self::ENABLE){ + TimingsHandler::setEnabled(true); + \GlobalLogger::get()->debug("Enabled timings"); + }elseif($this->operation === self::DISABLE){ + TimingsHandler::setEnabled(false); + \GlobalLogger::get()->debug("Disabled timings"); + }elseif($this->operation === self::RELOAD){ + TimingsHandler::reload(); + \GlobalLogger::get()->debug("Reset timings"); + }else{ + throw new \InvalidArgumentException("Invalid operation $this->operation"); + } + } +} diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 563af69bff..ee737f537b 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -30,12 +30,14 @@ use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\player\Player; use pocketmine\scheduler\AsyncTask; +use pocketmine\scheduler\Task; use pocketmine\scheduler\TaskHandler; use function get_class; use function str_starts_with; abstract class Timings{ public const GROUP_MINECRAFT = "Minecraft"; + /** @deprecated No longer used */ public const GROUP_BREAKDOWN = "Minecraft - Breakdown"; private static bool $initialized = false; @@ -123,11 +125,16 @@ abstract class Timings{ /** @var TimingsHandler[] */ private static array $asyncTaskProgressUpdate = []; + /** @var TimingsHandler[] */ private static array $asyncTaskCompletion = []; /** @var TimingsHandler[] */ private static array $asyncTaskError = []; + public static TimingsHandler $asyncTaskWorkers; + /** @var TimingsHandler[] */ + private static array $asyncTaskRun = []; + public static function init() : void{ if(self::$initialized){ return; @@ -187,11 +194,17 @@ abstract class Timings{ self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync); self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync); + self::$asyncTaskWorkers = new TimingsHandler("Async Task Workers"); + self::$playerCommand = new TimingsHandler("Player Command"); self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache"); } + /** + * @template TTask of Task + * @phpstan-param TaskHandler $task + */ public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{ self::init(); $name = "Task: " . $task->getTaskName(); @@ -339,6 +352,9 @@ abstract class Timings{ return self::$asyncTaskCompletion[$taskClass]; } + /** + * @deprecated No longer used + */ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ $taskClass = $task::class; if(!isset(self::$asyncTaskError[$taskClass])){ @@ -352,4 +368,18 @@ abstract class Timings{ return self::$asyncTaskError[$taskClass]; } + + public static function getAsyncTaskRunTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ + $taskClass = $task::class; + if(!isset(self::$asyncTaskRun[$taskClass])){ + self::init(); + self::$asyncTaskRun[$taskClass] = new TimingsHandler( + "AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Run", + self::$asyncTaskWorkers, + $group + ); + } + + return self::$asyncTaskRun[$taskClass]; + } } diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php index 574dd6d2b8..25f139d91d 100644 --- a/src/timings/TimingsHandler.php +++ b/src/timings/TimingsHandler.php @@ -23,20 +23,66 @@ declare(strict_types=1); namespace pocketmine\timings; +use pmmp\thread\Thread as NativeThread; +use pocketmine\promise\Promise; +use pocketmine\promise\PromiseResolver; use pocketmine\Server; +use pocketmine\utils\ObjectSet; use pocketmine\utils\Utils; +use function array_merge; +use function array_push; use function hrtime; use function implode; use function spl_object_id; +/** + * @phpstan-type CollectPromise Promise> + */ class TimingsHandler{ - private const FORMAT_VERSION = 2; //peak timings fix + private const FORMAT_VERSION = 3; //thread timings collection private static bool $enabled = false; private static int $timingStart = 0; - /** @return string[] */ - public static function printTimings() : array{ + /** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */ + private static ?ObjectSet $toggleCallbacks = null; + /** @phpstan-var ObjectSet<\Closure() : void> */ + private static ?ObjectSet $reloadCallbacks = null; + /** @phpstan-var ObjectSet<\Closure() : list> */ + private static ?ObjectSet $collectCallbacks = null; + + /** + * @phpstan-template T of object + * @phpstan-param ?ObjectSet $where + * @phpstan-param-out ObjectSet $where + * @phpstan-return ObjectSet + */ + private static function lazyGetSet(?ObjectSet &$where) : ObjectSet{ + //workaround for phpstan bug - allows us to ignore 1 error instead of 6 without suppressing other errors + return $where ??= new ObjectSet(); + } + + /** + * @phpstan-return ObjectSet<\Closure(bool $enable) : void> + */ + public static function getToggleCallbacks() : ObjectSet{ return self::lazyGetSet(self::$toggleCallbacks); } + + /** + * @phpstan-return ObjectSet<\Closure() : void> + */ + public static function getReloadCallbacks() : ObjectSet{ return self::lazyGetSet(self::$reloadCallbacks); } + + /** + * @phpstan-return ObjectSet<\Closure() : list> + */ + public static function getCollectCallbacks() : ObjectSet{ return self::lazyGetSet(self::$collectCallbacks); } + + /** + * @return string[] + * @phpstan-return list + */ + public static function printCurrentThreadRecords() : array{ + $threadId = NativeThread::getCurrentThread()?->getThreadId(); $groups = []; foreach(TimingsRecord::getAll() as $timings){ @@ -49,7 +95,7 @@ class TimingsHandler{ $avg = $time / $count; - $group = $timings->getGroup(); + $group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : ""); $groups[$group][] = implode(" ", [ $timings->getName(), "Time: $time", @@ -72,6 +118,16 @@ class TimingsHandler{ } } + return $result; + } + + /** + * @return string[] + * @phpstan-return list + */ + private static function printFooter() : array{ + $result = []; + $result[] = "# Version " . Server::getInstance()->getVersion(); $result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion(); @@ -79,29 +135,96 @@ class TimingsHandler{ $sampleTime = hrtime(true) - self::$timingStart; $result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)"; + return $result; } + /** + * @deprecated This only collects timings from the main thread. Collecting timings from all threads is an async + * operation, so it can't be done synchronously. + * + * @return string[] + */ + public static function printTimings() : array{ + $records = self::printCurrentThreadRecords(); + $footer = self::printFooter(); + + return [...$records, ...$footer]; + } + + /** + * Collects timings asynchronously, allowing timings from multiple threads to be aggregated into a single report. + * + * NOTE: You need to add a callback to collectCallbacks if you want to include timings from other threads. They + * won't be automatically collected if you don't, since the main thread has no way to access them. + * + * This is an asynchronous operation, and the result is returned as a promise. + * The caller must add a callback to the returned promise to get the complete timings report. + * + * @phpstan-return Promise> + */ + public static function requestPrintTimings() : Promise{ + $thisThreadRecords = self::printCurrentThreadRecords(); + + $otherThreadRecordPromises = []; + if(self::$collectCallbacks !== null){ + foreach(self::$collectCallbacks as $callback){ + $callbackPromises = $callback(); + array_push($otherThreadRecordPromises, ...$callbackPromises); + } + } + + /** @phpstan-var PromiseResolver> $resolver */ + $resolver = new PromiseResolver(); + Promise::all($otherThreadRecordPromises)->onCompletion( + function(array $promisedRecords) use ($resolver, $thisThreadRecords) : void{ + $resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]); + }, + function() : void{ + throw new \AssertionError("This promise is not expected to be rejected"); + } + ); + + return $resolver->getPromise(); + } + public static function isEnabled() : bool{ return self::$enabled; } public static function setEnabled(bool $enable = true) : void{ + if($enable === self::$enabled){ + return; + } self::$enabled = $enable; - self::reload(); + self::internalReload(); + if(self::$toggleCallbacks !== null){ + foreach(self::$toggleCallbacks as $callback){ + $callback($enable); + } + } } public static function getStartTime() : float{ return self::$timingStart; } - public static function reload() : void{ + private static function internalReload() : void{ TimingsRecord::reset(); if(self::$enabled){ self::$timingStart = hrtime(true); } } + public static function reload() : void{ + self::internalReload(); + if(self::$reloadCallbacks !== null){ + foreach(self::$reloadCallbacks as $callback){ + $callback(); + } + } + } + public static function tick(bool $measure = true) : void{ if(self::$enabled){ TimingsRecord::tick($measure); diff --git a/src/timings/TimingsRecord.php b/src/timings/TimingsRecord.php index 2e4928d8a6..390ab74e51 100644 --- a/src/timings/TimingsRecord.php +++ b/src/timings/TimingsRecord.php @@ -131,7 +131,7 @@ final class TimingsRecord{ } public function stopTiming(int $now) : void{ - if($this->start == 0){ + if($this->start === 0){ return; } if(self::$currentRecord !== $this){ diff --git a/src/utils/Config.php b/src/utils/Config.php index cabc8fe166..7d0501935e 100644 --- a/src/utils/Config.php +++ b/src/utils/Config.php @@ -54,6 +54,7 @@ use const CASE_LOWER; use const JSON_BIGINT_AS_STRING; use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; +use const PHP_INT_MAX; use const YAML_UTF8_ENCODING; /** @@ -72,7 +73,7 @@ class Config{ /** * @var mixed[] - * @phpstan-var array + * @phpstan-var array */ private array $config = []; @@ -339,7 +340,7 @@ class Config{ } public function setNested(string $key, mixed $value) : void{ - $vars = explode(".", $key); + $vars = explode(".", $key, limit: PHP_INT_MAX); $base = array_shift($vars); if(!isset($this->config[$base])){ @@ -366,7 +367,7 @@ class Config{ return $this->nestedCache[$key]; } - $vars = explode(".", $key); + $vars = explode(".", $key, limit: PHP_INT_MAX); $base = array_shift($vars); if(isset($this->config[$base])){ $base = $this->config[$base]; @@ -390,7 +391,7 @@ class Config{ $this->nestedCache = []; $this->changed = true; - $vars = explode(".", $key); + $vars = explode(".", $key, limit: PHP_INT_MAX); $currentNode = &$this->config; while(count($vars) > 0){ @@ -450,7 +451,7 @@ class Config{ /** * @return mixed[] - * @phpstan-return list|array + * @phpstan-return list|array */ public function getAll(bool $keys = false) : array{ return ($keys ? array_keys($this->config) : $this->config); @@ -458,7 +459,6 @@ class Config{ /** * @param mixed[] $defaults - * @phpstan-param array $defaults */ public function setDefaults(array $defaults) : void{ $this->fillDefaults($defaults, $this->config); @@ -467,13 +467,11 @@ class Config{ /** * @param mixed[] $default * @param mixed[] $data reference parameter - * @phpstan-param array $default - * @phpstan-param array $data - * @phpstan-param-out array $data + * @phpstan-param-out array $data */ private function fillDefaults(array $default, array &$data) : int{ $changed = 0; - foreach(Utils::stringifyKeys($default) as $k => $v){ + foreach(Utils::promoteKeys($default) as $k => $v){ if(is_array($v)){ if(!isset($data[$k]) || !is_array($data[$k])){ $data[$k] = []; @@ -498,7 +496,7 @@ class Config{ */ public static function parseList(string $content) : array{ $result = []; - foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){ + foreach(explode("\n", trim(str_replace("\r\n", "\n", $content)), limit: PHP_INT_MAX) as $v){ $v = trim($v); if($v === ""){ continue; @@ -509,8 +507,8 @@ class Config{ } /** - * @param string[] $entries - * @phpstan-param list $entries + * @param string[]|int[] $entries + * @phpstan-param list $entries */ public static function writeList(array $entries) : string{ return implode("\n", $entries); @@ -518,11 +516,11 @@ class Config{ /** * @param string[]|int[]|float[]|bool[] $config - * @phpstan-param array $config + * @phpstan-param array $config */ public static function writeProperties(array $config) : string{ $content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n"; - foreach(Utils::stringifyKeys($config) as $k => $v){ + foreach(Utils::promoteKeys($config) as $k => $v){ if(is_bool($v)){ $v = $v ? "on" : "off"; } @@ -534,7 +532,7 @@ class Config{ /** * @return string[]|int[]|float[]|bool[] - * @phpstan-return array + * @phpstan-return array */ public static function parseProperties(string $content) : array{ $result = []; @@ -560,7 +558,7 @@ class Config{ }; break; } - $result[(string) $k] = $v; + $result[$k] = $v; } } diff --git a/src/utils/Git.php b/src/utils/Git.php index 041d795a10..2b9e242bcb 100644 --- a/src/utils/Git.php +++ b/src/utils/Git.php @@ -39,7 +39,7 @@ final class Git{ * @param bool $dirty reference parameter, set to whether the repo has local changes */ public static function getRepositoryState(string $dir, bool &$dirty) : ?string{ - if(Process::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 && $out !== false && strlen($out = trim($out)) === 40){ + if(Process::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 && strlen($out = trim($out)) === 40){ if(Process::execute("git -C \"$dir\" diff --quiet") === 1 || Process::execute("git -C \"$dir\" diff --cached --quiet") === 1){ //Locally-modified $dirty = true; } diff --git a/src/utils/Internet.php b/src/utils/Internet.php index 07b3c7a338..4b0e00f4af 100644 --- a/src/utils/Internet.php +++ b/src/utils/Internet.php @@ -60,6 +60,7 @@ use const CURLOPT_RETURNTRANSFER; use const CURLOPT_SSL_VERIFYHOST; use const CURLOPT_SSL_VERIFYPEER; use const CURLOPT_TIMEOUT_MS; +use const PHP_INT_MAX; use const SOCK_DGRAM; use const SOL_UDP; @@ -100,7 +101,7 @@ class Internet{ } $ip = self::getURL("http://ifconfig.me/ip"); - if($ip !== null && ($addr = trim($ip->getBody())) != ""){ + if($ip !== null && ($addr = trim($ip->getBody())) !== ""){ return self::$ip = $addr; } @@ -227,9 +228,10 @@ class Internet{ $rawHeaders = substr($raw, 0, $headerSize); $body = substr($raw, $headerSize); $headers = []; - foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){ + //TODO: explore if we can set these limits lower + foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){ $headerGroup = []; - foreach(explode("\r\n", $rawHeaderGroup) as $line){ + foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){ $nameValue = explode(":", $line, 2); if(isset($nameValue[1])){ $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); diff --git a/src/utils/Process.php b/src/utils/Process.php index 1370ab27c7..90149870af 100644 --- a/src/utils/Process.php +++ b/src/utils/Process.php @@ -174,8 +174,17 @@ final class Process{ return -1; } - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); + $out = stream_get_contents($pipes[1]); + if($out === false){ + throw new AssumptionFailedError("Presume this can't happen for proc_open ... ???"); + } + $stdout = $out; + + $err = stream_get_contents($pipes[2]); + if($err === false){ + throw new AssumptionFailedError("Presume this can't happen for proc_open ... ???"); + } + $stderr = $err; foreach($pipes as $p){ fclose($p); diff --git a/src/utils/ReversePriorityQueue.php b/src/utils/ReversePriorityQueue.php index 53fd0f08a1..03f1aea8d3 100644 --- a/src/utils/ReversePriorityQueue.php +++ b/src/utils/ReversePriorityQueue.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\utils; /** - * @phpstan-template TPriority + * @phpstan-template TPriority of numeric * @phpstan-template TValue * @phpstan-extends \SplPriorityQueue */ diff --git a/src/utils/SignalHandler.php b/src/utils/SignalHandler.php index 82ae02da6a..75d38ff970 100644 --- a/src/utils/SignalHandler.php +++ b/src/utils/SignalHandler.php @@ -36,14 +36,12 @@ use const SIGTERM; final class SignalHandler{ /** @phpstan-var (\Closure(int) : void)|null */ - private ?\Closure $interruptCallback; + private ?\Closure $interruptCallback = null; /** * @phpstan-param \Closure() : void $interruptCallback */ public function __construct(\Closure $interruptCallback){ - $this->interruptCallback = $interruptCallback; - if(function_exists('sapi_windows_set_ctrl_handler')){ sapi_windows_set_ctrl_handler($this->interruptCallback = function(int $signo) use ($interruptCallback) : void{ if($signo === PHP_WINDOWS_EVENT_CTRL_C || $signo === PHP_WINDOWS_EVENT_CTRL_BREAK){ diff --git a/src/utils/Terminal.php b/src/utils/Terminal.php index 49b4224ec8..bbd2329691 100644 --- a/src/utils/Terminal.php +++ b/src/utils/Terminal.php @@ -59,6 +59,17 @@ abstract class Terminal{ public static string $COLOR_YELLOW = ""; public static string $COLOR_WHITE = ""; public static string $COLOR_MINECOIN_GOLD = ""; + public static string $COLOR_MATERIAL_QUARTZ = ""; + public static string $COLOR_MATERIAL_IRON = ""; + public static string $COLOR_MATERIAL_NETHERITE = ""; + public static string $COLOR_MATERIAL_REDSTONE = ""; + public static string $COLOR_MATERIAL_COPPER = ""; + public static string $COLOR_MATERIAL_GOLD = ""; + public static string $COLOR_MATERIAL_EMERALD = ""; + public static string $COLOR_MATERIAL_DIAMOND = ""; + public static string $COLOR_MATERIAL_LAPIS = ""; + public static string $COLOR_MATERIAL_AMETHYST = ""; + public static string $COLOR_MATERIAL_RESIN = ""; private static ?bool $formattingCodes = null; @@ -111,6 +122,17 @@ abstract class Terminal{ self::$COLOR_YELLOW = $color(227); self::$COLOR_WHITE = $color(231); self::$COLOR_MINECOIN_GOLD = $color(184); + self::$COLOR_MATERIAL_QUARTZ = $color(188); + self::$COLOR_MATERIAL_IRON = $color(251); + self::$COLOR_MATERIAL_NETHERITE = $color(237); + self::$COLOR_MATERIAL_REDSTONE = $color(88); + self::$COLOR_MATERIAL_COPPER = $color(131); + self::$COLOR_MATERIAL_GOLD = $color(178); + self::$COLOR_MATERIAL_EMERALD = $color(35); + self::$COLOR_MATERIAL_DIAMOND = $color(37); + self::$COLOR_MATERIAL_LAPIS = $color(24); + self::$COLOR_MATERIAL_AMETHYST = $color(98); + self::$COLOR_MATERIAL_RESIN = $color(208); } protected static function getEscapeCodes() : void{ @@ -144,15 +166,26 @@ abstract class Terminal{ self::$COLOR_YELLOW = $colors >= 256 ? $setaf(227) : $setaf(11); self::$COLOR_WHITE = $colors >= 256 ? $setaf(231) : $setaf(15); self::$COLOR_MINECOIN_GOLD = $colors >= 256 ? $setaf(184) : $setaf(11); + self::$COLOR_MATERIAL_QUARTZ = $colors >= 256 ? $setaf(188) : $setaf(7); + self::$COLOR_MATERIAL_IRON = $colors >= 256 ? $setaf(251) : $setaf(7); + self::$COLOR_MATERIAL_NETHERITE = $colors >= 256 ? $setaf(237) : $setaf(1); + self::$COLOR_MATERIAL_REDSTONE = $colors >= 256 ? $setaf(88) : $setaf(9); + self::$COLOR_MATERIAL_COPPER = $colors >= 256 ? $setaf(131) : $setaf(3); + self::$COLOR_MATERIAL_GOLD = $colors >= 256 ? $setaf(178) : $setaf(11); + self::$COLOR_MATERIAL_EMERALD = $colors >= 256 ? $setaf(35) : $setaf(2); + self::$COLOR_MATERIAL_DIAMOND = $colors >= 256 ? $setaf(37) : $setaf(14); + self::$COLOR_MATERIAL_LAPIS = $colors >= 256 ? $setaf(24) : $setaf(12); + self::$COLOR_MATERIAL_AMETHYST = $colors >= 256 ? $setaf(98) : $setaf(13); + self::$COLOR_MATERIAL_RESIN = $colors >= 256 ? $setaf(208) : $setaf(11); }else{ - self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = $setaf(0); - self::$COLOR_RED = self::$COLOR_DARK_RED = $setaf(1); - self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = $setaf(2); - self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = $setaf(3); - self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = $setaf(4); - self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = $setaf(5); - self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = $setaf(6); - self::$COLOR_GRAY = self::$COLOR_WHITE = $setaf(7); + self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = self::$COLOR_MATERIAL_NETHERITE = $setaf(0); + self::$COLOR_RED = self::$COLOR_DARK_RED = self::$COLOR_MATERIAL_REDSTONE = self::$COLOR_MATERIAL_COPPER = $setaf(1); + self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = self::$COLOR_MATERIAL_EMERALD = $setaf(2); + self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = self::$COLOR_MATERIAL_RESIN = $setaf(3); + self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = self::$COLOR_MATERIAL_LAPIS = $setaf(4); + self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = self::$COLOR_MATERIAL_AMETHYST = $setaf(5); + self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = self::$COLOR_MATERIAL_DIAMOND = $setaf(6); + self::$COLOR_GRAY = self::$COLOR_WHITE = self::$COLOR_MATERIAL_QUARTZ = self::$COLOR_MATERIAL_IRON = $setaf(7); } } @@ -191,12 +224,10 @@ abstract class Terminal{ public static function toANSI(string $string) : string{ $newString = ""; foreach(TextFormat::tokenize($string) as $token){ - $newString .= match($token){ + $newString .= match ($token) { TextFormat::BOLD => Terminal::$FORMAT_BOLD, TextFormat::OBFUSCATED => Terminal::$FORMAT_OBFUSCATED, TextFormat::ITALIC => Terminal::$FORMAT_ITALIC, - TextFormat::UNDERLINE => Terminal::$FORMAT_UNDERLINE, - TextFormat::STRIKETHROUGH => Terminal::$FORMAT_STRIKETHROUGH, TextFormat::RESET => Terminal::$FORMAT_RESET, TextFormat::BLACK => Terminal::$COLOR_BLACK, TextFormat::DARK_BLUE => Terminal::$COLOR_DARK_BLUE, @@ -215,6 +246,17 @@ abstract class Terminal{ TextFormat::YELLOW => Terminal::$COLOR_YELLOW, TextFormat::WHITE => Terminal::$COLOR_WHITE, TextFormat::MINECOIN_GOLD => Terminal::$COLOR_MINECOIN_GOLD, + TextFormat::MATERIAL_QUARTZ => Terminal::$COLOR_MATERIAL_QUARTZ, + TextFormat::MATERIAL_IRON => Terminal::$COLOR_MATERIAL_IRON, + TextFormat::MATERIAL_NETHERITE => Terminal::$COLOR_MATERIAL_NETHERITE, + TextFormat::MATERIAL_REDSTONE => Terminal::$COLOR_MATERIAL_REDSTONE, + TextFormat::MATERIAL_COPPER => Terminal::$COLOR_MATERIAL_COPPER, + TextFormat::MATERIAL_GOLD => Terminal::$COLOR_MATERIAL_GOLD, + TextFormat::MATERIAL_EMERALD => Terminal::$COLOR_MATERIAL_EMERALD, + TextFormat::MATERIAL_DIAMOND => Terminal::$COLOR_MATERIAL_DIAMOND, + TextFormat::MATERIAL_LAPIS => Terminal::$COLOR_MATERIAL_LAPIS, + TextFormat::MATERIAL_AMETHYST => Terminal::$COLOR_MATERIAL_AMETHYST, + TextFormat::MATERIAL_RESIN => Terminal::$COLOR_MATERIAL_RESIN, default => $token, }; } diff --git a/src/utils/TextFormat.php b/src/utils/TextFormat.php index dfd6a359ae..0c948592af 100644 --- a/src/utils/TextFormat.php +++ b/src/utils/TextFormat.php @@ -63,6 +63,17 @@ abstract class TextFormat{ public const YELLOW = TextFormat::ESCAPE . "e"; public const WHITE = TextFormat::ESCAPE . "f"; public const MINECOIN_GOLD = TextFormat::ESCAPE . "g"; + public const MATERIAL_QUARTZ = TextFormat::ESCAPE . "h"; + public const MATERIAL_IRON = TextFormat::ESCAPE . "i"; + public const MATERIAL_NETHERITE = TextFormat::ESCAPE . "j"; + public const MATERIAL_REDSTONE = TextFormat::ESCAPE . "m"; + public const MATERIAL_COPPER = TextFormat::ESCAPE . "n"; + public const MATERIAL_GOLD = TextFormat::ESCAPE . "p"; + public const MATERIAL_EMERALD = TextFormat::ESCAPE . "q"; + public const MATERIAL_DIAMOND = TextFormat::ESCAPE . "s"; + public const MATERIAL_LAPIS = TextFormat::ESCAPE . "t"; + public const MATERIAL_AMETHYST = TextFormat::ESCAPE . "u"; + public const MATERIAL_RESIN = TextFormat::ESCAPE . "v"; public const COLORS = [ self::BLACK => self::BLACK, @@ -82,19 +93,30 @@ abstract class TextFormat{ self::YELLOW => self::YELLOW, self::WHITE => self::WHITE, self::MINECOIN_GOLD => self::MINECOIN_GOLD, + self::MATERIAL_QUARTZ => self::MATERIAL_QUARTZ, + self::MATERIAL_IRON => self::MATERIAL_IRON, + self::MATERIAL_NETHERITE => self::MATERIAL_NETHERITE, + self::MATERIAL_REDSTONE => self::MATERIAL_REDSTONE, + self::MATERIAL_COPPER => self::MATERIAL_COPPER, + self::MATERIAL_GOLD => self::MATERIAL_GOLD, + self::MATERIAL_EMERALD => self::MATERIAL_EMERALD, + self::MATERIAL_DIAMOND => self::MATERIAL_DIAMOND, + self::MATERIAL_LAPIS => self::MATERIAL_LAPIS, + self::MATERIAL_AMETHYST => self::MATERIAL_AMETHYST, + self::MATERIAL_RESIN => self::MATERIAL_RESIN, ]; public const OBFUSCATED = TextFormat::ESCAPE . "k"; public const BOLD = TextFormat::ESCAPE . "l"; - public const STRIKETHROUGH = TextFormat::ESCAPE . "m"; - public const UNDERLINE = TextFormat::ESCAPE . "n"; + /** @deprecated */ + public const STRIKETHROUGH = ""; + /** @deprecated */ + public const UNDERLINE = ""; public const ITALIC = TextFormat::ESCAPE . "o"; public const FORMATS = [ self::OBFUSCATED => self::OBFUSCATED, self::BOLD => self::BOLD, - self::STRIKETHROUGH => self::STRIKETHROUGH, - self::UNDERLINE => self::UNDERLINE, self::ITALIC => self::ITALIC, ]; @@ -130,7 +152,7 @@ abstract class TextFormat{ * @return string[] */ public static function tokenize(string $string) : array{ - $result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-gk-or])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-v])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); if($result === false) throw self::makePcreError(); return $result; } @@ -144,7 +166,7 @@ abstract class TextFormat{ $string = mb_scrub($string, 'UTF-8'); $string = self::preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console) if($removeFormat){ - $string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-gk-or]/u", "", $string)); + $string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-v]/u", "", $string)); } return str_replace("\x1b", "", self::preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string)); } @@ -155,7 +177,7 @@ abstract class TextFormat{ * @param string $placeholder default "&" */ public static function colorize(string $string, string $placeholder = "&") : string{ - return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-gk-or])/u', TextFormat::ESCAPE . '$1', $string); + return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-v])/u', TextFormat::ESCAPE . '$1', $string); } /** @@ -183,6 +205,20 @@ abstract class TextFormat{ return $baseFormat . str_replace(TextFormat::RESET, $baseFormat, $string); } + /** + * Converts any Java formatting codes in the given string to Bedrock. + * + * As of 1.21.50, strikethrough (§m) and underline (§n) are not supported by Bedrock, and these symbols are instead + * used to represent additional colours in Bedrock. To avoid unintended formatting, this function currently strips + * those formatting codes to prevent unintended colour display in formatted text. + * + * If Bedrock starts to support these formats in the future, this function will be updated to translate them rather + * than removing them. + */ + public static function javaToBedrock(string $string) : string{ + return str_replace([TextFormat::ESCAPE . "m", TextFormat::ESCAPE . "n"], "", $string); + } + /** * Returns an HTML-formatted string with colors/markup */ @@ -190,104 +226,47 @@ abstract class TextFormat{ $newString = ""; $tokens = 0; foreach(self::tokenize($string) as $token){ - switch($token){ - case TextFormat::BOLD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::OBFUSCATED: - //$newString .= ""; - //++$tokens; - break; - case TextFormat::ITALIC: - $newString .= ""; - ++$tokens; - break; - case TextFormat::UNDERLINE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::STRIKETHROUGH: - $newString .= ""; - ++$tokens; - break; - case TextFormat::RESET: - $newString .= str_repeat("", $tokens); - $tokens = 0; - break; - - //Colors - case TextFormat::BLACK: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_BLUE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_GREEN: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_AQUA: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_RED: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_PURPLE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::GOLD: - $newString .= ""; - ++$tokens; - break; - case TextFormat::GRAY: - $newString .= ""; - ++$tokens; - break; - case TextFormat::DARK_GRAY: - $newString .= ""; - ++$tokens; - break; - case TextFormat::BLUE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::GREEN: - $newString .= ""; - ++$tokens; - break; - case TextFormat::AQUA: - $newString .= ""; - ++$tokens; - break; - case TextFormat::RED: - $newString .= ""; - ++$tokens; - break; - case TextFormat::LIGHT_PURPLE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::YELLOW: - $newString .= ""; - ++$tokens; - break; - case TextFormat::WHITE: - $newString .= ""; - ++$tokens; - break; - case TextFormat::MINECOIN_GOLD: - $newString .= ""; - ++$tokens; - break; - default: - $newString .= $token; - break; + $formatString = match($token){ + TextFormat::BLACK => "color:#000", + TextFormat::DARK_BLUE => "color:#00A", + TextFormat::DARK_GREEN => "color:#0A0", + TextFormat::DARK_AQUA => "color:#0AA", + TextFormat::DARK_RED => "color:#A00", + TextFormat::DARK_PURPLE => "color:#A0A", + TextFormat::GOLD => "color:#FA0", + TextFormat::GRAY => "color:#AAA", + TextFormat::DARK_GRAY => "color:#555", + TextFormat::BLUE => "color:#55F", + TextFormat::GREEN => "color:#5F5", + TextFormat::AQUA => "color:#5FF", + TextFormat::RED => "color:#F55", + TextFormat::LIGHT_PURPLE => "color:#F5F", + TextFormat::YELLOW => "color:#FF5", + TextFormat::WHITE => "color:#FFF", + TextFormat::MINECOIN_GOLD => "color:#dd0", + TextFormat::MATERIAL_QUARTZ => "color:#e2d3d1", + TextFormat::MATERIAL_IRON => "color:#cec9c9", + TextFormat::MATERIAL_NETHERITE => "color:#44393a", + TextFormat::MATERIAL_REDSTONE => "color:#961506", + TextFormat::MATERIAL_COPPER => "color:#b4684d", + TextFormat::MATERIAL_GOLD => "color:#deb02c", + TextFormat::MATERIAL_EMERALD => "color:#119f36", + TextFormat::MATERIAL_DIAMOND => "color:#2cb9a8", + TextFormat::MATERIAL_LAPIS => "color:#20487a", + TextFormat::MATERIAL_AMETHYST => "color:#9a5cc5", + TextFormat::MATERIAL_RESIN => "color:#fc7812", + TextFormat::BOLD => "font-weight:bold", + TextFormat::ITALIC => "font-style:italic", + default => null + }; + if($formatString !== null){ + $newString .= ""; + ++$tokens; + }elseif($token === TextFormat::RESET){ + $newString .= str_repeat("", $tokens); + $tokens = 0; + }else{ + $newString .= $token; } } diff --git a/src/utils/Timezone.php b/src/utils/Timezone.php index 7f1dcf0e41..6723b12ebd 100644 --- a/src/utils/Timezone.php +++ b/src/utils/Timezone.php @@ -134,7 +134,7 @@ abstract class Timezone{ $offset = $matches[2]; - if($offset == ""){ + if($offset === ""){ return "UTC"; } @@ -156,7 +156,7 @@ abstract class Timezone{ $offset = trim(exec('date +%:z')); - if($offset == "+00:00"){ + if($offset === "+00:00"){ return "UTC"; } @@ -195,7 +195,7 @@ abstract class Timezone{ $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; //After date_parse is done, put the sign back - if($negative_offset == true){ + if($negative_offset){ $offset = -abs($offset); } @@ -204,7 +204,7 @@ abstract class Timezone{ //That's been a bug in PHP since 2008! foreach(timezone_abbreviations_list() as $zones){ foreach($zones as $timezone){ - if($timezone['timezone_id'] !== null && $timezone['offset'] == $offset){ + if($timezone['timezone_id'] !== null && $timezone['offset'] === $offset){ return $timezone['timezone_id']; } } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 46d6732166..046296cf48 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -67,6 +67,8 @@ use function is_nan; use function is_object; use function is_string; use function mb_check_encoding; +use function mt_getrandmax; +use function mt_rand; use function ob_end_clean; use function ob_get_contents; use function ob_start; @@ -80,7 +82,7 @@ use function preg_match_all; 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; @@ -121,7 +123,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 @@ -165,6 +167,7 @@ final class Utils{ /** * @phpstan-return \Closure(object) : object + * @deprecated */ public static function cloneCallback() : \Closure{ return static function(object $o){ @@ -177,15 +180,13 @@ final class Utils{ * @phpstan-template TValue of object * * @param object[] $array - * @phpstan-param array $array + * @phpstan-param array|list $array * * @return object[] - * @phpstan-return array + * @phpstan-return ($array is list ? list : array) */ public static function cloneObjectArray(array $array) : array{ - /** @phpstan-var \Closure(TValue) : TValue $callback */ - $callback = self::cloneCallback(); - return array_map($callback, $array); + return array_map(fn(object $o) => clone $o, $array); } /** @@ -218,7 +219,7 @@ final class Utils{ $mac = implode("\n", $mac); if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches) > 0){ foreach($matches[1] as $i => $v){ - if($v == "00-00-00-00-00-00"){ + if($v === "00-00-00-00-00-00"){ unset($matches[1][$i]); } } @@ -232,7 +233,7 @@ final class Utils{ $mac = implode("\n", $mac); if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches) > 0){ foreach($matches[1] as $i => $v){ - if($v == "00:00:00:00:00:00"){ + if($v === "00:00:00:00:00:00"){ unset($matches[1][$i]); } } @@ -263,14 +264,7 @@ final class Utils{ } /** - * Returns the current Operating System - * Windows => win - * MacOS => mac - * iOS => ios - * Android => android - * Linux => Linux - * BSD => bsd - * Other => other + * @return string one of the Utils::OS_* constants */ public static function getOS(bool $recalculate = false) : string{ if(self::$os === null || $recalculate){ @@ -375,7 +369,7 @@ final class Utils{ debug_zval_dump($value); $contents = ob_get_contents(); if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here"); - $ret = explode("\n", $contents); + $ret = explode("\n", $contents, limit: 2); ob_end_clean(); if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){ @@ -403,7 +397,8 @@ final class Utils{ } /** - * @param mixed[] $trace + * @param mixed[][] $trace + * @phpstan-param list>|null $trace * @return string[] */ public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{ @@ -445,6 +440,7 @@ final class Utils{ * @phpstan-param list> $trace * * @return string[] + * @phpstan-return list */ public static function printableTrace(array $trace, int $maxStringLength = 80) : array{ $messages = []; @@ -456,7 +452,7 @@ final class Utils{ }else{ $args = $trace[$i]["params"]; } - /** @var mixed[] $args */ + /** @phpstan-var array $args */ $paramsList = []; $offset = 0; @@ -466,7 +462,15 @@ final class Utils{ } $params = implode(", ", $paramsList); } - $messages[] = "#$i " . (isset($trace[$i]["file"]) ? Filesystem::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" || $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")"; + $messages[] = "#$i " . + (isset($trace[$i]["file"]) ? Filesystem::cleanPath($trace[$i]["file"]) : "") . + "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . + (isset($trace[$i]["class"]) ? + $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" || $trace[$i]["type"] === "->") ? "->" : "::") : + "" + ) . + $trace[$i]["function"] . + "(" . Utils::printable($params) . ")"; } return $messages; } @@ -487,7 +491,7 @@ final class Utils{ $rawFrame = $rawTrace[$frameId]; $safeTrace[$frameId] = new ThreadCrashInfoFrame( $printableFrame, - $rawFrame["file"] ?? "unknown", + $rawFrame["file"] ?? null, $rawFrame["line"] ?? 0 ); } @@ -583,7 +587,7 @@ final class Utils{ * @phpstan-param \Closure(TMemberType) : void $validator */ public static function validateArrayValueType(array $array, \Closure $validator) : void{ - foreach($array as $k => $v){ + foreach(Utils::promoteKeys($array) as $k => $v){ try{ $validator($v); }catch(\TypeError $e){ @@ -608,6 +612,18 @@ final class Utils{ } } + /** + * Gets rid of PHPStan BenevolentUnionType on array keys, so that wrong type errors get reported properly + * Use this if you don't care what the key type is and just want proper PHPStan error reporting + * + * @phpstan-template TValueType + * @phpstan-param array $array + * @phpstan-return array + */ + public static function promoteKeys(array $array) : array{ + return $array; + } + public static function checkUTF8(string $string) : void{ if(!mb_check_encoding($string, 'UTF-8')){ throw new \InvalidArgumentException("Text must be valid UTF-8"); @@ -675,4 +691,12 @@ final class Utils{ //jit not available return null; } + + /** + * Returns a random float between 0.0 and 1.0 + * Drop-in replacement for lcg_value() + */ + public static function getRandomFloat() : float{ + return mt_rand() / mt_getrandmax(); + } } diff --git a/src/world/BlockTransaction.php b/src/world/BlockTransaction.php index 5f7e9f9faa..46cbc7903f 100644 --- a/src/world/BlockTransaction.php +++ b/src/world/BlockTransaction.php @@ -28,7 +28,10 @@ use pocketmine\math\Vector3; use pocketmine\utils\Utils; class BlockTransaction{ - /** @var Block[][][] */ + /** + * @var Block[][][] + * @phpstan-var array>> + */ private array $blocks = []; /** diff --git a/src/world/World.php b/src/world/World.php index fbfa05b71d..3a7d0c538b 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\ReversePriorityQueue; +use pocketmine\utils\Utils; use pocketmine\world\biome\Biome; use pocketmine\world\biome\BiomeRegistry; use pocketmine\world\format\Chunk; @@ -112,6 +113,7 @@ use function array_keys; use function array_map; use function array_merge; use function array_sum; +use function array_values; use function assert; use function cos; use function count; @@ -120,7 +122,6 @@ use function get_class; use function gettype; use function is_a; use function is_object; -use function lcg_value; use function max; use function microtime; use function min; @@ -167,6 +168,9 @@ class World implements ChunkManager{ public const DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK = 3; + //TODO: this could probably do with being a lot bigger + private const BLOCK_CACHE_SIZE_CAP = 2048; + /** * @var Player[] entity runtime ID => Player * @phpstan-var array @@ -202,6 +206,7 @@ class World implements ChunkManager{ * @phpstan-var array> */ private array $blockCache = []; + private int $blockCacheSize = 0; /** * @var AxisAlignedBB[][][] chunkHash => [relativeBlockHash => AxisAlignedBB[]] * @phpstan-var array>> @@ -370,6 +375,8 @@ class World implements ChunkManager{ private \Logger $logger; + private RuntimeBlockStateRegistry $blockStateRegistry; + /** * @phpstan-return ChunkPosHash */ @@ -483,6 +490,7 @@ class World implements ChunkManager{ $this->displayName = $this->provider->getWorldData()->getName(); $this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName"); + $this->blockStateRegistry = RuntimeBlockStateRegistry::getInstance(); $this->minY = $this->provider->getWorldMinY(); $this->maxY = $this->provider->getWorldMaxY(); @@ -554,7 +562,7 @@ class World implements ChunkManager{ }catch(BlockStateDeserializeException){ continue; } - $block = RuntimeBlockStateRegistry::getInstance()->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData)); + $block = $this->blockStateRegistry->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData)); }else{ //TODO: we probably ought to log an error here continue; @@ -565,7 +573,7 @@ class World implements ChunkManager{ } } - foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state){ + foreach($this->blockStateRegistry->getAllKnownStates() as $state){ $dontTickName = $dontTickBlocks[$state->getTypeId()] ?? null; if($dontTickName === null && $state->ticksRandomly()){ $this->randomTickBlocks[$state->getStateId()] = true; @@ -653,6 +661,7 @@ class World implements ChunkManager{ $this->provider->close(); $this->blockCache = []; + $this->blockCacheSize = 0; $this->blockCollisionBoxCache = []; $this->unloaded = true; @@ -673,7 +682,6 @@ class World implements ChunkManager{ * Used for broadcasting sounds and particles with specific targets. * * @param Player[] $allowed - * @phpstan-param list $allowed * * @return array */ @@ -1084,7 +1092,6 @@ class World implements ChunkManager{ /** * @param Vector3[] $blocks - * @phpstan-param list $blocks * * @return ClientboundPacket[] * @phpstan-return list @@ -1138,13 +1145,16 @@ class World implements ChunkManager{ public function clearCache(bool $force = false) : void{ if($force){ $this->blockCache = []; + $this->blockCacheSize = 0; $this->blockCollisionBoxCache = []; }else{ - $count = 0; + //Recalculate this when we're asked - blockCacheSize may be higher than the real size + $this->blockCacheSize = 0; foreach($this->blockCache as $list){ - $count += count($list); - if($count > 2048){ + $this->blockCacheSize += count($list); + if($this->blockCacheSize > self::BLOCK_CACHE_SIZE_CAP){ $this->blockCache = []; + $this->blockCacheSize = 0; break; } } @@ -1152,7 +1162,7 @@ class World implements ChunkManager{ $count = 0; foreach($this->blockCollisionBoxCache as $list){ $count += count($list); - if($count > 2048){ + if($count > self::BLOCK_CACHE_SIZE_CAP){ //TODO: Is this really the best logic? $this->blockCollisionBoxCache = []; break; @@ -1161,6 +1171,19 @@ class World implements ChunkManager{ } } + private function trimBlockCache() : void{ + $before = $this->blockCacheSize; + //Since PHP maintains key order, earliest in foreach should be the oldest entries + //Older entries are less likely to be hot, so destroying these should usually have the lowest impact on performance + foreach($this->blockCache as $chunkHash => $blocks){ + unset($this->blockCache[$chunkHash]); + $this->blockCacheSize -= count($blocks); + if($this->blockCacheSize < self::BLOCK_CACHE_SIZE_CAP){ + break; + } + } + } + /** * @return true[] fullID => dummy * @phpstan-return array @@ -1343,7 +1366,7 @@ class World implements ChunkManager{ * TODO: phpstan can't infer these types yet :( * @phpstan-var array $blockLight * @phpstan-var array $skyLight - * @phpstan-var array $heightMap + * @phpstan-var non-empty-list $heightMap */ if($this->unloaded || ($chunk = $this->getChunk($chunkX, $chunkZ)) === null || $chunk->isLightPopulated() === true){ return; @@ -1374,7 +1397,7 @@ class World implements ChunkManager{ $entity->onRandomUpdate(); } - $blockFactory = RuntimeBlockStateRegistry::getInstance(); + $blockFactory = $this->blockStateRegistry; foreach($chunk->getSubChunks() as $Y => $subChunk){ if(!$subChunk->isEmptyFast()){ $k = 0; @@ -1435,8 +1458,8 @@ class World implements ChunkManager{ $this->provider->saveChunk($chunkX, $chunkZ, new ChunkData( $chunk->getSubChunks(), $chunk->isPopulated(), - array_map(fn(Entity $e) => $e->saveNBT(), array_filter($this->getChunkEntities($chunkX, $chunkZ), fn(Entity $e) => $e->canSaveWithChunk())), - array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()), + array_map(fn(Entity $e) => $e->saveNBT(), array_values(array_filter($this->getChunkEntities($chunkX, $chunkZ), fn(Entity $e) => $e->canSaveWithChunk()))), + array_map(fn(Tile $t) => $t->saveNBT(), array_values($chunk->getTiles())), ), $chunk->getTerrainDirtyFlags()); $chunk->clearTerrainDirtyFlags(); } @@ -1508,24 +1531,48 @@ class World implements ChunkManager{ $collides = []; + $collisionInfo = $this->blockStateRegistry->collisionInfo; if($targetFirst){ for($z = $minZ; $z <= $maxZ; ++$z){ + $zOverflow = $z === $minZ || $z === $maxZ; for($x = $minX; $x <= $maxX; ++$x){ + $zxOverflow = $zOverflow || $x === $minX || $x === $maxX; for($y = $minY; $y <= $maxY; ++$y){ - $block = $this->getBlockAt($x, $y, $z); - if($block->collidesWithBB($bb)){ - return [$block]; + $overflow = $zxOverflow || $y === $minY || $y === $maxY; + + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); + if($overflow ? + $stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) : + match ($stateCollisionInfo) { + RuntimeBlockStateRegistry::COLLISION_CUBE => true, + RuntimeBlockStateRegistry::COLLISION_NONE => false, + default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) + } + ){ + return [$this->getBlockAt($x, $y, $z)]; } } } } }else{ + //TODO: duplicated code :( this way is better for performance though for($z = $minZ; $z <= $maxZ; ++$z){ + $zOverflow = $z === $minZ || $z === $maxZ; for($x = $minX; $x <= $maxX; ++$x){ + $zxOverflow = $zOverflow || $x === $minX || $x === $maxX; for($y = $minY; $y <= $maxY; ++$y){ - $block = $this->getBlockAt($x, $y, $z); - if($block->collidesWithBB($bb)){ - $collides[] = $block; + $overflow = $zxOverflow || $y === $minY || $y === $maxY; + + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); + if($overflow ? + $stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) : + match ($stateCollisionInfo) { + RuntimeBlockStateRegistry::COLLISION_CUBE => true, + RuntimeBlockStateRegistry::COLLISION_NONE => false, + default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) + } + ){ + $collides[] = $this->getBlockAt($x, $y, $z); } } } @@ -1535,23 +1582,64 @@ class World implements ChunkManager{ return $collides; } + /** + * @param int[] $collisionInfo + * @phpstan-param array $collisionInfo + */ + private function getBlockCollisionInfo(int $x, int $y, int $z, array $collisionInfo) : int{ + if(!$this->isInWorld($x, $y, $z)){ + return RuntimeBlockStateRegistry::COLLISION_NONE; + } + $chunk = $this->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE); + if($chunk === null){ + return RuntimeBlockStateRegistry::COLLISION_NONE; + } + $stateId = $chunk + ->getSubChunk($y >> SubChunk::COORD_BIT_SIZE) + ->getBlockStateId( + $x & SubChunk::COORD_MASK, + $y & SubChunk::COORD_MASK, + $z & SubChunk::COORD_MASK + ); + return $collisionInfo[$stateId]; + } + /** * Returns a list of all block AABBs which overlap the full block area at the given coordinates. * This checks a padding of 1 block around the coordinates to account for oversized AABBs of blocks like fences. * Larger AABBs (>= 2 blocks on any axis) are not accounted for. * + * @param int[] $collisionInfo + * @phpstan-param array $collisionInfo + * * @return AxisAlignedBB[] + * @phpstan-return list */ - private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{ - $block = $this->getBlockAt($x, $y, $z); - $boxes = $block->getCollisionBoxes(); + private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{ + $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo); + $boxes = match($stateCollisionInfo){ + RuntimeBlockStateRegistry::COLLISION_NONE => [], + RuntimeBlockStateRegistry::COLLISION_CUBE => [AxisAlignedBB::one()->offset($x, $y, $z)], + default => $this->getBlockAt($x, $y, $z)->getCollisionBoxes() + }; - $cellBB = AxisAlignedBB::one()->offset($x, $y, $z); - foreach(Facing::OFFSET as [$dx, $dy, $dz]){ - $extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes(); - foreach($extraBoxes as $extraBox){ - if($extraBox->intersectsWith($cellBB)){ - $boxes[] = $extraBox; + //overlapping AABBs can't make any difference if this is a cube, so we can save some CPU cycles in this common case + if($stateCollisionInfo !== RuntimeBlockStateRegistry::COLLISION_CUBE){ + $cellBB = null; + foreach(Facing::OFFSET as [$dx, $dy, $dz]){ + $offsetX = $x + $dx; + $offsetY = $y + $dy; + $offsetZ = $z + $dz; + $stateCollisionInfo = $this->getBlockCollisionInfo($offsetX, $offsetY, $offsetZ, $collisionInfo); + if($stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW){ + //avoid allocating this unless it's needed + $cellBB ??= AxisAlignedBB::one()->offset($x, $y, $z); + $extraBoxes = $this->getBlockAt($offsetX, $offsetY, $offsetZ)->getCollisionBoxes(); + foreach($extraBoxes as $extraBox){ + if($extraBox->intersectsWith($cellBB)){ + $boxes[] = $extraBox; + } + } } } } @@ -1573,13 +1661,15 @@ class World implements ChunkManager{ $collides = []; + $collisionInfo = $this->blockStateRegistry->collisionInfo; + for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ $chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE); for($y = $minY; $y <= $maxY; ++$y){ $relativeBlockHash = World::chunkBlockHash($x, $y, $z); - $boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z); + $boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z, $collisionInfo); foreach($boxes as $blockBB){ if($blockBB->intersectsWith($bb)){ @@ -1774,7 +1864,7 @@ class World implements ChunkManager{ return; } - $blockFactory = RuntimeBlockStateRegistry::getInstance(); + $blockFactory = $this->blockStateRegistry; $this->timings->doBlockSkyLightUpdates->startTiming(); if($this->skyLightUpdate === null){ $this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight); @@ -1893,7 +1983,7 @@ class World implements ChunkManager{ $chunk = $this->chunks[$chunkHash] ?? null; if($chunk !== null){ - $block = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK)); + $block = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK)); }else{ $addToCache = false; $block = VanillaBlocks::AIR(); @@ -1921,6 +2011,10 @@ class World implements ChunkManager{ if($addToCache && $relativeBlockHash !== null){ $this->blockCache[$chunkHash][$relativeBlockHash] = $block; + + if(++$this->blockCacheSize >= self::BLOCK_CACHE_SIZE_CAP){ + $this->trimBlockCache(); + } } return $block; @@ -1967,6 +2061,7 @@ class World implements ChunkManager{ $relativeBlockHash = World::chunkBlockHash($x, $y, $z); unset($this->blockCache[$chunkHash][$relativeBlockHash]); + $this->blockCacheSize--; unset($this->blockCollisionBoxCache[$chunkHash][$relativeBlockHash]); //blocks like fences have collision boxes that reach into neighbouring blocks, so we need to invalidate the //caches for those blocks as well @@ -1998,10 +2093,10 @@ class World implements ChunkManager{ return null; } - $itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item); + $itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item); $itemEntity->setPickupDelay($delay); - $itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1)); + $itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1)); $itemEntity->spawnToAll(); return $itemEntity; @@ -2014,13 +2109,12 @@ class World implements ChunkManager{ * @phpstan-return list */ public function dropExperience(Vector3 $pos, int $amount) : array{ - /** @var ExperienceOrb[] $orbs */ $orbs = []; foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){ - $orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split); + $orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split); - $orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2)); + $orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2)); $orb->spawnToAll(); $orbs[] = $orb; @@ -2037,7 +2131,7 @@ class World implements ChunkManager{ * @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; @@ -2173,19 +2267,25 @@ class World implements ChunkManager{ 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 !== ItemUseResult::NONE){ - return $result === 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; @@ -2542,7 +2642,7 @@ class World implements ChunkManager{ $localY = $tilePosition->getFloorY(); $localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK; - $newBlock = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ)); + $newBlock = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ)); $expectedTileClass = $newBlock->getIdInfo()->getTileClass(); if( $expectedTileClass === null || //new block doesn't expect a tile @@ -2564,6 +2664,7 @@ class World implements ChunkManager{ $this->chunks[$chunkHash] = $chunk; + $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); @@ -2848,6 +2949,8 @@ class World implements ChunkManager{ $this->logger->debug("Chunk $x $z has been upgraded, will be saved at the next autosave opportunity"); } $this->chunks[$chunkHash] = $chunk; + + $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); @@ -2984,8 +3087,8 @@ class World implements ChunkManager{ $this->provider->saveChunk($x, $z, new ChunkData( $chunk->getSubChunks(), $chunk->isPopulated(), - array_map(fn(Entity $e) => $e->saveNBT(), array_filter($this->getChunkEntities($x, $z), fn(Entity $e) => $e->canSaveWithChunk())), - array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()), + array_map(fn(Entity $e) => $e->saveNBT(), array_values(array_filter($this->getChunkEntities($x, $z), fn(Entity $e) => $e->canSaveWithChunk()))), + array_map(fn(Tile $t) => $t->saveNBT(), array_values($chunk->getTiles())), ), $chunk->getTerrainDirtyFlags()); }finally{ $this->timings->syncChunkSave->stopTiming(); @@ -3007,6 +3110,7 @@ class World implements ChunkManager{ } unset($this->chunks[$chunkHash]); + $this->blockCacheSize -= count($this->blockCache[$chunkHash] ?? []); unset($this->blockCache[$chunkHash]); unset($this->blockCollisionBoxCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); @@ -3047,6 +3151,7 @@ class World implements ChunkManager{ * @phpstan-return Promise */ public function requestSafeSpawn(?Vector3 $spawn = null) : Promise{ + /** @phpstan-var PromiseResolver $resolver */ $resolver = new PromiseResolver(); $spawn ??= $this->getSpawnLocation(); /* @@ -3218,6 +3323,7 @@ class World implements ChunkManager{ private function enqueuePopulationRequest(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $this->addChunkHashToPopulationRequestQueue($chunkHash); + /** @phpstan-var PromiseResolver $resolver */ $resolver = $this->chunkPopulationRequestMap[$chunkHash] = new PromiseResolver(); if($associatedChunkLoader === null){ $temporaryLoader = new class implements ChunkLoader{}; diff --git a/src/world/WorldManager.php b/src/world/WorldManager.php index ff603a2dfc..7ff4ed6e02 100644 --- a/src/world/WorldManager.php +++ b/src/world/WorldManager.php @@ -56,7 +56,10 @@ use function trim; class WorldManager{ public const TICKS_PER_AUTOSAVE = 300 * Server::TARGET_TICKS_PER_SECOND; - /** @var World[] */ + /** + * @var World[] + * @phpstan-var array + */ private array $worlds = []; private ?World $defaultWorld = null; @@ -76,6 +79,7 @@ class WorldManager{ /** * @return World[] + * @phpstan-return array */ public function getWorlds() : array{ return $this->worlds; diff --git a/src/world/format/Chunk.php b/src/world/format/Chunk.php index e4c877dc91..9ea5d3f8e6 100644 --- a/src/world/format/Chunk.php +++ b/src/world/format/Chunk.php @@ -57,7 +57,10 @@ class Chunk{ */ protected \SplFixedArray $subChunks; - /** @var Tile[] */ + /** + * @var Tile[] + * @phpstan-var array + */ protected array $tiles = []; protected HeightArray $heightMap; @@ -210,6 +213,7 @@ class Chunk{ /** * @return Tile[] + * @phpstan-return array */ public function getTiles() : array{ return $this->tiles; @@ -237,6 +241,7 @@ class Chunk{ /** * @return int[] + * @phpstan-return non-empty-list */ public function getHeightMapArray() : array{ return $this->heightMap->getValues(); @@ -244,6 +249,7 @@ class Chunk{ /** * @param int[] $values + * @phpstan-param non-empty-list $values */ public function setHeightMapArray(array $values) : void{ $this->heightMap = new HeightArray($values); diff --git a/src/world/format/HeightArray.php b/src/world/format/HeightArray.php index 27f9cecb73..03094c3c8e 100644 --- a/src/world/format/HeightArray.php +++ b/src/world/format/HeightArray.php @@ -36,7 +36,7 @@ final class HeightArray{ /** * @param int[] $values ZZZZXXXX key bit order - * @phpstan-param list $values + * @phpstan-param non-empty-list $values */ public function __construct(array $values){ if(count($values) !== 256){ @@ -66,7 +66,7 @@ final class HeightArray{ /** * @return int[] ZZZZXXXX key bit order - * @phpstan-return list + * @phpstan-return non-empty-list */ public function getValues() : array{ return $this->array->toArray(); diff --git a/src/world/format/SubChunk.php b/src/world/format/SubChunk.php index 3f7e943e31..d8546e7e92 100644 --- a/src/world/format/SubChunk.php +++ b/src/world/format/SubChunk.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace pocketmine\world\format; use function array_map; -use function array_values; use function count; class SubChunk{ @@ -36,6 +35,7 @@ class SubChunk{ * SubChunk constructor. * * @param PalettedBlockArray[] $blockLayers + * @phpstan-param list $blockLayers */ public function __construct( private int $emptyBlockId, @@ -85,6 +85,7 @@ class SubChunk{ /** * @return PalettedBlockArray[] + * @phpstan-return list */ public function getBlockLayers() : array{ return $this->blockLayers; @@ -129,17 +130,18 @@ class SubChunk{ } public function collectGarbage() : void{ - foreach($this->blockLayers as $k => $layer){ + $cleanedLayers = []; + foreach($this->blockLayers as $layer){ $layer->collectGarbage(); foreach($layer->getPalette() as $p){ if($p !== $this->emptyBlockId){ + $cleanedLayers[] = $layer; continue 2; } } - unset($this->blockLayers[$k]); } - $this->blockLayers = array_values($this->blockLayers); + $this->blockLayers = $cleanedLayers; $this->biomes->collectGarbage(); if($this->skyLight !== null && $this->skyLight->isUniform(0)){ diff --git a/src/world/format/io/BaseWorldProvider.php b/src/world/format/io/BaseWorldProvider.php index f863fdf747..6fcb8e10be 100644 --- a/src/world/format/io/BaseWorldProvider.php +++ b/src/world/format/io/BaseWorldProvider.php @@ -65,6 +65,8 @@ abstract class BaseWorldProvider implements WorldProvider{ abstract protected function loadLevelData() : WorldData; private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{ + //TODO: missing type info in stubs + /** @phpstan-var list $palette */ $palette = $blockArray->getPalette(); $newPalette = []; @@ -81,11 +83,11 @@ abstract class BaseWorldProvider implements WorldProvider{ } try{ - $newPalette[$k] = $this->blockStateDeserializer->deserialize($newStateData); + $newPalette[] = $this->blockStateDeserializer->deserialize($newStateData); }catch(BlockStateDeserializeException $e){ //this should never happen anyway - if the upgrader returned an invalid state, we have bigger problems $blockDecodeErrors[] = "Palette offset $k / Failed to deserialize upgraded state $id:$meta: " . $e->getMessage(); - $newPalette[$k] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); + $newPalette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData()); } } diff --git a/src/world/format/io/ChunkData.php b/src/world/format/io/ChunkData.php index 458e001962..235ac07bfc 100644 --- a/src/world/format/io/ChunkData.php +++ b/src/world/format/io/ChunkData.php @@ -32,6 +32,10 @@ final class ChunkData{ * @param SubChunk[] $subChunks * @param CompoundTag[] $entityNBT * @param CompoundTag[] $tileNBT + * + * @phpstan-param array $subChunks + * @phpstan-param list $entityNBT + * @phpstan-param list $tileNBT */ public function __construct( private array $subChunks, @@ -42,14 +46,21 @@ final class ChunkData{ /** * @return SubChunk[] + * @phpstan-return array */ public function getSubChunks() : array{ return $this->subChunks; } public function isPopulated() : bool{ return $this->populated; } - /** @return CompoundTag[] */ + /** + * @return CompoundTag[] + * @phpstan-return list + */ public function getEntityNBT() : array{ return $this->entityNBT; } - /** @return CompoundTag[] */ + /** + * @return CompoundTag[] + * @phpstan-return list + */ public function getTileNBT() : array{ return $this->tileNBT; } } diff --git a/src/world/format/io/FastChunkSerializer.php b/src/world/format/io/FastChunkSerializer.php index 6e18f27ac5..35a8ff42f7 100644 --- a/src/world/format/io/FastChunkSerializer.php +++ b/src/world/format/io/FastChunkSerializer.php @@ -112,7 +112,6 @@ final class FastChunkSerializer{ $y = Binary::signByte($stream->getByte()); $airBlockId = $stream->getInt(); - /** @var PalettedBlockArray[] $layers */ $layers = []; for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){ $layers[] = self::deserializePalettedArray($stream); diff --git a/src/world/format/io/FormatConverter.php b/src/world/format/io/FormatConverter.php index 1d485afa21..421d707fa6 100644 --- a/src/world/format/io/FormatConverter.php +++ b/src/world/format/io/FormatConverter.php @@ -33,6 +33,7 @@ use function basename; use function crc32; use function file_exists; use function floor; +use function flush; use function microtime; use function mkdir; use function random_bytes; @@ -150,6 +151,10 @@ class FormatConverter{ $diff = $time - $thisRound; $thisRound = $time; $this->logger->info("Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) . " chunks/sec)"); + flush(); + } + if(($counter % (2 ** 16)) === 0){ + $new->doGarbageCollection(); } } $total = microtime(true) - $start; diff --git a/src/world/format/io/GlobalItemDataHandlers.php b/src/world/format/io/GlobalItemDataHandlers.php index ea5568c7cb..f622584784 100644 --- a/src/world/format/io/GlobalItemDataHandlers.php +++ b/src/world/format/io/GlobalItemDataHandlers.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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\ItemIdMetaUpgrader; 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 @@ final class GlobalItemDataHandlers{ 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/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index 709dadf974..a9ca43bc3d 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -51,12 +51,12 @@ use function time; class BedrockWorldData extends BaseNbtWorldData{ public const CURRENT_STORAGE_VERSION = 10; - public const CURRENT_STORAGE_NETWORK_VERSION = 712; + public const CURRENT_STORAGE_NETWORK_VERSION = 776; public const CURRENT_CLIENT_VERSION_TARGET = [ 1, //major 21, //minor - 20, //patch - 0, //revision + 60, //patch + 33, //revision 0 //is beta ]; diff --git a/src/world/format/io/leveldb/ChunkVersion.php b/src/world/format/io/leveldb/ChunkVersion.php index e399570de9..a69e4ff459 100644 --- a/src/world/format/io/leveldb/ChunkVersion.php +++ b/src/world/format/io/leveldb/ChunkVersion.php @@ -71,4 +71,5 @@ final class ChunkVersion{ 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 20b55922cd..41c4778670 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -78,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; @@ -654,6 +654,8 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $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: @@ -709,7 +711,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $nbt = new LittleEndianNbtSerializer(); - /** @var CompoundTag[] $entities */ $entities = []; if(($entityData = $this->db->get($index . ChunkDataKey::ENTITIES)) !== false && $entityData !== ""){ try{ @@ -719,7 +720,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ } } - /** @var CompoundTag[] $tiles */ $tiles = []; if(($tileData = $this->db->get($index . ChunkDataKey::BLOCK_ENTITIES)) !== false && $tileData !== ""){ try{ diff --git a/src/world/format/io/region/RegionGarbageMap.php b/src/world/format/io/region/RegionGarbageMap.php index d1e9504524..9e4e232ee0 100644 --- a/src/world/format/io/region/RegionGarbageMap.php +++ b/src/world/format/io/region/RegionGarbageMap.php @@ -31,7 +31,10 @@ use const SORT_NUMERIC; final class RegionGarbageMap{ - /** @var RegionLocationTableEntry[] */ + /** + * @var RegionLocationTableEntry[] + * @phpstan-var array + */ private array $entries = []; private bool $clean = false; @@ -48,7 +51,6 @@ final class RegionGarbageMap{ * @param RegionLocationTableEntry[]|null[] $locationTable */ public static function buildFromLocationTable(array $locationTable) : self{ - /** @var RegionLocationTableEntry[] $usedMap */ $usedMap = []; foreach($locationTable as $entry){ if($entry === null){ @@ -62,12 +64,10 @@ final class RegionGarbageMap{ ksort($usedMap, SORT_NUMERIC); - /** @var RegionLocationTableEntry[] $garbageMap */ $garbageMap = []; - /** @var RegionLocationTableEntry|null $prevEntry */ $prevEntry = null; - foreach($usedMap as $firstSector => $entry){ + foreach($usedMap as $entry){ $prevEndPlusOne = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR); $currentStart = $entry->getFirstSector(); if($prevEndPlusOne < $currentStart){ diff --git a/src/world/format/io/region/RegionLoader.php b/src/world/format/io/region/RegionLoader.php index f040b8a472..b65f5346cc 100644 --- a/src/world/format/io/region/RegionLoader.php +++ b/src/world/format/io/region/RegionLoader.php @@ -67,7 +67,10 @@ class RegionLoader{ /** @var resource */ protected $filePointer; protected int $nextSector = self::FIRST_SECTOR; - /** @var RegionLocationTableEntry[]|null[] */ + /** + * @var RegionLocationTableEntry[]|null[] + * @phpstan-var list + */ protected array $locationTable = []; protected RegionGarbageMap $garbageTable; public int $lastUsed; @@ -327,7 +330,6 @@ class RegionLoader{ * @throws CorruptedRegionException */ private function checkLocationTableValidity() : void{ - /** @var int[] $usedOffsets */ $usedOffsets = []; $fileSize = filesize($this->filePath); @@ -355,7 +357,7 @@ class RegionLoader{ } ksort($usedOffsets, SORT_NUMERIC); $prevLocationIndex = null; - foreach($usedOffsets as $startOffset => $locationTableIndex){ + foreach($usedOffsets as $locationTableIndex){ if($this->locationTable[$locationTableIndex] === null){ continue; } diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 1feca61be1..8fe7928b81 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -33,10 +33,8 @@ use pocketmine\world\format\io\exception\CorruptedChunkException; use pocketmine\world\format\io\LoadedChunkData; use pocketmine\world\format\io\WorldData; use Symfony\Component\Filesystem\Path; -use function assert; use function file_exists; use function is_dir; -use function is_int; use function morton2d_encode; use function rename; use function scandir; @@ -60,7 +58,12 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ public static function isValid(string $path) : bool{ if(file_exists(Path::join($path, "level.dat")) && is_dir($regionPath = Path::join($path, "region"))){ - foreach(scandir($regionPath, SCANDIR_SORT_NONE) as $file){ + $files = scandir($regionPath, SCANDIR_SORT_NONE); + if($files === false){ + //we can't tell the type if we don't have read perms + return false; + } + foreach($files as $file){ $extPos = strrpos($file, "."); if($extPos !== false && substr($file, $extPos + 1) === static::getRegionFileExtension()){ //we don't care if other region types exist, we only care if this format is possible @@ -72,7 +75,10 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ return false; } - /** @var RegionLoader[] */ + /** + * @var RegionLoader[] + * @phpstan-var array + */ protected array $regions = []; protected function loadLevelData() : WorldData{ @@ -90,10 +96,12 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ } /** - * @param int $regionX reference parameter - * @param int $regionZ reference parameter + * @param int|null $regionX reference parameter + * @param int|null $regionZ reference parameter * @phpstan-param-out int $regionX * @phpstan-param-out int $regionZ + * + * TODO: make this private */ public static function getRegionIndex(int $chunkX, int $chunkZ, &$regionX, &$regionZ) : void{ $regionX = $chunkX >> 5; @@ -151,6 +159,8 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ /** * @return CompoundTag[] + * @phpstan-return list + * * @throws CorruptedChunkException */ protected static function getCompoundList(string $context, ListTag $list) : array{ @@ -192,7 +202,6 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ public function loadChunk(int $chunkX, int $chunkZ) : ?LoadedChunkData{ $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); - assert(is_int($regionX) && is_int($regionZ)); if(!file_exists($this->pathToRegion($regionX, $regionZ))){ return null; @@ -206,6 +215,9 @@ abstract class RegionWorldProvider extends BaseWorldProvider{ return null; } + /** + * @phpstan-return \RegexIterator + */ private function createRegionIterator() : \RegexIterator{ return new \RegexIterator( new \FilesystemIterator( diff --git a/src/world/generator/FlatGeneratorOptions.php b/src/world/generator/FlatGeneratorOptions.php index ab10c8a475..8271ebcbf9 100644 --- a/src/world/generator/FlatGeneratorOptions.php +++ b/src/world/generator/FlatGeneratorOptions.php @@ -26,11 +26,12 @@ namespace pocketmine\world\generator; use pocketmine\data\bedrock\BiomeIds; use pocketmine\item\LegacyStringToItemParser; use pocketmine\item\LegacyStringToItemParserException; +use pocketmine\world\World; use function array_map; -use function count; use function explode; use function preg_match; use function preg_match_all; +use const PHP_INT_MAX; /** * @internal @@ -71,12 +72,11 @@ final class FlatGeneratorOptions{ */ public static function parseLayers(string $layers) : array{ $result = []; - $split = array_map('\trim', explode(',', $layers)); + $split = array_map('\trim', explode(',', $layers, limit: World::Y_MAX - World::Y_MIN)); $y = 0; $itemParser = LegacyStringToItemParser::getInstance(); foreach($split as $line){ - preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches); - if(count($matches) !== 3){ + if(preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches) !== 1){ throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\""); } @@ -98,7 +98,7 @@ final class FlatGeneratorOptions{ * @throws InvalidGeneratorOptionsException */ public static function parsePreset(string $presetString) : self{ - $preset = explode(";", $presetString); + $preset = explode(";", $presetString, limit: 4); $blocks = $preset[1] ?? ""; $biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS); $optionsString = $preset[3] ?? ""; @@ -111,15 +111,16 @@ final class FlatGeneratorOptions{ $params = true; if($matches[3][$i] !== ""){ $params = []; - $p = explode(" ", $matches[3][$i]); + $p = explode(" ", $matches[3][$i], limit: PHP_INT_MAX); foreach($p as $k){ - $k = explode("=", $k); + //TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given e.g. treecount=20=1 + $k = explode("=", $k, limit: 3); if(isset($k[1])){ $params[$k[0]] = $k[1]; } } } - $options[(string) $option] = $params; + $options[$option] = $params; } return new self($structure, $biomeId, $options); } diff --git a/src/world/generator/PopulationTask.php b/src/world/generator/PopulationTask.php index 8479a79fa7..bad1343240 100644 --- a/src/world/generator/PopulationTask.php +++ b/src/world/generator/PopulationTask.php @@ -76,7 +76,10 @@ class PopulationTask extends AsyncTask{ $chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null; - /** @var string[] $serialChunks */ + /** + * @var string[] $serialChunks + * @phpstan-var array $serialChunks + */ $serialChunks = igbinary_unserialize($this->adjacentChunks); $chunks = array_map( function(?string $serialized) : ?Chunk{ @@ -92,7 +95,6 @@ class PopulationTask extends AsyncTask{ self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk); - /** @var Chunk[] $resultChunks */ $resultChunks = []; //this is just to keep phpstan's type inference happy foreach($chunks as $relativeChunkHash => $c){ World::getXZ($relativeChunkHash, $relativeX, $relativeZ); diff --git a/src/world/generator/normal/Normal.php b/src/world/generator/normal/Normal.php index 1d4805e165..a440f1e5f8 100644 --- a/src/world/generator/normal/Normal.php +++ b/src/world/generator/normal/Normal.php @@ -126,10 +126,10 @@ class Normal extends Generator{ $hash = (int) $hash; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; - if($xNoise == 3){ + if($xNoise === 3){ $xNoise = 1; } - if($zNoise == 3){ + if($zNoise === 3){ $zNoise = 1; } diff --git a/src/world/light/LightPopulationTask.php b/src/world/light/LightPopulationTask.php index 5aa0ead65a..29d9578311 100644 --- a/src/world/light/LightPopulationTask.php +++ b/src/world/light/LightPopulationTask.php @@ -44,7 +44,7 @@ class LightPopulationTask extends AsyncTask{ private string $resultBlockLightArrays; /** - * @phpstan-param \Closure(array $blockLight, array $skyLight, array $heightMap) : void $onCompletion + * @phpstan-param \Closure(array $blockLight, array $skyLight, non-empty-list $heightMap) : void $onCompletion */ public function __construct(Chunk $chunk, \Closure $onCompletion){ $this->chunk = FastChunkSerializer::serializeTerrain($chunk); @@ -80,7 +80,10 @@ class LightPopulationTask extends AsyncTask{ } public function onCompletion() : void{ - /** @var int[] $heightMapArray */ + /** + * @var int[] $heightMapArray + * @phpstan-var non-empty-list $heightMapArray + */ $heightMapArray = igbinary_unserialize($this->resultHeightMap); /** @var LightArray[] $skyLightArrays */ @@ -90,7 +93,7 @@ class LightPopulationTask extends AsyncTask{ /** * @var \Closure - * @phpstan-var \Closure(array $blockLight, array $skyLight, array $heightMap>) : void + * @phpstan-var \Closure(array $blockLight, array $skyLight, non-empty-list $heightMap) : void */ $callback = $this->fetchLocal(self::TLS_KEY_COMPLETION_CALLBACK); $callback($blockLightArrays, $skyLightArrays, $heightMapArray); diff --git a/src/world/sound/BottleEmptySound.php b/src/world/sound/BottleEmptySound.php new file mode 100644 index 0000000000..bb2a0be2ad --- /dev/null +++ b/src/world/sound/BottleEmptySound.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/IceBombHitSound.php b/src/world/sound/IceBombHitSound.php new file mode 100644 index 0000000000..7dcdeebf67 --- /dev/null +++ b/src/world/sound/IceBombHitSound.php @@ -0,0 +1,34 @@ + /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/plugins/TesterPlugin/src/event/ChildEvent.php b/tests/phpstan/DummyPluginOwned.php similarity index 90% rename from tests/plugins/TesterPlugin/src/event/ChildEvent.php rename to tests/phpstan/DummyPluginOwned.php index b71d2627fc..b63975dcfb 100644 --- a/tests/plugins/TesterPlugin/src/event/ChildEvent.php +++ b/tests/phpstan/DummyPluginOwned.php @@ -21,8 +21,8 @@ declare(strict_types=1); -namespace pmmp\TesterPlugin\event; - -class ChildEvent extends ParentEvent{ +namespace pocketmine\plugin; +class DummyPluginOwned{ + use PluginOwnedTrait; } diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 2d0e6d398d..d3adde422d 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -1,1212 +1,1423 @@ parameters: ignoreErrors: - - message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" - count: 2 - path: ../../../build/make-release.php - - - - message: "#^Parameter \\#1 \\$strings of function pocketmine\\\\build\\\\server_phar\\\\preg_quote_array expects array\\, array\\ given\\.$#" + message: '#^Parameter \#1 \$strings of function pocketmine\\build\\server_phar\\preg_quote_array expects array\, array\ given\.$#' + identifier: argument.type count: 1 path: ../../../build/server-phar.php - - message: "#^Do\\-while loop condition is always false\\.$#" + message: '#^Do\-while loop condition is always false\.$#' + identifier: doWhile.alwaysFalse count: 1 path: ../../../src/PocketMine.php - - message: "#^Parameter \\#1 \\$array of static method pocketmine\\\\plugin\\\\PluginGraylist\\:\\:fromArray\\(\\) expects array, mixed given\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/Server.php - - message: "#^Cannot cast mixed to int\\.$#" + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/Server.php + + - + message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\\> but returns array\\>\.$#' + identifier: return.type + count: 1 + path: ../../../src/Server.php + + - + message: '#^Parameter \#1 \$generatorName of closure expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/Server.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int count: 2 path: ../../../src/ServerConfigGroup.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 2 path: ../../../src/ServerConfigGroup.php - - message: "#^Cannot access offset 'git' on mixed\\.$#" + message: '#^Cannot access offset ''git'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 1 path: ../../../src/VersionInfo.php - - message: "#^Method pocketmine\\\\VersionInfo\\:\\:GIT_HASH\\(\\) should return string but returns mixed\\.$#" + message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/VersionInfo.php - - message: "#^Static property pocketmine\\\\VersionInfo\\:\\:\\$gitHash \\(string\\|null\\) does not accept mixed\\.$#" + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic count: 1 - path: ../../../src/VersionInfo.php + path: ../../../src/block/Block.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Block.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Block.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:setBlockStateId\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\format\\Chunk\:\:setBlockStateId\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Block.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Block.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Cactus.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/block/ChorusFlower.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/block/ChorusFlower.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/block/ChorusFlower.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getRealBlockSkyLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getRealBlockSkyLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DaylightSensor.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getRealBlockSkyLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getRealBlockSkyLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DaylightSensor.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getRealBlockSkyLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getRealBlockSkyLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DaylightSensor.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#1 \\$xDiff of class pocketmine\\\\world\\\\particle\\\\DragonEggTeleportParticle constructor expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$xDiff of class pocketmine\\world\\particle\\DragonEggTeleportParticle constructor expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int\\<\\-64, 319\\> given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int\<\-64, 319\> given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#2 \\$yDiff of class pocketmine\\\\world\\\\particle\\\\DragonEggTeleportParticle constructor expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$yDiff of class pocketmine\\world\\particle\\DragonEggTeleportParticle constructor expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#3 \\$zDiff of class pocketmine\\\\world\\\\particle\\\\DragonEggTeleportParticle constructor expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$zDiff of class pocketmine\\world\\particle\\DragonEggTeleportParticle constructor expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/DragonEgg.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Fire.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getHighestAdjacentFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getHighestAdjacentFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getHighestAdjacentFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/FrostedIce.php - - message: "#^Parameter \\#1 \\$min of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$min of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#2 \\$max of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$max of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Grass.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentBlockLight\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getHighestAdjacentBlockLight\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Ice.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentBlockLight\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getHighestAdjacentBlockLight\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Ice.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getHighestAdjacentBlockLight\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getHighestAdjacentBlockLight\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Ice.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Leaves.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Leaves.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Leaves.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Liquid.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Liquid.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Liquid.php - - message: "#^Parameter \\#1 \\$min of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$min of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Mycelium.php - - message: "#^Parameter \\#2 \\$max of function mt_rand expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$max of function mt_rand expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/Mycelium.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/RedMushroom.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/RedMushroom.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getFullLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/RedMushroom.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/SnowLayer.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/SnowLayer.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/SnowLayer.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/Sugarcane.php - - message: "#^Parameter \\#1 \\$x of class pocketmine\\\\math\\\\Vector3 constructor expects float\\|int, int\\|null given\\.$#" + message: '#^Parameter \#1 \$x of class pocketmine\\math\\Vector3 constructor expects float\|int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, int\\|null given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#2 \\$value of method pocketmine\\\\nbt\\\\tag\\\\CompoundTag\\:\\:setInt\\(\\) expects int, int\\|null given\\.$#" + message: '#^Parameter \#2 \$value of method pocketmine\\nbt\\tag\\CompoundTag\:\:setInt\(\) expects int, int\|null given\.$#' + identifier: argument.type count: 4 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#3 \\$z of class pocketmine\\\\math\\\\Vector3 constructor expects float\\|int, int\\|null given\\.$#" + message: '#^Parameter \#3 \$z of class pocketmine\\math\\Vector3 constructor expects float\|int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, int\\|null given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/tile/Chest.php - - message: "#^Property pocketmine\\\\block\\\\tile\\\\Chest\\:\\:\\$pairX \\(int\\|null\\) does not accept float\\|int\\.$#" + message: '#^Property pocketmine\\block\\tile\\Chest\:\:\$pairX \(int\|null\) does not accept float\|int\.$#' + identifier: assign.propertyType count: 2 path: ../../../src/block/tile/Chest.php - - message: "#^Property pocketmine\\\\block\\\\tile\\\\Chest\\:\\:\\$pairZ \\(int\\|null\\) does not accept float\\|int\\.$#" + message: '#^Property pocketmine\\block\\tile\\Chest\:\:\$pairZ \(int\|null\) does not accept float\|int\.$#' + identifier: assign.propertyType count: 2 path: ../../../src/block/tile/Chest.php - - message: "#^Constant pocketmine\\\\block\\\\tile\\\\MobHead\\:\\:TAG_MOUTH_MOVING is unused\\.$#" + message: '#^Constant pocketmine\\block\\tile\\MobHead\:\:TAG_MOUTH_MOVING is unused\.$#' + identifier: classConstant.unused count: 1 path: ../../../src/block/tile/MobHead.php - - message: "#^Constant pocketmine\\\\block\\\\tile\\\\MobHead\\:\\:TAG_MOUTH_TICK_COUNT is unused\\.$#" + message: '#^Constant pocketmine\\block\\tile\\MobHead\:\:TAG_MOUTH_TICK_COUNT is unused\.$#' + identifier: classConstant.unused count: 1 path: ../../../src/block/tile/MobHead.php - - message: "#^Parameter \\#2 \\$value of method pocketmine\\\\nbt\\\\tag\\\\CompoundTag\\:\\:setInt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$value of method pocketmine\\nbt\\tag\\CompoundTag\:\:setInt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 3 path: ../../../src/block/tile/Spawnable.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/block/tile/TileFactory.php + + - + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/utils/CropGrowthHelper.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/utils/CropGrowthHelper.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/block/utils/CropGrowthHelper.php - - message: "#^Cannot call method addParticle\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method addParticle\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/ParticleCommand.php - - message: "#^Cannot call method getSeed\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method getSeed\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/SeedCommand.php - - message: "#^Cannot call method setSpawnLocation\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method setSpawnLocation\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/SetWorldSpawnCommand.php - - message: "#^Cannot call method getTime\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method getTime\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/command/defaults/TimeCommand.php - - message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#" - count: 2 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$stream of function fseek expects resource, resource\\|false given\\.$#" - count: 1 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$stream of function fwrite expects resource, resource\\|false given\\.$#" - count: 1 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$stream of function stream_get_contents expects resource, resource\\|false given\\.$#" - count: 1 - path: ../../../src/command/defaults/TimingsCommand.php - - - - message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$path of static method pocketmine\\utils\\Filesystem\:\:cleanPath\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/crash/CrashDump.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Binary operation "\." between ''Error\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Binary operation "\." between ''File\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Binary operation "\." between ''Line\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Binary operation "\." between ''Type\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/crash/CrashDumpRenderer.php + + - + message: '#^Parameter \#1 \$blockToItemId of class pocketmine\\data\\bedrock\\item\\BlockItemIdMap constructor expects array\, array\ given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/data/bedrock/item/BlockItemIdMap.php + + - + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/Living.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/Living.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/Living.php - - message: "#^Parameter \\#2 \\$x of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/FallingBlock.php - - message: "#^Parameter \\#3 \\$y of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/FallingBlock.php - - message: "#^Parameter \\#4 \\$z of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/FallingBlock.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/Painting.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/Painting.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/object/Painting.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/entity/projectile/Projectile.php - - message: "#^Parameter \\#2 \\$recipe of class pocketmine\\\\event\\\\inventory\\\\CraftItemEvent constructor expects pocketmine\\\\crafting\\\\CraftingRecipe, pocketmine\\\\crafting\\\\CraftingRecipe\\|null given\\.$#" + message: '#^Parameter \#2 \$recipe of class pocketmine\\event\\inventory\\CraftItemEvent constructor expects pocketmine\\crafting\\CraftingRecipe, pocketmine\\crafting\\CraftingRecipe\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/inventory/transaction/CraftingTransaction.php - - message: "#^Parameter \\#3 \\$repetitions of class pocketmine\\\\event\\\\inventory\\\\CraftItemEvent constructor expects int, int\\|null given\\.$#" + message: '#^Parameter \#3 \$repetitions of class pocketmine\\event\\inventory\\CraftItemEvent constructor expects int, int\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/inventory/transaction/CraftingTransaction.php - - message: "#^Cannot cast mixed to int\\.$#" + message: '#^Parameter &\$haveItems @param\-out type of method pocketmine\\inventory\\transaction\\InventoryTransaction\:\:matchItems\(\) expects list\, array\, pocketmine\\item\\Item\> given\.$#' + identifier: paramOut.type + count: 1 + path: ../../../src/inventory/transaction/InventoryTransaction.php + + - + message: '#^Parameter &\$needItems @param\-out type of method pocketmine\\inventory\\transaction\\InventoryTransaction\:\:matchItems\(\) expects list\, array\, pocketmine\\item\\Item\> given\.$#' + identifier: paramOut.type + count: 1 + path: ../../../src/inventory/transaction/InventoryTransaction.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int count: 2 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$buffer of method pocketmine\\\\nbt\\\\BaseNbtSerializer\\:\\:read\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$buffer of method pocketmine\\nbt\\BaseNbtSerializer\:\:read\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$string of function base64_decode expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$string of function base64_decode expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$string of function hex2bin expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$string of function hex2bin expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/item/Item.php - - message: "#^Parameter \\#1 \\$result of method pocketmine\\\\network\\\\mcpe\\\\compression\\\\CompressBatchPromise\\:\\:resolve\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$result of method pocketmine\\network\\mcpe\\compression\\CompressBatchPromise\:\:resolve\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/ChunkRequestTask.php - - message: "#^Match expression does not handle remaining values\\: pocketmine\\\\crafting\\\\FurnaceType\\:\\:CAMPFIRE\\|pocketmine\\\\crafting\\\\FurnaceType\\:\\:SOUL_CAMPFIRE$#" - count: 1 - path: ../../../src/network/mcpe/InventoryManager.php - - - - message: "#^Cannot call method doFirstSpawn\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method doFirstSpawn\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getAttributeMap\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getAttributeMap\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getLanguage\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getLanguage\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 4 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getLocation\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getLocation\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getUsedChunkStatus\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method getUsedChunkStatus\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getUsername\\(\\) on pocketmine\\\\player\\\\PlayerInfo\\|null\\.$#" + message: '#^Cannot call method getUsername\(\) on pocketmine\\player\\PlayerInfo\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method getUuid\\(\\) on pocketmine\\\\player\\\\PlayerInfo\\|null\\.$#" + message: '#^Cannot call method getUuid\(\) on pocketmine\\player\\PlayerInfo\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method sendData\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method sendData\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method setNoClientPredictions\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" + message: '#^Cannot call method setNoClientPredictions\(\) on pocketmine\\player\\Player\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Cannot call method syncAll\\(\\) on pocketmine\\\\network\\\\mcpe\\\\InventoryManager\\|null\\.$#" + message: '#^Cannot call method syncAll\(\) on pocketmine\\network\\mcpe\\InventoryManager\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$clientPub of class pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask constructor expects string, string\\|null given\\.$#" + message: '#^Parameter \#1 \$clientPub of class pocketmine\\network\\mcpe\\encryption\\PrepareEncryptionTask constructor expects string, string\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAbilities\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$for of method pocketmine\\network\\mcpe\\NetworkSession\:\:syncAbilities\(\) expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\DeathPacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$player of class pocketmine\\network\\mcpe\\handler\\DeathPacketHandler constructor expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\InGamePacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$player of class pocketmine\\network\\mcpe\\handler\\InGamePacketHandler constructor expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$playerInfo of class pocketmine\\\\event\\\\player\\\\PlayerResourcePackOfferEvent constructor expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#" + message: '#^Parameter \#1 \$playerInfo of class pocketmine\\event\\player\\PlayerResourcePackOfferEvent constructor expects pocketmine\\player\\PlayerInfo, pocketmine\\player\\PlayerInfo\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$target of method pocketmine\\\\command\\\\Command\\:\\:testPermissionSilent\\(\\) expects pocketmine\\\\command\\\\CommandSender, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#1 \$target of method pocketmine\\command\\Command\:\:testPermissionSilent\(\) expects pocketmine\\command\\CommandSender, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectAdded\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$entity of method pocketmine\\network\\mcpe\\EntityEventBroadcaster\:\:onEntityEffectAdded\(\) expects pocketmine\\entity\\Living, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectRemoved\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$entity of method pocketmine\\network\\mcpe\\EntityEventBroadcaster\:\:onEntityEffectRemoved\(\) expects pocketmine\\entity\\Living, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$entity of method pocketmine\\network\\mcpe\\EntityEventBroadcaster\:\:syncAttributes\(\) expects pocketmine\\entity\\Living, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\PreSpawnPacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" + message: '#^Parameter \#2 \$player of class pocketmine\\network\\mcpe\\handler\\PreSpawnPacketHandler constructor expects pocketmine\\player\\Player, pocketmine\\player\\Player\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#2 \\$playerInfo of method pocketmine\\\\Server\\:\\:createPlayer\\(\\) expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#" + message: '#^Parameter \#2 \$playerInfo of method pocketmine\\Server\:\:createPlayer\(\) expects pocketmine\\player\\PlayerInfo, pocketmine\\player\\PlayerInfo\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#3 \\$inventoryManager of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\InGamePacketHandler constructor expects pocketmine\\\\network\\\\mcpe\\\\InventoryManager, pocketmine\\\\network\\\\mcpe\\\\InventoryManager\\|null given\\.$#" + message: '#^Parameter \#3 \$inventoryManager of class pocketmine\\network\\mcpe\\handler\\InGamePacketHandler constructor expects pocketmine\\network\\mcpe\\InventoryManager, pocketmine\\network\\mcpe\\InventoryManager\|null given\.$#' + identifier: argument.type count: 2 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#4 \\$inventoryManager of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\PreSpawnPacketHandler constructor expects pocketmine\\\\network\\\\mcpe\\\\InventoryManager, pocketmine\\\\network\\\\mcpe\\\\InventoryManager\\|null given\\.$#" + message: '#^Parameter \#4 \$inventoryManager of class pocketmine\\network\\mcpe\\handler\\PreSpawnPacketHandler constructor expects pocketmine\\network\\mcpe\\InventoryManager, pocketmine\\network\\mcpe\\InventoryManager\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\auth\\\\ProcessLoginTask\\:\\:\\$chain \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\network\\mcpe\\auth\\ProcessLoginTask\:\:\$chain \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/network/mcpe/auth/ProcessLoginTask.php - - message: "#^Parameter \\#1 \\$result of method pocketmine\\\\network\\\\mcpe\\\\compression\\\\CompressBatchPromise\\:\\:resolve\\(\\) expects string, mixed given\\.$#" + message: '#^Parameter \#1 \$result of method pocketmine\\network\\mcpe\\compression\\CompressBatchPromise\:\:resolve\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/network/mcpe/compression/CompressBatchTask.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask\\:\\:\\$serverPrivateKey \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\network\\mcpe\\encryption\\PrepareEncryptionTask\:\:\$serverPrivateKey \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/network/mcpe/encryption/PrepareEncryptionTask.php - - message: "#^Method pocketmine\\\\permission\\\\DefaultPermissions\\:\\:registerPermission\\(\\) should return pocketmine\\\\permission\\\\Permission but returns pocketmine\\\\permission\\\\Permission\\|null\\.$#" + message: '#^Method pocketmine\\permission\\DefaultPermissions\:\:registerPermission\(\) should return pocketmine\\permission\\Permission but returns pocketmine\\permission\\Permission\|null\.$#' + identifier: return.type count: 1 path: ../../../src/permission/DefaultPermissions.php - - message: "#^Parameter \\#1 \\$value of static method pocketmine\\\\permission\\\\PermissionParser\\:\\:defaultFromString\\(\\) expects bool\\|string, mixed given\\.$#" + message: '#^Parameter \#1 \$value of static method pocketmine\\permission\\PermissionParser\:\:defaultFromString\(\) expects bool\|string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/permission/PermissionParser.php - - message: "#^Parameter \\#2 \\$description of class pocketmine\\\\permission\\\\Permission constructor expects pocketmine\\\\lang\\\\Translatable\\|string\\|null, mixed given\\.$#" + message: '#^Parameter \#2 \$description of class pocketmine\\permission\\Permission constructor expects pocketmine\\lang\\Translatable\|string\|null, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/permission/PermissionParser.php - - message: "#^Cannot call method getSpawnLocation\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" + message: '#^Cannot call method getSpawnLocation\(\) on pocketmine\\world\\World\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/player/Player.php - - message: "#^Method pocketmine\\\\player\\\\Player\\:\\:getSpawn\\(\\) should return pocketmine\\\\world\\\\Position but returns pocketmine\\\\world\\\\Position\\|null\\.$#" + message: '#^Method pocketmine\\player\\Player\:\:getSpawn\(\) should return pocketmine\\world\\Position but returns pocketmine\\world\\Position\|null\.$#' + identifier: return.type count: 1 path: ../../../src/player/Player.php - - message: "#^Method pocketmine\\\\plugin\\\\PluginBase\\:\\:getConfig\\(\\) should return pocketmine\\\\utils\\\\Config but returns pocketmine\\\\utils\\\\Config\\|null\\.$#" + message: '#^Method pocketmine\\plugin\\PluginBase\:\:getConfig\(\) should return pocketmine\\utils\\Config but returns pocketmine\\utils\\Config\|null\.$#' + identifier: return.type count: 1 path: ../../../src/plugin/PluginBase.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#1 \\$haystack of function stripos expects string, mixed given\\.$#" + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$authors \(array\\) does not accept list\\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$main \(string\) does not accept mixed\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$name \(string\) does not accept mixed\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/plugin/PluginDescription.php - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$main \\(string\\) does not accept mixed\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$name \\(string\\) does not accept mixed\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Cannot call method addChild\\(\\) on pocketmine\\\\permission\\\\Permission\\|null\\.$#" + message: '#^Cannot call method addChild\(\) on pocketmine\\permission\\Permission\|null\.$#' + identifier: method.nonObject count: 4 path: ../../../src/plugin/PluginManager.php - - message: "#^Method pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:getPackSize\\(\\) should return int but returns int\\<0, max\\>\\|false\\.$#" + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/plugin/PluginManager.php + + - + message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#' + identifier: return.type count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Method pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:getSha256\\(\\) should return string but returns string\\|false\\.$#" + message: '#^Property pocketmine\\resourcepacks\\ZippedResourcePack\:\:\$fileResource \(resource\) does not accept resource\|false\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#" + message: '#^Property pocketmine\\resourcepacks\\ZippedResourcePack\:\:\$sha256 \(string\|null\) does not accept string\|false\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$fileResource \\(resource\\) does not accept resource\\|false\\.$#" - count: 1 - path: ../../../src/resourcepacks/ZippedResourcePack.php - - - - message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$sha256 \\(string\\|null\\) does not accept string\\|false\\.$#" - count: 1 - path: ../../../src/resourcepacks/ZippedResourcePack.php - - - - message: "#^Property pocketmine\\\\scheduler\\\\BulkCurlTask\\:\\:\\$operations \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\scheduler\\BulkCurlTask\:\:\$operations \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/scheduler/BulkCurlTask.php - - message: "#^Cannot call method getNextRun\\(\\) on array\\\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\.$#" + message: '#^Cannot call method getNextRun\(\) on array\\>\|int\|pocketmine\\scheduler\\TaskHandler\\.$#' + identifier: method.nonObject count: 1 path: ../../../src/scheduler/TaskScheduler.php - - message: "#^Cannot access offset string on mixed\\.$#" + message: '#^Cannot access offset string on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 2 path: ../../../src/utils/Config.php - - message: "#^Method pocketmine\\\\utils\\\\Config\\:\\:fixYAMLIndexes\\(\\) should return string but returns string\\|null\\.$#" + message: '#^Method pocketmine\\utils\\Config\:\:fixYAMLIndexes\(\) should return string but returns string\|null\.$#' + identifier: return.type count: 1 path: ../../../src/utils/Config.php - - message: "#^Parameter \\#1 \\$config of static method pocketmine\\\\utils\\\\Config\\:\\:writeProperties\\(\\) expects array\\, array\\ given\\.$#" + message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\, array\ given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Config.php - - message: "#^Parameter \\#1 \\$string of function trim expects string, string\\|false given\\.$#" + message: '#^Parameter \#1 \$string of function trim expects string, string\|false given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Timezone.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Binary operation "\." between mixed and ''\-\>''\|''\:\:'' results in an error\.$#' + identifier: binaryOp.invalid count: 1 path: ../../../src/utils/Utils.php - - message: "#^Method pocketmine\\\\utils\\\\Utils\\:\\:printable\\(\\) should return string but returns string\\|null\\.$#" + message: '#^Binary operation "\." between non\-falsy\-string and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 2 + path: ../../../src/utils/Utils.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#" + message: '#^Method pocketmine\\utils\\Utils\:\:printable\(\) should return string but returns string\|null\.$#' + identifier: return.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#1 \\$trace of static method pocketmine\\\\utils\\\\Utils\\:\\:printableTrace\\(\\) expects array\\\\>, array given\\.$#" + message: '#^Parameter \#1 \$path of static method pocketmine\\utils\\Filesystem\:\:cleanPath\(\) expects string, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#" + message: '#^Parameter \#2 \$file of class pocketmine\\thread\\ThreadCrashInfoFrame constructor expects string\|null, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#3 \\$line of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects int, mixed given\\.$#" + message: '#^Parameter \#3 \$line of class pocketmine\\thread\\ThreadCrashInfoFrame constructor expects int, mixed given\.$#' + identifier: argument.type count: 1 path: ../../../src/utils/Utils.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/Explosion.php - - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable count: 1 path: ../../../src/world/World.php - - message: "#^Cannot access offset 'data' on array\\{priority\\: int, data\\: pocketmine\\\\math\\\\Vector3\\}\\|int\\|pocketmine\\\\math\\\\Vector3\\.$#" + message: '#^Cannot access offset ''data'' on array\{priority\: int, data\: pocketmine\\math\\Vector3\}\|int\|pocketmine\\math\\Vector3\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 1 path: ../../../src/world/World.php - - message: "#^Cannot access offset 'priority' on array\\{priority\\: int, data\\: pocketmine\\\\math\\\\Vector3\\}\\|int\\|pocketmine\\\\math\\\\Vector3\\.$#" + message: '#^Cannot access offset ''priority'' on array\{priority\: int, data\: pocketmine\\math\\Vector3\}\|int\|pocketmine\\math\\Vector3\.$#' + identifier: offsetAccess.nonOffsetAccessible count: 1 path: ../../../src/world/World.php - - message: "#^Cannot cast mixed to string\\.$#" + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$x of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$y of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getTileAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:isInWorld\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\World\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Parameter \\#4 \\$z of method pocketmine\\\\block\\\\Block\\:\\:position\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/World.php - - message: "#^Method pocketmine\\\\world\\\\biome\\\\BiomeRegistry\\:\\:getBiome\\(\\) should return pocketmine\\\\world\\\\biome\\\\Biome but returns pocketmine\\\\world\\\\biome\\\\Biome\\|null\\.$#" + message: '#^Method pocketmine\\world\\biome\\BiomeRegistry\:\:getBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/biome/BiomeRegistry.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:getSubChunk\\(\\) should return pocketmine\\\\world\\\\format\\\\SubChunk but returns pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Method pocketmine\\world\\format\\Chunk\:\:getSubChunk\(\) should return pocketmine\\world\\format\\SubChunk but returns pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$x of static method pocketmine\\world\\format\\Chunk\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of static method pocketmine\\world\\format\\Chunk\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#3 \$z of static method pocketmine\\world\\format\\Chunk\:\:blockHash\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\HeightArray\\:\\:get\\(\\) should return int but returns int\\|null\\.$#" + message: '#^Method pocketmine\\world\\format\\HeightArray\:\:get\(\) should return int but returns int\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/HeightArray.php - - message: "#^Only numeric types are allowed in %%, int\\<0, max\\>\\|false given on the left side\\.$#" + message: '#^Only numeric types are allowed in %%, int\<0, max\>\|false given on the left side\.$#' + identifier: mod.leftNonNumeric count: 1 path: ../../../src/world/format/io/region/RegionLoader.php - - message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#" + message: '#^Parameter \#2 \$size of function ftruncate expects int\<0, max\>, int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/format/io/region/RegionLoader.php - - message: "#^Parameter \\#2 \\$size of function ftruncate expects int\\<0, max\\>, int given\\.$#" + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic count: 1 - path: ../../../src/world/format/io/region/RegionLoader.php + path: ../../../src/world/generator/GeneratorRegisterTask.php - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Cannot access offset 1 on mixed\\.$#" - count: 2 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Cannot access offset 2 on mixed\\.$#" - count: 2 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Cannot cast mixed to int\\.$#" - count: 4 - path: ../../../src/world/format/io/region/RegionWorldProvider.php - - - - message: "#^Method pocketmine\\\\world\\\\generator\\\\biome\\\\BiomeSelector\\:\\:pickBiome\\(\\) should return pocketmine\\\\world\\\\biome\\\\Biome but returns pocketmine\\\\world\\\\biome\\\\Biome\\|null\\.$#" + message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' + identifier: return.type count: 1 path: ../../../src/world/generator/biome/BiomeSelector.php - - message: "#^Cannot call method getBiomeId\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/generator/hell/Nether.php - - message: "#^Offset int does not exist on SplFixedArray\\\\|null\\.$#" - count: 4 - path: ../../../src/world/generator/noise/Noise.php - - - - message: "#^Parameter \\$q0 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#" + message: '#^Parameter \$q0 of static method pocketmine\\world\\generator\\noise\\Noise\:\:linearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/generator/noise/Noise.php - - message: "#^Parameter \\$q1 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#" + message: '#^Parameter \$q1 of static method pocketmine\\world\\generator\\noise\\Noise\:\:linearLerp\(\) expects float, float\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/generator/noise/Noise.php - - message: "#^Cannot call method getBiomeId\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/generator/normal/Normal.php - - message: "#^Parameter \\#1 \\$start of method pocketmine\\\\utils\\\\Random\\:\\:nextRange\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#1 \$start of method pocketmine\\utils\\Random\:\:nextRange\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Parameter \\#2 \\$end of method pocketmine\\\\utils\\\\Random\\:\\:nextRange\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$end of method pocketmine\\utils\\Random\:\:nextRange\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\ChunkManager\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\ChunkManager\:\:getBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 2 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\ChunkManager\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#" + message: '#^Parameter \#2 \$y of method pocketmine\\world\\ChunkManager\:\:setBlockAt\(\) expects int, float\|int given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/generator/object/TallGrass.php - - message: "#^Cannot call method getBlockLightArray\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockLightArray\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/BlockLightUpdate.php - - message: "#^Cannot call method getBlockStateId\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockStateId\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/BlockLightUpdate.php - - message: "#^Cannot call method getSubChunks\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getSubChunks\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/BlockLightUpdate.php - - message: "#^Property pocketmine\\\\world\\\\light\\\\LightPopulationTask\\:\\:\\$resultBlockLightArrays \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\world\\light\\LightPopulationTask\:\:\$resultBlockLightArrays \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/world/light/LightPopulationTask.php - - message: "#^Property pocketmine\\\\world\\\\light\\\\LightPopulationTask\\:\\:\\$resultHeightMap \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\world\\light\\LightPopulationTask\:\:\$resultHeightMap \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/world/light/LightPopulationTask.php - - message: "#^Property pocketmine\\\\world\\\\light\\\\LightPopulationTask\\:\\:\\$resultSkyLightArrays \\(string\\) does not accept string\\|null\\.$#" + message: '#^Property pocketmine\\world\\light\\LightPopulationTask\:\:\$resultSkyLightArrays \(string\) does not accept string\|null\.$#' + identifier: assign.propertyType count: 1 path: ../../../src/world/light/LightPopulationTask.php - - message: "#^Cannot call method getBlockSkyLightArray\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockSkyLightArray\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getBlockStateId\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method getBlockStateId\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getHeightMap\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getHeightMap\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 6 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getHeightMapArray\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getHeightMapArray\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method getSubChunk\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method getSubChunk\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method setHeightMap\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method setHeightMap\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 2 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Cannot call method setHeightMapArray\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" + message: '#^Cannot call method setHeightMapArray\(\) on pocketmine\\world\\format\\Chunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Parameter \\#1 \\$chunk of static method pocketmine\\\\world\\\\light\\\\SkyLightUpdate\\:\\:recalculateHeightMap\\(\\) expects pocketmine\\\\world\\\\format\\\\Chunk, pocketmine\\\\world\\\\format\\\\Chunk\\|null given\\.$#" + message: '#^Parameter \#1 \$chunk of static method pocketmine\\world\\light\\SkyLightUpdate\:\:recalculateHeightMap\(\) expects pocketmine\\world\\format\\Chunk, pocketmine\\world\\format\\Chunk\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - message: "#^Parameter \\#1 \\$chunk of static method pocketmine\\\\world\\\\light\\\\SkyLightUpdate\\:\\:recalculateHeightMapColumn\\(\\) expects pocketmine\\\\world\\\\format\\\\Chunk, pocketmine\\\\world\\\\format\\\\Chunk\\|null given\\.$#" + message: '#^Parameter \#1 \$chunk of static method pocketmine\\world\\light\\SkyLightUpdate\:\:recalculateHeightMapColumn\(\) expects pocketmine\\world\\format\\Chunk, pocketmine\\world\\format\\Chunk\|null given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - - - 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: "#^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/impossible-generics.neon b/tests/phpstan/configs/impossible-generics.neon index b0e67d294b..e0b944e695 100644 --- a/tests/phpstan/configs/impossible-generics.neon +++ b/tests/phpstan/configs/impossible-generics.neon @@ -1,12 +1,14 @@ parameters: ignoreErrors: - - message: "#^Method pocketmine\\\\event\\\\RegisteredListener\\:\\:__construct\\(\\) has parameter \\$handler with no signature specified for Closure\\.$#" + message: '#^Method pocketmine\\event\\RegisteredListener\:\:__construct\(\) has parameter \$handler with no signature specified for Closure\.$#' + identifier: missingType.callable count: 1 path: ../../../src/event/RegisteredListener.php - - message: "#^Method pocketmine\\\\event\\\\RegisteredListener\\:\\:getHandler\\(\\) return type has no signature specified for Closure\\.$#" + message: '#^Method pocketmine\\event\\RegisteredListener\:\:getHandler\(\) return type has no signature specified for Closure\.$#' + identifier: missingType.callable count: 1 path: ../../../src/event/RegisteredListener.php diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index de38903bd3..aeb3fae29f 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -1,52 +1,260 @@ parameters: ignoreErrors: - - message: "#^Method pocketmine\\\\block\\\\CakeWithCandle\\:\\:onInteractCandle\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" + message: '#^Access to an undefined property object\:\:\$crashId\.$#' + identifier: property.notFound count: 1 - path: ../../../src/block/CakeWithCandle.php + path: ../../../src/Server.php - - message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Access to an undefined property object\:\:\$crashUrl\.$#' + identifier: property.notFound + count: 1 + path: ../../../src/Server.php + + - + message: '#^Access to an undefined property object\:\:\$error\.$#' + identifier: property.notFound + count: 1 + path: ../../../src/Server.php + + - + message: '#^Method pocketmine\\block\\Block\:\:readStateFromWorld\(\) is marked as impure but does not have any side effects\.$#' + identifier: impureMethod.pure + count: 1 + path: ../../../src/block/Block.php + + - + message: '#^Method pocketmine\\block\\DoubleTallGrass\:\:traitGetDropsForIncompatibleTool\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: ../../../src/block/DoubleTallGrass.php - - message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:ACACIA_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod count: 1 - path: ../../../src/entity/projectile/Projectile.php + path: ../../../src/block/VanillaBlocks.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\PthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#" + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:BIRCH_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CHERRY_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:CRIMSON_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:DARK_OAK_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:JUNGLE_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:MANGROVE_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:OAK_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:PALE_OAK_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:SPRUCE_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Creating callable from a non\-native static method pocketmine\\item\\VanillaItems\:\:WARPED_SIGN\(\)\.$#' + identifier: callable.nonNativeMethod + count: 1 + path: ../../../src/block/VanillaBlocks.php + + - + message: '#^Strict comparison using \=\=\= between \*NEVER\* and 5 will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: ../../../src/command/defaults/TeleportCommand.php + + - + message: '#^Method pocketmine\\crafting\\ShapedRecipe\:\:getIngredientMap\(\) should return list\\> but returns array\, non\-empty\-array\, pocketmine\\crafting\\RecipeIngredient\|null\>\>\.$#' + identifier: return.type + count: 1 + path: ../../../src/crafting/ShapedRecipe.php + + - + message: '#^Property pocketmine\\crash\\CrashDumpData\:\:\$parameters \(list\\) does not accept array\.$#' + identifier: assign.propertyType + count: 1 + path: ../../../src/crash/CrashDump.php + + - + message: '#^Property pocketmine\\item\\WritableBookBase\:\:\$pages \(list\\) does not accept non\-empty\-array\\.$#' + identifier: assign.propertyType + count: 1 + path: ../../../src/item/WritableBookBase.php + + - + message: '#^Parameter \#3 \$input of class pocketmine\\network\\mcpe\\protocol\\types\\recipe\\ShapedRecipe constructor expects list\\>, array\, non\-empty\-array\, pocketmine\\network\\mcpe\\protocol\\types\\recipe\\RecipeIngredient\>\> given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/network/mcpe/cache/CraftingDataCache.php + + - + message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 1 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: ../../../src/network/mcpe/compression/ZlibCompressor.php + + - + message: '#^Method pocketmine\\network\\mcpe\\compression\\ZlibCompressor\:\:getNetworkId\(\) never returns 255 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: ../../../src/network/mcpe/compression/ZlibCompressor.php + + - + message: '#^Parameter \#1 \$states of class pocketmine\\network\\mcpe\\convert\\BlockStateDictionary constructor expects list\, array\, pocketmine\\network\\mcpe\\convert\\BlockStateDictionaryEntry\> given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/network/mcpe/convert/BlockStateDictionary.php + + - + message: '#^Cannot access offset ''default'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: ../../../src/network/mcpe/convert/LegacySkinAdapter.php + + - + message: '#^Property pocketmine\\network\\mcpe\\raklib\\PthreadsChannelWriter\:\:\$buffer is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: ../../../src/network/mcpe/raklib/PthreadsChannelWriter.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\SnoozeAwarePthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#" + message: '#^Property pocketmine\\network\\mcpe\\raklib\\SnoozeAwarePthreadsChannelWriter\:\:\$buffer is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: ../../../src/network/mcpe/raklib/SnoozeAwarePthreadsChannelWriter.php - - message: "#^Dead catch \\- RuntimeException is never thrown in the try block\\.$#" + message: '#^Dead catch \- RuntimeException is never thrown in the try block\.$#' + identifier: catch.neverThrown count: 1 path: ../../../src/plugin/PluginManager.php - - message: "#^Casting to int something that's already int\\.$#" + message: '#^Binary operation "\." between mixed and ''/''\|''\\\\'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/thread/ThreadSafeClassLoader.php + + - + message: '#^Binary operation "\." between mixed and non\-falsy\-string results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/thread/ThreadSafeClassLoader.php + + - + message: '#^Method pocketmine\\timings\\TimingsHandler\:\:lazyGetSet\(\) should return pocketmine\\utils\\ObjectSet\ but returns pocketmine\\utils\\ObjectSet\\.$#' + identifier: return.type + count: 1 + path: ../../../src/timings/TimingsHandler.php + + - + message: '#^Parameter &\$where @param\-out type of method pocketmine\\timings\\TimingsHandler\:\:lazyGetSet\(\) expects pocketmine\\utils\\ObjectSet\, pocketmine\\utils\\ObjectSet\ given\.$#' + identifier: paramOut.type + count: 1 + path: ../../../src/timings/TimingsHandler.php + + - + message: '#^Binary operation "\*" between mixed and 3600 results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/utils/Timezone.php + + - + message: '#^Binary operation "\*" between mixed and 60 results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/utils/Timezone.php + + - + message: '#^Binary operation "\+" between \(float\|int\) and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: ../../../src/utils/Timezone.php + + - + message: '#^Property pocketmine\\world\\format\\io\\region\\RegionLoader\:\:\$locationTable \(list\\) does not accept non\-empty\-array\\.$#' + identifier: assign.propertyType + count: 2 + path: ../../../src/world/format/io/region/RegionLoader.php + + - + message: '#^Property pocketmine\\world\\format\\io\\region\\RegionLoader\:\:\$locationTable \(list\\) does not accept non\-empty\-array\, pocketmine\\world\\format\\io\\region\\RegionLocationTableEntry\|null\>\.$#' + identifier: assign.propertyType + count: 3 + path: ../../../src/world/format/io/region/RegionLoader.php + + - + message: '#^Method pocketmine\\world\\format\\io\\region\\RegionWorldProvider\:\:createRegionIterator\(\) should return RegexIterator\ but returns RegexIterator\\>\.$#' + identifier: return.type + count: 1 + path: ../../../src/world/format/io/region/RegionWorldProvider.php + + - + message: '#^Casting to int something that''s already int\.$#' + identifier: cast.useless count: 1 path: ../../../src/world/generator/normal/Normal.php - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFalse\\(\\) with false will always evaluate to true\\.$#" + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertFalse\(\) with false will always evaluate to true\.$#' + identifier: staticMethod.alreadyNarrowedType count: 1 path: ../../phpunit/promise/PromiseTest.php - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false and 'All promise should…' will always evaluate to false\\.$#" + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with false and ''All promise should…'' will always evaluate to false\.$#' + identifier: staticMethod.impossibleType count: 1 path: ../../phpunit/promise/PromiseTest.php - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false will always evaluate to false\\.$#" + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with false will always evaluate to false\.$#' + identifier: staticMethod.impossibleType count: 2 path: ../../phpunit/promise/PromiseTest.php + - + message: '#^Strict comparison using \=\=\= between 0 and 0 will always evaluate to true\.$#' + identifier: identical.alwaysTrue + count: 1 + path: ../rules/UnsafeForeachArrayOfStringRule.php + diff --git a/tests/phpstan/configs/spl-fixed-array-sucks.neon b/tests/phpstan/configs/spl-fixed-array-sucks.neon index daa6361dd0..05524fb8ca 100644 --- a/tests/phpstan/configs/spl-fixed-array-sucks.neon +++ b/tests/phpstan/configs/spl-fixed-array-sucks.neon @@ -1,22 +1,32 @@ parameters: ignoreErrors: - - message: "#^Cannot call method collectGarbage\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#" + message: '#^Cannot call method collectGarbage\(\) on pocketmine\\world\\format\\SubChunk\|null\.$#' + identifier: method.nonObject count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:getSubChunks\\(\\) should return array\\ but returns array\\\\.$#" + message: '#^Method pocketmine\\world\\format\\Chunk\:\:getSubChunks\(\) should return array\ but returns array\\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\)\\: mixed\\)\\|null, Closure\\(pocketmine\\\\world\\\\format\\\\SubChunk\\)\\: pocketmine\\\\world\\\\format\\\\SubChunk given\\.$#" + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(pocketmine\\world\\format\\SubChunk\|null\)\: mixed\)\|null, Closure\(pocketmine\\world\\format\\SubChunk\)\: pocketmine\\world\\format\\SubChunk given\.$#' + identifier: argument.type count: 1 path: ../../../src/world/format/Chunk.php - - message: "#^Method pocketmine\\\\world\\\\format\\\\HeightArray\\:\\:getValues\\(\\) should return array\\ but returns array\\\\.$#" + message: '#^Method pocketmine\\world\\format\\HeightArray\:\:getValues\(\) should return non\-empty\-list\ but returns array\\.$#' + identifier: return.type count: 1 path: ../../../src/world/format/HeightArray.php + - + message: '#^Offset int might not exist on SplFixedArray\\|null\.$#' + identifier: offsetAccess.notFound + count: 4 + path: ../../../src/world/generator/noise/Noise.php + diff --git a/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php b/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php index 4fa7670224..5753bb6280 100644 --- a/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php +++ b/tests/phpstan/rules/DeprecatedLegacyEnumAccessRule.php @@ -28,7 +28,6 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\TypeWithClassName; use pocketmine\utils\LegacyEnumShimTrait; use function sprintf; @@ -51,18 +50,15 @@ final class DeprecatedLegacyEnumAccessRule implements Rule{ $scope->resolveTypeByName($node->class) : $scope->getType($node->class); - if(!$classType instanceof TypeWithClassName){ - return []; - } + $errors = []; + $reflections = $classType->getObjectClassReflections(); + foreach($reflections as $reflection){ + if(!$reflection->hasTraitUse(LegacyEnumShimTrait::class) || !$reflection->implementsInterface(\UnitEnum::class)){ + continue; + } - $reflection = $classType->getClassReflection(); - if($reflection === null || !$reflection->hasTraitUse(LegacyEnumShimTrait::class) || !$reflection->implementsInterface(\UnitEnum::class)){ - return []; - } - - if(!$reflection->hasNativeMethod($caseName)){ - return [ - RuleErrorBuilder::message(sprintf( + if(!$reflection->hasNativeMethod($caseName)){ + $errors[] = RuleErrorBuilder::message(sprintf( 'Use of legacy enum case accessor %s::%s().', $reflection->getName(), $caseName @@ -70,10 +66,11 @@ final class DeprecatedLegacyEnumAccessRule implements Rule{ 'Access the enum constant directly instead (remove the brackets), e.g. %s::%s', $reflection->getName(), $caseName - ))->build() - ]; + ))->identifier('pocketmine.enum.deprecatedAccessor') + ->build(); + } } - return []; + return $errors; } } diff --git a/tests/phpstan/rules/DisallowDynamicNewRule.php b/tests/phpstan/rules/DisallowDynamicNewRule.php new file mode 100644 index 0000000000..c25e6a18bd --- /dev/null +++ b/tests/phpstan/rules/DisallowDynamicNewRule.php @@ -0,0 +1,55 @@ + + */ +final class DisallowDynamicNewRule implements Rule{ + + public function getNodeType() : string{ + return New_::class; + } + + public function processNode(Node $node, Scope $scope) : array{ + /** @var New_ $node */ + if($node->class instanceof Expr){ + return [ + RuleErrorBuilder::message("Dynamic new is not allowed.") + ->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.") + ->identifier("pocketmine.new.dynamic") + ->build() + ]; + } + + return []; + } +} diff --git a/tests/phpstan/rules/DisallowEnumComparisonRule.php b/tests/phpstan/rules/DisallowEnumComparisonRule.php index fc5377173a..d73cc3972a 100644 --- a/tests/phpstan/rules/DisallowEnumComparisonRule.php +++ b/tests/phpstan/rules/DisallowEnumComparisonRule.php @@ -30,7 +30,6 @@ use PhpParser\Node\Expr\BinaryOp\NotIdentical; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -61,7 +60,7 @@ class DisallowEnumComparisonRule implements Rule{ $node instanceof Identical ? '===' : '!==', $leftType->describe(VerbosityLevel::value()), $rightType->describe(VerbosityLevel::value()) - ))->build()]; + ))->identifier('pocketmine.enum.badComparison')->build()]; } return []; } @@ -69,7 +68,7 @@ class DisallowEnumComparisonRule implements Rule{ private function checkForEnumTypes(Type $comparedType) : bool{ //TODO: what we really want to do here is iterate over the contained types, but there's no universal way to //do that. This might break with other circumstances. - if($comparedType instanceof ObjectType){ + if($comparedType->isObject()->yes()){ $types = [$comparedType]; }elseif($comparedType instanceof UnionType){ $types = $comparedType->getTypes(); @@ -77,12 +76,14 @@ class DisallowEnumComparisonRule implements Rule{ return false; } foreach($types as $containedType){ - if(!($containedType instanceof ObjectType)){ + if(!($containedType->isObject()->yes())){ continue; } - $class = $containedType->getClassReflection(); - if($class !== null && $class->hasTraitUse(EnumTrait::class)){ - return true; + $classes = $containedType->getObjectClassReflections(); + foreach($classes as $class){ + if($class->hasTraitUse(EnumTrait::class)){ + return true; + } } } return false; diff --git a/tests/phpstan/rules/DisallowForeachByReferenceRule.php b/tests/phpstan/rules/DisallowForeachByReferenceRule.php index 79124d3283..eb65897057 100644 --- a/tests/phpstan/rules/DisallowForeachByReferenceRule.php +++ b/tests/phpstan/rules/DisallowForeachByReferenceRule.php @@ -44,6 +44,7 @@ final class DisallowForeachByReferenceRule implements Rule{ return [ RuleErrorBuilder::message("Foreach by-reference is not allowed, because it has surprising behaviour.") ->tip("If the value variable is used outside of the foreach construct (e.g. in a second foreach), the iterable's contents will be unexpectedly altered.") + ->identifier('pocketmine.foreach.byRef') ->build() ]; } diff --git a/tests/phpstan/rules/ExplodeLimitRule.php b/tests/phpstan/rules/ExplodeLimitRule.php new file mode 100644 index 0000000000..4e8a341ad8 --- /dev/null +++ b/tests/phpstan/rules/ExplodeLimitRule.php @@ -0,0 +1,92 @@ + + */ +final class ExplodeLimitRule implements Rule{ + private ReflectionProvider $reflectionProvider; + + public function __construct( + ReflectionProvider $reflectionProvider + ){ + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType() : string{ + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope) : array{ + if(!$node->name instanceof Name){ + return []; + } + + if(!$this->reflectionProvider->hasFunction($node->name, $scope)){ + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + + if($functionReflection->getName() !== 'explode'){ + return []; + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->getArgs(), + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + + $normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + + if($normalizedFuncCall === null){ + return []; + } + + $count = count($normalizedFuncCall->getArgs()); + if($count !== 3){ + return [ + RuleErrorBuilder::message('The $limit parameter of explode() must be set to prevent malicious client data wasting resources.') + ->identifier("pocketmine.explode.limit") + ->build() + ]; + } + + return []; + } +} diff --git a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php index e42d329275..34056131b5 100644 --- a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php +++ b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php @@ -28,6 +28,7 @@ use PhpParser\Node\Stmt\Foreach_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\ClassStringType; use PHPStan\Type\IntegerType; use PHPStan\Type\StringType; @@ -62,8 +63,17 @@ final class UnsafeForeachArrayOfStringRule implements Rule{ $hasCastableKeyTypes = false; $expectsIntKeyTypes = false; - TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes) : Type{ - if($type instanceof IntegerType){ + $implicitType = false; + $benevolentUnionDepth = 0; + TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes, &$benevolentUnionDepth, &$implicitType) : Type{ + if($type instanceof BenevolentUnionType){ + $implicitType = true; + $benevolentUnionDepth++; + $result = $traverse($type); + $benevolentUnionDepth--; + return $result; + } + if($type instanceof IntegerType && $benevolentUnionDepth === 0){ $expectsIntKeyTypes = true; return $type; } @@ -78,12 +88,20 @@ final class UnsafeForeachArrayOfStringRule implements Rule{ return $type; }); if($hasCastableKeyTypes && !$expectsIntKeyTypes){ - $func = Utils::stringifyKeys(...); + $tip = $implicitType ? + sprintf( + "Declare a key type using @phpstan-var or @phpstan-param, or use %s() to promote the key type to get proper error reporting", + Utils::getNiceClosureName(Utils::promoteKeys(...)) + ) : + sprintf( + "Use %s() to get a \Generator that will force the keys to string", + Utils::getNiceClosureName(Utils::stringifyKeys(...)), + ); return [ RuleErrorBuilder::message(sprintf( "Unsafe foreach on array with key type %s (they might be casted to int).", $iterableType->getIterableKeyType()->describe(VerbosityLevel::value()) - ))->tip(sprintf("Use %s() for a safe Generator-based iterator.", Utils::getNiceClosureName($func)))->build() + ))->tip($tip)->identifier('pocketmine.foreach.stringKeys')->build() ]; } return []; diff --git a/tests/phpstan/stubs/chunkutils2.stub b/tests/phpstan/stubs/chunkutils2.stub new file mode 100644 index 0000000000..b23e4a7fd0 --- /dev/null +++ b/tests/phpstan/stubs/chunkutils2.stub @@ -0,0 +1,15 @@ + $palette + */ + public static function fromData(int $bitsPerBlock, string $wordArray, array $palette): PalettedBlockArray {} + + /** + * @return list + */ + public function getPalette(): array {} +} diff --git a/tests/phpunit/block/BlockTest.php b/tests/phpunit/block/BlockTest.php index 6ade2bffe2..971564720e 100644 --- a/tests/phpunit/block/BlockTest.php +++ b/tests/phpunit/block/BlockTest.php @@ -27,6 +27,7 @@ use PHPUnit\Framework\TestCase; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; +use function get_debug_type; use function implode; use function is_array; use function is_int; @@ -95,11 +96,12 @@ class BlockTest extends TestCase{ } /** - * @return int[] - * @phpstan-return array + * @return int[][]|string[][] + * @phpstan-return array{array, array} */ public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{ $newTable = []; + $newTileMap = []; $idNameLookup = []; //if we ever split up block registration into multiple registries (e.g. separating chemistry blocks), @@ -118,36 +120,70 @@ class BlockTest extends TestCase{ } $idName = $idNameLookup[$block->getTypeId()]; $newTable[$idName] = ($newTable[$idName] ?? 0) + 1; - } - return $newTable; + $tileClass = $block->getIdInfo()->getTileClass(); + if($tileClass !== null){ + if(isset($newTileMap[$idName]) && $newTileMap[$idName] !== $tileClass){ + throw new AssumptionFailedError("Tile entity $tileClass for $idName is inconsistent"); + } + $newTileMap[$idName] = $tileClass; + } + } + return [$newTable, $newTileMap]; } /** - * @phpstan-param array $actual + * @phpstan-param array $actualStateCounts + * @phpstan-param array $actualTiles * * @return string[] */ - public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{ - $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR); + public static function computeConsistencyCheckDiff(string $expectedFile, array $actualStateCounts, array $actualTiles) : array{ + $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 3, JSON_THROW_ON_ERROR); if(!is_array($expected)){ - throw new AssumptionFailedError("Old table should be array"); + throw new AssumptionFailedError("Old table should be array{stateCounts: array, tiles: array}"); + } + $expectedStates = $expected["stateCounts"] ?? []; + $expectedTiles = $expected["tiles"] ?? []; + if(!is_array($expectedStates)){ + throw new AssumptionFailedError("stateCounts should be an array, but have " . get_debug_type($expectedStates)); + } + if(!is_array($expectedTiles)){ + throw new AssumptionFailedError("tiles should be an array, but have " . get_debug_type($expectedTiles)); } $errors = []; - foreach($expected as $typeName => $numStates){ + foreach(Utils::promoteKeys($expectedStates) as $typeName => $numStates){ if(!is_string($typeName) || !is_int($numStates)){ throw new AssumptionFailedError("Old table should be array"); } - if(!isset($actual[$typeName])){ + if(!isset($actualStateCounts[$typeName])){ $errors[] = "Removed block type $typeName ($numStates permutations)"; - }elseif($actual[$typeName] !== $numStates){ - $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName]; + }elseif($actualStateCounts[$typeName] !== $numStates){ + $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actualStateCounts[$typeName]; } } - foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){ - if(!isset($expected[$typeName])){ - $errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)"; + foreach(Utils::stringifyKeys($actualStateCounts) as $typeName => $numStates){ + if(!isset($expectedStates[$typeName])){ + $errors[] = "Added block type $typeName (" . $actualStateCounts[$typeName] . " permutations)"; + } + } + + foreach(Utils::promoteKeys($expectedTiles) as $typeName => $tile){ + if(!is_string($typeName) || !is_string($tile)){ + throw new AssumptionFailedError("Tile table should be array"); + } + if(isset($actualStateCounts[$typeName])){ + if(!isset($actualTiles[$typeName])){ + $errors[] = "$typeName no longer has a tile"; + }elseif($actualTiles[$typeName] !== $tile){ + $errors[] = "$typeName has changed tile ($tile -> " . $actualTiles[$typeName] . ")"; + } + } + } + foreach(Utils::promoteKeys($actualTiles) as $typeName => $tile){ + if(isset($expectedStates[$typeName]) && !isset($expectedTiles[$typeName])){ + $errors[] = "$typeName has a tile when it previously didn't ($tile)"; } } @@ -155,8 +191,8 @@ class BlockTest extends TestCase{ } public function testConsistency() : void{ - $newTable = self::computeConsistencyCheckTable($this->blockFactory); - $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable); + [$newTable, $newTileMap] = self::computeConsistencyCheckTable($this->blockFactory); + $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable, $newTileMap); self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors)); } diff --git a/tests/phpunit/block/BlockTypeIdsTest.php b/tests/phpunit/block/BlockTypeIdsTest.php index 32c8d49029..cbfc07eaf7 100644 --- a/tests/phpunit/block/BlockTypeIdsTest.php +++ b/tests/phpunit/block/BlockTypeIdsTest.php @@ -35,8 +35,12 @@ class BlockTypeIdsTest extends TestCase{ $constants = $reflect->getConstants(); unset($constants['FIRST_UNUSED_BLOCK_ID']); + self::assertNotEmpty($constants, "We should never have zero type IDs"); - self::assertSame($reflect->getConstant('FIRST_UNUSED_BLOCK_ID'), max($constants) + 1, "FIRST_UNUSED_BLOCK_ID must be one higher than the highest fixed type ID"); + $max = max($constants); + self::assertIsInt($max, "Max type ID should always be an integer"); + + self::assertSame($reflect->getConstant('FIRST_UNUSED_BLOCK_ID'), $max + 1, "FIRST_UNUSED_BLOCK_ID must be one higher than the highest fixed type ID"); } public function testNoDuplicates() : void{ @@ -51,6 +55,7 @@ class BlockTypeIdsTest extends TestCase{ 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/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index 97c24b52ec..0b91509889 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -1,707 +1,793 @@ { - "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_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_ORE": 1, - "CORAL": 10, - "CORAL_BLOCK": 10, - "CORAL_FAN": 20, - "CORNFLOWER": 1, - "CRACKED_DEEPSLATE_BRICKS": 1, - "CRACKED_DEEPSLATE_TILES": 1, - "CRACKED_NETHER_BRICKS": 1, - "CRACKED_POLISHED_BLACKSTONE_BRICKS": 1, - "CRACKED_STONE_BRICKS": 1, - "CRAFTING_TABLE": 1, - "CRIMSON_BUTTON": 12, - "CRIMSON_DOOR": 32, - "CRIMSON_FENCE": 1, - "CRIMSON_FENCE_GATE": 16, - "CRIMSON_HYPHAE": 6, - "CRIMSON_PLANKS": 1, - "CRIMSON_PRESSURE_PLATE": 2, - "CRIMSON_ROOTS": 1, - "CRIMSON_SIGN": 16, - "CRIMSON_SLAB": 3, - "CRIMSON_STAIRS": 8, - "CRIMSON_STEM": 6, - "CRIMSON_TRAPDOOR": 16, - "CRIMSON_WALL_SIGN": 4, - "CRYING_OBSIDIAN": 1, - "CUT_COPPER": 8, - "CUT_COPPER_SLAB": 24, - "CUT_COPPER_STAIRS": 64, - "CUT_RED_SANDSTONE": 1, - "CUT_RED_SANDSTONE_SLAB": 3, - "CUT_SANDSTONE": 1, - "CUT_SANDSTONE_SLAB": 3, - "DANDELION": 1, - "DARK_OAK_BUTTON": 12, - "DARK_OAK_DOOR": 32, - "DARK_OAK_FENCE": 1, - "DARK_OAK_FENCE_GATE": 16, - "DARK_OAK_LEAVES": 4, - "DARK_OAK_LOG": 6, - "DARK_OAK_PLANKS": 1, - "DARK_OAK_PRESSURE_PLATE": 2, - "DARK_OAK_SAPLING": 2, - "DARK_OAK_SIGN": 16, - "DARK_OAK_SLAB": 3, - "DARK_OAK_STAIRS": 8, - "DARK_OAK_TRAPDOOR": 16, - "DARK_OAK_WALL_SIGN": 4, - "DARK_OAK_WOOD": 6, - "DARK_PRISMARINE": 1, - "DARK_PRISMARINE_SLAB": 3, - "DARK_PRISMARINE_STAIRS": 8, - "DAYLIGHT_SENSOR": 32, - "DEAD_BUSH": 1, - "DEEPSLATE": 3, - "DEEPSLATE_BRICKS": 1, - "DEEPSLATE_BRICK_SLAB": 3, - "DEEPSLATE_BRICK_STAIRS": 8, - "DEEPSLATE_BRICK_WALL": 162, - "DEEPSLATE_COAL_ORE": 1, - "DEEPSLATE_COPPER_ORE": 1, - "DEEPSLATE_DIAMOND_ORE": 1, - "DEEPSLATE_EMERALD_ORE": 1, - "DEEPSLATE_GOLD_ORE": 1, - "DEEPSLATE_IRON_ORE": 1, - "DEEPSLATE_LAPIS_LAZULI_ORE": 1, - "DEEPSLATE_REDSTONE_ORE": 2, - "DEEPSLATE_TILES": 1, - "DEEPSLATE_TILE_SLAB": 3, - "DEEPSLATE_TILE_STAIRS": 8, - "DEEPSLATE_TILE_WALL": 162, - "DETECTOR_RAIL": 12, - "DIAMOND": 1, - "DIAMOND_ORE": 1, - "DIORITE": 1, - "DIORITE_SLAB": 3, - "DIORITE_STAIRS": 8, - "DIORITE_WALL": 162, - "DIRT": 3, - "DOUBLE_PITCHER_CROP": 4, - "DOUBLE_TALLGRASS": 2, - "DRAGON_EGG": 1, - "DRIED_KELP": 1, - "DYED_CANDLE": 128, - "DYED_SHULKER_BOX": 16, - "ELEMENT_ACTINIUM": 1, - "ELEMENT_ALUMINUM": 1, - "ELEMENT_AMERICIUM": 1, - "ELEMENT_ANTIMONY": 1, - "ELEMENT_ARGON": 1, - "ELEMENT_ARSENIC": 1, - "ELEMENT_ASTATINE": 1, - "ELEMENT_BARIUM": 1, - "ELEMENT_BERKELIUM": 1, - "ELEMENT_BERYLLIUM": 1, - "ELEMENT_BISMUTH": 1, - "ELEMENT_BOHRIUM": 1, - "ELEMENT_BORON": 1, - "ELEMENT_BROMINE": 1, - "ELEMENT_CADMIUM": 1, - "ELEMENT_CALCIUM": 1, - "ELEMENT_CALIFORNIUM": 1, - "ELEMENT_CARBON": 1, - "ELEMENT_CERIUM": 1, - "ELEMENT_CESIUM": 1, - "ELEMENT_CHLORINE": 1, - "ELEMENT_CHROMIUM": 1, - "ELEMENT_COBALT": 1, - "ELEMENT_CONSTRUCTOR": 4, - "ELEMENT_COPERNICIUM": 1, - "ELEMENT_COPPER": 1, - "ELEMENT_CURIUM": 1, - "ELEMENT_DARMSTADTIUM": 1, - "ELEMENT_DUBNIUM": 1, - "ELEMENT_DYSPROSIUM": 1, - "ELEMENT_EINSTEINIUM": 1, - "ELEMENT_ERBIUM": 1, - "ELEMENT_EUROPIUM": 1, - "ELEMENT_FERMIUM": 1, - "ELEMENT_FLEROVIUM": 1, - "ELEMENT_FLUORINE": 1, - "ELEMENT_FRANCIUM": 1, - "ELEMENT_GADOLINIUM": 1, - "ELEMENT_GALLIUM": 1, - "ELEMENT_GERMANIUM": 1, - "ELEMENT_GOLD": 1, - "ELEMENT_HAFNIUM": 1, - "ELEMENT_HASSIUM": 1, - "ELEMENT_HELIUM": 1, - "ELEMENT_HOLMIUM": 1, - "ELEMENT_HYDROGEN": 1, - "ELEMENT_INDIUM": 1, - "ELEMENT_IODINE": 1, - "ELEMENT_IRIDIUM": 1, - "ELEMENT_IRON": 1, - "ELEMENT_KRYPTON": 1, - "ELEMENT_LANTHANUM": 1, - "ELEMENT_LAWRENCIUM": 1, - "ELEMENT_LEAD": 1, - "ELEMENT_LITHIUM": 1, - "ELEMENT_LIVERMORIUM": 1, - "ELEMENT_LUTETIUM": 1, - "ELEMENT_MAGNESIUM": 1, - "ELEMENT_MANGANESE": 1, - "ELEMENT_MEITNERIUM": 1, - "ELEMENT_MENDELEVIUM": 1, - "ELEMENT_MERCURY": 1, - "ELEMENT_MOLYBDENUM": 1, - "ELEMENT_MOSCOVIUM": 1, - "ELEMENT_NEODYMIUM": 1, - "ELEMENT_NEON": 1, - "ELEMENT_NEPTUNIUM": 1, - "ELEMENT_NICKEL": 1, - "ELEMENT_NIHONIUM": 1, - "ELEMENT_NIOBIUM": 1, - "ELEMENT_NITROGEN": 1, - "ELEMENT_NOBELIUM": 1, - "ELEMENT_OGANESSON": 1, - "ELEMENT_OSMIUM": 1, - "ELEMENT_OXYGEN": 1, - "ELEMENT_PALLADIUM": 1, - "ELEMENT_PHOSPHORUS": 1, - "ELEMENT_PLATINUM": 1, - "ELEMENT_PLUTONIUM": 1, - "ELEMENT_POLONIUM": 1, - "ELEMENT_POTASSIUM": 1, - "ELEMENT_PRASEODYMIUM": 1, - "ELEMENT_PROMETHIUM": 1, - "ELEMENT_PROTACTINIUM": 1, - "ELEMENT_RADIUM": 1, - "ELEMENT_RADON": 1, - "ELEMENT_RHENIUM": 1, - "ELEMENT_RHODIUM": 1, - "ELEMENT_ROENTGENIUM": 1, - "ELEMENT_RUBIDIUM": 1, - "ELEMENT_RUTHENIUM": 1, - "ELEMENT_RUTHERFORDIUM": 1, - "ELEMENT_SAMARIUM": 1, - "ELEMENT_SCANDIUM": 1, - "ELEMENT_SEABORGIUM": 1, - "ELEMENT_SELENIUM": 1, - "ELEMENT_SILICON": 1, - "ELEMENT_SILVER": 1, - "ELEMENT_SODIUM": 1, - "ELEMENT_STRONTIUM": 1, - "ELEMENT_SULFUR": 1, - "ELEMENT_TANTALUM": 1, - "ELEMENT_TECHNETIUM": 1, - "ELEMENT_TELLURIUM": 1, - "ELEMENT_TENNESSINE": 1, - "ELEMENT_TERBIUM": 1, - "ELEMENT_THALLIUM": 1, - "ELEMENT_THORIUM": 1, - "ELEMENT_THULIUM": 1, - "ELEMENT_TIN": 1, - "ELEMENT_TITANIUM": 1, - "ELEMENT_TUNGSTEN": 1, - "ELEMENT_URANIUM": 1, - "ELEMENT_VANADIUM": 1, - "ELEMENT_XENON": 1, - "ELEMENT_YTTERBIUM": 1, - "ELEMENT_YTTRIUM": 1, - "ELEMENT_ZERO": 1, - "ELEMENT_ZINC": 1, - "ELEMENT_ZIRCONIUM": 1, - "EMERALD": 1, - "EMERALD_ORE": 1, - "ENCHANTING_TABLE": 1, - "ENDER_CHEST": 4, - "END_PORTAL_FRAME": 8, - "END_ROD": 6, - "END_STONE": 1, - "END_STONE_BRICKS": 1, - "END_STONE_BRICK_SLAB": 3, - "END_STONE_BRICK_STAIRS": 8, - "END_STONE_BRICK_WALL": 162, - "FAKE_WOODEN_SLAB": 3, - "FARMLAND": 1304, - "FERN": 1, - "FIRE": 16, - "FLETCHING_TABLE": 1, - "FLOWERING_AZALEA_LEAVES": 4, - "FLOWER_POT": 1, - "FROGLIGHT": 9, - "FROSTED_ICE": 4, - "FURNACE": 8, - "GILDED_BLACKSTONE": 1, - "GLASS": 1, - "GLASS_PANE": 1, - "GLAZED_TERRACOTTA": 64, - "GLOWING_ITEM_FRAME": 12, - "GLOWING_OBSIDIAN": 1, - "GLOWSTONE": 1, - "GLOW_LICHEN": 64, - "GOLD": 1, - "GOLD_ORE": 1, - "GRANITE": 1, - "GRANITE_SLAB": 3, - "GRANITE_STAIRS": 8, - "GRANITE_WALL": 162, - "GRASS": 1, - "GRASS_PATH": 1, - "GRAVEL": 1, - "GREEN_TORCH": 5, - "HANGING_ROOTS": 1, - "HARDENED_CLAY": 1, - "HARDENED_GLASS": 1, - "HARDENED_GLASS_PANE": 1, - "HAY_BALE": 3, - "HONEYCOMB": 1, - "HOPPER": 10, - "ICE": 1, - "INFESTED_CHISELED_STONE_BRICK": 1, - "INFESTED_COBBLESTONE": 1, - "INFESTED_CRACKED_STONE_BRICK": 1, - "INFESTED_MOSSY_STONE_BRICK": 1, - "INFESTED_STONE": 1, - "INFESTED_STONE_BRICK": 1, - "INFO_UPDATE": 1, - "INFO_UPDATE2": 1, - "INVISIBLE_BEDROCK": 1, - "IRON": 1, - "IRON_BARS": 1, - "IRON_DOOR": 32, - "IRON_ORE": 1, - "IRON_TRAPDOOR": 16, - "ITEM_FRAME": 12, - "JUKEBOX": 1, - "JUNGLE_BUTTON": 12, - "JUNGLE_DOOR": 32, - "JUNGLE_FENCE": 1, - "JUNGLE_FENCE_GATE": 16, - "JUNGLE_LEAVES": 4, - "JUNGLE_LOG": 6, - "JUNGLE_PLANKS": 1, - "JUNGLE_PRESSURE_PLATE": 2, - "JUNGLE_SAPLING": 2, - "JUNGLE_SIGN": 16, - "JUNGLE_SLAB": 3, - "JUNGLE_STAIRS": 8, - "JUNGLE_TRAPDOOR": 16, - "JUNGLE_WALL_SIGN": 4, - "JUNGLE_WOOD": 6, - "LAB_TABLE": 4, - "LADDER": 4, - "LANTERN": 2, - "LAPIS_LAZULI": 1, - "LAPIS_LAZULI_ORE": 1, - "LARGE_FERN": 2, - "LAVA": 32, - "LAVA_CAULDRON": 6, - "LECTERN": 8, - "LEGACY_STONECUTTER": 1, - "LEVER": 16, - "LIGHT": 16, - "LIGHTNING_ROD": 6, - "LILAC": 2, - "LILY_OF_THE_VALLEY": 1, - "LILY_PAD": 1, - "LIT_PUMPKIN": 4, - "LOOM": 4, - "MAGMA": 1, - "MANGROVE_BUTTON": 12, - "MANGROVE_DOOR": 32, - "MANGROVE_FENCE": 1, - "MANGROVE_FENCE_GATE": 16, - "MANGROVE_LEAVES": 4, - "MANGROVE_LOG": 6, - "MANGROVE_PLANKS": 1, - "MANGROVE_PRESSURE_PLATE": 2, - "MANGROVE_ROOTS": 1, - "MANGROVE_SIGN": 16, - "MANGROVE_SLAB": 3, - "MANGROVE_STAIRS": 8, - "MANGROVE_TRAPDOOR": 16, - "MANGROVE_WALL_SIGN": 4, - "MANGROVE_WOOD": 6, - "MATERIAL_REDUCER": 4, - "MELON": 1, - "MELON_STEM": 40, - "MOB_HEAD": 35, - "MONSTER_SPAWNER": 1, - "MOSSY_COBBLESTONE": 1, - "MOSSY_COBBLESTONE_SLAB": 3, - "MOSSY_COBBLESTONE_STAIRS": 8, - "MOSSY_COBBLESTONE_WALL": 162, - "MOSSY_STONE_BRICKS": 1, - "MOSSY_STONE_BRICK_SLAB": 3, - "MOSSY_STONE_BRICK_STAIRS": 8, - "MOSSY_STONE_BRICK_WALL": 162, - "MUD": 1, - "MUDDY_MANGROVE_ROOTS": 3, - "MUD_BRICKS": 1, - "MUD_BRICK_SLAB": 3, - "MUD_BRICK_STAIRS": 8, - "MUD_BRICK_WALL": 162, - "MUSHROOM_STEM": 1, - "MYCELIUM": 1, - "NETHERITE": 1, - "NETHERRACK": 1, - "NETHER_BRICKS": 1, - "NETHER_BRICK_FENCE": 1, - "NETHER_BRICK_SLAB": 3, - "NETHER_BRICK_STAIRS": 8, - "NETHER_BRICK_WALL": 162, - "NETHER_GOLD_ORE": 1, - "NETHER_PORTAL": 2, - "NETHER_QUARTZ_ORE": 1, - "NETHER_REACTOR_CORE": 1, - "NETHER_WART": 4, - "NETHER_WART_BLOCK": 1, - "NOTE_BLOCK": 1, - "OAK_BUTTON": 12, - "OAK_DOOR": 32, - "OAK_FENCE": 1, - "OAK_FENCE_GATE": 16, - "OAK_LEAVES": 4, - "OAK_LOG": 6, - "OAK_PLANKS": 1, - "OAK_PRESSURE_PLATE": 2, - "OAK_SAPLING": 2, - "OAK_SIGN": 16, - "OAK_SLAB": 3, - "OAK_STAIRS": 8, - "OAK_TRAPDOOR": 16, - "OAK_WALL_SIGN": 4, - "OAK_WOOD": 6, - "OBSIDIAN": 1, - "ORANGE_TULIP": 1, - "OXEYE_DAISY": 1, - "PACKED_ICE": 1, - "PACKED_MUD": 1, - "PEONY": 2, - "PINK_PETALS": 16, - "PINK_TULIP": 1, - "PITCHER_CROP": 3, - "PITCHER_PLANT": 2, - "PODZOL": 1, - "POLISHED_ANDESITE": 1, - "POLISHED_ANDESITE_SLAB": 3, - "POLISHED_ANDESITE_STAIRS": 8, - "POLISHED_BASALT": 3, - "POLISHED_BLACKSTONE": 1, - "POLISHED_BLACKSTONE_BRICKS": 1, - "POLISHED_BLACKSTONE_BRICK_SLAB": 3, - "POLISHED_BLACKSTONE_BRICK_STAIRS": 8, - "POLISHED_BLACKSTONE_BRICK_WALL": 162, - "POLISHED_BLACKSTONE_BUTTON": 12, - "POLISHED_BLACKSTONE_PRESSURE_PLATE": 2, - "POLISHED_BLACKSTONE_SLAB": 3, - "POLISHED_BLACKSTONE_STAIRS": 8, - "POLISHED_BLACKSTONE_WALL": 162, - "POLISHED_DEEPSLATE": 1, - "POLISHED_DEEPSLATE_SLAB": 3, - "POLISHED_DEEPSLATE_STAIRS": 8, - "POLISHED_DEEPSLATE_WALL": 162, - "POLISHED_DIORITE": 1, - "POLISHED_DIORITE_SLAB": 3, - "POLISHED_DIORITE_STAIRS": 8, - "POLISHED_GRANITE": 1, - "POLISHED_GRANITE_SLAB": 3, - "POLISHED_GRANITE_STAIRS": 8, - "POLISHED_TUFF": 1, - "POLISHED_TUFF_SLAB": 3, - "POLISHED_TUFF_STAIRS": 8, - "POLISHED_TUFF_WALL": 162, - "POPPY": 1, - "POTATOES": 8, - "POTION_CAULDRON": 6, - "POWERED_RAIL": 12, - "PRISMARINE": 1, - "PRISMARINE_BRICKS": 1, - "PRISMARINE_BRICKS_SLAB": 3, - "PRISMARINE_BRICKS_STAIRS": 8, - "PRISMARINE_SLAB": 3, - "PRISMARINE_STAIRS": 8, - "PRISMARINE_WALL": 162, - "PUMPKIN": 1, - "PUMPKIN_STEM": 40, - "PURPLE_TORCH": 5, - "PURPUR": 1, - "PURPUR_PILLAR": 3, - "PURPUR_SLAB": 3, - "PURPUR_STAIRS": 8, - "QUARTZ": 1, - "QUARTZ_BRICKS": 1, - "QUARTZ_PILLAR": 3, - "QUARTZ_SLAB": 3, - "QUARTZ_STAIRS": 8, - "RAIL": 10, - "RAW_COPPER": 1, - "RAW_GOLD": 1, - "RAW_IRON": 1, - "REDSTONE": 1, - "REDSTONE_COMPARATOR": 16, - "REDSTONE_LAMP": 2, - "REDSTONE_ORE": 2, - "REDSTONE_REPEATER": 32, - "REDSTONE_TORCH": 10, - "REDSTONE_WIRE": 16, - "RED_MUSHROOM": 1, - "RED_MUSHROOM_BLOCK": 11, - "RED_NETHER_BRICKS": 1, - "RED_NETHER_BRICK_SLAB": 3, - "RED_NETHER_BRICK_STAIRS": 8, - "RED_NETHER_BRICK_WALL": 162, - "RED_SAND": 1, - "RED_SANDSTONE": 1, - "RED_SANDSTONE_SLAB": 3, - "RED_SANDSTONE_STAIRS": 8, - "RED_SANDSTONE_WALL": 162, - "RED_TORCH": 5, - "RED_TULIP": 1, - "REINFORCED_DEEPSLATE": 1, - "RESERVED6": 1, - "ROSE_BUSH": 2, - "SAND": 1, - "SANDSTONE": 1, - "SANDSTONE_SLAB": 3, - "SANDSTONE_STAIRS": 8, - "SANDSTONE_WALL": 162, - "SCULK": 1, - "SEA_LANTERN": 1, - "SEA_PICKLE": 8, - "SHROOMLIGHT": 1, - "SHULKER_BOX": 1, - "SLIME": 1, - "SMALL_DRIPLEAF": 8, - "SMITHING_TABLE": 1, - "SMOKER": 8, - "SMOOTH_BASALT": 1, - "SMOOTH_QUARTZ": 1, - "SMOOTH_QUARTZ_SLAB": 3, - "SMOOTH_QUARTZ_STAIRS": 8, - "SMOOTH_RED_SANDSTONE": 1, - "SMOOTH_RED_SANDSTONE_SLAB": 3, - "SMOOTH_RED_SANDSTONE_STAIRS": 8, - "SMOOTH_SANDSTONE": 1, - "SMOOTH_SANDSTONE_SLAB": 3, - "SMOOTH_SANDSTONE_STAIRS": 8, - "SMOOTH_STONE": 1, - "SMOOTH_STONE_SLAB": 3, - "SNOW": 1, - "SNOW_LAYER": 8, - "SOUL_CAMPFIRE": 8, - "SOUL_FIRE": 1, - "SOUL_LANTERN": 2, - "SOUL_SAND": 1, - "SOUL_SOIL": 1, - "SOUL_TORCH": 5, - "SPONGE": 2, - "SPORE_BLOSSOM": 1, - "SPRUCE_BUTTON": 12, - "SPRUCE_DOOR": 32, - "SPRUCE_FENCE": 1, - "SPRUCE_FENCE_GATE": 16, - "SPRUCE_LEAVES": 4, - "SPRUCE_LOG": 6, - "SPRUCE_PLANKS": 1, - "SPRUCE_PRESSURE_PLATE": 2, - "SPRUCE_SAPLING": 2, - "SPRUCE_SIGN": 16, - "SPRUCE_SLAB": 3, - "SPRUCE_STAIRS": 8, - "SPRUCE_TRAPDOOR": 16, - "SPRUCE_WALL_SIGN": 4, - "SPRUCE_WOOD": 6, - "STAINED_CLAY": 16, - "STAINED_GLASS": 16, - "STAINED_GLASS_PANE": 16, - "STAINED_HARDENED_GLASS": 16, - "STAINED_HARDENED_GLASS_PANE": 16, - "STONE": 1, - "STONECUTTER": 4, - "STONE_BRICKS": 1, - "STONE_BRICK_SLAB": 3, - "STONE_BRICK_STAIRS": 8, - "STONE_BRICK_WALL": 162, - "STONE_BUTTON": 12, - "STONE_PRESSURE_PLATE": 2, - "STONE_SLAB": 3, - "STONE_STAIRS": 8, - "SUGARCANE": 16, - "SUNFLOWER": 2, - "SWEET_BERRY_BUSH": 4, - "TALL_GRASS": 1, - "TINTED_GLASS": 1, - "TNT": 4, - "TORCH": 5, - "TORCHFLOWER": 1, - "TORCHFLOWER_CROP": 2, - "TRAPPED_CHEST": 4, - "TRIPWIRE": 16, - "TRIPWIRE_HOOK": 16, - "TUFF": 1, - "TUFF_BRICKS": 1, - "TUFF_BRICK_SLAB": 3, - "TUFF_BRICK_STAIRS": 8, - "TUFF_BRICK_WALL": 162, - "TUFF_SLAB": 3, - "TUFF_STAIRS": 8, - "TUFF_WALL": 162, - "TWISTING_VINES": 26, - "UNDERWATER_TORCH": 5, - "VINES": 16, - "WALL_BANNER": 64, - "WALL_CORAL_FAN": 40, - "WARPED_BUTTON": 12, - "WARPED_DOOR": 32, - "WARPED_FENCE": 1, - "WARPED_FENCE_GATE": 16, - "WARPED_HYPHAE": 6, - "WARPED_PLANKS": 1, - "WARPED_PRESSURE_PLATE": 2, - "WARPED_ROOTS": 1, - "WARPED_SIGN": 16, - "WARPED_SLAB": 3, - "WARPED_STAIRS": 8, - "WARPED_STEM": 6, - "WARPED_TRAPDOOR": 16, - "WARPED_WALL_SIGN": 4, - "WARPED_WART_BLOCK": 1, - "WATER": 32, - "WATER_CAULDRON": 6, - "WEEPING_VINES": 26, - "WEIGHTED_PRESSURE_PLATE_HEAVY": 16, - "WEIGHTED_PRESSURE_PLATE_LIGHT": 16, - "WHEAT": 8, - "WHITE_TULIP": 1, - "WITHER_ROSE": 1, - "WOOL": 16 + "stateCounts": { + "ACACIA_BUTTON": 12, + "ACACIA_DOOR": 32, + "ACACIA_FENCE": 1, + "ACACIA_FENCE_GATE": 16, + "ACACIA_LEAVES": 4, + "ACACIA_LOG": 6, + "ACACIA_PLANKS": 1, + "ACACIA_PRESSURE_PLATE": 2, + "ACACIA_SAPLING": 2, + "ACACIA_SIGN": 16, + "ACACIA_SLAB": 3, + "ACACIA_STAIRS": 8, + "ACACIA_TRAPDOOR": 16, + "ACACIA_WALL_SIGN": 4, + "ACACIA_WOOD": 6, + "ACTIVATOR_RAIL": 12, + "AIR": 1, + "ALLIUM": 1, + "ALL_SIDED_MUSHROOM_STEM": 1, + "AMETHYST": 1, + "AMETHYST_CLUSTER": 24, + "ANCIENT_DEBRIS": 1, + "ANDESITE": 1, + "ANDESITE_SLAB": 3, + "ANDESITE_STAIRS": 8, + "ANDESITE_WALL": 162, + "ANVIL": 12, + "AZALEA_LEAVES": 4, + "AZURE_BLUET": 1, + "BAMBOO": 12, + "BAMBOO_SAPLING": 2, + "BANNER": 256, + "BARREL": 12, + "BARRIER": 1, + "BASALT": 3, + "BEACON": 1, + "BED": 256, + "BEDROCK": 2, + "BEETROOTS": 8, + "BELL": 16, + "BIG_DRIPLEAF_HEAD": 16, + "BIG_DRIPLEAF_STEM": 4, + "BIRCH_BUTTON": 12, + "BIRCH_DOOR": 32, + "BIRCH_FENCE": 1, + "BIRCH_FENCE_GATE": 16, + "BIRCH_LEAVES": 4, + "BIRCH_LOG": 6, + "BIRCH_PLANKS": 1, + "BIRCH_PRESSURE_PLATE": 2, + "BIRCH_SAPLING": 2, + "BIRCH_SIGN": 16, + "BIRCH_SLAB": 3, + "BIRCH_STAIRS": 8, + "BIRCH_TRAPDOOR": 16, + "BIRCH_WALL_SIGN": 4, + "BIRCH_WOOD": 6, + "BLACKSTONE": 1, + "BLACKSTONE_SLAB": 3, + "BLACKSTONE_STAIRS": 8, + "BLACKSTONE_WALL": 162, + "BLAST_FURNACE": 8, + "BLUE_ICE": 1, + "BLUE_ORCHID": 1, + "BLUE_TORCH": 5, + "BONE_BLOCK": 3, + "BOOKSHELF": 1, + "BREWING_STAND": 8, + "BRICKS": 1, + "BRICK_SLAB": 3, + "BRICK_STAIRS": 8, + "BRICK_WALL": 162, + "BROWN_MUSHROOM": 1, + "BROWN_MUSHROOM_BLOCK": 11, + "BUDDING_AMETHYST": 1, + "CACTUS": 16, + "CAKE": 7, + "CAKE_WITH_CANDLE": 2, + "CAKE_WITH_DYED_CANDLE": 32, + "CALCITE": 1, + "CAMPFIRE": 8, + "CANDLE": 8, + "CARPET": 16, + "CARROTS": 8, + "CARTOGRAPHY_TABLE": 1, + "CARVED_PUMPKIN": 4, + "CAULDRON": 1, + "CAVE_VINES": 104, + "CHAIN": 3, + "CHEMICAL_HEAT": 1, + "CHERRY_BUTTON": 12, + "CHERRY_DOOR": 32, + "CHERRY_FENCE": 1, + "CHERRY_FENCE_GATE": 16, + "CHERRY_LEAVES": 4, + "CHERRY_LOG": 6, + "CHERRY_PLANKS": 1, + "CHERRY_PRESSURE_PLATE": 2, + "CHERRY_SIGN": 16, + "CHERRY_SLAB": 3, + "CHERRY_STAIRS": 8, + "CHERRY_TRAPDOOR": 16, + "CHERRY_WALL_SIGN": 4, + "CHERRY_WOOD": 6, + "CHEST": 4, + "CHISELED_BOOKSHELF": 256, + "CHISELED_COPPER": 8, + "CHISELED_DEEPSLATE": 1, + "CHISELED_NETHER_BRICKS": 1, + "CHISELED_POLISHED_BLACKSTONE": 1, + "CHISELED_QUARTZ": 3, + "CHISELED_RED_SANDSTONE": 1, + "CHISELED_RESIN_BRICKS": 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, + "PALE_OAK_BUTTON": 12, + "PALE_OAK_DOOR": 32, + "PALE_OAK_FENCE": 1, + "PALE_OAK_FENCE_GATE": 16, + "PALE_OAK_LEAVES": 4, + "PALE_OAK_LOG": 6, + "PALE_OAK_PLANKS": 1, + "PALE_OAK_PRESSURE_PLATE": 2, + "PALE_OAK_SIGN": 16, + "PALE_OAK_SLAB": 3, + "PALE_OAK_STAIRS": 8, + "PALE_OAK_TRAPDOOR": 16, + "PALE_OAK_WALL_SIGN": 4, + "PALE_OAK_WOOD": 6, + "PEONY": 2, + "PINK_PETALS": 16, + "PINK_TULIP": 1, + "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, + "RESIN": 1, + "RESIN_BRICKS": 1, + "RESIN_BRICK_SLAB": 3, + "RESIN_BRICK_STAIRS": 8, + "RESIN_BRICK_WALL": 162, + "RESIN_CLUMP": 64, + "ROSE_BUSH": 2, + "SAND": 1, + "SANDSTONE": 1, + "SANDSTONE_SLAB": 3, + "SANDSTONE_STAIRS": 8, + "SANDSTONE_WALL": 162, + "SCULK": 1, + "SEA_LANTERN": 1, + "SEA_PICKLE": 8, + "SHROOMLIGHT": 1, + "SHULKER_BOX": 1, + "SLIME": 1, + "SMALL_DRIPLEAF": 8, + "SMITHING_TABLE": 1, + "SMOKER": 8, + "SMOOTH_BASALT": 1, + "SMOOTH_QUARTZ": 1, + "SMOOTH_QUARTZ_SLAB": 3, + "SMOOTH_QUARTZ_STAIRS": 8, + "SMOOTH_RED_SANDSTONE": 1, + "SMOOTH_RED_SANDSTONE_SLAB": 3, + "SMOOTH_RED_SANDSTONE_STAIRS": 8, + "SMOOTH_SANDSTONE": 1, + "SMOOTH_SANDSTONE_SLAB": 3, + "SMOOTH_SANDSTONE_STAIRS": 8, + "SMOOTH_STONE": 1, + "SMOOTH_STONE_SLAB": 3, + "SNOW": 1, + "SNOW_LAYER": 8, + "SOUL_CAMPFIRE": 8, + "SOUL_FIRE": 1, + "SOUL_LANTERN": 2, + "SOUL_SAND": 1, + "SOUL_SOIL": 1, + "SOUL_TORCH": 5, + "SPONGE": 2, + "SPORE_BLOSSOM": 1, + "SPRUCE_BUTTON": 12, + "SPRUCE_DOOR": 32, + "SPRUCE_FENCE": 1, + "SPRUCE_FENCE_GATE": 16, + "SPRUCE_LEAVES": 4, + "SPRUCE_LOG": 6, + "SPRUCE_PLANKS": 1, + "SPRUCE_PRESSURE_PLATE": 2, + "SPRUCE_SAPLING": 2, + "SPRUCE_SIGN": 16, + "SPRUCE_SLAB": 3, + "SPRUCE_STAIRS": 8, + "SPRUCE_TRAPDOOR": 16, + "SPRUCE_WALL_SIGN": 4, + "SPRUCE_WOOD": 6, + "STAINED_CLAY": 16, + "STAINED_GLASS": 16, + "STAINED_GLASS_PANE": 16, + "STAINED_HARDENED_GLASS": 16, + "STAINED_HARDENED_GLASS_PANE": 16, + "STONE": 1, + "STONECUTTER": 4, + "STONE_BRICKS": 1, + "STONE_BRICK_SLAB": 3, + "STONE_BRICK_STAIRS": 8, + "STONE_BRICK_WALL": 162, + "STONE_BUTTON": 12, + "STONE_PRESSURE_PLATE": 2, + "STONE_SLAB": 3, + "STONE_STAIRS": 8, + "SUGARCANE": 16, + "SUNFLOWER": 2, + "SWEET_BERRY_BUSH": 4, + "TALL_GRASS": 1, + "TINTED_GLASS": 1, + "TNT": 4, + "TORCH": 5, + "TORCHFLOWER": 1, + "TORCHFLOWER_CROP": 2, + "TRAPPED_CHEST": 4, + "TRIPWIRE": 16, + "TRIPWIRE_HOOK": 16, + "TUFF": 1, + "TUFF_BRICKS": 1, + "TUFF_BRICK_SLAB": 3, + "TUFF_BRICK_STAIRS": 8, + "TUFF_BRICK_WALL": 162, + "TUFF_SLAB": 3, + "TUFF_STAIRS": 8, + "TUFF_WALL": 162, + "TWISTING_VINES": 26, + "UNDERWATER_TORCH": 5, + "VINES": 16, + "WALL_BANNER": 64, + "WALL_CORAL_FAN": 40, + "WARPED_BUTTON": 12, + "WARPED_DOOR": 32, + "WARPED_FENCE": 1, + "WARPED_FENCE_GATE": 16, + "WARPED_HYPHAE": 6, + "WARPED_PLANKS": 1, + "WARPED_PRESSURE_PLATE": 2, + "WARPED_ROOTS": 1, + "WARPED_SIGN": 16, + "WARPED_SLAB": 3, + "WARPED_STAIRS": 8, + "WARPED_STEM": 6, + "WARPED_TRAPDOOR": 16, + "WARPED_WALL_SIGN": 4, + "WARPED_WART_BLOCK": 1, + "WATER": 32, + "WATER_CAULDRON": 6, + "WEEPING_VINES": 26, + "WEIGHTED_PRESSURE_PLATE_HEAVY": 16, + "WEIGHTED_PRESSURE_PLATE_LIGHT": 16, + "WHEAT": 8, + "WHITE_TULIP": 1, + "WITHER_ROSE": 1, + "WOOL": 16 + }, + "tiles": { + "ACACIA_SIGN": "pocketmine\\block\\tile\\Sign", + "ACACIA_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "BANNER": "pocketmine\\block\\tile\\Banner", + "BARREL": "pocketmine\\block\\tile\\Barrel", + "BEACON": "pocketmine\\block\\tile\\Beacon", + "BED": "pocketmine\\block\\tile\\Bed", + "BELL": "pocketmine\\block\\tile\\Bell", + "BIRCH_SIGN": "pocketmine\\block\\tile\\Sign", + "BIRCH_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "BLAST_FURNACE": "pocketmine\\block\\tile\\BlastFurnace", + "BREWING_STAND": "pocketmine\\block\\tile\\BrewingStand", + "CAMPFIRE": "pocketmine\\block\\tile\\Campfire", + "CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "CHERRY_SIGN": "pocketmine\\block\\tile\\Sign", + "CHERRY_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "CHEST": "pocketmine\\block\\tile\\Chest", + "CHISELED_BOOKSHELF": "pocketmine\\block\\tile\\ChiseledBookshelf", + "CRIMSON_SIGN": "pocketmine\\block\\tile\\Sign", + "CRIMSON_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "DARK_OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "DARK_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "DAYLIGHT_SENSOR": "pocketmine\\block\\tile\\DaylightSensor", + "DYED_SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", + "ENCHANTING_TABLE": "pocketmine\\block\\tile\\EnchantTable", + "ENDER_CHEST": "pocketmine\\block\\tile\\EnderChest", + "FLOWER_POT": "pocketmine\\block\\tile\\FlowerPot", + "FURNACE": "pocketmine\\block\\tile\\NormalFurnace", + "GLOWING_ITEM_FRAME": "pocketmine\\block\\tile\\GlowingItemFrame", + "HOPPER": "pocketmine\\block\\tile\\Hopper", + "ITEM_FRAME": "pocketmine\\block\\tile\\ItemFrame", + "JUKEBOX": "pocketmine\\block\\tile\\Jukebox", + "JUNGLE_SIGN": "pocketmine\\block\\tile\\Sign", + "JUNGLE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "LAVA_CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "LECTERN": "pocketmine\\block\\tile\\Lectern", + "MANGROVE_SIGN": "pocketmine\\block\\tile\\Sign", + "MANGROVE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "MOB_HEAD": "pocketmine\\block\\tile\\MobHead", + "MONSTER_SPAWNER": "pocketmine\\block\\tile\\MonsterSpawner", + "NOTE_BLOCK": "pocketmine\\block\\tile\\Note", + "OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "PALE_OAK_SIGN": "pocketmine\\block\\tile\\Sign", + "PALE_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron", + "REDSTONE_COMPARATOR": "pocketmine\\block\\tile\\Comparator", + "SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox", + "SMOKER": "pocketmine\\block\\tile\\Smoker", + "SOUL_CAMPFIRE": "pocketmine\\block\\tile\\Campfire", + "SPRUCE_SIGN": "pocketmine\\block\\tile\\Sign", + "SPRUCE_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "TRAPPED_CHEST": "pocketmine\\block\\tile\\Chest", + "WALL_BANNER": "pocketmine\\block\\tile\\Banner", + "WARPED_SIGN": "pocketmine\\block\\tile\\Sign", + "WARPED_WALL_SIGN": "pocketmine\\block\\tile\\Sign", + "WATER_CAULDRON": "pocketmine\\block\\tile\\Cauldron" + } } \ No newline at end of file diff --git a/tests/phpunit/block/regenerate_consistency_check.php b/tests/phpunit/block/regenerate_consistency_check.php index e86f70d70e..eb4ccf6c8b 100644 --- a/tests/phpunit/block/regenerate_consistency_check.php +++ b/tests/phpunit/block/regenerate_consistency_check.php @@ -28,11 +28,11 @@ require dirname(__DIR__, 3) . '/vendor/autoload.php'; /* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */ -$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance()); +[$newTable, $newTiles] = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance()); $oldTablePath = __DIR__ . '/block_factory_consistency_check.json'; if(file_exists($oldTablePath)){ - $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable); + $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable, $newTiles); if(count($errors) > 0){ echo count($errors) . " changes detected:\n"; @@ -47,5 +47,6 @@ if(file_exists($oldTablePath)){ } ksort($newTable, SORT_STRING); +ksort($newTiles, SORT_STRING); -file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); +file_put_contents($oldTablePath, json_encode(["stateCounts" => $newTable, "tiles" => $newTiles], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); diff --git a/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php b/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php new file mode 100644 index 0000000000..31ae2e27a4 --- /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/upgrade/BlockStateUpgraderTest.php b/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php index 4d4d321ec0..91afd8ed92 100644 --- a/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php +++ b/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php @@ -24,8 +24,10 @@ declare(strict_types=1); 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{ @@ -210,6 +212,23 @@ class BlockStateUpgraderTest extends TestCase{ 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 */ diff --git a/tests/phpunit/event/EventTest.php b/tests/phpunit/event/EventTest.php new file mode 100644 index 0000000000..7410cc3bb0 --- /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 edff36639d..c61043dab8 100644 --- a/tests/phpunit/event/HandlerListManagerTest.php +++ b/tests/phpunit/event/HandlerListManagerTest.php @@ -24,6 +24,12 @@ declare(strict_types=1); 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 1bac06bbb0..3831698098 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 92a95363e3..b48d8f5269 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/GrandchildEvent.php b/tests/phpunit/event/fixtures/TestChildEvent.php similarity index 89% rename from tests/plugins/TesterPlugin/src/event/GrandchildEvent.php rename to tests/phpunit/event/fixtures/TestChildEvent.php index 40c37c5679..569a2c069a 100644 --- a/tests/plugins/TesterPlugin/src/event/GrandchildEvent.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 GrandchildEvent extends ChildEvent{ +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 8b159df91a..cf744eb2ce 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 3f0fa572fe..54ec3dbb12 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 e414651211..362ce693b5 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 cc95589351..3fd3a6cf0e 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 68f7df6300..bfe50f9f3d 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/phpunit/event/fixtures/TestParentEvent.php b/tests/phpunit/event/fixtures/TestParentEvent.php new file mode 100644 index 0000000000..c204422722 --- /dev/null +++ b/tests/phpunit/event/fixtures/TestParentEvent.php @@ -0,0 +1,30 @@ +getConstants(); unset($constants['FIRST_UNUSED_ITEM_ID']); + self::assertNotEmpty($constants, "We should never have zero type IDs"); - self::assertSame($reflect->getConstant('FIRST_UNUSED_ITEM_ID'), max($constants) + 1, "FIRST_UNUSED_ITEM_ID must be one higher than the highest fixed type ID"); + $max = max($constants); + self::assertIsInt($max, "Max type ID should always be an integer"); + + self::assertSame($reflect->getConstant('FIRST_UNUSED_ITEM_ID'), $max + 1, "FIRST_UNUSED_ITEM_ID must be one higher than the highest fixed type ID"); } public function testNoDuplicates() : void{ @@ -54,6 +58,7 @@ class ItemTypeIdsTest extends TestCase{ } $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/scheduler/AsyncPoolTest.php b/tests/phpunit/scheduler/AsyncPoolTest.php index 53ec15c12b..88985cc399 100644 --- a/tests/phpunit/scheduler/AsyncPoolTest.php +++ b/tests/phpunit/scheduler/AsyncPoolTest.php @@ -71,6 +71,7 @@ class AsyncPoolTest extends TestCase{ } public function testThreadSafeSetResult() : void{ + /** @phpstan-var PromiseResolver> $resolver */ $resolver = new PromiseResolver(); $resolver->getPromise()->onCompletion( function(ThreadSafeArray $result) : void{ diff --git a/tests/phpunit/utils/CloningRegistryTraitTest.php b/tests/phpunit/utils/CloningRegistryTraitTest.php index 7f8298ff98..e3b53ecb56 100644 --- a/tests/phpunit/utils/CloningRegistryTraitTest.php +++ b/tests/phpunit/utils/CloningRegistryTraitTest.php @@ -47,7 +47,7 @@ final class CloningRegistryTraitTest extends TestCase{ public function testGetAllClone() : void{ $list1 = TestCloningRegistry::getAll(); $list2 = TestCloningRegistry::getAll(); - foreach($list1 as $k => $member){ + foreach(Utils::promoteKeys($list1) as $k => $member){ self::assertNotSame($member, $list2[$k], "VanillaBlocks ought to clone its members"); } } diff --git a/tests/phpunit/utils/TestCloningRegistry.php b/tests/phpunit/utils/TestCloningRegistry.php index ade94d461a..d65b8abaac 100644 --- a/tests/phpunit/utils/TestCloningRegistry.php +++ b/tests/phpunit/utils/TestCloningRegistry.php @@ -37,9 +37,13 @@ final class TestCloningRegistry{ /** * @return \stdClass[] + * @phpstan-return array */ public static function getAll() : array{ - /** @var \stdClass[] $result */ + /** + * @var \stdClass[] $result + * @phpstan-var array $result + */ $result = self::_registryGetAll(); return $result; } diff --git a/tests/phpunit/utils/fixtures/TestTrait.php b/tests/phpunit/utils/fixtures/TestTrait.php index bc32c0cff6..3e749c0b1d 100644 --- a/tests/phpunit/utils/fixtures/TestTrait.php +++ b/tests/phpunit/utils/fixtures/TestTrait.php @@ -23,6 +23,6 @@ declare(strict_types=1); namespace pocketmine\utils\fixtures; -trait TestTrait{ +trait TestTrait{ // @phpstan-ignore trait.unused } diff --git a/tests/plugins/DevTools b/tests/plugins/DevTools index c6dca357c7..a030d39e51 160000 --- a/tests/plugins/DevTools +++ b/tests/plugins/DevTools @@ -1 +1 @@ -Subproject commit c6dca357c7e8a37ce3479a1bedfe849451e072e3 +Subproject commit a030d39e51d267b5cd7e09069844a06910072ae7 diff --git a/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php b/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php deleted file mode 100644 index efe20f8d8f..0000000000 --- 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 26d3441f4a..08b59dbacb 100644 --- a/tests/plugins/TesterPlugin/src/Main.php +++ b/tests/plugins/TesterPlugin/src/Main.php @@ -57,7 +57,7 @@ class Main extends PluginBase implements Listener{ }), 10); $this->waitingTests = [ - new EventHandlerInheritanceTest($this), + //Add test objects here ]; } diff --git a/tests/travis.sh b/tests/travis.sh index 094f659053..a4674fb6f2 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/generate-blockstate-upgrade-schema.php b/tools/blockstate-upgrade-schema-utils.php similarity index 51% rename from tools/generate-blockstate-upgrade-schema.php rename to tools/blockstate-upgrade-schema-utils.php index 54984d4591..7c34b77284 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -21,36 +21,51 @@ declare(strict_types=1); -namespace pocketmine\tools\generate_blockstate_upgrade_schema; +namespace pocketmine\tools\blockstate_upgrade_schema_utils; use pocketmine\data\bedrock\block\BlockStateData; +use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap; -use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenedName; +use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenInfo; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap; use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; +use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; +use Symfony\Component\Filesystem\Path; use function array_key_first; use function array_key_last; use function array_keys; use function array_map; use function array_shift; +use function array_unique; use function array_values; use function count; use function dirname; +use function fclose; +use function file_exists; use function file_put_contents; +use function fopen; use function fwrite; +use function get_class; +use function get_debug_type; use function implode; +use function is_dir; +use function is_numeric; use function json_encode; use function ksort; use function min; +use function preg_match; +use function scandir; use function sort; use function strlen; use function strrev; @@ -83,18 +98,18 @@ function encodeProperty(Tag $tag) : string{ } /** + * @param TreeRoot[] $oldNewStateList + * @phpstan-param list $oldNewStateList + * * @return BlockStateMapping[][] * @phpstan-return array> */ -function loadUpgradeTable(string $file, bool $reverse) : array{ - $contents = Filesystem::fileGetContents($file); - $data = (new NetworkNbtSerializer())->readMultiple($contents); - +function buildUpgradeTableFromData(array $oldNewStateList, bool $reverse) : array{ $result = []; - for($i = 0; isset($data[$i]); $i += 2){ - $oldTag = $data[$i]->mustGetCompoundTag(); - $newTag = $data[$i + 1]->mustGetCompoundTag(); + 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); @@ -107,6 +122,17 @@ function loadUpgradeTable(string $file, bool $reverse) : array{ 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 @@ -159,6 +185,11 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra $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)]; @@ -254,6 +285,45 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra } } + 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){ @@ -308,43 +378,100 @@ function findCommonSuffix(array $strings) : string{ 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 BlockStateUpgradeSchemaFlattenedName[][] - * @phpstan-return array> + * @return BlockStateUpgradeSchemaFlattenInfo[][] + * @phpstan-return array> */ -function buildFlattenPropertyRules(array $candidateFlattenedValues) : array{ +function buildFlattenPropertyRules(array $candidateFlattenedValues, array $candidateFlattenPropertyTypes) : array{ $flattenPropertyRules = []; foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){ foreach(Utils::stringifyKeys($filters) as $filter => $valueToId){ - $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; - } - } - - $flattenPropertyRules[$propertyName][$filter] = new BlockStateUpgradeSchemaFlattenedName( - $idPrefix, - $propertyName, - $idSuffix, - $valueMap - ); + $flattenPropertyRules[$propertyName][$filter] = buildFlattenPropertyRule($valueToId, $propertyName, $candidateFlattenPropertyTypes[$propertyName]); } } ksort($flattenPropertyRules, SORT_STRING); @@ -398,64 +525,64 @@ function processRemappedStates(array $upgradeTable) : array{ } } } + $orderedUnchanged = []; foreach(Utils::stringifyKeys($unchangedStatesByNewName) as $newName => $unchangedStates){ - ksort($unchangedStates); - $unchangedStatesByNewName[$newName] = $unchangedStates; + sort($unchangedStates); + $orderedUnchanged[$newName] = $unchangedStates; } + $unchangedStatesByNewName = $orderedUnchanged; $notFlattenedProperties = []; $candidateFlattenedValues = []; + $candidateFlattenedPropertyTypes = []; foreach($upgradeTable as $pair){ foreach(Utils::stringifyKeys($pair->old->getStates()) as $propertyName => $propertyValue){ if(isset($notFlattenedProperties[$propertyName])){ continue; } - if(!$propertyValue instanceof StringTag){ - $notFlattenedProperties[$propertyName] = true; - continue; - } - $rawValue = $propertyValue->getValue(); - if($rawValue === ""){ - $notFlattenedProperties[$propertyName] = true; - 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); - if(isset($candidateFlattenedValues[$propertyName][$rawFilter])){ - $valuesToIds = $candidateFlattenedValues[$propertyName][$rawFilter]; - $existingNewId = $valuesToIds[$rawValue] ?? null; - if($existingNewId !== null && $existingNewId !== $pair->new->getName()){ - //this old value is associated with multiple new IDs - bad candidate for flattening - $notFlattenedProperties[$propertyName] = true; - continue; - } - foreach(Utils::stringifyKeys($valuesToIds) as $otherRawValue => $otherNewId){ - if($otherRawValue === $rawValue){ - continue; - } - if($otherNewId === $pair->new->getName()){ - //this old value maps to the same new ID as another old value - bad candidate for flattening - $notFlattenedProperties[$propertyName] = true; - continue 2; - } - } + $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; } - $candidateFlattenedValues[$propertyName][$rawFilter][$rawValue] = $pair->new->getName(); } } foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){ unset($candidateFlattenedValues[$propertyName]); } - $flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues); + $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 = []; @@ -475,8 +602,8 @@ function processRemappedStates(array $upgradeTable) : array{ ksort($cleanedNewState); if($flattenProperty !== null){ $flattenedValue = $cleanedOldState[$flattenProperty] ?? null; - if(!$flattenedValue instanceof StringTag){ - throw new AssumptionFailedError("This should always be a TAG_String ($newName $flattenProperty)"); + 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]); } @@ -496,8 +623,35 @@ function processRemappedStates(array $upgradeTable) : array{ if($existing === null || $existing->equals($remap)){ $list[$rawOldState] = $remap; }else{ - //match criteria is borked - throw new AssumptionFailedError("Match criteria resulted in two ambiguous remaps"); + //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"); } } @@ -506,7 +660,8 @@ function processRemappedStates(array $upgradeTable) : array{ usort($list, function(BlockStateUpgradeSchemaBlockRemap $a, BlockStateUpgradeSchemaBlockRemap $b) : int{ return count($b->oldState) <=> count($a->oldState); }); - return array_values($list); + //usort discards keys, so this is already a list + return $list; } /** @@ -556,10 +711,15 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad throw new \RuntimeException("States with the same ID should be fully consistent"); } }else{ - //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); + //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); + } } } @@ -567,18 +727,42 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad } /** - * @param string[] $argv + * @param BlockStateMapping[][] $upgradeTable + * @phpstan-param array> $upgradeTable */ -function main(array $argv) : int{ - if(count($argv) !== 3){ - fwrite(STDERR, "Required arguments: input file path, output file path\n"); - return 1; +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; + } + } } - $input = $argv[1]; - $output = $argv[2]; + return true; +} - $table = loadUpgradeTable($input, false); +/** + * @param string[] $argv + */ +function cmdGenerate(array $argv) : int{ + $upgradeTableFile = $argv[2]; + $schemaFile = $argv[3]; + + $table = loadUpgradeTableFromFile($upgradeTableFile, false); ksort($table, SORT_STRING); @@ -587,13 +771,187 @@ function main(array $argv) : int{ \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( - $output, + $schemaFile, json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" ); - \GlobalLogger::get()->info("Schema file $output generated successfully."); + \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 cmdDumpTable(array $argv) : int{ + $tableFile = $argv[2]; + $outputFile = $argv[3]; + + $output = fopen($outputFile, 'wb'); + if($output === false){ + fwrite(STDERR, "Failed to open output file: $outputFile\n"); + return 1; + } + + $table = loadUpgradeTableFromFile($tableFile, reverse: false); + ksort($table, SORT_STRING); + + foreach(Utils::stringifyKeys($table) as $oldName => $mappings){ + fwrite($output, "---------- MAPPING LIST: $oldName ----------\n"); + foreach($mappings as $mapping){ + $oldNbt = $mapping->old->toVanillaNbt(); + $oldNbt->setInt("version", $mapping->new->getVersion()); + + //intentionally not reused result of toVanillaNbt otherwise output wouldn't include version + fwrite($output, "OLD: " . $mapping->old->toVanillaNbt() . "\n"); + if(!$oldNbt->equals($mapping->new->toVanillaNbt())){ + fwrite($output, "NEW: " . $mapping->new->toVanillaNbt() . "\n"); + }else{ + fwrite($output, "NEW: version bump only (" . $mapping->new->getVersion() . ")\n"); + } + fwrite($output, "-----\n"); + } + } + + fclose($output); + \GlobalLogger::get()->info("Table dump file $outputFile generated successfully."); + return 0; +} + +/** + * @param string[] $argv + */ +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(...)], + "dump-table" => [["palette upgrade table file", "txt output file"], cmdDumpTable(...)] + ]; + + $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/compact-regions.php b/tools/compact-regions.php index 6959c82fe2..ab80792d36 100644 --- a/tools/compact-regions.php +++ b/tools/compact-regions.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\tools\compact_regions; +use pocketmine\utils\Utils; use pocketmine\world\format\io\exception\CorruptedChunkException; use pocketmine\world\format\io\region\CorruptedRegionException; use pocketmine\world\format\io\region\RegionLoader; @@ -59,6 +60,7 @@ const SUPPORTED_EXTENSIONS = [ /** * @param int[] $files * @phpstan-param array $files + * @phpstan-param-out array $files */ function find_regions_recursive(string $dir, array &$files) : void{ $dirFiles = scandir($dir, SCANDIR_SORT_NONE); @@ -74,7 +76,12 @@ function find_regions_recursive(string $dir, array &$files) : void{ in_array(pathinfo($fullPath, PATHINFO_EXTENSION), SUPPORTED_EXTENSIONS, true) && is_file($fullPath) ){ - $files[$fullPath] = filesize($fullPath); + $size = filesize($fullPath); + if($size === false){ + //If we can't get the size of the file, we probably don't have perms to read it, so ignore it + continue; + } + $files[$fullPath] = $size; }elseif(is_dir($fullPath)){ find_regions_recursive($fullPath, $files); } @@ -112,7 +119,7 @@ function main(array $argv) : int{ $corruptedFiles = []; $doneCount = 0; $totalCount = count($files); - foreach($files as $file => $size){ + foreach(Utils::stringifyKeys($files) as $file => $size){ try{ $oldRegion = RegionLoader::loadExisting($file); }catch(CorruptedRegionException $e){ @@ -162,8 +169,9 @@ function main(array $argv) : int{ clearstatcache(); $newSize = 0; - foreach($files as $file => $oldSize){ - $newSize += file_exists($file) ? filesize($file) : 0; + foreach(Utils::stringifyKeys($files) as $file => $oldSize){ + $size = file_exists($file) ? filesize($file) : 0; + $newSize += $size !== false ? $size : 0; } $diff = $currentSize - $newSize; $logger->info("Finished compaction of " . count($files) . " files. Freed " . number_format($diff) . " bytes of space (" . round(($diff / $currentSize) * 100, 2) . "% reduction)."); diff --git a/tools/convert-world.php b/tools/convert-world.php index d4d15ce57b..828ccb470d 100644 --- a/tools/convert-world.php +++ b/tools/convert-world.php @@ -52,7 +52,7 @@ $writableFormats = array_filter($providerManager->getAvailableProviders(), fn(Wo $requiredOpts = [ "world" => "path to the input world for conversion", "backup" => "path to back up the original files", - "format" => "desired output format (can be one of: " . implode(",", array_keys($writableFormats)) . ")" + "format" => "desired output format (can be one of: " . implode(", ", array_keys($writableFormats)) . ")" ]; $usageMessage = "Options:\n"; foreach($requiredOpts as $_opt => $_desc){ @@ -89,7 +89,7 @@ if(count($oldProviderClasses) === 0){ exit(1); } if(count($oldProviderClasses) > 1){ - fwrite(STDERR, "Ambiguous input world format: matched " . count($oldProviderClasses) . " (" . implode(array_keys($oldProviderClasses)) . ")" . PHP_EOL); + fwrite(STDERR, "Ambiguous input world format: matched " . count($oldProviderClasses) . " (" . implode(", ", array_keys($oldProviderClasses)) . ")" . PHP_EOL); exit(1); } $oldProviderClass = array_shift($oldProviderClasses); diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 16c3062b84..50639f51da 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -35,6 +35,7 @@ use pocketmine\crafting\json\SmithingTrimRecipeData; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeNames; +use pocketmine\inventory\json\CreativeGroupData; use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -48,15 +49,17 @@ use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket; use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket; use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\CreativeContentPacket; +use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\StartGamePacket; use pocketmine\network\mcpe\protocol\types\CacheableNbt; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield; +use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\network\mcpe\protocol\types\recipe\ComplexAliasItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; @@ -134,6 +137,19 @@ class ParserPacketHandler extends PacketHandler{ return base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($statePropertiesTag))); } + /** + * @param ItemStackData[] $items + */ + private function creativeGroupEntryToJson(CreativeGroupEntry $entry, array $items) : CreativeGroupData{ + $data = new CreativeGroupData(); + + $data->group_name = $entry->getCategoryName(); + $data->group_icon = $entry->getIcon()->getId() === 0 ? null : $this->itemStackToJson($entry->getIcon()); + $data->items = $items; + + return $data; + } + private function itemStackToJson(ItemStack $itemStack) : ItemStackData{ if($itemStack->getId() === 0){ throw new InvalidArgumentException("Cannot serialize a null itemstack"); @@ -198,12 +214,12 @@ class ParserPacketHandler extends PacketHandler{ $result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object); ksort($result, SORT_STRING); - foreach($result as $property => $value){ + foreach(Utils::promoteKeys($result) as $property => $value){ if(is_object($value)){ $result[$property] = self::objectToOrderedArray($value); }elseif(is_array($value)){ $array = []; - foreach($value as $k => $v){ + foreach(Utils::promoteKeys($value) as $k => $v){ if(is_object($v)){ $array[$k] = self::objectToOrderedArray($v); }else{ @@ -224,7 +240,7 @@ class ParserPacketHandler extends PacketHandler{ } if(is_array($object)){ $result = []; - foreach($object as $k => $v){ + foreach(Utils::promoteKeys($object) as $k => $v){ $result[$k] = self::sort($v); } return $result; @@ -234,31 +250,68 @@ class ParserPacketHandler extends PacketHandler{ } public function handleStartGame(StartGamePacket $packet) : bool{ - $this->itemTypeDictionary = new ItemTypeDictionary($packet->itemTable); - - echo "updating legacy item ID mapping table\n"; - $table = []; - foreach($packet->itemTable as $entry){ - $table[$entry->getStringId()] = [ - "runtime_id" => $entry->getNumericId(), - "component_based" => $entry->isComponentBased() - ]; - } - ksort($table, SORT_STRING); - file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); - - foreach($packet->levelSettings->experiments->getExperiments() as $name => $experiment){ + foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){ echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n"; } return true; } + public function handleItemRegistry(ItemRegistryPacket $packet) : bool{ + $this->itemTypeDictionary = new ItemTypeDictionary($packet->getEntries()); + + echo "updating legacy item ID mapping table\n"; + $emptyNBT = new CompoundTag(); + $table = []; + foreach($packet->getEntries() as $entry){ + $table[$entry->getStringId()] = [ + "runtime_id" => $entry->getNumericId(), + "component_based" => $entry->isComponentBased(), + "version" => $entry->getVersion(), + ]; + + $componentNBT = $entry->getComponentNbt()->getRoot(); + if(!$componentNBT->equals($emptyNBT)){ + $table[$entry->getStringId()]["component_nbt"] = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($componentNBT))); + } + } + ksort($table, SORT_STRING); + file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); + + echo "updating item registry\n"; + $items = array_map(function(ItemTypeEntry $entry) : array{ + return self::objectToOrderedArray($entry); + }, $packet->getEntries()); + file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); + return true; + } + public function handleCreativeContent(CreativeContentPacket $packet) : bool{ echo "updating creative inventory data\n"; - $items = array_map(function(CreativeContentEntry $entry) : array{ - return self::objectToOrderedArray($this->itemStackToJson($entry->getItem())); - }, $packet->getEntries()); - file_put_contents($this->bedrockDataPath . '/creativeitems.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); + + $groupItems = []; + foreach($packet->getItems() as $itemEntry){ + $groupItems[$itemEntry->getGroupId()][] = $this->itemStackToJson($itemEntry->getItem()); + } + + static $typeMap = [ + CreativeContentPacket::CATEGORY_CONSTRUCTION => "construction", + CreativeContentPacket::CATEGORY_NATURE => "nature", + CreativeContentPacket::CATEGORY_EQUIPMENT => "equipment", + CreativeContentPacket::CATEGORY_ITEMS => "items", + ]; + + $groupCategories = []; + foreach(Utils::promoteKeys($packet->getGroups()) as $groupId => $group){ + $category = $typeMap[$group->getCategoryId()] ?? throw new PacketHandlingException("Unknown creative category ID " . $group->getCategoryId()); + //FIXME: objectToOrderedArray might mess with the order of groupItems + //this isn't a problem right now because it's a list, but could cause problems in the future + $groupCategories[$category][] = self::objectToOrderedArray($this->creativeGroupEntryToJson($group, $groupItems[$groupId])); + } + + foreach(Utils::promoteKeys($groupCategories) as $category => $categoryGroups){ + file_put_contents($this->bedrockDataPath . '/creative/' . $category . '.json', json_encode($categoryGroups, JSON_PRETTY_PRINT) . "\n"); + } + return true; } @@ -317,8 +370,8 @@ class ParserPacketHandler extends PacketHandler{ $char = ord("A"); $outputsByKey = []; - foreach($entry->getInput() as $x => $row){ - foreach($row as $y => $ingredient){ + foreach(Utils::promoteKeys($entry->getInput()) as $x => $row){ + foreach(Utils::promoteKeys($row) as $y => $ingredient){ if($ingredient->getDescriptor() === null){ $shape[$x][$y] = " "; }else{ @@ -337,7 +390,7 @@ class ParserPacketHandler extends PacketHandler{ } $unlockingIngredients = $entry->getUnlockingRequirement()->getUnlockingIngredients(); return new ShapedRecipeData( - array_map(fn(array $array) => implode('', $array), $shape), + array_map(fn(array $array) => implode('', array_values($array)), array_values($shape)), $outputsByKey, array_map(fn(ItemStack $output) => $this->itemStackToJson($output), $entry->getOutput()), $entry->getBlockName(), @@ -399,7 +452,7 @@ class ParserPacketHandler extends PacketHandler{ 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", @@ -454,7 +507,7 @@ class ParserPacketHandler extends PacketHandler{ //this sorts the data into a canonical order to make diffs between versions reliable //how the data is ordered doesn't matter as long as it's reproducible - foreach($recipes as $_type => $entries){ + foreach(Utils::promoteKeys($recipes) as $_type => $entries){ $_sortedRecipes = []; $_seen = []; foreach($entries as $entry){ @@ -475,10 +528,10 @@ class ParserPacketHandler extends PacketHandler{ } ksort($recipes, SORT_STRING); - foreach($recipes as $type => $entries){ + foreach(Utils::promoteKeys($recipes) as $type => $entries){ echo "$type: " . count($entries) . "\n"; } - foreach($recipes as $type => $entries){ + foreach(Utils::promoteKeys($recipes) as $type => $entries){ file_put_contents(Path::join($recipesPath, $type . '.json'), json_encode($entries, JSON_PRETTY_PRINT) . "\n"); } @@ -571,7 +624,7 @@ function main(array $argv) : int{ } foreach($packets as $lineNum => $line){ - $parts = explode(':', $line); + $parts = explode(':', $line, limit: 3); if(count($parts) !== 2){ fwrite(STDERR, 'Wrong packet format at line ' . ($lineNum + 1) . ', expected read:base64 or write:base64'); return 1; diff --git a/tools/generate-item-upgrade-schema.php b/tools/generate-item-upgrade-schema.php index 4eee925392..7ad473b23b 100644 --- a/tools/generate-item-upgrade-schema.php +++ b/tools/generate-item-upgrade-schema.php @@ -31,10 +31,12 @@ namespace pocketmine\tools\generate_item_upgrade_schema; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\utils\Filesystem; +use pocketmine\utils\Utils; use Symfony\Component\Filesystem\Path; use function count; use function dirname; use function file_put_contents; +use function fwrite; use function is_array; use function json_decode; use function json_encode; @@ -45,6 +47,7 @@ use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; use const SCANDIR_SORT_ASCENDING; use const SORT_STRING; +use const STDERR; require dirname(__DIR__) . '/vendor/autoload.php'; @@ -56,7 +59,7 @@ if(count($argv) !== 4){ [, $mappingTableFile, $upgradeSchemasDir, $outputFile] = $argv; $target = json_decode(Filesystem::fileGetContents($mappingTableFile), true, JSON_THROW_ON_ERROR); -if(!is_array($target)){ +if(!is_array($target) || !isset($target["simple"]) || !is_array($target["simple"]) || !isset($target["complex"]) || !is_array($target["complex"])){ \GlobalLogger::get()->error("Invalid mapping table file"); exit(1); } @@ -93,7 +96,7 @@ foreach($files as $file){ $newDiff = []; -foreach($target["simple"] as $oldId => $newId){ +foreach(Utils::promoteKeys($target["simple"]) as $oldId => $newId){ $previousNewId = $merged["simple"][$oldId] ?? null; if( $previousNewId === $newId || //if previous schemas already accounted for this @@ -107,8 +110,12 @@ if(isset($newDiff["renamedIds"])){ ksort($newDiff["renamedIds"], SORT_STRING); } -foreach($target["complex"] as $oldId => $mappings){ - foreach($mappings as $meta => $newId){ +foreach(Utils::promoteKeys($target["complex"]) as $oldId => $mappings){ + if(!is_array($mappings)){ + fwrite(STDERR, "Complex mapping for $oldId is not an array\n"); + exit(1); + } + foreach(Utils::promoteKeys($mappings) as $meta => $newId){ if(($merged["complex"][$oldId][$meta] ?? null) !== $newId){ if($oldId === "minecraft:spawn_egg" && $meta === 130 && ($newId === "minecraft:axolotl_bucket" || $newId === "minecraft:axolotl_spawn_egg")){ //TODO: hack for vanilla bug workaround diff --git a/tools/simulate-chunk-selector.php b/tools/simulate-chunk-selector.php index 0b279268ab..3d5e167cf8 100644 --- a/tools/simulate-chunk-selector.php +++ b/tools/simulate-chunk-selector.php @@ -53,6 +53,10 @@ use const STR_PAD_LEFT; require dirname(__DIR__) . '/vendor/autoload.php'; +/** + * @phpstan-param positive-int $scale + * @phpstan-param positive-int $radius + */ function newImage(int $scale, int $radius) : \GdImage{ $image = Utils::assumeNotFalse(imagecreatetruecolor($scale * $radius * 2, $scale * $radius * 2)); imagesavealpha($image, true); @@ -149,6 +153,18 @@ if($radius === null){ fwrite(STDERR, "Please specify a radius using --radius\n"); exit(1); } +if($radius < 1){ + fwrite(STDERR, "Radius cannot be less than 1\n"); + exit(1); +} +if($scale < 1){ + fwrite(STDERR, "Scale cannot be less than 1\n"); + exit(1); +} +if($nChunksPerStep < 1){ + fwrite(STDERR, "Chunks per step cannot be less than 1\n"); + exit(1); +} $outputDirectory = null; if(isset($opts["output"])){